@ultimat3/cli 9.0.0 → 11.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 (78) hide show
  1. package/CLAUDE.md +71 -0
  2. package/package.json +28 -26
  3. package/src/affected.ts +0 -3
  4. package/src/app-boundaries.ts +4 -5
  5. package/src/app-env.ts +7 -2
  6. package/src/browser-launcher.ts +0 -2
  7. package/src/budgets.ts +10 -3
  8. package/src/cmd-build.ts +2 -2
  9. package/src/cmd-db-backfill.ts +14 -1
  10. package/src/cmd-db-branch.ts +2 -2
  11. package/src/cmd-deploy.ts +14 -3
  12. package/src/cmd-dev.ts +3 -0
  13. package/src/cmd-docs.ts +2 -1
  14. package/src/cmd-doctor.ts +3 -5
  15. package/src/cmd-env.ts +2 -2
  16. package/src/cmd-fix.ts +2 -4
  17. package/src/cmd-new.ts +36 -6
  18. package/src/cmd-shot.ts +22 -2
  19. package/src/command.ts +12 -0
  20. package/src/db-finding.ts +2 -2
  21. package/src/db-seed.ts +0 -3
  22. package/src/dev-assets.ts +6 -0
  23. package/src/dev-cache.ts +12 -5
  24. package/src/dev-hooks.ts +8 -0
  25. package/src/dev-lock.ts +8 -7
  26. package/src/dev-purge.ts +8 -2
  27. package/src/dev-render.ts +5 -2
  28. package/src/dev-roles.ts +26 -2
  29. package/src/dev-runtime.ts +11 -3
  30. package/src/dev-storage.ts +8 -2
  31. package/src/dev-sync.ts +37 -2
  32. package/src/dispatch.ts +8 -0
  33. package/src/document-styles.ts +2 -1
  34. package/src/drift.ts +3 -2
  35. package/src/error-catalog.ts +11 -1
  36. package/src/error-codes.ts +19 -2
  37. package/src/error-contract.ts +30 -7
  38. package/src/error-pages.ts +79 -0
  39. package/src/errors.ts +26 -29
  40. package/src/favicon.ts +113 -0
  41. package/src/fix-path.ts +104 -0
  42. package/src/flag-reads.ts +2 -2
  43. package/src/generate-write.ts +3 -2
  44. package/src/guards.ts +4 -4
  45. package/src/hold.ts +73 -7
  46. package/src/index.ts +16 -6
  47. package/src/island-bundle.ts +32 -4
  48. package/src/island-routes.ts +7 -1
  49. package/src/live-routes.ts +181 -0
  50. package/src/mcp-errors.ts +7 -2
  51. package/src/messages.ts +6 -4
  52. package/src/metrics-endpoint.ts +0 -2
  53. package/src/output.ts +2 -2
  54. package/src/prerender.ts +64 -22
  55. package/src/script-csp.ts +17 -0
  56. package/src/serve.ts +12 -1
  57. package/src/static-report.ts +41 -3
  58. package/src/templates/admin-page.ts +11 -7
  59. package/src/templates/imports.ts +26 -0
  60. package/src/templates/route.ts +2 -2
  61. package/src/templates/scaffold-app.ts +18 -6
  62. package/src/templates/scaffold-auth.ts +151 -0
  63. package/src/templates/scaffold-container.ts +12 -0
  64. package/src/templates/scaffold-docs.ts +1 -1
  65. package/src/templates/scaffold-domain-package.ts +3 -1
  66. package/src/templates/scaffold-mcp-package.ts +6 -3
  67. package/src/templates/scaffold-repo.ts +17 -8
  68. package/src/templates/slice-foundation.ts +3 -5
  69. package/src/test-shards.ts +2 -2
  70. package/src/tsconfig-references.ts +2 -2
  71. package/src/verify-checks.ts +10 -3
  72. package/src/verify-floor.ts +8 -6
  73. package/src/verify-run.ts +2 -2
  74. package/src/verify-step.ts +2 -1
  75. package/src/verify-test-run.ts +2 -2
  76. package/src/workspace-checks.ts +10 -12
  77. package/src/workspace-graph.ts +3 -2
  78. package/src/write-line.ts +7 -1
package/CLAUDE.md CHANGED
@@ -193,6 +193,7 @@ cast: a `null` where an id was expected would otherwise become a mutation agains
193
193
  | `fix-imports.ts` | which of those factories a file can call that it did not declare — one relative specifier, one file read |
194
194
  | `error-contract.ts` | the rules, the two checks that turn them into findings, and `collectDeclaredCodes` |
195
195
  | `fix-command.ts` | resolving an `x <command>` a `fix:` cites against the registry |
196
+ | `fix-path.ts` | resolving a PATH or a glob a `fix:` cites against the root the gate is running in |
196
197
  | `source-files.ts` | which files are shipped source — shared with `filesize`, never a second list |
197
198
 
198
199
  **A `fix:` may not cite a command this build does not ship.** Six shipped fix lines named
@@ -201,6 +202,26 @@ one passed — the text rule checks that a fix NAMES a command, never that the r
201
202
  `fix-command.ts` resolves the citation, and a PLANNED command fails too: `x logs` parses, `x help`
202
203
  lists it, and running it hands the reader `X_NOT_IMPLEMENTED` instead of the fix.
203
204
 
205
+ **A `fix:` may not cite a file this repo does not have, either.** That was the other half, and
206
+ nothing resolved it: `X_UI_RUNTIME_MISSING` told its reader to paste a line no generator ever wrote,
207
+ through every gate since it shipped (#274, #246). A file token is one of the four things that make a
208
+ fix an instruction at all (`COMMAND_TOKENS`), so `fix-path.ts` is built from the SAME extension list
209
+ — a token that satisfies the instruction rule is exactly the token this one has to resolve, and two
210
+ lists would be a citation the second rule cannot see. `X_ERROR_FIX_PATH_MISSING` is its own code:
211
+ `X_ERROR_FIX_INVALID` means the fix is not an instruction, this one means it is one and points at
212
+ nothing, and the repairs differ.
213
+
214
+ It is narrow so a finding never has to be argued with — three shapes are not judged at all, because
215
+ each resolves against something other than the root the gate is running in: a scoped specifier
216
+ (`@ultimat3/ui/global.scss`, which resolves through `node_modules`), a dot-relative path
217
+ (`./global.scss`, which resolves against the reader's own file) and any path whose **parent
218
+ directory** this root does not have (`src/errors.ts`, `apps/web/server.ts`,
219
+ `packages/i18n/catalogs/en.json` — all three name a directory a generated app has and this repo does
220
+ not). What is left is the citation a root really can answer: a directory that exists, named as
221
+ holding a file it does not hold. A glob must match at least one file. Measured over all three roots
222
+ the gate runs in — the framework, `examples/dummy`, `dummy/social-media-clone` — **117 path citations
223
+ read, 0 findings**, so it enforces outright with no pin table.
224
+
204
225
  The rule is **conditional, and that is load-bearing**: *if* a fix cites `x <command>`, it must
205
226
  resolve. It does not require every fix to name one — `set OTEL_EXPORTER_OTLP_ENDPOINT=…` and
206
227
  `counter('orders_total', { maxSeries: 4000 })` are executable and correctly cite nothing, and a
@@ -542,7 +563,9 @@ hand-written layout and `readMigrations` skips it — read as a migration it sor
542
563
  | `otlp-export.ts` | the exporters `OTEL_EXPORTER_OTLP_ENDPOINT` switches on, and their drain hooks |
543
564
  | `dev-render.ts` | one HTTP route per registered `route`, through render's own mode function |
544
565
  | `style-csp.ts` | the `style-src` sha256 of every inline `<style>` the web role serves |
566
+ | `script-csp.ts` | the `script-src` sha256 of every inline `<script>` it serves — the hydration runtime, from `@ultimat3/render`'s own `HYDRATE_RUNTIME_BODIES` |
545
567
  | `dev-assets.ts` | the image pipeline's only HTTP surface: `/icons/*` and `/media/*` |
568
+ | `favicon.ts` | `/favicon.ico`: the app's own file, and the bytes the framework answers with when there is none |
546
569
  | `dev-hooks.ts` | the pipeline's `authorize` seam, decided from the app's own `Policy` objects |
547
570
  | `dev-roles.ts` | `--role` selection plus start/stop for `web`, `sync`, `worker`, `scheduler` |
548
571
  | `dev-dashboard.ts` | the `DevSources` hooks only this process can answer, and the two CLI panels |
@@ -654,6 +677,23 @@ missing.
654
677
  | `prerender.ts` | build first, write the chunks into the export, then measure |
655
678
  | `budgets.ts` | `measureDocumentJs` weighs `data-x-entry` as well as `<script src>` |
656
679
 
680
+ **A stats row is keyed by the route's DECLARED path, and holds its heaviest page**
681
+ (`As of 2026-08-23`). `checkBudgets` looks a route up by `route.url` off the manifest, which is the
682
+ pattern (`/blog/:slug`), and `prerenderSite` pushed `artifact.path` — the filled one
683
+ (`/blog/hello`). So no dynamic static route had ever been weighed: each was `X_BUDGET_UNMEASURED`
684
+ and `X_BUDGET_EXCEEDED` could not fire for the whole class. The heaviest page and not the first,
685
+ because a budget is a ceiling and the page that breaks it is the one the route answers for; the
686
+ report's `emitted` list still names every filled path.
687
+
688
+ **Both halves of the CSP are computed at boot, `As of 2026-08-23`.** `style-csp.ts` was alone, and
689
+ the hydration runtime is emitted INLINE in every document carrying an island — so with
690
+ `script-src 'self' 'wasm-unsafe-eval'` no island booted in any container. `x dev` sends the policy
691
+ report-only (`dev: true`), which is exactly why nobody saw it: the page hydrated on a laptop and
692
+ never in the image. `startWeb` extends both directives; the property is pinned end to end by
693
+ `dev-roles-script-csp.test.ts`, which parses a served document, hashes every executable inline
694
+ script in it and asserts the response's own `script-src` names each one — with `dev: false`, the
695
+ only mode in which the policy can block anything.
696
+
657
697
  **One `Bun.build` per island, never one call with N entry points**, and `splitting: false`. The
658
698
  island's `src` is a string, so no import edge reaches it and the page's graph stays the page's
659
699
  (axiom 6) — a shared chunk would put that number back behind a graph walk, and the budget compares
@@ -719,6 +759,21 @@ width is not one of the eight. Anything outside it is still served; only the `pu
719
759
  no caller gains a new 4xx. `?q=` is deliberately still unbounded here — the closed set for quality
720
760
  is `@ultimat3/seo`'s to declare, not this file's.
721
761
 
762
+ **`/favicon.ico` is a mechanism, not a scaffolded file.** Every browser requests it unprompted, the
763
+ scaffold wrote none and neither served surface mounted a route, so a permanent 404 sat in the console
764
+ of every app the framework produces — noise that trains the reader to ignore console errors, which is
765
+ the opposite of what `--json` and an executable `fix:` are for (#272). Two rungs and one path: the
766
+ app's own `apps/web/site/favicon.ico` wins, and `favicon.ts` answers a 32x32 PNG encoded through
767
+ `@ultimat3/core`'s own pipeline when there is none — the same encoder `x new`'s icon goes through, so
768
+ there is no second image format in the tree and no base64 blob nobody can verify. It is deliberately
769
+ NOT derived from `ICON_SOURCE`: resizing the install icon needs `@ultimat3/pwa`'s pipeline and would
770
+ make the answer depend on a file that may be absent, which is a third rung under a mechanism that has
771
+ exactly two. The file is read per REQUEST, so dropping one into a running `x dev` takes effect
772
+ without a restart. It mounts through `assetRoutes`, which is the one route set `serve.ts` and
773
+ `cmd-dev.ts` both compose — a favicon added to one of them alone is a 404 that comes back in
774
+ production only — and `prerenderSite` writes the same bytes into the static export, because an
775
+ artifact served with no process behind it has to carry every byte the browser will ask for.
776
+
722
777
  `ICON_SOURCE` lives here, not in `cmd-doctor.ts`, because this is the module that reads it: the
723
778
  diagnostic checks what `x dev` serves, so one constant cannot pass the check and serve nothing.
724
779
  It is a **PNG** — core decodes PNG and JPEG only, and the SVG this used to name could never
@@ -745,6 +800,22 @@ watcher — *after* the drain, so an in-flight request still has the database it
745
800
  Ctrl-C is therefore the same three phases production runs, not a kill that leaves `.x/pgdata`
746
801
  locked by a process that no longer exists.
747
802
 
803
+ **The release runs INSIDE the drain's own deadline, `As of 2026-08-23`, and it is the same
804
+ deadline.** `drain()` ABANDONS a hook that overruns `ShutdownReason.deadlineAt` — the process is
805
+ meant to exit without it — and `release` re-enters the very same teardown one call later:
806
+ `app.stop()` → `startRoles().stop()` → `worker.stop()`, memoised in the package that owns it, so
807
+ awaiting it is awaiting the promise the drain just walked away from. Unbounded, that hung past
808
+ `terminationGracePeriodSeconds` and the kubelet SIGKILLed a process that had already drained
809
+ cleanly. The budget is the hook's own `reason.deadlineAt`, not a stopwatch of ours, so there is one
810
+ number and not two; an overrun is logged as `X_SHUTDOWN_TIMEOUT` and a REJECTION still rejects,
811
+ because `dispatch` awaits the hold inside its own `try`.
812
+
813
+ **`options.exit` has exactly one caller: `runRole` in `serve.ts`.** `bin.ts` ends in
814
+ `process.exit(code)`, so `x dev` and `x mcp` need nothing; `apps/web/server.ts` — which is what a
815
+ container runs — has no such line, and one non-unref'd interval anywhere in the app then holds an
816
+ event loop with nothing left to do. A function rather than a boolean because `process.exit` inside
817
+ a library is untestable, and the caller is the one that knows.
818
+
748
819
  Commands: `bun test`, `bunx tsc --noEmit -p tsconfig.json`.
749
820
 
750
821
  ## A declared flag with no reader is a promise `x help` makes and nothing keeps
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/cli",
3
- "version": "9.0.0",
3
+ "version": "11.0.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,31 +37,33 @@
37
37
  },
38
38
  "dependencies": {
39
39
  "@babel/core": "^7.28.4",
40
- "@ultimat3/action": "9.0.0",
41
- "@ultimat3/admin": "9.0.0",
42
- "@ultimat3/ai": "9.0.0",
43
- "@ultimat3/auth": "9.0.0",
44
- "@ultimat3/cache": "9.0.0",
45
- "@ultimat3/core": "9.0.0",
46
- "@ultimat3/db": "9.0.0",
47
- "@ultimat3/entity": "9.0.0",
48
- "@ultimat3/http": "9.0.0",
49
- "@ultimat3/i18n": "9.0.0",
50
- "@ultimat3/jobs": "9.0.0",
51
- "@ultimat3/mail": "9.0.0",
52
- "@ultimat3/manifest": "9.0.0",
53
- "@ultimat3/mcp": "9.0.0",
54
- "@ultimat3/policy": "9.0.0",
55
- "@ultimat3/pwa": "9.0.0",
56
- "@ultimat3/query": "9.0.0",
57
- "@ultimat3/realtime": "9.0.0",
58
- "@ultimat3/render": "9.0.0",
59
- "@ultimat3/schema": "9.0.0",
60
- "@ultimat3/scraping": "9.0.0",
61
- "@ultimat3/seo": "9.0.0",
62
- "@ultimat3/storage": "9.0.0",
63
- "@ultimat3/testing": "9.0.0",
64
- "@ultimat3/time": "9.0.0",
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",
65
67
  "babel-preset-solid": "^1.9.15"
66
68
  }
67
69
  }
package/src/affected.ts CHANGED
@@ -13,7 +13,6 @@
13
13
  // directory is checkout-relative while a caller's scan yields paths relative to its own root.
14
14
  import { join, relative } from 'node:path';
15
15
  import { singleLine, UltimateError } from '@ultimat3/core';
16
- import { docsFor } from './error-codes';
17
16
  import { BadFlagError } from './errors';
18
17
  import type { ExecResult, Runner } from './exec';
19
18
  import { execOutput } from './exec';
@@ -193,7 +192,6 @@ export async function gitRoot(runner: Runner, cwd: string, command: string): Pro
193
192
  code: 'X_CLI_UNEXPECTED',
194
193
  cause: `x ${command} reads its diff from git and "git rev-parse --show-toplevel" exited ${result.code} in ${cwd}: ${singleLine(execOutput(result))}`,
195
194
  fix: `run x ${command} from inside a git checkout — confirm with: git rev-parse --show-toplevel`,
196
- docs: docsFor('X_CLI_UNEXPECTED'),
197
195
  });
198
196
  }
199
197
 
@@ -245,7 +243,6 @@ export async function changedFiles(
245
243
  code: 'X_CLI_UNEXPECTED',
246
244
  cause: `"${failed.command.join(' ')}" exited ${failed.code} in ${options.cwd}: ${singleLine(execOutput(failed))}`,
247
245
  fix: `run it yourself to see why: ${failed.command.join(' ')}`,
248
- docs: docsFor('X_CLI_UNEXPECTED'),
249
246
  });
250
247
  }
251
248
  return [...new Set(runs.flatMap((run) => paths(run.stdout)))].sort();
@@ -14,6 +14,7 @@
14
14
  import { join as joinPath } from 'node:path';
15
15
  // The POSIX variants resolve specifiers against import-graph keys, which are POSIX on every host.
16
16
  import { dirname, join, normalize, relative } from 'node:path/posix';
17
+ import { ERROR_DOCS_URL } from '@ultimat3/core';
17
18
  import type { BoundaryRule, ImportGraph } from '@ultimat3/render';
18
19
  import { checkSurfaceBoundary, importGraph, SURFACES } from '@ultimat3/render';
19
20
  import type { Finding } from './output';
@@ -48,8 +49,6 @@ const CODE_OF: Readonly<Record<BoundaryRule, BoundaryCode>> = {
48
49
  */
49
50
  export const boundaryCodeOf = (rule: BoundaryRule): BoundaryCode => CODE_OF[rule];
50
51
 
51
- const docs = (code: BoundaryCode): string => `https://ultimate.dev/errors/${code}`;
52
-
53
52
  const isRoute = (path: string): boolean => /\/(page|layout|route)\.[cm]?tsx?$/.test(path);
54
53
  const isService = (path: string): boolean => /\/service\.[cm]?ts$/.test(path);
55
54
  const isDbSpecifier = (specifier: string): boolean =>
@@ -128,7 +127,7 @@ const surfaceFindings = (graph: ImportGraph): readonly Finding[] =>
128
127
  code,
129
128
  cause: violation.cause,
130
129
  fix: violation.fix,
131
- docs: docs(code),
130
+ docs: ERROR_DOCS_URL,
132
131
  at: violation.importer,
133
132
  };
134
133
  });
@@ -198,7 +197,7 @@ function layerFindings(scanned: readonly ScannedFile[]): readonly Finding[] {
198
197
  code: 'X_BOUNDARY_ROUTE_TO_DB',
199
198
  cause: `route imports the database ("${specifier}") — routes call actions and queries`,
200
199
  fix: generate('query', file.path, 'then call it from'),
201
- docs: docs('X_BOUNDARY_ROUTE_TO_DB'),
200
+ docs: ERROR_DOCS_URL,
202
201
  at: file.path,
203
202
  });
204
203
  }
@@ -207,7 +206,7 @@ function layerFindings(scanned: readonly ScannedFile[]): readonly Finding[] {
207
206
  code: 'X_BOUNDARY_SERVICE_TO_HTTP',
208
207
  cause: `service imports HTTP ("${specifier}") — a service that knows about requests cannot be reused by a job`,
209
208
  fix: generate('action', file.path, 'read the request there and pass plain values to'),
210
- docs: docs('X_BOUNDARY_SERVICE_TO_HTTP'),
209
+ docs: ERROR_DOCS_URL,
211
210
  at: file.path,
212
211
  });
213
212
  }
package/src/app-env.ts CHANGED
@@ -7,7 +7,12 @@
7
7
  import { existsSync } from 'node:fs';
8
8
  import { join } from 'node:path';
9
9
  import type { EnvSchema, EnvVarDecl } from '@ultimat3/core';
10
- import { checkEnvExample, ENV_EXAMPLE_PATH, renderEnvExample } from '@ultimat3/core';
10
+ import {
11
+ checkEnvExample,
12
+ ENV_EXAMPLE_PATH,
13
+ ERROR_DOCS_URL,
14
+ renderEnvExample,
15
+ } from '@ultimat3/core';
11
16
  import { APP_CONFIG_FILE } from './app-root';
12
17
  import type { Finding } from './output';
13
18
  import { findingFrom } from './output';
@@ -63,7 +68,7 @@ const driftFinding = (cause: string): Finding => ({
63
68
  // The generator, not the assertion: `assertEnvExample`'s own fix is a `Bun.write(…)` call for
64
69
  // an app that has a schema object in scope, and a gate reader has a shell.
65
70
  fix: 'x env example',
66
- docs: 'https://ultimate.dev/errors/X_ENV_EXAMPLE_DRIFT',
71
+ docs: ERROR_DOCS_URL,
67
72
  at: ENV_EXAMPLE_PATH,
68
73
  });
69
74
 
@@ -7,7 +7,6 @@ import { existsSync } from 'node:fs';
7
7
  import { UltimateError } from '@ultimat3/core';
8
8
  import type { CdpLauncherLike, ScrapeDriver } from '@ultimat3/scraping';
9
9
  import { localBrowser } from '@ultimat3/scraping';
10
- import { docsFor } from './error-codes';
11
10
 
12
11
  /**
13
12
  * The one library this works against. Playwright is not an alternative and is not a flag:
@@ -33,7 +32,6 @@ export class ShotBrowserMissingError extends UltimateError {
33
32
  // one literal, and a fix line the gate cannot read is a fix line nothing holds to the
34
33
  // contract. `browser-launcher.test.ts` pins it against the constant instead.
35
34
  fix: 'bun add -d puppeteer-core',
36
- docs: docsFor('X_SHOT_BROWSER_MISSING'),
37
35
  meta: { root: input.root, package: BROWSER_PACKAGE },
38
36
  });
39
37
  }
package/src/budgets.ts CHANGED
@@ -8,6 +8,7 @@
8
8
 
9
9
  import { existsSync } from 'node:fs';
10
10
  import { join } from 'node:path';
11
+ import { ERROR_DOCS_URL } from '@ultimat3/core';
11
12
  import type { Manifest, RouteFact } from '@ultimat3/manifest';
12
13
  import { formatBytes, parseByteBudget } from '@ultimat3/render';
13
14
  import type { Finding } from './output';
@@ -15,6 +16,12 @@ import type { Finding } from './output';
15
16
  export const BUILD_STATS_FILE = join('.x', 'build-stats.json');
16
17
 
17
18
  export interface RouteStats {
19
+ /**
20
+ * The route's DECLARED path — `/blog/:slug`, never `/blog/hello`. `checkBudgets` looks a row up
21
+ * by `route.url` off the manifest, which is the pattern, so a row keyed by a filled path is a
22
+ * row nothing can find: every dynamic static route read as `X_BUDGET_UNMEASURED`. A route that
23
+ * prerenders many pages contributes ONE row, holding its heaviest.
24
+ */
18
25
  readonly path: string;
19
26
  readonly jsBytes: number;
20
27
  /**
@@ -75,7 +82,7 @@ function unmeasuredFinding(url: string, declared: string, built: boolean): Findi
75
82
  fix: built
76
83
  ? `x build --target static --json # its "unmeasured" list says why ${url} could not be weighed`
77
84
  : 'x build --target static --json && x verify --json',
78
- docs: 'https://ultimate.dev/errors/X_BUDGET_UNMEASURED',
85
+ docs: ERROR_DOCS_URL,
79
86
  at: url,
80
87
  };
81
88
  }
@@ -106,7 +113,7 @@ export function checkBudgets(
106
113
  code: 'X_BUDGET_EXCEEDED',
107
114
  cause: `${route.url} ships ${formatBytes(measured.jsBytes)} of JS over a ${formatBytes(js)} budget via ${chainOf(measured)}`,
108
115
  fix: `x routes --json to see the chain, then move the heavy import behind hydrate: 'interaction'`,
109
- docs: 'https://ultimate.dev/errors/X_BUDGET_EXCEEDED',
116
+ docs: ERROR_DOCS_URL,
110
117
  at: route.url,
111
118
  });
112
119
  }
@@ -115,7 +122,7 @@ export function checkBudgets(
115
122
  code: 'X_BUDGET_EXCEEDED',
116
123
  cause: `${route.url} LCP ${measured.lcpMs}ms over the ${lcp}ms budget`,
117
124
  fix: `raise the budget in defineRoute, or switch render to 'isr' to serve it prebuilt`,
118
- docs: 'https://ultimate.dev/errors/X_BUDGET_EXCEEDED',
125
+ docs: ERROR_DOCS_URL,
119
126
  at: route.url,
120
127
  });
121
128
  }
package/src/cmd-build.ts CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  import { existsSync } from 'node:fs';
5
5
  import { join } from 'node:path';
6
- import { frameworkVersion, VERSION_DEFINE } from '@ultimat3/core';
6
+ import { ERROR_DOCS_URL, frameworkVersion, VERSION_DEFINE } from '@ultimat3/core';
7
7
  import { requireAppRoot } from './app-root';
8
8
  import { runVerify } from './cmd-verify';
9
9
  import type { CliCommand, CommandContext } from './command';
@@ -143,7 +143,7 @@ export function buildResult(input: {
143
143
  code: 'X_BUILD_FAILED',
144
144
  cause: `${input.command.join(' ')} exited ${result.code}`,
145
145
  fix: target === 'docker' ? 'x doctor --json && docker info' : 'x verify --json',
146
- docs: 'https://ultimate.dev/errors/X_BUILD_FAILED',
146
+ docs: ERROR_DOCS_URL,
147
147
  },
148
148
  ],
149
149
  data: {
@@ -27,6 +27,7 @@ import type { CommandResult } from './output';
27
27
  import { findingFrom } from './output';
28
28
  import type { ParsedArgs } from './parse';
29
29
  import { flagBool, flagString } from './parse';
30
+ import { quoteArg } from './shell-quote';
30
31
 
31
32
  /** Which of the four questions an invocation asked. `pass` is `<name>` and `--all` both. */
32
33
  type BackfillShape = 'list' | 'pending' | 'pass';
@@ -73,11 +74,23 @@ function readShape(args: ParsedArgs): { readonly shape: BackfillShape; readonly
73
74
  // `--name` is a FILTER under `--list` and a target everywhere else, so it selects a shape only
74
75
  // where `--list` is absent. Two spellings of one target are still two, and are refused.
75
76
  const target = list ? undefined : (positional ?? named);
77
+ // Under `--list` the positional is DROPPED by the line above, and until 2026-08 nothing said so:
78
+ // `x db backfill cleanup --list` printed the whole ledger and reported `ok: true` while the
79
+ // operator had named one sweep. `--name` is the filter's one spelling here, so this is a refusal
80
+ // and not a second reading of the argument — the same rule every other shape in this function
81
+ // follows, and the same argv one flag over (`--pending cleanup`) has always been refused.
82
+ if (list && positional !== undefined) {
83
+ return refuseShape(
84
+ 'name',
85
+ `x db backfill --list filters the ledger with --name, so the positional "${positional}" selects nothing — it is a pass target in every other shape`,
86
+ `x db backfill --list --name ${quoteArg(positional)} --json`,
87
+ );
88
+ }
76
89
  if (!list && positional !== undefined && named !== undefined) {
77
90
  return refuseShape(
78
91
  'name',
79
92
  `x db backfill names two backfills ("${positional}" and "${named}") — a pass sweeps the positional or --name, never both`,
80
- `x db backfill ${positional} --write --json`,
93
+ `x db backfill ${quoteArg(positional)} --write --json`,
81
94
  );
82
95
  }
83
96
  const asked = [
@@ -3,7 +3,7 @@
3
3
  // `x db branch ls` used to clone a database called `ls`, because the argument was the name.
4
4
  // The facts (what a branch is, per mode) are `db-branch.ts`; the client lifetime is here.
5
5
 
6
- import { nearestName } from '@ultimat3/core';
6
+ import { ERROR_DOCS_URL, nearestName } from '@ultimat3/core';
7
7
  import { createPostgresClient, type DbClient } from '@ultimat3/db';
8
8
  import type { CommandContext } from './command';
9
9
  import type { BranchRow } from './db-branch';
@@ -215,6 +215,6 @@ function notABranch(services: DevServices, name: string): CommandResult {
215
215
  code: 'X_DB_BRANCH_FAILED',
216
216
  cause: `"${name}" is not a branch of this database, so nothing was dropped (it would be ${target})`,
217
217
  fix: LIST_FIX,
218
- docs: 'https://ultimate.dev/errors/X_DB_BRANCH_FAILED',
218
+ docs: ERROR_DOCS_URL,
219
219
  });
220
220
  }
package/src/cmd-deploy.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  // anything that runs containers can execute.
4
4
 
5
5
  import { join } from 'node:path';
6
+ import { ERROR_DOCS_URL } from '@ultimat3/core';
6
7
  import { requireAppRoot } from './app-root';
7
8
  import type { CliCommand, CommandContext } from './command';
8
9
  import { BadFlagError, UnknownCommandError } from './errors';
@@ -31,6 +32,13 @@ import { quoteArg } from './shell-quote';
31
32
  * honours. Both compose definitions carry it — `docker/docker-compose.prod.yml`'s `backfill` and
32
33
  * the one `templates/scaffold-container.ts` scaffolds, which also gates on `migrate` completing.
33
34
  * This paragraph said they "still owe" both for as long as they have had them.
35
+ *
36
+ * COMPOSE-ONLY, and `backfill` is why that has to be written down. `docker/helm`'s `roles:` map is
37
+ * `web|sync|worker|scheduler|replicator` plus a `migrate` Job — there is no `backfill` object in
38
+ * the chart at all — and the helm plan is one `helm upgrade --install`, so this list describes
39
+ * neither the objects a chart deploy creates nor the steps it runs. Every reader of it is therefore
40
+ * inside the compose branch; the SUMMARY reads `plan.steps` instead, or `x deploy --method helm`
41
+ * reports a post-deploy sweep to an operator that nothing will ever run.
34
42
  */
35
43
  export const DEPLOY_ROLES = ['migrate', 'web', 'sync', 'worker', 'scheduler', 'backfill'] as const;
36
44
 
@@ -202,11 +210,14 @@ export const deployCommand: CliCommand = {
202
210
  env: { ...plan.env },
203
211
  steps: plan.steps.map((step) => ({ role: step.role, command: step.command.join(' ') })),
204
212
  };
213
+ // The roles THIS plan has, never the compose list: on `--method helm` there is one step,
214
+ // `all`, and naming `backfill` there promises an operator a sweep the chart cannot run.
215
+ const roles = plan.steps.map((step) => step.role).join(',');
205
216
  if (flagBool(ctx.args, 'dry-run')) {
206
217
  return {
207
218
  ok: true,
208
219
  command: 'deploy',
209
- summary: msg('cli.deploy.plan', { images: 1, roles: DEPLOY_ROLES.join(',') }),
220
+ summary: msg('cli.deploy.plan', { images: 1, roles }),
210
221
  data: planJson,
211
222
  lines: plan.steps.map(
212
223
  (step) => ` ${step.role.padEnd(10)} ${stepLine(plan.env, step.command)}`,
@@ -225,7 +236,7 @@ export const deployCommand: CliCommand = {
225
236
  code: 'X_DEPLOY_FAILED',
226
237
  cause: `role "${step.role}" step exited ${result.code}`,
227
238
  fix: `${stepLine(plan.env, step.command)} # run it directly to see the full output`,
228
- docs: 'https://ultimate.dev/errors/X_DEPLOY_FAILED',
239
+ docs: ERROR_DOCS_URL,
229
240
  },
230
241
  ],
231
242
  data: planJson,
@@ -235,7 +246,7 @@ export const deployCommand: CliCommand = {
235
246
  return {
236
247
  ok: true,
237
248
  command: 'deploy',
238
- summary: msg('cli.deploy.plan', { images: 1, roles: DEPLOY_ROLES.join(',') }),
249
+ summary: msg('cli.deploy.plan', { images: 1, roles }),
239
250
  data: planJson,
240
251
  };
241
252
  },
package/src/cmd-dev.ts CHANGED
@@ -199,6 +199,9 @@ export async function startDev(options: StartDevOptions): Promise<DevServer> {
199
199
  // declaration, and `x dev` and `serve.ts` must not be able to disagree about where the app's
200
200
  // sign-in page is.
201
201
  signInPath: await loadSignInPath(options.root),
202
+ // The same seam `serve.ts` passes: the app's own error page is a FILE, so the root is what
203
+ // `startWeb` needs to find one.
204
+ root: options.root,
202
205
  // The one document this process serves that the app did not write; `startRoles` covers the
203
206
  // app's own surfaces itself. `x dev` sends the policy report-only, so an uncovered `<style>`
204
207
  // here is a console report rather than a blank page — which is how this reached production.
package/src/cmd-docs.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  // should not have to guess which of 29 packages holds the answer, and it must never be handed a
5
5
  // URL: `node_modules` already contains every doc, because the published artifact IS the source.
6
6
 
7
+ import { ERROR_DOCS_URL } from '@ultimat3/core';
7
8
  import type { DocEntry, DocHit } from '@ultimat3/manifest';
8
9
  import { nearestTopics, scanInstalledDocs, searchDocs } from '@ultimat3/manifest';
9
10
  import type { CliCommand, CommandContext } from './command';
@@ -22,7 +23,7 @@ const unresolvedFinding = (): Finding => ({
22
23
  code: 'X_CLI_UNEXPECTED',
23
24
  cause: '@ultimat3/core does not resolve from the installed CLI, so no docs could be read',
24
25
  fix: 'bun install && x doctor --json',
25
- docs: 'https://ultimate.dev/errors/X_CLI_UNEXPECTED',
26
+ docs: ERROR_DOCS_URL,
26
27
  at: import.meta.dir,
27
28
  });
28
29
 
package/src/cmd-doctor.ts CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { existsSync } from 'node:fs';
6
6
  import { join } from 'node:path';
7
- import { tryResolveEnvironment, usesDevCursorSecret } from '@ultimat3/core';
7
+ import { ERROR_DOCS_URL, tryResolveEnvironment, usesDevCursorSecret } from '@ultimat3/core';
8
8
  import { STORAGE_SIGNING_SECRET_KEY, usesDevStorageSecret } from '@ultimat3/storage';
9
9
  import { findAppRoot, REQUIRED_BUN, versionAtLeast } from './app-root';
10
10
  import type { CliCommand, CommandContext } from './command';
@@ -49,12 +49,10 @@ export interface DoctorProbe {
49
49
  snapshots(): Promise<readonly Finding[]>;
50
50
  }
51
51
 
52
- const docs = (code: string): string => `https://ultimate.dev/errors/${code}`;
53
-
54
52
  const finding = (code: string, cause: string, fix: string, at?: string): Finding =>
55
53
  at === undefined
56
- ? { code, cause, fix, docs: docs(code) }
57
- : { code, cause, fix, docs: docs(code), at };
54
+ ? { code, cause, fix, docs: ERROR_DOCS_URL }
55
+ : { code, cause, fix, docs: ERROR_DOCS_URL, at };
58
56
 
59
57
  export const OFFLINE_FALLBACK = 'apps/web/app/offline.tsx';
60
58
 
package/src/cmd-env.ts CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  // Bun ships no path-join primitive, and `.env.example` is written app-root-relative.
6
6
  import { join } from 'node:path';
7
- import { checkEnv, ENV_EXAMPLE_PATH, maskedEnvValues } from '@ultimat3/core';
7
+ import { checkEnv, ENV_EXAMPLE_PATH, ERROR_DOCS_URL, maskedEnvValues } from '@ultimat3/core';
8
8
  import { ENV_SCHEMA_EXPORT, envExampleFor, loadEnvSchema } from './app-env';
9
9
  import { APP_CONFIG_FILE, requireAppRoot } from './app-root';
10
10
  import type { CliCommand, CommandContext } from './command';
@@ -54,7 +54,7 @@ async function checkProcessEnv(ctx: CommandContext): Promise<CommandResult> {
54
54
  code: 'X_ENV_MISSING',
55
55
  cause: `${issue.key} is ${issue.reason} (expected ${issue.expected})`,
56
56
  fix: issue.fix,
57
- docs: 'https://ultimate.dev/errors/X_ENV_MISSING',
57
+ docs: ERROR_DOCS_URL,
58
58
  at: ENV_EXAMPLE_PATH,
59
59
  }));
60
60
  return {
package/src/cmd-fix.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  // (`docs/architecture/02-boundaries.md`) — a caller runs the printed edit, or the generated
4
4
  // `git mv`, itself.
5
5
 
6
- import { nearestName } from '@ultimat3/core';
6
+ import { ERROR_DOCS_URL, nearestName } from '@ultimat3/core';
7
7
  import { appImportGraph, readAppSources } from './app-boundaries';
8
8
  import { requireAppRoot } from './app-root';
9
9
  import type { BoundaryCut } from './boundary-cuts';
@@ -18,8 +18,6 @@ export { planBoundaryCuts };
18
18
 
19
19
  export const FIX_SUBCOMMANDS = ['boundary'] as const;
20
20
 
21
- const docsUrl = (code: string): string => `https://ultimate.dev/errors/${code}`;
22
-
23
21
  /**
24
22
  * Accept either an app-root-relative path or a suffix that matches exactly one scanned file —
25
23
  * an agent copying the path out of a `fix:` line has the short form
@@ -81,7 +79,7 @@ const findingForCut = (cut: BoundaryCut): Finding => ({
81
79
  code: cut.code,
82
80
  cause: cut.cause,
83
81
  fix: cut.edit,
84
- docs: docsUrl(cut.code),
82
+ docs: ERROR_DOCS_URL,
85
83
  at: cut.at,
86
84
  });
87
85