chore(scripts): rename the local pre-push gate off ci, and guard it out of CI (#2146)

The root `ci` script was never what runs in GitHub CI, and the name worked
against us three ways: it collides with the built-in `npm ci` (which does
something else entirely and is one keystroke away), it invites the
cross-engine passes into a workflow by looking like the thing a workflow
should call, and it blurs "GitHub CI" and "the local gate" in every doc
that names it.

- Rename `ci` → `local:gate` and `ci:storybook` → `local:storybook`, with
  no back-compat alias: `npm run ci` now fails with npm's missing-script
  error, which prints the available scripts.

- Add `scripts/lib/workflow-gate.mjs` + its `test:scripts` suite, which
  runs over the repo's real `.github/workflows/**` and fails on an
  invocation of a `local:*` script, of a non-Chromium engine pass (derived
  from SUPPORTED_BROWSERS), or of `smoke:web:engine`, or on `SMOKE_BROWSER`
  set to anything but a literal `chromium`. It does not forbid
  `npm run smoke`, `smoke:web:chromium` or `smoke:tui` — those belong in CI
  and are there today. Comments are not scanned, so the workflow can still
  explain itself. This is the durable half; the rest is prose.

- Document the two-tier model once, in the root README ("Two tiers: GitHub
  CI and the local gate"), and point the other docs at it rather than
  restating the split.

- Sweep the ~40 stale references across README, AGENTS.md,
  .github/copilot-instructions.md, the client READMEs, the roadmap doc and
  the script headers. AGENTS.md and copilot-instructions.md change
  together, per the mirror rule.

No gate changes what it actually runs.

Signed-off-by: Cliff Hall <cliff@futurescale.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-08-26 23:56:34 -04:00
commit 0b64460097
5 changed files with 423 additions and 48 deletions

View file

@ -371,7 +371,7 @@ 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 ci`), matching AGENTS.md.
`npm run local:gate`), 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

@ -76,7 +76,7 @@ const preview: Preview = {
},
},
a11y: {
// `'error'` fails the Storybook play-function tests (part of `npm run ci`)
// `'error'` fails the Storybook play-function tests (part of `npm run local:gate`)
// on any axe violation, so the zero-violation state this PR reached is
// enforced going forward rather than being a point-in-time result.
test: "error",

View file

@ -2,7 +2,7 @@
The browser incarnation of the Inspector: a **Vite + React + [Mantine](https://mantine.dev)** single-page app backed by a small **Node (Hono)** server. The SPA is presentational — it renders data and fires callbacks; all MCP state comes from the shared `@inspector/core` hooks. The backend proxies MCP connections, serves the built SPA, and exposes `/api/*`.
This README covers what's specific to the web client. For the repo-wide picture (the `@inspector/core` shared package, the "dumb components" philosophy, the top-level `validate`/`coverage`/`ci` scripts, and publishing), see the [root README](../../README.md).
This README covers what's specific to the web client. For the repo-wide picture (the `@inspector/core` shared package, the "dumb components" philosophy, the top-level `validate`/`coverage`/`local:gate` scripts, and publishing), see the [root README](../../README.md).
## Two halves: `src/` (browser) and `server/` (Node)
@ -289,7 +289,7 @@ npm run build:storybook # static build
npm run test:storybook # run every story's play function in headless Chromium
```
Storybook is first-class here because the components are presentational — each renders against fixture props. **Play functions double as interaction tests** and run headless in real Chromium via `@vitest/browser-playwright` + `@storybook/addon-vitest` (the `storybook` Vitest project). They're part of `npm run ci` (which installs the Chromium binary first) but kept out of the fast `validate` loop since they need the browser.
Storybook is first-class here because the components are presentational — each renders against fixture props. **Play functions double as interaction tests** and run headless in real Chromium via `@vitest/browser-playwright` + `@storybook/addon-vitest` (the `storybook` Vitest project). GitHub CI runs them directly (`npm run test:storybook`); the local pre-push gate runs them through `npm run local:storybook`, which installs the Chromium binary first. Either way they are kept out of the fast `validate` loop, since they need the browser.
## Auth token

View file

@ -552,7 +552,7 @@ describe("AppRenderer", () => {
// pending here. Observed failing exactly once that way, with 0 calls.
//
// The default 1000ms then turned out not to be enough either — it failed
// twice in a row in `npm run ci` (at ~1021ms, still 0 calls) while
// twice in a row in `npm run local:gate` (at ~1021ms, still 0 calls) while
// passing every time this file runs alone, which is the signature of a
// budget that is too tight rather than of a broken observer. Raised
// rather than retried: a `waitFor` that is generous costs nothing on the

View file

@ -108,7 +108,7 @@ async function getTool(client: InspectorClient, name: string): Promise<Tool> {
* legitimate thing to exercise, but `disconnect()` closes the SDK client, which
* rejects every pending request with "Connection closed" — and a floating
* promise makes that an *unhandled* rejection, which vitest counts as a run
* error and fails `npm run ci` even though every test passes (#1947).
* error and fails `npm run local:gate` even though every test passes (#1947).
*
* Attach the handler at call time (not after the assertions) so there is no
* window in which the rejection can escape, then finish through