diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 145d2545..065a26de 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,35 +4,55 @@ on: pull_request: branches: [main, dev] +# Design notes: +# - Matrix parallelism cuts wall time from ~3-4min to ~60-90s (cache warm). +# - Each matrix leg restores the same cache; `Ensure deps` fills gaps on cache miss. +# - fail-fast: false so every failure is visible in one run (no re-pushing to see the next failure). +# - Test leg runs build:all inline because it needs dist/ artifacts; still parallel with other legs. + jobs: validate: runs-on: [self-hosted, linux, x64] + strategy: + fail-fast: false + matrix: + check: + - { name: typecheck, cmd: 'bun run typecheck' } + - { name: lint, cmd: 'bun run lint' } + - { name: format, cmd: 'bun run format:check' } + - { name: build, cmd: 'bun run build:all' } + - { name: test, cmd: 'bun run build:all && bun run test:all' } + name: ${{ matrix.check.name }} steps: - name: Checkout code uses: actions/checkout@v4 - - name: Clean stale artifacts - run: rm -rf node_modules ui/node_modules dist - - name: Setup Bun uses: oven-sh/setup-bun@v2 with: bun-version: '1.3.9' - no-cache: true - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '22' - - name: Install dependencies + - name: Restore bun + node_modules cache + uses: actions/cache@v4 + with: + path: | + ~/.bun/install/cache + node_modules + ui/node_modules + key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock', 'ui/bun.lock') }} + restore-keys: | + ${{ runner.os }}-bun- + + - name: Ensure dependencies run: | - bun install --frozen-lockfile - cd ui && bun install --frozen-lockfile + [ -d node_modules ] || bun install --frozen-lockfile + [ -d ui/node_modules ] || (cd ui && bun install --frozen-lockfile) - - name: Build package - run: bun run build:all - - - name: Validate (typecheck + lint + format + tests) - run: bun run validate + - name: Run ${{ matrix.check.name }} + run: ${{ matrix.check.cmd }} diff --git a/.husky/pre-push b/.husky/pre-push index 1a54c477..4024c22d 100755 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -29,6 +29,7 @@ echo " base: $BASE_BRANCH" bun run typecheck bun run lint:fix bun run format:check +bun run build:all git fetch origin "$BASE_BRANCH" --quiet || true DIFF_RANGE="HEAD" diff --git a/CLAUDE.md b/CLAUDE.md index 3c182ac2..11175260 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -15,6 +15,32 @@ AI-facing guidance for agent tooling when working with this repository. Tests set `process.env.CCS_HOME` to a temp directory. Code using `os.homedir()` directly will modify the user's real files. +## CI-First Protocol (MANDATORY) + +**A task is NOT complete until CI is green. After every `git push`, the AI agent MUST block on CI until it passes.** + +### Required Sequence +1. `git push` +2. **Immediately** run `gh pr checks --watch` (or `gh run watch`) and block until all checks complete. +3. If **green** → task may proceed to next step / be declared done. +4. If **red**: + - Pull failing logs: `gh run view --log-failed` (or `gh pr checks ` to identify the failing job, then `gh run view --log-failed`). + - Fix the root cause locally. Do NOT retry blindly. + - Commit and push again. Re-watch CI. +5. Applies to initial `gh pr create` AND every subsequent push on an open PR. + +### Fallback (when `--watch` is unavailable or flaky) +Poll with short sleep until no check is `pending` / `in_progress`: +```bash +until [ "$(gh pr checks --json state -q '[.[] | select(.state == "IN_PROGRESS" or .state == "PENDING" or .state == "QUEUED")] | length')" = "0" ]; do + sleep 10 +done +gh pr checks +``` + +### Absolute rule +AI MUST NOT declare a task done, close a session, or move to the next task while CI is red or still running. Leaving a PR red and moving on is the primary failure mode this protocol prevents. + ## Core Function Multi-provider profile and runtime manager for Claude Code, Factory Droid, @@ -201,9 +227,11 @@ bun run validate # Step 3: Final check (must pass) | Project | Command | Runs | |---------|---------|------| -| Main | `bun run validate` | typecheck + lint:fix + format:check + maintainability:check + test:all | +| Main | `bun run validate` | typecheck + lint:fix + format:check + test:all | | UI | `bun run validate` | typecheck + lint:fix + format:check | +**Note:** `maintainability:check` is a SEPARATE gate — not part of `validate`. Run it explicitly via `bun run maintainability:check[:strict|:warn]` when touching debt-sensitive code or before merging to protected branches. + ### ESLint Rules (ALL errors) | Rule | Level | Notes | @@ -238,7 +266,7 @@ bun run validate # Step 3: Final check (must pass) - Baseline file: `docs/metrics/maintainability-baseline.json` - Metric collector/check script: `scripts/maintainability-baseline.js` - Branch-aware gate wrapper: `scripts/maintainability-check.js` -- Enforcement path: `bun run maintainability:check` (included in `bun run validate`) +- Enforcement path: `bun run maintainability:check` (run separately — NOT part of `bun run validate`; invoked by `validate:ci-parity` on protected branches) - Gate modes: - `strict`: protected branches (`main`, `dev`, `hotfix/*`, `kai/hotfix-*`) and equivalent CI refs - `warn`: pull request CI and non-protected local branches (non-blocking for parallel PR workflow) @@ -499,27 +527,30 @@ rm -rf ~/.ccs # Clean environment **IMPORTANT:** Use `bun run dev` at CCS root for always up-to-date code. Do NOT use `ccs config` during development as it uses the globally installed version. -## Pre-Commit Checklist +## Two-Tier Pre-Push Checklist -**Quality (BLOCKERS):** -- [ ] `bun run format` — formatting fixed -- [ ] `bun run validate` — all checks pass -- [ ] `bun run validate:ci-parity` — CI parity passed (required before protected-branch pushes; recommended before PRs) -- [ ] `cd ui && bun run format && bun run validate` — if UI changed -- [ ] If touching debt-sensitive code, run `bun run maintainability:check:strict` before opening/merging PR +Optimized for iterative push-then-review workflow. Do NOT run the full gate on every push — CI is the safety net. Run the full gate once before asking for review / merge. + +### Tier 1 — Iterative push (feature branch) +Husky `pre-push` auto-runs: `typecheck + lint:fix + format:check + build:all` plus targeted tests based on changed files. AI does **nothing extra** at push time. + +**After push (MANDATORY):** follow the [CI-First Protocol](#ci-first-protocol-mandatory) — watch CI until green. Do not move on while CI is red. + +### Tier 2 — Before requesting review / merge +Run ONCE, not per push: +- [ ] `bun run validate:ci-parity` — full build + validate matches CI +- [ ] `gh pr checks ` — all checks green +- [ ] If touching debt-sensitive code: `bun run maintainability:check:strict` - [ ] If strict mode fails and increase is intentional: `bun run maintainability:baseline` and commit `docs/metrics/maintainability-baseline.json` +- [ ] If UI changed: `cd ui && bun run format && bun run validate` -**Code:** +### Code / Docs / Standards (verify before merge) - [ ] Conventional commit format (`feat:`, `fix:`, etc.) - [ ] Respective `--help` updated (see Help Location Reference) — if CLI changed - [ ] Tests added/updated — if behavior changed - [ ] README.md updated — if user-facing - -**Documentation:** - [ ] CCS docs updated (owner: `~/CloudPersonal/ccs/docs/`) — if CLI/config changed - [ ] Local `docs/` updated — if architecture changed - -**Standards:** - [ ] CLI output ASCII only (NO emojis in terminal output), NO_COLOR respected - [ ] YAGNI/KISS/DRY alignment verified - [ ] No manual version bump or tags diff --git a/tests/unit/cliproxy/routing-strategy.test.ts b/tests/unit/cliproxy/routing-strategy.test.ts index 187ab5cd..3a195e14 100644 --- a/tests/unit/cliproxy/routing-strategy.test.ts +++ b/tests/unit/cliproxy/routing-strategy.test.ts @@ -116,11 +116,9 @@ describe('cliproxy routing strategy service', () => { expect(result.applied).toBe('config-only'); expect(result.strategy).toBe('fill-first'); - const { getConfigPathForPort } = await import('../../../src/cliproxy/config/path-resolver'); - const configPath = getConfigPathForPort(routingTarget.port); - const configContent = fs.readFileSync(configPath, 'utf8'); - expect(configContent).toContain('routing:'); - expect(configContent).toContain('strategy: fill-first'); + const { loadUnifiedConfig } = await import('../../../src/config/unified-config-loader'); + const persisted = loadUnifiedConfig(); + expect(persisted?.cliproxy?.routing?.strategy).toBe('fill-first'); }); }); diff --git a/tests/unit/hooks/websearch-transformer.test.ts b/tests/unit/hooks/websearch-transformer.test.ts index 7678bb40..6aea3175 100644 --- a/tests/unit/hooks/websearch-transformer.test.ts +++ b/tests/unit/hooks/websearch-transformer.test.ts @@ -465,6 +465,7 @@ global.fetch = async (url) => { }), env: { ...process.env, + CCS_PROFILE_TYPE: NEUTRAL_PROFILE_TYPE, CCS_WEBSEARCH_ENABLED: '1', CCS_WEBSEARCH_SKIP: '0', CCS_WEBSEARCH_BRAVE: '0',