Merge pull request #1044 from kaitranntt/kai/chore/ci-speed-and-ai-doc-gate

chore(ci): parallelize CI matrix + cache, tighten AI-facing quality gate
This commit is contained in:
Kai (Tam Nhu) Tran
2026-04-19 15:33:28 -04:00
committed by GitHub
5 changed files with 82 additions and 31 deletions
+32 -12
View File
@@ -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 }}
+1
View File
@@ -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"
+45 -14
View File
@@ -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 <n>` to identify the failing job, then `gh run view <run-id> --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 <n> --json state -q '[.[] | select(.state == "IN_PROGRESS" or .state == "PENDING" or .state == "QUEUED")] | length')" = "0" ]; do
sleep 10
done
gh pr checks <n>
```
### 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 <n>` — 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
+3 -5
View File
@@ -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');
});
});
@@ -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',