ADR-0009: CI Quality Gates
Date: 2026-07-28 Status: Accepted Last revised: 2026-09-23 — 门禁表按现状更新:git 钩子改由 lefthook 托管(ADR 0018),pre-push 是一张门禁表(并行) 聚合命令 pnpm --silent prepush(含 lint:root / types:check:root / lint:docs-refs / lint:adr / lint:doc-ts / lint:doc-budget);CI 步骤补 boundaries / affected 自检 / syncpack / pnpm change check / lint:root / types:check:root / lint:adr / lint:doc-ts / lint:doc-budget; test 任务的依赖是 ["^build"];@walnut/axios 已更名 @walnut/http。
Context
The monorepo has 3 apps and 4 packages. Pre-merge validation needs to catch type errors, lint violations, and regressions before they reach production.
Current state:
simple-git-hooks: pre-commit runslint-staged, pre-push runspnpm types:checkturbo.jsonhastesttask but only@walnut/serverhas vitest configured@walnut/utils,@walnut/contract,@walnut/clienthave vitest config but no tests yet- No CI workflow running tests on push/PR
Decision
Three-tier quality gate:
| Gate | When | What | Blocks |
|---|---|---|---|
| Pre-commit | git commit | lint-staged (ESLint auto-fix on staged files) | Commit |
| Pre-push | git push | pnpm --silent prepush — five sections: boundaries → types:check → syncpack:lint → lint:workflows (actionlint) → pnpm change check (fixed-group lockstep). Hook definition lives in lefthook.yml, see ADR 0018 | Push |
| CI | Push to main / PR | pnpm boundaries → affected lint / types:check / test → affected-set sanity check → pnpm syncpack:lint → pnpm change check → builds | Merge |
Test requirements by package:
| Package | Test requirement |
|---|---|
@walnut/utils | Required — pure functions, easy to test |
@walnut/contract | Required — snapshot tests for constants, no-duplicate-code validation |
@walnut/client | Eventually — complex browser/Vue code, needs jsdom setup |
@walnut/http | Eventually — adapter tests need mock server |
@walnut/admin | Eventually — complex SPA, needs component/E2E tests |
@walnut/server | Existing — vitest + playwright already configured |
turbo.json test task: dependsOn: ["^build"] — a package's tests need its upstream dependencies' build output (@walnut/contract / @walnut/utils publish a CJS dist/ for the backend's require path, see ADR 0002), while the package under test is not built first: Vitest compiles it from source with esbuild. ["^build"] (not ["build"]) is the correct chain per industry standard (see 行业调研:测试体系).
Alternatives considered
- Keep the pre-decision setup (
simple-git-hookswith a pre-push that only ranpnpm types:check, and no CI workflow running tests) —— pre-merge validation has to catch type errors, lint violations, and regressions before they reach production. - Run the test suite in the pre-push hook instead of in CI —— pre-push is deliberately kept in the ten-second class, so it executes no tests; CI is what catches test failures without blocking the local development flow.
Consequences
- Pre-push stays in the ten-second class (no test execution) even though it now runs five sections
- CI catches test failures without blocking local development flow
- Shared packages (
utils,contract) get test coverage first — highest blast radius - Version drift inside
versioning.fixedis caught on the PR that introduces it (pnpm change check), not at release time turbo testruns affected packages only (via--affectedwith an explicitTURBO_SCM_BASE/TURBO_SCM_HEADbase)