@ultimat3/cli 11.0.0 → 11.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CLAUDE.md CHANGED
@@ -256,6 +256,33 @@ that named the code in its `<PKG>_BORROWED_ERROR_CODES`. The docs check reads it
256
256
  framework's own `framework.manifest.json`, because a second scanner over a narrower file set is a
257
257
  manifest that claims completeness it does not have.
258
258
 
259
+ **A `code:` is a literal, a module-scope const in the same file, or a finding** — `As of 2026-08-23`,
260
+ and until then it was a literal or silence. `scanCodes` matched `code\s*[:=]\s*'X_…'`, so
261
+ `const STALE = 'X_DOC_PACKAGE_GRAPH_STALE'` followed by `code: STALE` — the DRY thing to write, and
262
+ what `scripts/package-map-graph.ts` really wrote — was a declaration to nobody: no manifest row, no
263
+ row demanded on `wiki/Error-Codes.md`, no entry for `bun run gate-codes`, and `x errors explain`
264
+ answering `X_ERROR_CODE_UNKNOWN` for a code the build throws. Silent, and in the **permissive**
265
+ direction: the DRYer the author, the less the gate saw (#277).
266
+
267
+ `scanCodeDeclarations` is that one pass, and it returns both halves. It resolves the identifier
268
+ against the module-scope consts of the **same file** — anchored at column 0, which is what makes it
269
+ module scope without a parser — and reports every name it cannot resolve as `X_ERROR_CODE_UNRESOLVED`
270
+ rather than skipping it, which is the whole point: a scanner that reads only what it likes enforces
271
+ only what it sees. `scanCodes` is its `.sites`, so the manifest, the docs check, `bun run gate-codes`
272
+ and `x errors explain` (through `scanCodeFixSites`, which resolves the same way) cannot see different
273
+ sets. Cross-file resolution was **refused** even though `fix-imports.ts` already does the harder
274
+ version for `fix:`: it would make the scan async for every caller, and the finding is the better
275
+ answer anyway — one file holds both the code and its only spelling.
276
+
277
+ Three shapes are deliberately not judged, each measured over the framework and both tracked apps
278
+ before the rule shipped. A name that resolves to something that is **not** a code is an answer, not
279
+ a gap (`const STATUS_NOT_FOUND = 404` in `@ultimat3/realtime`'s NATS fake, the one live instance). A
280
+ **table read** is not judged — `SEO_ERROR_CODES.metaMissing` is how `@ultimat3/seo` and
281
+ `@ultimat3/ui` raise all 18 of their codes, and the registry those literals live in already declares
282
+ them. A **lowercase** name is not judged: 164 sit at a `code:` position in this tree and every one is
283
+ a type annotation (`readonly code: string`) or a re-raise (`code: opts.code`). Measured on all three
284
+ roots: **0 findings**, so it enforces outright with no pin table.
285
+
259
286
  An empty `fix`, or a `fix` that says `check` / `make sure` / `try` / `see the docs` and names no
260
287
  command, call or file path, is `X_ERROR_FIX_INVALID`. A declared code the host's error reference
261
288
  does not name is `X_ERROR_CODE_UNDOCUMENTED` — `wiki/Error-Codes.md` here, nothing in a generated
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/cli",
3
- "version": "11.0.0",
3
+ "version": "11.1.0",
4
4
  "description": "The `x` binary: new, dev, build, verify, generate, db, mcp, doctor, deploy",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -37,33 +37,33 @@
37
37
  },
38
38
  "dependencies": {
39
39
  "@babel/core": "^7.28.4",
40
- "@ultimat3/action": "11.0.0",
41
- "@ultimat3/admin": "11.0.0",
42
- "@ultimat3/ai": "11.0.0",
43
- "@ultimat3/auth": "11.0.0",
44
- "@ultimat3/cache": "11.0.0",
45
- "@ultimat3/core": "11.0.0",
46
- "@ultimat3/db": "11.0.0",
47
- "@ultimat3/entity": "11.0.0",
48
- "@ultimat3/flags": "11.0.0",
49
- "@ultimat3/http": "11.0.0",
50
- "@ultimat3/i18n": "11.0.0",
51
- "@ultimat3/jobs": "11.0.0",
52
- "@ultimat3/mail": "11.0.0",
53
- "@ultimat3/manifest": "11.0.0",
54
- "@ultimat3/mcp": "11.0.0",
55
- "@ultimat3/money": "11.0.0",
56
- "@ultimat3/policy": "11.0.0",
57
- "@ultimat3/pwa": "11.0.0",
58
- "@ultimat3/query": "11.0.0",
59
- "@ultimat3/realtime": "11.0.0",
60
- "@ultimat3/render": "11.0.0",
61
- "@ultimat3/schema": "11.0.0",
62
- "@ultimat3/scraping": "11.0.0",
63
- "@ultimat3/seo": "11.0.0",
64
- "@ultimat3/storage": "11.0.0",
65
- "@ultimat3/testing": "11.0.0",
66
- "@ultimat3/time": "11.0.0",
40
+ "@ultimat3/action": "11.1.0",
41
+ "@ultimat3/admin": "11.1.0",
42
+ "@ultimat3/ai": "11.1.0",
43
+ "@ultimat3/auth": "11.1.0",
44
+ "@ultimat3/cache": "11.1.0",
45
+ "@ultimat3/core": "11.1.0",
46
+ "@ultimat3/db": "11.1.0",
47
+ "@ultimat3/entity": "11.1.0",
48
+ "@ultimat3/flags": "11.1.0",
49
+ "@ultimat3/http": "11.1.0",
50
+ "@ultimat3/i18n": "11.1.0",
51
+ "@ultimat3/jobs": "11.1.0",
52
+ "@ultimat3/mail": "11.1.0",
53
+ "@ultimat3/manifest": "11.1.0",
54
+ "@ultimat3/mcp": "11.1.0",
55
+ "@ultimat3/money": "11.1.0",
56
+ "@ultimat3/policy": "11.1.0",
57
+ "@ultimat3/pwa": "11.1.0",
58
+ "@ultimat3/query": "11.1.0",
59
+ "@ultimat3/realtime": "11.1.0",
60
+ "@ultimat3/render": "11.1.0",
61
+ "@ultimat3/schema": "11.1.0",
62
+ "@ultimat3/scraping": "11.1.0",
63
+ "@ultimat3/seo": "11.1.0",
64
+ "@ultimat3/storage": "11.1.0",
65
+ "@ultimat3/testing": "11.1.0",
66
+ "@ultimat3/time": "11.1.0",
67
67
  "babel-preset-solid": "^1.9.15"
68
68
  }
69
69
  }
@@ -26,6 +26,10 @@ export const CLI_OWNED_ERROR_CODES = [
26
26
  'X_ERROR_FIX_PATH_MISSING',
27
27
  'X_ERROR_CODE_UNDOCUMENTED',
28
28
  'X_ERROR_CODE_UNREGISTERED',
29
+ // The third: a `code:` the scan cannot read at all. `const STALE = 'X_…'` in another file and
30
+ // `code: STALE` here is invisible to every reader of the code set, and silence there is
31
+ // permissive — the DRYer the author, the less the gate sees (#277).
32
+ 'X_ERROR_CODE_UNRESOLVED',
29
33
  // Reported as `Finding`s rather than thrown, and unregistered until now because of it — so
30
34
  // `x errors explain X_TYPECHECK_FAILED` refused a code `x verify` had just printed. A finding
31
35
  // carries an `X_*` code to the same reader a throw does; the registry is what makes that code
@@ -171,6 +175,7 @@ export const CLI_ERROR_TITLES: Readonly<Record<CliOwnedErrorCode, string>> = {
171
175
  X_ERROR_FIX_PATH_MISSING: "an error's fix line cites a file this repository does not have",
172
176
  X_ERROR_CODE_UNDOCUMENTED: 'a shipped error code has no row in the error reference',
173
177
  X_ERROR_CODE_UNREGISTERED: 'the error reference documents a code no package registers',
178
+ X_ERROR_CODE_UNRESOLVED: 'an error code is written as a name this repository cannot resolve',
174
179
  X_STORAGE_UNWRITABLE: 'the storage disk this process needs cannot be written to',
175
180
  X_STORAGE_SECRET_DEV: 'upload grants would be signed with the shipped development key',
176
181
  X_CLI_UNEXPECTED: 'the CLI itself failed',
@@ -13,8 +13,8 @@ import { citedPathProblem, FILE_TOKEN_PATTERN } from './fix-path';
13
13
  import { scanFixSites } from './fix-scan';
14
14
  import type { Finding } from './output';
15
15
  import { eachSourceFile, isGenerated, isTest } from './source-files';
16
- import type { CodeSite, FixSite } from './ts-scan';
17
- import { isCodeRegistry, scanBorrowedCodes, scanCodes } from './ts-scan';
16
+ import type { CodeSite, FixSite, UnresolvedCodeSite } from './ts-scan';
17
+ import { isCodeRegistry, scanBorrowedCodes, scanCodeDeclarations, scanCodes } from './ts-scan';
18
18
 
19
19
  /** Advice, not instruction. The list is the one in `docs/architecture/04-error-contract.md`. */
20
20
  export const BANNED_PHRASES: readonly RegExp[] = [
@@ -259,6 +259,40 @@ export async function collectDeclaredCodes(root: string): Promise<readonly CodeS
259
259
  return [...sites.values()].map(([site]) => site).sort((a, b) => a.code.localeCompare(b.code));
260
260
  }
261
261
 
262
+ /**
263
+ * A `code:` this scan could not turn into a code. Its own finding rather than a silent skip, which
264
+ * is the whole of #277: `const STALE = 'X_DOC_PACKAGE_GRAPH_STALE'` and then `code: STALE` is the
265
+ * DRY thing to write, `scripts/package-map-graph.ts` wrote it, and the code went into no manifest,
266
+ * demanded no row on the reference, was exempt from `bun run gate-codes` and could not be explained
267
+ * — every reader silent, and every one of them permissive. Resolution closes the same-file case;
268
+ * this closes the rest, because a scanner that reads only what it likes enforces only what it sees.
269
+ */
270
+ const unresolvedCodeFinding = (site: UnresolvedCodeSite): Finding => ({
271
+ code: 'X_ERROR_CODE_UNRESOLVED',
272
+ cause: `the code at ${site.at}:${site.line} is the name ${site.name}, and no module-scope const in that file gives it a value — so the manifest, the reference page and "x errors explain" are all blind to whatever code it holds`,
273
+ fix: `write the X_* code as a string literal at ${site.at}:${site.line}, or declare it as a module-scope const in that same file`,
274
+ docs: ERROR_DOCS_URL,
275
+ at: `${site.at}:${site.line}`,
276
+ });
277
+
278
+ /**
279
+ * The other half of `collectDeclaredCodes`, on its own walk rather than folded into that one: this
280
+ * asks whether a file's codes are READABLE, and the collector's callers — `x manifest`, the gate's
281
+ * `errors` step, `bun run gate-codes` — want the codes and not the findings. It runs wherever
282
+ * source does, with no reference page to check against, which is why it is not a host check.
283
+ */
284
+ export async function checkErrorCodeResolution(root: string): Promise<readonly Finding[]> {
285
+ const findings: Finding[] = [];
286
+ for await (const source of eachSourceFile(root)) {
287
+ if (isTest(source) || isGenerated(source)) continue;
288
+ const text = await Bun.file(join(root, source)).text();
289
+ for (const site of scanCodeDeclarations(text, source).unresolved) {
290
+ findings.push(unresolvedCodeFinding(site));
291
+ }
292
+ }
293
+ return findings;
294
+ }
295
+
262
296
  /**
263
297
  * Every `X_*` code shipped source declares must appear on the repo's error reference. The page is
264
298
  * the host repo's to name — a framework monorepo publishes one, a generated app does not — which
package/src/index.ts CHANGED
@@ -130,6 +130,7 @@ export {
130
130
  COMMAND_TOKENS,
131
131
  checkErrorCodeDocs,
132
132
  checkErrorCodeRegistry,
133
+ checkErrorCodeResolution,
133
134
  checkErrorFixes,
134
135
  checkErrorFixReport,
135
136
  collectDeclaredCodes,
@@ -283,11 +284,19 @@ export { belongsToType, discoverTests, sampleFiles } from './test-select';
283
284
  export type { ReproduceOptions, RunShardsOptions, Shard } from './test-shards';
284
285
  export { planShards, reproduceFor, runShards, shardArgs } from './test-shards';
285
286
  export { availableCpus, defaultWorkers, WORKER_CEILING } from './test-workers';
286
- export type { CodeFixSite, CodeSite, FixSite, SourceSite } from './ts-scan';
287
+ export type {
288
+ CodeFixSite,
289
+ CodeScan,
290
+ CodeSite,
291
+ FixSite,
292
+ SourceSite,
293
+ UnresolvedCodeSite,
294
+ } from './ts-scan';
287
295
  export {
288
296
  isCodeRegistry,
289
297
  maskLiterals,
290
298
  scanBorrowedCodes,
299
+ scanCodeDeclarations,
291
300
  scanCodeFixSites,
292
301
  scanCodes,
293
302
  stripComments,
package/src/mcp-errors.ts CHANGED
@@ -62,6 +62,8 @@ const CLI_FIXES: Readonly<Record<CliErrorCode, string>> = {
62
62
  X_ERROR_CODE_UNDOCUMENTED: 'x verify --json # the finding names the code and the missing page',
63
63
  X_ERROR_CODE_UNREGISTERED:
64
64
  'x errors list --json # register the code in its package src/errors.ts, or move its row under "Reserved codes"',
65
+ X_ERROR_CODE_UNRESOLVED:
66
+ 'x verify --json # the finding names the file, the line and the name it could not resolve',
65
67
  X_CLI_UNEXPECTED: 'x doctor --json',
66
68
  X_TYPECHECK_FAILED: 'bunx tsc -b --pretty false',
67
69
  X_LINT_FAILED: 'bunx biome check --write .',
package/src/ts-scan.ts CHANGED
@@ -19,6 +19,17 @@ export interface CodeSite extends SourceSite {
19
19
  readonly code: string;
20
20
  }
21
21
 
22
+ export interface UnresolvedCodeSite extends SourceSite {
23
+ /** The identifier exactly as written at the `code:` position. */
24
+ readonly name: string;
25
+ }
26
+
27
+ /** One file's codes, and the names at a `code:` position this scan could not turn into one. */
28
+ export interface CodeScan {
29
+ readonly sites: readonly CodeSite[];
30
+ readonly unresolved: readonly UnresolvedCodeSite[];
31
+ }
32
+
22
33
  // `ReadonlySet`, so a consumer cannot mutate what every scan in this package reads.
23
34
  export const QUOTES: ReadonlySet<string> = new Set(["'", '"', '`']);
24
35
  export const OPENERS: ReadonlySet<string> = new Set(['(', '[', '{']);
@@ -202,27 +213,111 @@ const CODE_AT_KEY = /\bcode\s*[:=]\s*(['"`])(X_[A-Z0-9_]+)\1/g;
202
213
  const CODE_LITERAL = /(['"`])(X_[A-Z0-9_]+)\1/g;
203
214
  const CODE_KEY = /^[\t ]*(X_[A-Z0-9_]+)\s*:/gm;
204
215
 
216
+ /**
217
+ * A `code` KEY, and never a member assignment: `found.code = SOMETHING` projects somebody else's
218
+ * code and declares none. The literal form above keeps its looser `\b` deliberately — a scanner
219
+ * that stopped collecting a code it has collected for four majors would shrink the manifest.
220
+ */
221
+ const CODE_KEY_POSITION = /(?<![.\w$])code\s*[:=]\s*/g;
222
+
223
+ /** Cheap enough to run on every file, so the masking pass below is paid only where it can pay. */
224
+ const HAS_CODE_IDENTIFIER = /(?<![.\w$])code\s*[:=]\s*[A-Za-z_$]/;
225
+
226
+ /** Sticky: the value expression is read at an exact offset, never out of a slice that may cut. */
227
+ const VALUE_IDENTIFIER = /([A-Za-z_$][\w$]*)\s*([.([]?)/y;
228
+
229
+ /**
230
+ * A module-scope `const NAME = 'X_…'`, and the names of every other module-scope const. Anchored
231
+ * at column 0, which is what makes it module scope without a parser: a `const` inside a function
232
+ * can be shadowed by another in a sibling scope, and a resolver that picked one of them would be
233
+ * guessing. The second set is the answer "that name IS declared here, and it is not a code" —
234
+ * `const STATUS_NOT_FOUND = 404` in `@ultimat3/realtime`'s NATS fake is the live instance, and a
235
+ * rule that reported it would be a rule the reader has to argue with.
236
+ */
237
+ const CODE_CONST =
238
+ /^(?:export\s+)?const\s+([A-Za-z_$][\w$]*)\s*(?::[^=\n]*)?=\s*(['"`])(X_[A-Z0-9_]+)\2/gm;
239
+ const MODULE_CONST = /^(?:export\s+)?const\s+([A-Za-z_$][\w$]*)\s*[:=]/gm;
240
+
241
+ /** House shape for a constant. A lowercase name at a `code:` is a type annotation or a re-raise. */
242
+ const CODE_CONSTANT_NAME = /^[A-Z][A-Z0-9_]+$/;
243
+
244
+ interface ModuleConstants {
245
+ /** Name → the code it holds. */
246
+ readonly codes: ReadonlyMap<string, string>;
247
+ /** Every module-scope const name, code-valued or not. */
248
+ readonly names: ReadonlySet<string>;
249
+ }
250
+
251
+ function moduleConstants(text: string): ModuleConstants {
252
+ const codes = new Map<string, string>();
253
+ const names = new Set<string>();
254
+ for (const match of text.matchAll(MODULE_CONST)) names.add(match[1] as string);
255
+ for (const match of text.matchAll(CODE_CONST)) codes.set(match[1] as string, match[3] as string);
256
+ return { codes, names };
257
+ }
258
+
259
+ /**
260
+ * The bare identifier a key's value is, or `undefined` when the value is anything else. A member
261
+ * read, an index and a call are all refused: `SEO_ERROR_CODES.metaMissing` is how two packages
262
+ * raise every code they own, the registry branch below already collects those literals, and
263
+ * judging the read would report eighteen working sites as broken.
264
+ */
265
+ function valueIdentifier(masked: string, from: number): string | undefined {
266
+ VALUE_IDENTIFIER.lastIndex = from;
267
+ const match = VALUE_IDENTIFIER.exec(masked);
268
+ return match === null || match[2] !== '' ? undefined : match[1];
269
+ }
270
+
205
271
  /**
206
272
  * Codes this file declares: every `code:` / `code =` throw site, plus — in a package's own code
207
273
  * registry — every entry of its code list or title table, whichever shape it uses. A registry is
208
274
  * the only place a bare `X_*` literal is a declaration; anywhere else it is a reference (an env
209
275
  * var named `X_BUILD_ID`, an HTTP status map keyed by code) and collecting it would invent a code.
276
+ *
277
+ * A `code:` written as an IDENTIFIER is resolved against the module-scope consts of the same file,
278
+ * and reported as `unresolved` when nothing there gives it a value (#277). Both halves matter and
279
+ * neither is optional: `const STALE = 'X_DOC_PACKAGE_GRAPH_STALE'` is what a DRY author writes, and
280
+ * a scan that skipped it silently left the code out of the manifest, out of `wiki/Error-Codes.md`'s
281
+ * demanded rows, out of `bun run gate-codes` and out of `x errors explain` — permissive, and quiet.
282
+ * The identifier half reads the MASKED text: `packages/cli/src/templates/` emits app source by the
283
+ * dozen inside template literals, and a `code: STALE` in one of those is text, not a declaration.
210
284
  */
211
- export function scanCodes(source: string, at: string): readonly CodeSite[] {
285
+ export function scanCodeDeclarations(source: string, at: string): CodeScan {
212
286
  const text = stripComments(source);
213
287
  const lineAt = lineIndex(text);
214
288
  const sites = new Map<string, CodeSite>();
289
+ const unresolved: UnresolvedCodeSite[] = [];
215
290
  const add = (code: string, index: number): void => {
216
291
  if (!sites.has(code)) sites.set(code, { at, line: lineAt(index), code });
217
292
  };
218
293
  for (const match of text.matchAll(CODE_AT_KEY)) add(match[2] as string, match.index);
294
+ if (HAS_CODE_IDENTIFIER.test(text)) {
295
+ const masked = maskLiterals(source);
296
+ const constants = moduleConstants(text);
297
+ for (const key of masked.matchAll(CODE_KEY_POSITION)) {
298
+ const name = valueIdentifier(masked, key.index + key[0].length);
299
+ if (name === undefined) continue;
300
+ const code = constants.codes.get(name);
301
+ if (code !== undefined) add(code, key.index);
302
+ else if (!constants.names.has(name) && CODE_CONSTANT_NAME.test(name)) {
303
+ unresolved.push({ at, line: lineAt(key.index), name });
304
+ }
305
+ }
306
+ }
219
307
  if (isCodeRegistry(text)) {
220
308
  for (const match of text.matchAll(CODE_LITERAL)) add(match[2] as string, match.index);
221
309
  for (const match of text.matchAll(CODE_KEY)) add(match[1] as string, match.index);
222
310
  }
223
- return [...sites.values()];
311
+ return { sites: [...sites.values()], unresolved };
224
312
  }
225
313
 
314
+ /**
315
+ * The codes alone, for every caller that has no report to attach a finding to. One scanner, one
316
+ * answer: the manifest, the docs check, `bun run gate-codes` and `x errors explain` all read this.
317
+ */
318
+ export const scanCodes = (source: string, at: string): readonly CodeSite[] =>
319
+ scanCodeDeclarations(source, at).sites;
320
+
226
321
  export interface CodeFixSite extends CodeSite {
227
322
  /**
228
323
  * The fix literal exactly as written, `${…}` included. Absent when the throw site builds its
@@ -250,6 +345,8 @@ function soleLiteral(
250
345
  return found.length === 1 ? found[0] : undefined;
251
346
  }
252
347
 
348
+ const CODE_NAME = /^X_[A-Z0-9_]+$/;
349
+
253
350
  /**
254
351
  * Every `X_*` code paired with the `fix:` written beside it — in the SAME object literal, which is
255
352
  * the whole rule. `new UltimateError({ code, cause, fix })` is the one shape this framework raises
@@ -263,6 +360,15 @@ function soleLiteral(
263
360
  export function scanCodeFixSites(source: string, at: string): readonly CodeFixSite[] {
264
361
  const masked = maskLiterals(source);
265
362
  const lineAt = lineIndex(masked);
363
+ // Lazily, because most files hold no `code:` at all and stripping is a whole extra pass over
364
+ // the text. Same resolver `scanCodeDeclarations` reads, so `x errors explain` can never see a
365
+ // smaller set of throw sites than the manifest does.
366
+ let constants: ModuleConstants | undefined;
367
+ const constantCode = (name: string | undefined): string | undefined => {
368
+ if (name === undefined) return undefined;
369
+ constants ??= moduleConstants(stripComments(source));
370
+ return constants.codes.get(name);
371
+ };
266
372
  const keys = new Map<number, { readonly kind: 'code' | 'fix'; readonly from: number }>();
267
373
  for (const key of masked.matchAll(CODE_OR_FIX_KEY)) {
268
374
  keys.set(key.index, {
@@ -293,11 +399,16 @@ export function scanCodeFixSites(source: string, at: string): readonly CodeFixSi
293
399
  const scope = stack.at(-1);
294
400
  if (key === undefined || scope === undefined) continue;
295
401
  const literal = soleLiteral(masked, source, key.from, lineAt);
296
- if (literal === undefined) continue;
297
402
  if (key.kind === 'fix') {
298
- if (!fixes.has(scope)) fixes.set(scope, literal.fix);
299
- } else if (/^X_[A-Z0-9_]+$/.test(literal.fix) && !codes.has(scope)) {
300
- codes.set(scope, { at, line: literal.line, code: literal.fix });
403
+ if (literal !== undefined && !fixes.has(scope)) fixes.set(scope, literal.fix);
404
+ continue;
405
+ }
406
+ // A fix has no second reading, so it stays literal-only; a code has exactly one, which is the
407
+ // module-scope const its own file declares it in.
408
+ const code =
409
+ literal === undefined ? constantCode(valueIdentifier(masked, key.from)) : literal.fix;
410
+ if (code !== undefined && CODE_NAME.test(code) && !codes.has(scope)) {
411
+ codes.set(scope, { at, line: literal?.line ?? lineAt(key.from), code });
301
412
  }
302
413
  }
303
414
  return [...codes].map(([scope, site]) => {
@@ -24,7 +24,7 @@ import { checkBudgets, readBuildStats } from './budgets';
24
24
  import { checkDestructiveMigrations } from './db-destructive';
25
25
  import { checkDocumentStyles, documentSurfaces } from './document-styles';
26
26
  import { checkSourceDrift } from './drift';
27
- import { checkErrorFixReport } from './error-contract';
27
+ import { checkErrorCodeResolution, checkErrorFixReport } from './error-contract';
28
28
  import { guardFindings } from './guards';
29
29
  import { catalogFindings } from './i18n-registration';
30
30
  import { liveRouteFindings } from './live-routes';
@@ -125,7 +125,15 @@ export const VERIFY_STEPS: readonly VerifyStep[] = [
125
125
  // it does not have. "checked 412, could not read 27" is what a reader can act on.
126
126
  async run(ctx) {
127
127
  const report = await checkErrorFixReport(ctx.root);
128
- const findings = [...report.findings, ...(await hostFindings(ctx, 'errors'))];
128
+ // The third rule on this step, and the one that is about the code rather than the fix: a
129
+ // `code:` reached through a name nothing in its own file declares is a code no reader of the
130
+ // set can see — not the manifest, not the reference page's coverage rule, not
131
+ // `x errors explain`. It runs here because it needs source and nothing else (#277).
132
+ const findings = [
133
+ ...report.findings,
134
+ ...(await checkErrorCodeResolution(ctx.root)),
135
+ ...(await hostFindings(ctx, 'errors')),
136
+ ];
129
137
  return {
130
138
  ...fromFindings(findings),
131
139
  output: msg('cli.verify.fixCoverage', {