@ultimat3/cli 9.0.0 → 10.0.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.
Files changed (53) hide show
  1. package/package.json +26 -26
  2. package/src/affected.ts +0 -3
  3. package/src/app-boundaries.ts +4 -5
  4. package/src/app-env.ts +7 -2
  5. package/src/browser-launcher.ts +0 -2
  6. package/src/budgets.ts +4 -3
  7. package/src/cmd-build.ts +2 -2
  8. package/src/cmd-db-branch.ts +2 -2
  9. package/src/cmd-deploy.ts +14 -3
  10. package/src/cmd-docs.ts +2 -1
  11. package/src/cmd-doctor.ts +3 -5
  12. package/src/cmd-env.ts +2 -2
  13. package/src/cmd-fix.ts +2 -4
  14. package/src/cmd-new.ts +2 -2
  15. package/src/cmd-shot.ts +22 -2
  16. package/src/db-finding.ts +2 -2
  17. package/src/db-seed.ts +0 -3
  18. package/src/dev-cache.ts +12 -5
  19. package/src/dev-lock.ts +8 -7
  20. package/src/dev-runtime.ts +11 -3
  21. package/src/dev-storage.ts +8 -2
  22. package/src/dev-sync.ts +37 -2
  23. package/src/document-styles.ts +2 -1
  24. package/src/drift.ts +3 -2
  25. package/src/error-codes.ts +9 -2
  26. package/src/error-contract.ts +5 -5
  27. package/src/errors.ts +1 -29
  28. package/src/flag-reads.ts +2 -2
  29. package/src/generate-write.ts +3 -2
  30. package/src/guards.ts +4 -4
  31. package/src/index.ts +2 -6
  32. package/src/island-bundle.ts +32 -4
  33. package/src/island-routes.ts +7 -1
  34. package/src/mcp-errors.ts +2 -2
  35. package/src/metrics-endpoint.ts +0 -2
  36. package/src/output.ts +2 -2
  37. package/src/prerender.ts +32 -19
  38. package/src/static-report.ts +41 -3
  39. package/src/templates/scaffold-container.ts +12 -0
  40. package/src/templates/scaffold-docs.ts +1 -1
  41. package/src/templates/scaffold-domain-package.ts +3 -1
  42. package/src/templates/scaffold-repo.ts +16 -8
  43. package/src/templates/slice-foundation.ts +3 -5
  44. package/src/test-shards.ts +2 -2
  45. package/src/tsconfig-references.ts +2 -2
  46. package/src/verify-checks.ts +3 -2
  47. package/src/verify-floor.ts +8 -6
  48. package/src/verify-run.ts +2 -2
  49. package/src/verify-step.ts +2 -1
  50. package/src/verify-test-run.ts +2 -2
  51. package/src/workspace-checks.ts +10 -12
  52. package/src/workspace-graph.ts +3 -2
  53. package/src/write-line.ts +7 -1
package/src/prerender.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  // only which routes qualify and where the bytes land.
5
5
 
6
6
  import { join } from 'node:path';
7
- import { renderThrowable } from '@ultimat3/core';
7
+ import { createContext, renderThrowable, runWithContext } from '@ultimat3/core';
8
8
  import type { RouteEntry } from '@ultimat3/render';
9
9
  import { routeEntries } from '@ultimat3/render';
10
10
  import { renderStatic } from '@ultimat3/render/server';
@@ -15,9 +15,13 @@ import { measureDocumentJs, writeBuildStats } from './budgets';
15
15
  import { routeDocument } from './dev-render';
16
16
  import type { IslandBundle } from './island-bundle';
17
17
  import { buildIslands, writeIslands } from './island-bundle';
18
- import type { SkippedRoute } from './static-report';
18
+ import type { SkippedRoute, UnmeasuredRoute } from './static-report';
19
19
  import { skippedRoute, skipReasonFor, writeStaticReport } from './static-report';
20
20
 
21
+ // Re-exported, never re-declared: `static-report.ts` owns the shape because the report on disk
22
+ // carries it, and this file already imports that module.
23
+ export type { UnmeasuredRoute };
24
+
21
25
  /**
22
26
  * `static` only. `isr` revalidates and `ssr`/`stream` need a process, so writing any of them to
23
27
  * disk would publish a page whose staleness nothing can correct — and the route already declared
@@ -46,12 +50,6 @@ export interface PrerenderedPage {
46
50
  readonly bytes: number;
47
51
  }
48
52
 
49
- /** A route that declared a budget and could not be rendered here, and what stopped it. */
50
- export interface UnmeasuredRoute {
51
- readonly path: string;
52
- readonly reason: string;
53
- }
54
-
55
53
  export interface PrerenderReport {
56
54
  readonly out: string;
57
55
  readonly buildId: string;
@@ -130,6 +128,22 @@ export async function prerenderSite(options: PrerenderOptions): Promise<Prerende
130
128
  const islands = await buildIslands(options.root);
131
129
  await writeIslands(islands, options.out);
132
130
 
131
+ // Every render below goes through `routeDocument`, which is the function a REQUEST reaches — and
132
+ // a request arrives inside `runWithContext`, installed by the HTTP pipeline (`dev-render.ts`).
133
+ // Called bare, any route whose component, `load` or `meta` reads `useContext()` threw
134
+ // `X_NO_CONTEXT`: measured against `examples/dummy`, `/posts/new` and `/settings` were filed
135
+ // unmeasured for that reason alone, and a `render: 'static'` route reading it failed the whole
136
+ // build. One context for the build, `role: 'web'` because that is the role serving these
137
+ // documents, and this build's own id so a component reading `ctx.buildId` stamps the artifact
138
+ // with the id the report and the stats carry.
139
+ const ctx = createContext({ role: 'web', buildId });
140
+ const document = (entry: RouteEntry, data: { url: string; params: Record<string, string> }) =>
141
+ runWithContext(ctx, () =>
142
+ routeDocument(entry, data, {
143
+ resolveIsland: (file: string) => islands.resolverFor(file),
144
+ }),
145
+ );
146
+
133
147
  for (const entry of routeEntries()) {
134
148
  const facts = { surface: entry.surface, render: entry.config.render, route: entry.path };
135
149
  const reason = skipReasonFor(facts);
@@ -141,11 +155,10 @@ export async function prerenderSite(options: PrerenderOptions): Promise<Prerende
141
155
  // routes it never used to touch would be a worse regression than the gap it closes. A route
142
156
  // that will not render here is reported, gets no stats entry, and stays `X_BUDGET_UNMEASURED`.
143
157
  try {
144
- const html = await routeDocument(
145
- entry,
146
- { url: new URL(entry.path, origin).href, params: {} },
147
- { resolveIsland: (file: string) => islands.resolverFor(file) },
148
- );
158
+ const html = await document(entry, {
159
+ url: new URL(entry.path, origin).href,
160
+ params: {},
161
+ });
149
162
  const measured = await measureDocumentJs(html, options.out);
150
163
  const chain = heaviestSource(islands, measured.entries);
151
164
  routes.push({
@@ -162,12 +175,7 @@ export async function prerenderSite(options: PrerenderOptions): Promise<Prerende
162
175
  }
163
176
  const artifacts = await renderStatic(
164
177
  entry,
165
- ({ path, params }) =>
166
- routeDocument(
167
- entry,
168
- { url: new URL(path, origin).href, params },
169
- { resolveIsland: (file: string) => islands.resolverFor(file) },
170
- ),
178
+ ({ path, params }) => document(entry, { url: new URL(path, origin).href, params }),
171
179
  { buildId },
172
180
  );
173
181
  // `enumeratePrerender` answers `[]` for a dynamic route with no `prerender()`, so a
@@ -209,6 +217,11 @@ export async function prerenderSite(options: PrerenderOptions): Promise<Prerende
209
217
  buildId,
210
218
  emitted: pages.map((page) => ({ route: page.route, path: page.path, file: page.file })),
211
219
  skipped,
220
+ // The list `X_BUDGET_UNMEASURED`'s `fix:` cites by name. It rode home on the in-process
221
+ // `PrerenderReport` and nowhere else, and `cmd-build.ts` discards a successful subprocess's
222
+ // stdout — so the one command the finding tells an author to run printed no `unmeasured` key
223
+ // and no reason. Written into the report is what makes the instruction true.
224
+ unmeasured,
212
225
  });
213
226
  return {
214
227
  out: options.out,
@@ -48,6 +48,19 @@ export type SkippedRoute = RouteFacts & {
48
48
  readonly why: string;
49
49
  };
50
50
 
51
+ /**
52
+ * A route that declared a budget and could not be rendered here, and what stopped it.
53
+ *
54
+ * Declared HERE and not in `prerender.ts`, though `prerenderSite` is what fills it: this file is
55
+ * the report's shape and `prerender.ts` imports it, so the other direction is a cycle. `prerender`
56
+ * re-exports the name for its own callers.
57
+ */
58
+ export type UnmeasuredRoute = {
59
+ /** The DECLARED path, as `X_BUDGET_UNMEASURED`'s `at:` spells it, so the two rows join. */
60
+ readonly path: string;
61
+ readonly reason: string;
62
+ };
63
+
51
64
  /** One HTML file in the artifact, and the declared route that produced it. */
52
65
  export type EmittedPage = {
53
66
  readonly route: string;
@@ -62,6 +75,15 @@ export type StaticReport = {
62
75
  readonly buildId: string;
63
76
  readonly emitted: readonly EmittedPage[];
64
77
  readonly skipped: readonly SkippedRoute[];
78
+ /**
79
+ * Every budgeted route this build could not weigh, with the reason. `X_BUDGET_UNMEASURED`'s own
80
+ * `fix:` cites this list by name — `x build --target static --json # its "unmeasured" list says
81
+ * why` — and until it was written here the list existed only on the in-process `PrerenderReport`
82
+ * and reached no `x` command's output at all: `cmd-build.ts` discards a successful subprocess's
83
+ * stdout. A `fix:` naming a key nothing prints is axiom 4 inverted at the step meant to unblock
84
+ * the reader.
85
+ */
86
+ readonly unmeasured: readonly UnmeasuredRoute[];
65
87
  };
66
88
 
67
89
  /**
@@ -139,6 +161,9 @@ const isSkipped = (value: unknown): value is SkippedRoute =>
139
161
  typeof value['why'] === 'string' &&
140
162
  inDomain(SKIP_REASONS, value['reason']);
141
163
 
164
+ const isUnmeasured = (value: unknown): value is UnmeasuredRoute =>
165
+ isRecord(value) && typeof value['path'] === 'string' && typeof value['reason'] === 'string';
166
+
142
167
  const isEmitted = (value: unknown): value is EmittedPage =>
143
168
  isRecord(value) &&
144
169
  typeof value['route'] === 'string' &&
@@ -152,13 +177,20 @@ const isEmitted = (value: unknown): value is EmittedPage =>
152
177
  */
153
178
  export function parseStaticReport(value: unknown): StaticReport | undefined {
154
179
  if (!isRecord(value)) return undefined;
155
- const { target, out, buildId, emitted, skipped } = value;
180
+ const { target, out, buildId, emitted, skipped, unmeasured } = value;
156
181
  if (target !== 'static' || typeof out !== 'string' || typeof buildId !== 'string') {
157
182
  return undefined;
158
183
  }
159
184
  if (!Array.isArray(emitted) || !emitted.every(isEmitted)) return undefined;
160
185
  if (!Array.isArray(skipped) || !skipped.every(isSkipped)) return undefined;
161
- return { target, out, buildId, emitted, skipped };
186
+ // OPTIONAL on the way in, total on the way out. `.x/` survives a checkout of an older commit,
187
+ // and a report written before this field existed has to read as "nothing unmeasured" — refusing
188
+ // it would answer `X_BUDGET_UNMEASURED` for every route on a build that had weighed them all.
189
+ // Present and malformed is still no report, exactly as a malformed skip row is.
190
+ if (unmeasured !== undefined && (!Array.isArray(unmeasured) || !unmeasured.every(isUnmeasured))) {
191
+ return undefined;
192
+ }
193
+ return { target, out, buildId, emitted, skipped, unmeasured: unmeasured ?? [] };
162
194
  }
163
195
 
164
196
  export async function writeStaticReport(root: string, report: StaticReport): Promise<string> {
@@ -198,7 +230,9 @@ export async function removeStaticReport(root: string): Promise<void> {
198
230
  export function staticReportData(report: StaticReport | undefined): Record<string, JsonValue> {
199
231
  // Never `{ ...report }`: `out` is an absolute build path and `buildId` is this run's, neither of
200
232
  // which the inventory is about — `data` already carries `artifact` and the build's own id.
201
- return report === undefined ? {} : { emitted: report.emitted, skipped: report.skipped };
233
+ return report === undefined
234
+ ? {}
235
+ : { emitted: report.emitted, skipped: report.skipped, unmeasured: report.unmeasured };
202
236
  }
203
237
 
204
238
  /**
@@ -209,6 +243,10 @@ export function renderStaticReport(report: StaticReport): readonly string[] {
209
243
  const rows: readonly (readonly string[])[] = [
210
244
  ...report.emitted.map((page) => ['emitted', page.route, page.file]),
211
245
  ...report.skipped.map((route) => ['skipped', route.route, route.why]),
246
+ // The same three columns, so the terminal and `--json` carry the same three lists. A route
247
+ // whose budget could not be weighed is invisible in `emitted` and, when it also rendered, in
248
+ // `skipped` too — and it is the row `X_BUDGET_UNMEASURED` sends its reader here to read.
249
+ ...report.unmeasured.map((route) => ['unmeasured', route.path, route.reason]),
212
250
  ];
213
251
  const widths = [0, 1].map((index) =>
214
252
  Math.max(...rows.map((row) => (row[index] ?? '').length), 0),
@@ -197,6 +197,18 @@ services:
197
197
  deploy: { replicas: 1 } # scales on concurrent websockets, no sticky sessions — pinned by the port
198
198
  # The sync role binds PORT + 1. PORT is unset here, so it is 3000 and this listens on 3001.
199
199
  ports: ['3001:3001']
200
+ # ...which is why the image's own HEALTHCHECK cannot be inherited here. It fetches $PORT —
201
+ # 3000 — and this role never binds it, so the container reports \`unhealthy\` from
202
+ # \`start_period\` onward and never recovers, and anything gated on \`sync: service_healthy\`
203
+ # would never start. Literal 3001 rather than an expression, for the same reason \`ports:\`
204
+ # above is literal: PORT is unset in this file, and two ways of saying one number drift.
205
+ # \`docker/helm\` states the same rule as \`PORT = .port - 1\`.
206
+ healthcheck:
207
+ test: ['CMD', 'bun', '--eval', "fetch('http://127.0.0.1:3001/readyz').then(r=>process.exit(r.ok?0:1),()=>process.exit(1))"]
208
+ interval: 10s
209
+ timeout: 3s
210
+ start_period: 30s
211
+ retries: 3
200
212
 
201
213
  worker:
202
214
  <<: *image
@@ -58,7 +58,7 @@ file in it is deletable.
58
58
 
59
59
  const readme = (app: NameSet): string => `# ${app.pascal}
60
60
 
61
- Built with [Ultimate](https://ultimate.dev). Bun-only, Postgres, SolidJS.
61
+ Built with [Ultimate](https://github.com/developerz-ai/ultimate). Bun-only, Postgres, SolidJS.
62
62
 
63
63
  ## 🚀 Start
64
64
 
@@ -31,6 +31,9 @@ export type Role = (typeof ROLES)[number];
31
31
  * Never a bare Error: an agent reading the failure needs the code, the cause and the exact command
32
32
  * that resolves it. Adding across currencies is a bug in the caller's data, not a runtime hiccup,
33
33
  * so the fix names the conversion that has to happen first.
34
+ *
35
+ * No \`docs:\`: \`UltimateError\` resolves the link from the code's registered descriptor, so it has
36
+ * one home. A per-code URL written here is a page that does not exist.
34
37
  */
35
38
  export class ${app.pascal}CurrencyMismatchError extends UltimateError {
36
39
  constructor(input: { readonly left: string; readonly right: string }) {
@@ -38,7 +41,6 @@ export class ${app.pascal}CurrencyMismatchError extends UltimateError {
38
41
  code: '${currencyCode(app)}',
39
42
  cause: \`cannot add \${input.left} to \${input.right}: Money is only additive inside one currency\`,
40
43
  fix: \`bun test packages/domain/src/index.test.ts # add() is defined only inside one currency — convert \${input.right} to \${input.left} before calling it\`,
41
- docs: 'https://ultimate.dev/errors/${currencyCode(app)}',
42
44
  });
43
45
  }
44
46
  }
@@ -123,13 +123,21 @@ const envDeclaration = (): string => `${envSchemaSource()}
123
123
  export const env = defineEnv(envSchema);`;
124
124
 
125
125
  /**
126
- * No `installPrompt`, no `afterSignInPath`, no `modelEnv`. All three were declared by
127
- * `defineConfig`, defaulted by it, and read by NO file; all three are DELETED from
128
- * `packages/core/src/config.ts` as of 2026-08-22, whose header now records the removal rather than
129
- * the marker this comment used to cite. So scaffolding one is no longer a switch with no wire — it
130
- * is `TS2353` in the generated app's first `x verify`. The note belongs HERE rather than in the
131
- * emitted file: an app author has no use for a comment about keys their config does not name.
132
- * `scaffold-config.test.ts` is what keeps them from growing back.
126
+ * No `installPrompt`, no `afterSignInPath`, no `modelEnv`, and no `realtime.tier`. All four were
127
+ * declared by `defineConfig`, defaulted by it, and read by NO file; all four are DELETED from
128
+ * `packages/core/src/config.ts` — the first three as of 2026-08-22, `tier` on 2026-08-23 — whose
129
+ * header now records the removals rather than the marker this comment used to cite. So scaffolding
130
+ * one is no longer a switch with no wire: it is `TS2353` in the generated app's first `x verify`,
131
+ * which is CI's `scaffold-smoke` job. The note belongs HERE rather than in the emitted file: an app
132
+ * author has no use for a comment about keys their config does not name.
133
+ *
134
+ * `tier` is the one that got through, and it says what the list was worth. It accepted
135
+ * `'channels' | 'live-queries' | 'local-first'`, so `'local-first'` read as a durable client store
136
+ * that does not exist (`createOpfsLocalStore` throws `X_NOT_IMPLEMENTED`) — silent, and shaped like
137
+ * a capability. Which realtime tier an app is on is decided by what it DECLARES: a `channel()`
138
+ * topic, a `live: true` query, a local store. `scaffold-config.test.ts` no longer holds a list of
139
+ * dead names to remember; it resolves every key path this literal writes against what `defineConfig`
140
+ * really returns, so the fourteenth deletion fails there with no edit here.
133
141
  */
134
142
  const appConfig = (
135
143
  app: NameSet,
@@ -154,7 +162,7 @@ export const config = defineConfig({
154
162
  cache: { tiers: ['request-memo', 'lru'] },
155
163
  jobs: { queues: ['${app.kebab}-default'], concurrency: 4 },
156
164
  // In-process transport by default; set urlEnv and transport: 'nats' to scale past one node.
157
- realtime: { enabled: true, tier: 'live-queries', transport: 'memory' },
165
+ realtime: { enabled: true, transport: 'memory' },
158
166
  pwa: { enabled: true, offline: 'runtime' },
159
167
  ai: { mcp: { expose: true, path: '/mcp' } },
160
168
  });
@@ -19,10 +19,7 @@ import { policyFiles } from './policy';
19
19
  */
20
20
  export type SliceModule = 'entity' | 'policy' | 'errors';
21
21
 
22
- /** The feature's own code, derived once. The `docs:` URL used to be the literal
23
- * `.../X_NOT_FOUND` beside a `code:` of `X_INVOICE_NOT_FOUND`, so following the link from a real
24
- * failure landed on a different code's page — the same interpolation `error-codes.ts`'s `docsFor`
25
- * already does for every framework code. */
22
+ /** The feature's own code, derived once. */
26
23
  const notFoundCode = (feature: NameSet): string =>
27
24
  `X_${feature.kebab.toUpperCase().split('-').join('_')}_NOT_FOUND`;
28
25
 
@@ -41,13 +38,14 @@ const errorsSource = (feature: NameSet): string => {
41
38
 
42
39
  import { UltimateError } from '@ultimat3/core';
43
40
 
41
+ // No \`docs:\`. \`UltimateError\` resolves it from the code's registered descriptor, so the link has
42
+ // one home; a per-code URL written here is a page that does not exist.
44
43
  export class ${feature.pascal}NotFoundError extends UltimateError {
45
44
  constructor(input: { id: string }) {
46
45
  super({
47
46
  code: '${errorCode}',
48
47
  cause: \`no ${feature.kebab} with id \${input.id}\`,
49
48
  fix: 'x queries list --json, then pass an id the ${feature.kebab} read returns',
50
- docs: 'https://ultimate.dev/errors/${errorCode}',
51
49
  });
52
50
  }
53
51
  }
@@ -3,8 +3,8 @@
3
3
  // cmd-test.ts because a printed reproduction is only true if it carries every input to the split —
4
4
  // that rule is this file's, and argv parsing is that one's.
5
5
 
6
+ import { ERROR_DOCS_URL } from '@ultimat3/core';
6
7
  import type { AffectedSelection } from './affected';
7
- import { docsFor } from './error-codes';
8
8
  import type { Runner } from './exec';
9
9
  import { execOutput } from './exec';
10
10
  import { msg } from './messages';
@@ -137,7 +137,7 @@ const failureOf = (shard: Shard, code: number, plan: ReproduceOptions): Finding
137
137
  code: 'X_TEST_SHARD_FAILED',
138
138
  cause: `shard ${shard.index} of ${plan.workers} exited ${code} (${shard.files.length} file(s))`,
139
139
  fix: reproduceFor(shard, plan),
140
- docs: docsFor('X_TEST_SHARD_FAILED'),
140
+ docs: ERROR_DOCS_URL,
141
141
  });
142
142
 
143
143
  export async function runShards(options: RunShardsOptions): Promise<CommandResult> {
@@ -7,7 +7,7 @@
7
7
  // root that has no `tsconfig.json` at all, so an `existsSync` ahead of it was a second question
8
8
  // with one answer.
9
9
  import { join } from 'node:path';
10
- import { docsFor } from './error-codes';
10
+ import { ERROR_DOCS_URL } from '@ultimat3/core';
11
11
  import type { Finding } from './output';
12
12
  import { maskLiterals, stripComments } from './ts-scan';
13
13
 
@@ -82,7 +82,7 @@ export const unreferencedFinding = (dir: string): Finding => ({
82
82
  code: 'X_PACKAGE_UNREFERENCED',
83
83
  cause: `packages/${dir} is a published workspace and ${ROOT_TSCONFIG} has no reference to it, so tsc -b never builds it`,
84
84
  fix: `add { "path": "./packages/${dir}" } to "references" in ${ROOT_TSCONFIG}, then run bunx tsc -b --pretty false`,
85
- docs: docsFor('X_PACKAGE_UNREFERENCED'),
85
+ docs: ERROR_DOCS_URL,
86
86
  at: ROOT_TSCONFIG,
87
87
  });
88
88
 
@@ -4,6 +4,7 @@
4
4
 
5
5
  import { existsSync } from 'node:fs';
6
6
  import { join } from 'node:path';
7
+ import { ERROR_DOCS_URL } from '@ultimat3/core';
7
8
  import type { Manifest } from '@ultimat3/manifest';
8
9
  import {
9
10
  AGENTS_MD_FILENAME,
@@ -330,7 +331,7 @@ async function specFindings(root: string, manifest: Manifest): Promise<readonly
330
331
  code: 'X_MANIFEST_STALE',
331
332
  cause: `${OPENAPI_FILE} does not match the actions the code registers`,
332
333
  fix: 'x manifest',
333
- docs: 'https://ultimate.dev/errors/X_MANIFEST_STALE',
334
+ docs: ERROR_DOCS_URL,
334
335
  at: OPENAPI_FILE,
335
336
  },
336
337
  ];
@@ -344,6 +345,6 @@ const seoFinding = (issue: MetaIssue): Finding => ({
344
345
  code: issue.code,
345
346
  cause: issue.cause,
346
347
  fix: issue.fix,
347
- docs: `https://ultimate.dev/errors/${issue.code}`,
348
+ docs: ERROR_DOCS_URL,
348
349
  at: issue.file,
349
350
  });
@@ -7,7 +7,7 @@
7
7
  // all, and `join` builds the host-separator path to it.
8
8
  import { existsSync } from 'node:fs';
9
9
  import { join } from 'node:path';
10
- import { docsFor } from './error-codes';
10
+ import { ERROR_DOCS_URL, renderThrowable } from '@ultimat3/core';
11
11
  import type { Finding } from './output';
12
12
  import { VERIFY_STEP_NAMES } from './verify-step';
13
13
 
@@ -43,8 +43,10 @@ export function parseVerifyFloor(
43
43
  try {
44
44
  payload = JSON.parse(text);
45
45
  } catch (error) {
46
- const reason = error instanceof Error ? error.message : String(error);
47
- return { steps: [], problems: [`it does not parse as JSON (${reason})`] };
46
+ // `renderThrowable`, never `error instanceof Error ? error.message : String(error)`: both halves
47
+ // run on a value this process did not build, and either can throw one line before the guard that
48
+ // was meant to make the path safe (`metrics-endpoint.ts` states the same rule over `stringField`).
49
+ return { steps: [], problems: [`it does not parse as JSON (${renderThrowable(error)})`] };
48
50
  }
49
51
  const steps = asRecord(payload)?.['steps'];
50
52
  if (!Array.isArray(steps)) {
@@ -92,7 +94,7 @@ export const vanishedSuiteFinding = (step: string): Finding => ({
92
94
  code: 'X_VERIFY_SUITE_VANISHED',
93
95
  cause: `${VERIFY_FLOOR_FILE} requires the ${step} step and this run found nothing for it to check`,
94
96
  fix: `x verify --json # restore the ${step} suite, or drop "${step}" from ${VERIFY_FLOOR_FILE} in the commit that says why`,
95
- docs: docsFor('X_VERIFY_SUITE_VANISHED'),
97
+ docs: ERROR_DOCS_URL,
96
98
  at: VERIFY_FLOOR_FILE,
97
99
  });
98
100
 
@@ -111,7 +113,7 @@ export const skippedSuiteFinding = (step: string, skipped: number): Finding => (
111
113
  code: 'X_VERIFY_SUITE_VANISHED',
112
114
  cause: `${VERIFY_FLOOR_FILE} requires the ${step} step and all ${skipped} test(s) it found skipped themselves, so nothing ran`,
113
115
  fix: `x test ${step} --json # then set what the suite skips without, or drop "${step}" from ${VERIFY_FLOOR_FILE} in the commit that says why`,
114
- docs: docsFor('X_VERIFY_SUITE_VANISHED'),
116
+ docs: ERROR_DOCS_URL,
115
117
  at: VERIFY_FLOOR_FILE,
116
118
  });
117
119
 
@@ -128,6 +130,6 @@ export const floorProblemFindings = (floor: VerifyFloor | undefined): readonly F
128
130
  code: 'X_CONFIG_INVALID',
129
131
  cause: `${VERIFY_FLOOR_FILE} is not a suite floor: ${problem}`,
130
132
  fix: `x verify --json # then write ${VERIFY_FLOOR_FILE} as {"steps":["unit","contract"]}, naming only steps it ran`,
131
- docs: docsFor('X_CONFIG_INVALID'),
133
+ docs: ERROR_DOCS_URL,
132
134
  at: VERIFY_FLOOR_FILE,
133
135
  }));
package/src/verify-run.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // `cmd-verify.ts` because `x build` and the MCP host run the gate without going through the
3
3
  // command: what a run means — never bail early, count what actually ran — belongs to neither.
4
4
 
5
- import { renderThrowable } from '@ultimat3/core';
5
+ import { ERROR_DOCS_URL, renderThrowable } from '@ultimat3/core';
6
6
  import { msg } from './messages';
7
7
  import type { CommandResult, Finding, StepResult } from './output';
8
8
  import {
@@ -139,6 +139,6 @@ function findingOf(error: unknown, step: string): Finding {
139
139
  code: 'X_VERIFY_FAILED',
140
140
  cause: `step "${step}" threw: ${cause}`,
141
141
  fix: 'x verify --json',
142
- docs: 'https://ultimate.dev/errors/X_VERIFY_FAILED',
142
+ docs: ERROR_DOCS_URL,
143
143
  };
144
144
  }
@@ -2,6 +2,7 @@
2
2
  // repo feeds it findings it could not produce on its own. Split from the step list so a step
3
3
  // implementation can live beside the code it checks without importing the list.
4
4
 
5
+ import { ERROR_DOCS_URL } from '@ultimat3/core';
5
6
  import type { ExecResult, Runner } from './exec';
6
7
  import { execOutput } from './exec';
7
8
  import type { Finding } from './output';
@@ -106,7 +107,7 @@ export function fromExec(result: ExecResult, finding: Omit<Finding, 'docs'>): St
106
107
  if (result.ok) return { ok: true, findings: [], output: execOutput(result) };
107
108
  return {
108
109
  ok: false,
109
- findings: [{ ...finding, docs: `https://ultimate.dev/errors/${finding.code}` }],
110
+ findings: [{ ...finding, docs: ERROR_DOCS_URL }],
110
111
  output: execOutput(result),
111
112
  };
112
113
  }
@@ -3,7 +3,7 @@
3
3
  // happens to them once selected — a wrong file list is never a race, and a race is never a
4
4
  // selection bug.
5
5
 
6
- import { docsFor } from './error-codes';
6
+ import { ERROR_DOCS_URL } from '@ultimat3/core';
7
7
  import type { Runner } from './exec';
8
8
  import { execOutput } from './exec';
9
9
  import type { Finding } from './output';
@@ -51,7 +51,7 @@ export async function runParallel(options: ParallelRunOptions): Promise<StepOutc
51
51
  // The reproduction has to name every input to the split, or it reruns a different file set:
52
52
  // `reproduceFor` is the one place that rule lives, shared with `x test`.
53
53
  fix: reproduceFor(shard, { workers: shards.length, type: options.type }),
54
- docs: docsFor('X_TEST_SHARD_FAILED'),
54
+ docs: ERROR_DOCS_URL,
55
55
  });
56
56
  }
57
57
  // Only the failing shards' output: a green 8-way split would otherwise print eight summaries,
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { existsSync } from 'node:fs';
7
7
  import { join } from 'node:path';
8
- import { renderCauseValue } from '@ultimat3/core';
8
+ import { ERROR_DOCS_URL, renderCauseValue } from '@ultimat3/core';
9
9
  import type { Finding } from './output';
10
10
  import { eachSourceFile, isGenerated } from './source-files';
11
11
  import { checkRootReferences } from './tsconfig-references';
@@ -14,13 +14,11 @@ export const LINE_CEILING = 500;
14
14
 
15
15
  export const PACKAGE_FILES = ['README.md', 'CLAUDE.md', 'tsconfig.json', 'src/index.ts'] as const;
16
16
 
17
- const docs = (code: string): string => `https://ultimate.dev/errors/${code}`;
18
-
19
17
  export const tooLongFinding = (path: string, lines: number): Finding => ({
20
18
  code: 'X_FILE_TOO_LONG',
21
19
  cause: `${path} is ${lines} lines, over the ${LINE_CEILING} line ceiling`,
22
20
  fix: `split ${path}: one file, one responsibility`,
23
- docs: docs('X_FILE_TOO_LONG'),
21
+ docs: ERROR_DOCS_URL,
24
22
  at: path,
25
23
  });
26
24
 
@@ -49,7 +47,7 @@ export const missingFileFinding = (dir: string, file: string, scaffolder: boolea
49
47
  fix: scaffolder
50
48
  ? `bun run scripts/new-package.ts ${dir} --only ${file}`
51
49
  : `add packages/${dir}/${file}, shaped like the one in a sibling package`,
52
- docs: docs('X_PACKAGE_SHAPE'),
50
+ docs: ERROR_DOCS_URL,
53
51
  at: `packages/${dir}/${file}`,
54
52
  });
55
53
 
@@ -68,7 +66,7 @@ export const badVersionFinding = (dir: string, found: unknown): Finding => ({
68
66
  // this finding with a TypeError from inside the gate.
69
67
  cause: `packages/${dir}/package.json has no semver "version" (found ${renderCauseValue(found)})`,
70
68
  fix: `set a semver "version" in packages/${dir}/package.json, then: bun run verify`,
71
- docs: docs('X_PACKAGE_SHAPE'),
69
+ docs: ERROR_DOCS_URL,
72
70
  at: `packages/${dir}/package.json`,
73
71
  });
74
72
 
@@ -103,7 +101,7 @@ export const versionSkewFinding = (dir: string, found: string, expected: string)
103
101
  code: 'X_RELEASE_VERSION_SKEW',
104
102
  cause: `packages/${dir} is at ${found}, not the lockstep version ${expected}`,
105
103
  fix: `bun run scripts/release.ts --version ${expected}`,
106
- docs: docs('X_RELEASE_VERSION_SKEW'),
104
+ docs: ERROR_DOCS_URL,
107
105
  at: `packages/${dir}/package.json`,
108
106
  });
109
107
 
@@ -116,7 +114,7 @@ export const pinSkewFinding = (
116
114
  code: 'X_RELEASE_VERSION_SKEW',
117
115
  cause: `packages/${dir} pins ${dep} at ${range}, not the lockstep version ${expected}`,
118
116
  fix: `bun run scripts/release.ts --version ${expected}`,
119
- docs: docs('X_RELEASE_VERSION_SKEW'),
117
+ docs: ERROR_DOCS_URL,
120
118
  at: `packages/${dir}/package.json`,
121
119
  });
122
120
 
@@ -167,7 +165,7 @@ export const missingPublishedFileFinding = (dir: string, entry: string): Finding
167
165
  code: 'X_PACKAGE_SHAPE',
168
166
  cause: `packages/${dir}/package.json ships "${entry}" in "files", but packages/${dir}/${entry} does not exist`,
169
167
  fix: `add packages/${dir}/${entry}, or drop "${entry}" from "files" in packages/${dir}/package.json`,
170
- docs: docs('X_PACKAGE_SHAPE'),
168
+ docs: ERROR_DOCS_URL,
171
169
  at: `packages/${dir}/package.json`,
172
170
  });
173
171
 
@@ -175,7 +173,7 @@ export const publishesTestsFinding = (dir: string): Finding => ({
175
173
  code: 'X_PACKAGE_SHAPE',
176
174
  cause: `packages/${dir}/package.json does not exclude ${TEST_EXCLUSION} from "files"`,
177
175
  fix: `add "${TEST_EXCLUSION}" to "files" in packages/${dir}/package.json, after "src"`,
178
- docs: docs('X_PACKAGE_SHAPE'),
176
+ docs: ERROR_DOCS_URL,
179
177
  at: `packages/${dir}/package.json`,
180
178
  });
181
179
 
@@ -196,7 +194,7 @@ export const buildArtifactsFinding = (dir: string, count: number): Finding => ({
196
194
  code: 'X_PACKAGE_SHAPE',
197
195
  cause: `packages/${dir}/src/ contains ${count} build artifacts (.d.ts, .js, .map files)`,
198
196
  fix: `find packages/${dir}/src ${SWEEP_PREDICATE} -delete`,
199
- docs: docs('X_PACKAGE_SHAPE'),
197
+ docs: ERROR_DOCS_URL,
200
198
  at: `packages/${dir}/src/`,
201
199
  });
202
200
 
@@ -209,7 +207,7 @@ export const noFilesAllowlistFinding = (dir: string): Finding => ({
209
207
  code: 'X_PACKAGE_SHAPE',
210
208
  cause: `packages/${dir}/package.json publishes with no "files" allowlist, so the tarball carries whatever is in the directory`,
211
209
  fix: `add "files": ["src", "${TEST_EXCLUSION}", "README.md", "LICENSE"] to packages/${dir}/package.json`,
212
- docs: docs('X_PACKAGE_SHAPE'),
210
+ docs: ERROR_DOCS_URL,
213
211
  at: `packages/${dir}/package.json`,
214
212
  });
215
213
 
@@ -4,6 +4,7 @@
4
4
  // for `tsc` and is invisible to bun, to `--filter` ordering and to any change-detection tool.
5
5
 
6
6
  import { join } from 'node:path';
7
+ import { ERROR_DOCS_URL } from '@ultimat3/core';
7
8
  import type { Finding } from './output';
8
9
  import { eachSourceFile, isGenerated, isTest, isVendored } from './source-files';
9
10
  import { maskLiterals } from './ts-scan';
@@ -184,7 +185,7 @@ export const unreadableWorkspaceFinding = (path: string): Finding => ({
184
185
  code: 'X_APP_PACKAGE_INVALID',
185
186
  cause: `${path} is claimed by the root "workspaces" globs and supplies no readable "name"`,
186
187
  fix: `repair the JSON and the "name" in ${path}, or drop its directory from "workspaces" in package.json`,
187
- docs: 'https://ultimate.dev/errors/X_APP_PACKAGE_INVALID',
188
+ docs: ERROR_DOCS_URL,
188
189
  at: path,
189
190
  });
190
191
 
@@ -198,7 +199,7 @@ export const undeclaredWorkspaceDepFinding = (
198
199
  // The exact line to paste, at the version the target really carries: a `workspace:*` range would
199
200
  // resolve and then fail `checkLockstep`, which compares a sibling pin against the version.
200
201
  fix: `add "${to.name}": "${to.version ?? '0.0.0'}" to "dependencies" in ${from.dir}/package.json`,
201
- docs: 'https://ultimate.dev/errors/X_WORKSPACE_DEP_UNDECLARED',
202
+ docs: ERROR_DOCS_URL,
202
203
  at: `${from.dir}/package.json`,
203
204
  });
204
205
 
package/src/write-line.ts CHANGED
@@ -5,6 +5,9 @@
5
5
 
6
6
  // `node:fs`, and unavoidable: Bun has no synchronous stdout write of its own.
7
7
  import { writeSync } from 'node:fs';
8
+ // The one import beyond `node:fs`, and it costs nothing here: `create-ultimate` reaches this
9
+ // module through `@ultimat3/cli`'s barrel, which has already evaluated core.
10
+ import { stringField } from '@ultimat3/core';
8
11
 
9
12
  /**
10
13
  * Write to stdout and be certain it arrived, even if the next statement exits the process.
@@ -29,7 +32,10 @@ function writeTo(fd: 1 | 2, line: string): void {
29
32
  try {
30
33
  written += writeSync(fd, buffer, written, buffer.length - written);
31
34
  } catch (cause) {
32
- if ((cause as NodeJS.ErrnoException).code !== 'EAGAIN') throw cause;
35
+ // `stringField`, never a cast plus a property read — the rule `metrics-endpoint.ts` states
36
+ // and `caught-value-reads.test.ts` enforces. Here it is also the difference between
37
+ // rethrowing and an infinite loop: a `code` that cannot be read must not read as `EAGAIN`.
38
+ if (stringField(cause, 'code') !== 'EAGAIN') throw cause;
33
39
  }
34
40
  }
35
41
  }