Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .claude/skills/pre-push-gate/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,25 @@ lowering the gate. See `/testing` for the acceptable reasons.
Also check the file is inside a gated directory — the web coverage `include` is
a whitelist, so a module placed outside it falls out of the gate silently.

### `verify:coverage-scope`

The last step of `coverage` (#2709). A client's `coverage-summary.json` is
missing files its own coverage `include`/`exclude` declares, so those files
were never judged by the per-file gate. The failure names the client and each
missing file. Two causes:

- **The run's glob matching changed** — typically a Vitest bump. The fix is
the client's `include` patterns (an absolute `path.join(dirname, …)` pattern
means the same thing under every matcher), never an entry that excuses the
files.
- **A file is in scope but the run legitimately drops it.** State it in
`KNOWN_OMISSIONS` in `scripts/verify-coverage-scope.mjs`, with the reason —
or, better, add it to the client's coverage `exclude`, which says the same
thing where the gate reads it.

"no `coverage-summary.json`" means the client's coverage did not run (or ran
without the `json-summary` reporter). Run `npm run coverage`.

### `smoke:web*`

⚠️ **An orphaned prod web server from a previous run fakes a rejection.** It
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,10 @@ jobs:
# This also covers the web integration project: web's `test:coverage`
# runs `--project=unit --project=integration --coverage`, so there is no
# separate integration step anywhere in this workflow.
#
# `coverage` ends with `verify:coverage-scope` (#2709), which fails if
# any client's report measured fewer files than its coverage `include`
# declares — the gate otherwise passes on whatever subset it was handed.
run: npm run coverage

# Publish the single `@modelcontextprotocol/inspector` package to npm on a
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ diagnose a failing gate — is the `testing` skill. These are the rules.
- **Root tooling**: a `scripts/*.mjs` helper with pure logic gets a sibling `*.test.mjs`. Keep that exact filename — `node --test` silently _skips_ a file its glob misses and still exits 0.
- **Render Ink components through the TUI's own `render`** (`clients/tui/__tests__/helpers/renderTui.tsx`), never `ink-testing-library`'s directly. It is the same function with every frame ANSI-stripped, which is what keeps an assertion on styled text from depending on the ambient environment: Ink writes styling _inside_ the styled run, so `<Text underline>I</Text>nfo` reaches the frame buffer with escapes between `I` and `nfo` and `toContain("Info")` fails. It only bites where chalk emits color — a developer whose shell exports `FORCE_COLOR` — so CI is green on a suite that is broken for them (#2207). A test that genuinely needs the raw bytes reads `stdout.lastFrame()` off the returned instance.
- **Render React components through `renderWithMantine`** (`src/test/renderWithMantine.tsx`); do not hand-roll a bare `MantineProvider`, which skips the project theme and the helper's options and drifts from every other test. Pass the `colorScheme` option to exercise a forced scheme rather than hand-rolling `defaultColorScheme`. Use `renderWithMantineTransitions` **only** when a test must assert mid-flight transition state, and read the long comment on the helper before changing anything about it.
- **The web coverage `include` is a whitelist.** It names `components`/`hooks`/`theme`/`lib`/`utils`/`server` plus the browser-consumed `core/*` runtime, so a module placed **outside** those directories falls out of the gate entirely, silently. Place new modules inside a gated directory. The documented exceptions — `src/App.tsx` and the `src/main.tsx` / `src/index.ts` bootstraps — are called out in a comment on the `include` array itself.
- **The web coverage `include` is a whitelist.** It names `components`/`hooks`/`theme`/`lib`/`utils`/`server` plus the browser-consumed `core/*` runtime, so a module placed **outside** those directories falls out of the gate entirely, silently. Place new modules inside a gated directory. The converse — the run measuring **fewer** files than the `include` declares, as web's did under vitest 5 (418 → 11, still green) — is not silent: `npm run verify:coverage-scope`, the last step of `coverage`, fails naming each missing file (#2709). Fix that by fixing the globs, never by excusing the files. The documented exceptions — `src/App.tsx` and the `src/main.tsx` / `src/index.ts` bootstraps — are called out in a comment on the `include` array itself.

### Test-gate timeouts are chosen values, stated once

Expand Down
3 changes: 3 additions & 0 deletions clients/web/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,9 @@ export default defineConfig(({ command }) => {
path.join(repoRoot, "core/mcp/remote/types.ts"),
path.join(repoRoot, "core/mcp/import/types.ts"),
"clients/web/server/types.ts",
// Type declarations only, so v8 reports no row for it at all — named
// here so `verify:coverage-scope` (#2709) is not left expecting one.
"src/utils/pendingReauth.ts",
// .d.ts files are declaration-only.
path.join(repoRoot, "**/*.d.ts"),
// inspectorClientEventTarget.ts is types + a single empty-body class
Expand Down
3 changes: 2 additions & 1 deletion docs/quality-gate.md

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
"verify:install-fresh": "node scripts/verify-install-fresh.mjs",
"verify:test-timeouts": "node scripts/verify-test-timeouts.mjs",
"verify:action-pins": "node scripts/verify-action-pins.mjs",
"verify:coverage-scope": "node scripts/verify-coverage-scope.mjs",
"validate:core": "npm run format:check:core && npm run format:check:scripts && npm run format:check:shared && npm run lint:core && npm run lint:shared",
"lint:core": "eslint \"core/**/*.{ts,tsx}\" --max-warnings 0",
"lint:shared": "eslint \"test-servers/src/**/*.{ts,tsx,mts,cts}\" vitest.shared.mts vitest.setup.shared.mts eslint.config.js --max-warnings 0",
Expand All @@ -95,7 +96,7 @@
"validate:tui": "cd clients/tui && npm run validate",
"validate:web": "cd clients/web && npm run validate",
"validate:launcher": "cd clients/launcher && npm run validate",
"coverage": "npm run coverage:web && npm run coverage:cli && npm run coverage:mcpdo && npm run coverage:tui && npm run coverage:launcher",
"coverage": "npm run coverage:web && npm run coverage:cli && npm run coverage:mcpdo && npm run coverage:tui && npm run coverage:launcher && npm run verify:coverage-scope",
"coverage:cli": "cd clients/cli && npm run test:coverage",
"coverage:mcpdo": "cd clients/mcpdo && npm run test:coverage",
"coverage:tui": "cd clients/tui && npm run test:coverage",
Expand Down
Loading
Loading