chore: run each client's test suite once in local:gate, not twice (#2341)

`local:gate` ran every client's unit suite bare inside `validate` and
again instrumented inside `coverage` — ~80s of a ~370s run re-executing
exactly the files the coverage pass runs a few minutes later. CI pays
nothing for that duplication because its two jobs run in parallel; the
gate runs serially, so it paid all of it.

The gate's first stage is now `local:validate`: the same `validate:guards`
and `validate:core`, then each client's new `check` script (`format:check`
+ `lint` + `typecheck` + `build`) instead of its `validate`, which is now
literally `check && test`. `npm run validate` and both CI jobs are
unchanged; `local:validate` sits in the `local:` namespace so the
workflow guard keeps it out of CI by construction.

The "strict superset" claim in AGENTS.md is reworded to what is now
true — every CI *check*, not every CI *invocation* — with the reasoning:
`@vitest/coverage-v8` reads V8's precise-coverage profiler and rewrites
no source, so the instrumented pass runs byte-identical code and is only
slower, the stricter direction for the failure class this repo sees.

Measured on the same worktree: 370s -> 262s wall (EXIT=0 both), the
four removed legs summing to 78.5s in the baseline run's own profile.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PsmUAZ6aLUAFqenjJKSSRp
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-09-12 19:53:08 -04:00
commit fe8b039030
7 changed files with 163 additions and 31 deletions

View file

@ -472,16 +472,19 @@ While the Web Client provides a rich visual interface, the CLI is designed for:
Like the other clients, the CLI self-validates from its own folder:
```bash
npm run validate # format:check && lint && typecheck && test (fast; no coverage gate)
npm run check # format:check && lint && typecheck (no tests)
npm run validate # check && test (fast; no coverage gate)
npm test # build test-servers + binary, then run all tests
npm run test:coverage # build + tests under the per-file ≥90 coverage gate
```
The CLI's `test` / `test:coverage` **build the binary first** (out-of-process
`e2e.test.ts` spawns it). `validate` is `format:check && lint && typecheck && test`
with no separate `build` step (`pretest` builds). Repo-root `validate:cli`
delegates here; the coverage gate is `npm run coverage` / `coverage:cli` (also in
`npm run local:gate`), matching AGENTS.md.
`e2e.test.ts` spawns it). `check` is `format:check && lint && typecheck` and
`validate` is `check && test`, with no separate `build` step (`pretest` builds).
Repo-root `validate:cli` delegates to `validate`, and the root `local:validate`
— the first stage of `npm run local:gate` — runs `check` instead, so the gate
runs the suite once, under `coverage` / `coverage:cli` (#2341), matching
AGENTS.md.
Tests run the CLI **in-process** (importing `runCli()`) so `src/` is measured
under coverage, with a thin out-of-process spawn layer for the real binary. See

View file

@ -17,7 +17,8 @@
"scripts": {
"build": "tsup",
"typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.test.json",
"validate": "npm run format:check && npm run lint && npm run typecheck && npm run test",
"check": "npm run format:check && npm run lint && npm run typecheck",
"validate": "npm run check && npm run test",
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "npm run test-servers:build && npm run build && vitest run --coverage",

View file

@ -110,7 +110,8 @@ stale fixture reports a product failure rather than a staleness one.
Like the web client, the launcher self-validates from its own folder:
```bash
npm run validate # format:check && lint && build && test:coverage
npm run check # format:check && lint && typecheck && build (no tests)
npm run validate # check && test
```
This has **no** dependency on the other clients being built — it only checks the
@ -118,7 +119,9 @@ launcher's own source. `eslint.config.js` is a Node-only flat config (the web
client's React/Storybook plugins stripped out), and the per-file coverage gate
covers `parse-launcher-argv.ts` (the pure arg-parsing logic); `src/index.ts` is
excluded as binary bootstrap and is instead exercised by the smokes above. The
repo-root `validate:launcher` simply delegates here (`cd clients/launcher && npm run validate`).
repo-root `validate:launcher` simply delegates here (`cd clients/launcher && npm run validate`),
and the root `local:validate` — the first stage of `npm run local:gate` — runs
`check` instead, so the gate runs the suite once, under `coverage:launcher` (#2341).
## Publishing

View file

@ -20,6 +20,7 @@
"format:check": "prettier --check src __tests__ scripts \"*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}\"",
"test": "vitest run",
"test:coverage": "vitest run --coverage",
"validate": "npm run format:check && npm run lint && npm run typecheck && npm run build && npm run test"
"check": "npm run format:check && npm run lint && npm run typecheck && npm run build",
"validate": "npm run check && npm run test"
}
}

View file

@ -93,12 +93,15 @@ The TUI provides terminal-native tabs and panes for interacting with your MCP se
Like the other clients, the TUI self-validates from its own folder:
```bash
npm run validate # format:check && lint && build && test:coverage
npm run check # format:check && lint && typecheck && build (no tests)
npm run validate # check && test
npm test # run all tests
npm run test:coverage # run tests under the per-file coverage gate
```
The repo-root `validate:tui` just delegates here. `eslint.config.js` registers
The repo-root `validate:tui` just delegates here; the root `local:validate`
(the first stage of `npm run local:gate`) runs `check` instead, so the gate
runs the suite once, under `coverage:tui` (#2341). `eslint.config.js` registers
`react-hooks` for the classic rules only (rules-of-hooks + exhaustive-deps); the
stricter react-hooks@7 rules are not enforced on the interim component surface
(#1501).

View file

@ -19,7 +19,8 @@
"dev": "vite-node --config vitest.config.ts dev.ts",
"build": "tsup",
"typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.test.json",
"validate": "npm run format:check && npm run lint && npm run typecheck && npm run build && npm run test",
"check": "npm run format:check && npm run lint && npm run typecheck && npm run build",
"validate": "npm run check && npm run test",
"test": "vitest run",
"test:coverage": "vitest run --coverage",
"lint": "eslint . --max-warnings 0",

View file

@ -15,7 +15,8 @@
"build:client": "vite build",
"build:runner": "tsup --config tsup.runner.config.ts",
"build:storybook": "storybook build",
"validate": "npm run format:check && npm run lint && npm run build && npm run test",
"check": "npm run format:check && npm run lint && npm run build",
"validate": "npm run check && npm run test",
"pretest": "npm run test-servers:build",
"test": "vitest run --project=unit",
"test:watch": "vitest --project=unit",