From affdaead80c3635f49ef562cac81bde8db0cab23 Mon Sep 17 00:00:00 2001 From: "Kai (Tam Nhu) Tran" <61256810+kaitranntt@users.noreply.github.com> Date: Fri, 9 Jan 2026 00:18:30 -0600 Subject: [PATCH] fix(delegation): improve profile discovery and CI workflow (#310) * fix(doctor): use dynamic profile discovery for delegation check Replace hardcoded ['glm', 'kimi'] list with DelegationValidator.getReadyProfiles() to detect all configured *.settings.json profiles including mm, or1, g7, etc. * fix(ci): exclude bot comments from triggering AI review Bot progress comments were triggering new workflow runs, which cancelled in-progress reviews due to concurrency group. Added check for github.event.comment.user.type != 'Bot'. * chore(release): 7.18.0-dev.1 [skip ci] * fix(delegation): only check profiles defined in config.yaml Previously getReadyProfiles() scanned all *.settings.json files, including orphan files (ghcp, kiro) not in config.yaml. Now reads from config.yaml: - profiles section (excluding 'default') - cliproxy.providers section Fixes doctor showing 11 profiles instead of configured 9. * chore(release): 7.18.0-dev.2 [skip ci] --------- Co-authored-by: github-actions[bot] --- .github/workflows/ai-review.yml | 5 ++-- package.json | 2 +- src/management/checks/profile-check.ts | 11 ++------ src/utils/delegation-validator.ts | 38 ++++++++++++++++++++------ 4 files changed, 36 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ai-review.yml b/.github/workflows/ai-review.yml index 62a088e6..7ba104ef 100644 --- a/.github/workflows/ai-review.yml +++ b/.github/workflows/ai-review.yml @@ -38,13 +38,14 @@ jobs: # Conditions: # - PR event: only on opened (not synchronize to avoid cancel-on-push) - # - Comment event: only if it's a PR and contains /review + # - Comment event: only if it's a PR, contains /review, and NOT from a bot if: > github.event_name == 'pull_request_target' || github.event_name == 'workflow_dispatch' || (github.event_name == 'issue_comment' && github.event.issue.pull_request && - contains(github.event.comment.body, '/review')) + contains(github.event.comment.body, '/review') && + github.event.comment.user.type != 'Bot') # CLIProxy environment for model routing env: diff --git a/package.json b/package.json index f1a8c1cd..816b1221 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kaitranntt/ccs", - "version": "7.18.0", + "version": "7.18.0-dev.2", "description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6", "keywords": [ "cli", diff --git a/src/management/checks/profile-check.ts b/src/management/checks/profile-check.ts index 7d80a4df..e29a0143 100644 --- a/src/management/checks/profile-check.ts +++ b/src/management/checks/profile-check.ts @@ -193,16 +193,9 @@ export class DelegationChecker implements IHealthChecker { return; } - // Check profile validity using DelegationValidator + // Check profile validity using DelegationValidator (dynamic discovery) const { DelegationValidator } = require('../../utils/delegation-validator'); - const readyProfiles: string[] = []; - - for (const profile of ['glm', 'kimi']) { - const validation = DelegationValidator.validate(profile); - if (validation.valid) { - readyProfiles.push(profile); - } - } + const readyProfiles = DelegationValidator.getReadyProfiles(); if (readyProfiles.length === 0) { spinner.warn(); diff --git a/src/utils/delegation-validator.ts b/src/utils/delegation-validator.ts index cec57e3f..76be898b 100644 --- a/src/utils/delegation-validator.ts +++ b/src/utils/delegation-validator.ts @@ -139,27 +139,49 @@ export class DelegationValidator { } /** - * Get all delegation-ready profiles + * Get all delegation-ready profiles from config.yaml + * Only returns profiles explicitly defined in config, not orphan settings files * @returns List of profile names ready for delegation */ static getReadyProfiles(): string[] { const homeDir = os.homedir(); const ccsDir = path.join(homeDir, '.ccs'); + const configPath = path.join(ccsDir, 'config.yaml'); if (!fs.existsSync(ccsDir)) { return []; } const profiles: string[] = []; - const entries = fs.readdirSync(ccsDir, { withFileTypes: true }); - // Look for *.settings.json files - for (const entry of entries) { - if (entry.isFile() && entry.name.endsWith('.settings.json')) { - const profileName = entry.name.replace('.settings.json', ''); - if (this.isReady(profileName)) { - profiles.push(profileName); + // Get profiles from config.yaml (excludes 'default' which uses ~/.claude/settings.json) + if (fs.existsSync(configPath)) { + try { + const yaml = require('js-yaml'); + const content = fs.readFileSync(configPath, 'utf8'); + const config = yaml.load(content) as Record; + + if (config.profiles && typeof config.profiles === 'object') { + for (const profileName of Object.keys(config.profiles as object)) { + if (profileName !== 'default' && this.isReady(profileName)) { + profiles.push(profileName); + } + } } + + // Also check CLIProxy providers (gemini, codex, agy, etc.) + if (config.cliproxy && typeof config.cliproxy === 'object') { + const cliproxy = config.cliproxy as Record; + if (Array.isArray(cliproxy.providers)) { + for (const provider of cliproxy.providers) { + if (typeof provider === 'string' && this.isReady(provider)) { + profiles.push(provider); + } + } + } + } + } catch { + // Config parse error, fall back to empty } }