@ultimat3/cli 29.4.0 → 30.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.
Files changed (448) hide show
  1. package/CLAUDE.md +64 -40
  2. package/README.md +61 -11
  3. package/package.json +42 -28
  4. package/src/affected-tests.ts +97 -0
  5. package/src/affected-unowned.ts +43 -0
  6. package/src/affected.ts +25 -6
  7. package/src/app-boundaries.ts +29 -1
  8. package/src/app-evals.ts +12 -4
  9. package/src/app-load-unresolved.ts +62 -0
  10. package/src/app-load.ts +30 -37
  11. package/src/app-mcp.ts +14 -2
  12. package/src/app-module-graph.ts +232 -0
  13. package/src/app-reload-graph.ts +2 -2
  14. package/src/app-root.ts +37 -1
  15. package/src/bin.ts +1 -0
  16. package/src/boundary-findings.ts +6 -0
  17. package/src/budgets-freshness.ts +32 -0
  18. package/src/budgets.ts +20 -1
  19. package/src/build-capture-env.ts +9 -0
  20. package/src/cdp-shot-a11y.ts +2 -39
  21. package/src/ci-tested.ts +243 -0
  22. package/src/client-emit-customer-runtime.ts +139 -0
  23. package/src/client-emit-customer.ts +108 -0
  24. package/src/client-emit-drift.ts +72 -0
  25. package/src/client-emit-header.ts +67 -0
  26. package/src/client-emit-schemas.ts +175 -0
  27. package/src/client-emit-types.ts +121 -0
  28. package/src/client-emit-vendor.ts +146 -0
  29. package/src/client-errors-routes.ts +229 -0
  30. package/src/client-errors-script.ts +74 -0
  31. package/src/client-vitals-route.ts +197 -0
  32. package/src/client-vitals-script.ts +56 -0
  33. package/src/cmd-affected.ts +23 -0
  34. package/src/cmd-build-spec.ts +7 -1
  35. package/src/cmd-build.ts +35 -9
  36. package/src/cmd-ci-spec.ts +42 -6
  37. package/src/cmd-ci.ts +99 -2
  38. package/src/cmd-client-customer.ts +134 -0
  39. package/src/cmd-client-source.ts +81 -0
  40. package/src/cmd-client-spec.ts +48 -0
  41. package/src/cmd-client-write.ts +41 -0
  42. package/src/cmd-client.ts +111 -0
  43. package/src/cmd-db-recount.ts +120 -0
  44. package/src/cmd-db-spec.ts +36 -6
  45. package/src/cmd-db.ts +11 -3
  46. package/src/cmd-deploy-compose.ts +61 -3
  47. package/src/cmd-deploy-drain.ts +10 -2
  48. package/src/cmd-deploy-helm.ts +11 -0
  49. package/src/cmd-deploy-preflight.ts +118 -0
  50. package/src/cmd-deploy-spec.ts +58 -6
  51. package/src/cmd-deploy-verify.ts +380 -0
  52. package/src/cmd-deploy.ts +42 -9
  53. package/src/cmd-docs.ts +34 -4
  54. package/src/cmd-doctor.ts +66 -9
  55. package/src/cmd-drill-spec.ts +25 -0
  56. package/src/cmd-drill.ts +176 -0
  57. package/src/cmd-env-init.ts +102 -0
  58. package/src/cmd-env-spec.ts +10 -3
  59. package/src/cmd-env.ts +15 -10
  60. package/src/cmd-errors-recent.ts +60 -0
  61. package/src/cmd-errors-spec.ts +9 -1
  62. package/src/cmd-errors.ts +4 -0
  63. package/src/cmd-exercise-spec.ts +19 -0
  64. package/src/cmd-exercise.ts +129 -0
  65. package/src/cmd-generate-spec.ts +9 -1
  66. package/src/cmd-generate.ts +32 -27
  67. package/src/cmd-graph-load.ts +97 -0
  68. package/src/cmd-graph-spec.ts +16 -0
  69. package/src/cmd-graph.ts +73 -0
  70. package/src/cmd-i18n.ts +4 -1
  71. package/src/cmd-impact-spec.ts +13 -0
  72. package/src/cmd-impact.ts +64 -0
  73. package/src/cmd-jobs-resolve.ts +76 -0
  74. package/src/cmd-jobs-spec.ts +21 -2
  75. package/src/cmd-jobs.ts +10 -6
  76. package/src/cmd-mail-spec.ts +28 -0
  77. package/src/cmd-mail.ts +70 -0
  78. package/src/cmd-mcp-lock.ts +69 -0
  79. package/src/cmd-mcp-spec.ts +23 -4
  80. package/src/cmd-mcp.ts +17 -3
  81. package/src/cmd-new-spec.ts +8 -1
  82. package/src/cmd-new.ts +61 -9
  83. package/src/cmd-planned.ts +0 -6
  84. package/src/cmd-repro-spec.ts +31 -0
  85. package/src/cmd-repro.ts +174 -0
  86. package/src/cmd-stop-spec.ts +21 -0
  87. package/src/cmd-stop.ts +166 -0
  88. package/src/cmd-test-spec.ts +17 -4
  89. package/src/cmd-test.ts +70 -14
  90. package/src/cmd-upgrade-spec.ts +26 -0
  91. package/src/cmd-upgrade.ts +274 -0
  92. package/src/cmd-verify-spec.ts +3 -3
  93. package/src/db-backfill.ts +6 -0
  94. package/src/db-diagnostics.ts +106 -0
  95. package/src/db-generate.ts +9 -0
  96. package/src/db-reset-template.ts +1 -1
  97. package/src/db-rolling.ts +45 -0
  98. package/src/db-seed-generate.ts +188 -0
  99. package/src/deploy-verify-findings.ts +98 -0
  100. package/src/deploy-verify-flags.ts +109 -0
  101. package/src/deploy-verify-grade.ts +185 -0
  102. package/src/deploy-verify-queues.ts +53 -0
  103. package/src/dev-boot.ts +35 -15
  104. package/src/dev-child-watch.ts +16 -4
  105. package/src/dev-dashboard-guard.ts +8 -1
  106. package/src/dev-dashboard.ts +7 -0
  107. package/src/dev-lock-identity.ts +61 -0
  108. package/src/dev-lock-port-holder.ts +80 -0
  109. package/src/dev-lock-steal.ts +124 -0
  110. package/src/dev-lock.ts +92 -120
  111. package/src/dev-mail-templates.ts +51 -0
  112. package/src/dev-rebuild.ts +26 -2
  113. package/src/dev-route-table.ts +17 -0
  114. package/src/dev-supervisor.ts +5 -2
  115. package/src/dev-watch-tree.ts +11 -10
  116. package/src/dev-watch.ts +1 -1
  117. package/src/dispatch.ts +29 -6
  118. package/src/docs-section.ts +69 -0
  119. package/src/docs-sources.ts +100 -0
  120. package/src/doctor-auth.ts +11 -2
  121. package/src/doctor-chart-alerts.ts +55 -0
  122. package/src/doctor-pooler.ts +107 -0
  123. package/src/doctor-rolling.ts +35 -0
  124. package/src/doctor-row-security.ts +40 -0
  125. package/src/document-options.ts +13 -0
  126. package/src/drill-sync-lane.ts +25 -0
  127. package/src/drill-targets.ts +39 -0
  128. package/src/duplicate-packages.ts +1 -1
  129. package/src/env-production.ts +93 -0
  130. package/src/error-codes-shot.ts +59 -0
  131. package/src/error-codes-tooling.ts +29 -0
  132. package/src/error-codes.ts +100 -54
  133. package/src/error-contract.ts +2 -2
  134. package/src/errors.ts +22 -3
  135. package/src/exercise-database.ts +99 -0
  136. package/src/exercise-findings.ts +159 -0
  137. package/src/exercise-overrides.ts +42 -0
  138. package/src/exercise-registration.ts +58 -0
  139. package/src/exercise-static.ts +88 -0
  140. package/src/exercise-subject.ts +69 -0
  141. package/src/failure-capture-boot.ts +30 -0
  142. package/src/framework-env.ts +7 -1
  143. package/src/framework-schema.ts +72 -5
  144. package/src/generate-catalog-locales.ts +19 -0
  145. package/src/generate-chart-alerts.ts +156 -0
  146. package/src/generate-feature-required.ts +65 -0
  147. package/src/generate-feature.ts +83 -6
  148. package/src/generate-files.ts +24 -0
  149. package/src/generate-format-written.ts +32 -0
  150. package/src/generate-kinds.ts +6 -0
  151. package/src/generate-sign-in-config.ts +100 -0
  152. package/src/generate-sign-in-manifest.ts +28 -0
  153. package/src/generate-sign-in-mode.ts +101 -0
  154. package/src/generate-sign-in.ts +250 -0
  155. package/src/generate-write.ts +44 -7
  156. package/src/hand-rolled-socket.ts +72 -0
  157. package/src/i18n-app-key-shapes.ts +29 -0
  158. package/src/i18n-audit.ts +10 -3
  159. package/src/i18n-registration.ts +18 -3
  160. package/src/index.ts +2 -0
  161. package/src/island-bundle.ts +4 -3
  162. package/src/island-disposer.ts +116 -0
  163. package/src/island-realtime-state.ts +34 -6
  164. package/src/island-realtime.ts +6 -2
  165. package/src/island-routes.ts +3 -2
  166. package/src/isr-query-advice.ts +6 -11
  167. package/src/isr-query-guard.ts +4 -3
  168. package/src/job-budget.ts +95 -0
  169. package/src/jobs-json.ts +11 -0
  170. package/src/lint-findings.ts +104 -0
  171. package/src/local-cli.ts +34 -4
  172. package/src/loopback-host.ts +2 -2
  173. package/src/mail-preview-findings.ts +24 -0
  174. package/src/mcp-errors.ts +82 -2
  175. package/src/mcp-host.ts +11 -1
  176. package/src/mcp-lock-findings.ts +57 -0
  177. package/src/measure-paths.ts +12 -4
  178. package/src/measure-scope.ts +27 -13
  179. package/src/messages-client.ts +23 -0
  180. package/src/messages-db.ts +56 -0
  181. package/src/messages-gate.ts +107 -0
  182. package/src/messages-repro.ts +11 -0
  183. package/src/messages-shot.ts +51 -0
  184. package/src/messages-upgrade.ts +45 -0
  185. package/src/messages.ts +53 -178
  186. package/src/metrics-endpoint.ts +23 -5
  187. package/src/new-shapes-generate.ts +71 -0
  188. package/src/new-shapes.ts +63 -0
  189. package/src/openapi-compile-ir.ts +68 -0
  190. package/src/openapi-compile-schema.ts +284 -0
  191. package/src/openapi-compile.ts +273 -0
  192. package/src/openapi-refuse.ts +45 -0
  193. package/src/otlp-export.ts +2 -1
  194. package/src/output-findings.ts +123 -0
  195. package/src/output.ts +32 -8
  196. package/src/package-boundaries.ts +110 -0
  197. package/src/page-pins.ts +23 -0
  198. package/src/plain-routes.ts +13 -0
  199. package/src/prerender-entries.ts +59 -0
  200. package/src/prerender-heaviest.ts +27 -0
  201. package/src/prerender-preload.ts +6 -0
  202. package/src/prerender-report.ts +12 -0
  203. package/src/prerender-seo-reads.ts +38 -0
  204. package/src/prerender-served.ts +22 -7
  205. package/src/prerender-vitals.ts +42 -0
  206. package/src/prerender.ts +61 -32
  207. package/src/pwa-artifacts.ts +4 -3
  208. package/src/pwa-manifest-locales.ts +1 -1
  209. package/src/registry.ts +18 -0
  210. package/src/repro-bundle.ts +65 -0
  211. package/src/repro-errors.ts +47 -0
  212. package/src/repro-job.ts +72 -0
  213. package/src/repro-literal.ts +41 -0
  214. package/src/repro-request.ts +48 -0
  215. package/src/repro-write.ts +100 -0
  216. package/src/retain-sweep.ts +27 -0
  217. package/src/retained-assets-boot.ts +113 -0
  218. package/src/retained-assets-layout.ts +110 -0
  219. package/src/retained-assets-read.ts +142 -0
  220. package/src/retained-assets-sweep.ts +70 -0
  221. package/src/retained-assets.ts +171 -0
  222. package/src/revalidated-response.ts +8 -5
  223. package/src/role-replicator.ts +7 -1
  224. package/src/role-start-devices.ts +119 -0
  225. package/src/role-start-types.ts +6 -0
  226. package/src/role-start.ts +22 -5
  227. package/src/role-sync.ts +7 -1
  228. package/src/route-islands.ts +42 -0
  229. package/src/route-table.ts +2 -1
  230. package/src/row-security-boot.ts +58 -0
  231. package/src/row-security-wanted.ts +32 -0
  232. package/src/runtime-admin.ts +0 -1
  233. package/src/runtime-assets-site-cache.ts +55 -0
  234. package/src/runtime-assets-site.ts +136 -0
  235. package/src/runtime-assets.ts +99 -55
  236. package/src/runtime-bindings.ts +19 -3
  237. package/src/runtime-cache.ts +23 -14
  238. package/src/runtime-isr-outcome.ts +114 -60
  239. package/src/runtime-purge.ts +14 -2
  240. package/src/runtime-queue.ts +31 -4
  241. package/src/runtime-render-document.ts +204 -0
  242. package/src/runtime-render.ts +48 -198
  243. package/src/runtime-replica-position.ts +62 -0
  244. package/src/runtime-replica.ts +18 -3
  245. package/src/runtime-s3-path-style.ts +17 -0
  246. package/src/runtime-services.ts +19 -6
  247. package/src/runtime-statement-bound.ts +22 -0
  248. package/src/runtime-storage-upload.ts +9 -0
  249. package/src/runtime-storage.ts +41 -81
  250. package/src/schema-diff-views.ts +54 -0
  251. package/src/schema-diff.ts +21 -3
  252. package/src/schema-drift.ts +28 -9
  253. package/src/schema-dump-drift.ts +91 -7
  254. package/src/script-csp.ts +1 -1
  255. package/src/seo-cards.ts +26 -0
  256. package/src/seo-routes.ts +22 -8
  257. package/src/serve-boot.ts +54 -21
  258. package/src/serve-drain.ts +1 -0
  259. package/src/serve-env.ts +26 -8
  260. package/src/serve-retire.ts +12 -6
  261. package/src/serve-web.ts +42 -4
  262. package/src/serve.ts +9 -7
  263. package/src/site-asset-routes.ts +7 -1
  264. package/src/site-config.ts +4 -0
  265. package/src/site-llms-pages.ts +106 -0
  266. package/src/site-llms.ts +64 -0
  267. package/src/site-seo-write.ts +4 -2
  268. package/src/site-seo.ts +55 -6
  269. package/src/source-mtime.ts +50 -0
  270. package/src/static-capture-errors.ts +41 -0
  271. package/src/static-capture.ts +56 -0
  272. package/src/static-cards-serve.ts +39 -0
  273. package/src/static-cards.ts +93 -0
  274. package/src/static-document.ts +14 -13
  275. package/src/static-images.ts +115 -0
  276. package/src/stream-hole-scope.ts +55 -0
  277. package/src/style-bundle.ts +1 -1
  278. package/src/style-routes.ts +3 -2
  279. package/src/sw-precache-plan.ts +2 -2
  280. package/src/sw-routes.ts +2 -2
  281. package/src/templates/action-tests.ts +306 -0
  282. package/src/templates/action.ts +81 -269
  283. package/src/templates/backfill.ts +1 -1
  284. package/src/templates/entity.ts +21 -5
  285. package/src/templates/github/ci.yml.ts +8 -9
  286. package/src/templates/guard-bare-error.ts +2 -2
  287. package/src/templates/guard-error-declared.ts +263 -0
  288. package/src/templates/guard-native-confirm.ts +132 -0
  289. package/src/templates/guard-raw-fetch.ts +151 -0
  290. package/src/templates/guard-raw-interval.ts +121 -0
  291. package/src/templates/guard-repo-raw-sql.ts +71 -2
  292. package/src/templates/job.ts +1 -1
  293. package/src/templates/naming.ts +5 -0
  294. package/src/templates/pinned/docker-compose.yml +6 -0
  295. package/src/templates/pinned-images.ts +8 -0
  296. package/src/templates/policy.ts +85 -2
  297. package/src/templates/query.ts +70 -13
  298. package/src/templates/resource-create.ts +3 -1
  299. package/src/templates/resource-form-island.ts +15 -4
  300. package/src/templates/resource-page-db.ts +83 -0
  301. package/src/templates/resource-page.ts +161 -52
  302. package/src/templates/route.ts +4 -6
  303. package/src/templates/scaffold-app.ts +3 -0
  304. package/src/templates/scaffold-auth.ts +2 -1
  305. package/src/templates/scaffold-bin.ts +5 -52
  306. package/src/templates/scaffold-claude-agents.ts +19 -19
  307. package/src/templates/scaffold-claude-commands.ts +28 -31
  308. package/src/templates/scaffold-claude.ts +26 -23
  309. package/src/templates/scaffold-container-compose.ts +6 -1
  310. package/src/templates/scaffold-container.ts +24 -4
  311. package/src/templates/scaffold-dashboard-bare.ts +3 -3
  312. package/src/templates/scaffold-dashboard-example-tests.ts +49 -9
  313. package/src/templates/scaffold-dashboard-example-view.ts +156 -0
  314. package/src/templates/scaffold-dashboard-example.ts +43 -131
  315. package/src/templates/scaffold-dashboard-shared.ts +3 -7
  316. package/src/templates/scaffold-db-client.ts +20 -6
  317. package/src/templates/scaffold-db-package.ts +5 -14
  318. package/src/templates/scaffold-docs.ts +73 -57
  319. package/src/templates/scaffold-domain-package.ts +18 -14
  320. package/src/templates/scaffold-entries.ts +7 -2
  321. package/src/templates/scaffold-exercise.ts +37 -0
  322. package/src/templates/scaffold-guards.ts +13 -1
  323. package/src/templates/scaffold-helm-alerts.ts +234 -0
  324. package/src/templates/scaffold-helm-templates.ts +7 -5
  325. package/src/templates/scaffold-helm.ts +46 -12
  326. package/src/templates/scaffold-i18n.ts +6 -2
  327. package/src/templates/scaffold-mcp-endpoint.ts +182 -0
  328. package/src/templates/scaffold-mcp-json.ts +31 -0
  329. package/src/templates/scaffold-mcp-package.ts +59 -56
  330. package/src/templates/scaffold-repo.ts +7 -2
  331. package/src/templates/scaffold-shell.ts +45 -79
  332. package/src/templates/scaffold-test-services.ts +13 -5
  333. package/src/templates/shapes/api-keys.ts +170 -0
  334. package/src/templates/shapes/api.ts +101 -0
  335. package/src/templates/shapes/blog-site.ts +340 -0
  336. package/src/templates/shapes/blog-slice.ts +265 -0
  337. package/src/templates/shapes/blog.ts +114 -0
  338. package/src/templates/shapes/dashboard-detail.ts +242 -0
  339. package/src/templates/shapes/dashboard.ts +53 -0
  340. package/src/templates/shapes/index.ts +17 -0
  341. package/src/templates/shapes/overlay.ts +99 -0
  342. package/src/templates/shapes/shared-edits.ts +46 -0
  343. package/src/templates/sign-in-action-tests.ts +237 -0
  344. package/src/templates/sign-in-authenticator.ts +112 -0
  345. package/src/templates/sign-in-oidc.ts +189 -0
  346. package/src/templates/sign-in-pages.ts +148 -0
  347. package/src/templates/sign-in-password.ts +216 -0
  348. package/src/templates/sign-in-shared.ts +268 -0
  349. package/src/templates/slice-foundation.ts +70 -15
  350. package/src/test-batches.ts +1 -1
  351. package/src/test-database.ts +1 -1
  352. package/src/test-shards.ts +7 -1
  353. package/src/test-workers.ts +18 -12
  354. package/src/tool-gate-names.ts +34 -0
  355. package/src/upgrade-agents-link.ts +49 -0
  356. package/src/upgrade-bun-floor.ts +225 -0
  357. package/src/upgrade-gate.ts +228 -0
  358. package/src/upgrade-pins.ts +63 -0
  359. package/src/upgrade-plan.ts +214 -0
  360. package/src/upgrade-registry.ts +53 -0
  361. package/src/upgrade-report.ts +139 -0
  362. package/src/upgrade-root.ts +74 -0
  363. package/src/verify-budgets-build.ts +118 -0
  364. package/src/verify-checks.ts +67 -24
  365. package/src/verify-coverage-run.ts +1 -1
  366. package/src/verify-e2e-lane.ts +41 -0
  367. package/src/verify-exercise.ts +64 -0
  368. package/src/verify-floor.ts +41 -0
  369. package/src/verify-merge.ts +19 -4
  370. package/src/verify-role-load.ts +2 -0
  371. package/src/verify-run.ts +28 -8
  372. package/src/verify-shard.ts +12 -2
  373. package/src/verify-skip-ceiling.ts +87 -0
  374. package/src/verify-step.ts +5 -0
  375. package/src/verify-tests.ts +25 -5
  376. package/src/view-drift.ts +55 -0
  377. package/src/worker-bundle.ts +3 -2
  378. package/src/workspace-graph.ts +38 -19
  379. package/src/write-generated.ts +43 -0
  380. package/src/zone-boot.ts +20 -0
  381. package/wiki/Actions.md +342 -0
  382. package/wiki/Admin-Dashboard.md +432 -0
  383. package/wiki/Agents.md +958 -0
  384. package/wiki/Auth.md +195 -0
  385. package/wiki/Bare-VM.md +111 -0
  386. package/wiki/Batching-And-Preloading.md +136 -0
  387. package/wiki/Building-Your-Own-Base.md +203 -0
  388. package/wiki/CI-Parallel-Gate.md +220 -0
  389. package/wiki/CLI-Reference.md +1894 -0
  390. package/wiki/Caching-And-Invalidation.md +255 -0
  391. package/wiki/Client-Data.md +194 -0
  392. package/wiki/Client-Navigation.md +513 -0
  393. package/wiki/Compared.md +93 -0
  394. package/wiki/Configuration.md +868 -0
  395. package/wiki/Contributing.md +12 -0
  396. package/wiki/Deploy-Recipes.md +122 -0
  397. package/wiki/Deployment.md +508 -0
  398. package/wiki/Entities-And-Migrations.md +1116 -0
  399. package/wiki/FAQ.md +175 -0
  400. package/wiki/Feature-Flags.md +65 -0
  401. package/wiki/Getting-Started.md +221 -0
  402. package/wiki/Home.md +160 -0
  403. package/wiki/I18n.md +239 -0
  404. package/wiki/Installation.md +206 -0
  405. package/wiki/Interface-Rules.md +339 -0
  406. package/wiki/Jobs-And-Workflows.md +783 -0
  407. package/wiki/Known-Gaps.md +185 -0
  408. package/wiki/MCP-And-AI.md +753 -0
  409. package/wiki/Mail.md +138 -0
  410. package/wiki/Migrating-An-Existing-App.md +594 -0
  411. package/wiki/Migrating-From-Django.md +54 -0
  412. package/wiki/Migrating-From-Laravel.md +53 -0
  413. package/wiki/Migrating-From-Node-Drizzle.md +53 -0
  414. package/wiki/Migrating-From-Rails.md +53 -0
  415. package/wiki/Migrations-And-Backfills.md +552 -0
  416. package/wiki/Money.md +338 -0
  417. package/wiki/N-Plus-One-Detection.md +197 -0
  418. package/wiki/Notify.md +208 -0
  419. package/wiki/Observability.md +245 -0
  420. package/wiki/Outbound-HTTP.md +109 -0
  421. package/wiki/PWA-And-Offline.md +343 -0
  422. package/wiki/Policies-And-Authz.md +251 -0
  423. package/wiki/Project-Layout.md +150 -0
  424. package/wiki/Queries-And-Live-Queries.md +429 -0
  425. package/wiki/Realtime.md +734 -0
  426. package/wiki/Resource-Management.md +121 -0
  427. package/wiki/Routes-And-Render-Modes.md +521 -0
  428. package/wiki/SEO.md +288 -0
  429. package/wiki/Scheduled-Tasks.md +175 -0
  430. package/wiki/Scraping.md +493 -0
  431. package/wiki/Shapes.md +63 -0
  432. package/wiki/Stability.md +97 -0
  433. package/wiki/Static-Assets.md +166 -0
  434. package/wiki/Storage-And-Uploads.md +404 -0
  435. package/wiki/Testing.md +1314 -0
  436. package/wiki/The-Eight-Primitives.md +247 -0
  437. package/wiki/Theming.md +362 -0
  438. package/wiki/Timezones-And-Dates.md +194 -0
  439. package/wiki/Troubleshooting.md +163 -0
  440. package/wiki/Tutorial-01-First-App.md +289 -0
  441. package/wiki/Tutorial-02-First-Feature.md +339 -0
  442. package/wiki/Tutorial-03-Auth-And-Admin.md +379 -0
  443. package/wiki/Tutorial-04-Jobs-And-Realtime.md +263 -0
  444. package/wiki/Tutorial-05-Deploy-Free.md +231 -0
  445. package/wiki/Tutorial-06-Growing-Up.md +158 -0
  446. package/wiki/UI-Components.md +156 -0
  447. package/wiki/Upgrading.md +3667 -0
  448. package/src/stored-object-headers.ts +0 -43
@@ -0,0 +1,59 @@
1
+ // The X_* codes of `x shot` and the dev MCP server's `ui.*` tools — the picture-taking surface —
2
+ // split out of `error-codes.ts` at its 500-line ceiling. `error-codes.ts` folds these into the
3
+ // CLI's one owned union and registers them in its one call; nothing else imports this file.
4
+
5
+ /** Codes `x shot` and `ui.shot` / `ui.interact` / `ui.diff` declare and own. */
6
+ export const CLI_SHOT_ERROR_CODES = [
7
+ 'X_SHOT_BROWSER_MISSING',
8
+ // `ui.shot` (the dev MCP server): a route it will not photograph, and why.
9
+ 'X_UI_SHOT_ROUTE_UNKNOWN',
10
+ 'X_UI_SHOT_ROUTE_UNBUDGETED',
11
+ // `ui.interact`: the four ways a step list is refused. All four REFUSE rather than trim or skip
12
+ // — a step dropped, a keystroke swallowed or a navigation followed changes what the picture is of.
13
+ 'X_UI_INTERACT_STEPS_INVALID',
14
+ 'X_UI_INTERACT_SECRET_FIELD',
15
+ 'X_UI_INTERACT_LEFT_APP',
16
+ 'X_UI_INTERACT_STEP_FAILED',
17
+ // `ui.diff`: the three ways two captures fail to become a comparison. The path gate is what
18
+ // lets a file-reading tool sit under `dev:read` — it reads `.x/shot/` and nothing else.
19
+ 'X_UI_DIFF_PATH_OUTSIDE',
20
+ 'X_UI_DIFF_FILE_MISSING',
21
+ 'X_UI_DIFF_SIZE_MISMATCH',
22
+ // `x shot --island` — one code per way a component's named state fails to become a picture.
23
+ // The last of the four is the one that gates: it is checked against the expansion computed
24
+ // before a browser existed, so a capture loop that swallowed a failure cannot exit 0.
25
+ 'X_SHOT_ISLAND_STATES_EMPTY',
26
+ 'X_SHOT_ISLAND_UNPHOTOGRAPHABLE',
27
+ 'X_SHOT_ISLAND_UNSTUBBED_REQUEST',
28
+ 'X_SHOT_ISLAND_MISSING',
29
+ // The raw-CDP shot page's own refusals (`cdp-shot-errors.ts` holds the classes): which host it
30
+ // may open, which element it was pointed at, and which key it was asked to press.
31
+ 'X_SHOT_HOST_REFUSED',
32
+ 'X_SHOT_ELEMENT_MISSING',
33
+ 'X_SHOT_ELEMENT_UNREADY',
34
+ 'X_SHOT_KEY_INVALID',
35
+ ] as const;
36
+
37
+ export type CliShotErrorCode = (typeof CLI_SHOT_ERROR_CODES)[number];
38
+
39
+ /** Their titles, typed over the list above so a code without one is a build error. */
40
+ export const CLI_SHOT_ERROR_TITLES: Readonly<Record<CliShotErrorCode, string>> = {
41
+ X_SHOT_BROWSER_MISSING: '`x shot` has no browser to launch',
42
+ X_UI_SHOT_ROUTE_UNKNOWN: 'a ui.* tool was asked for a path no route answers',
43
+ X_UI_SHOT_ROUTE_UNBUDGETED: 'a ui.* tool refused a route that declares no budget.js',
44
+ X_UI_INTERACT_STEPS_INVALID: 'a ui.interact step list is over its bounds or malformed',
45
+ X_UI_INTERACT_SECRET_FIELD: 'ui.interact refused to type into a password field',
46
+ X_UI_INTERACT_LEFT_APP: 'a ui.interact step navigated off the dev server origin',
47
+ X_UI_INTERACT_STEP_FAILED: 'a ui.interact step raised a scraping error',
48
+ X_UI_DIFF_PATH_OUTSIDE: 'ui.diff was handed a path that does not resolve inside .x/shot/',
49
+ X_UI_DIFF_FILE_MISSING: 'ui.diff was handed a capture that is not on disk',
50
+ X_UI_DIFF_SIZE_MISMATCH: 'ui.diff was handed two captures of different sizes',
51
+ X_SHOT_ISLAND_STATES_EMPTY: 'an island states file declares no manifest',
52
+ X_SHOT_ISLAND_UNPHOTOGRAPHABLE: 'the island never reached a state worth photographing',
53
+ X_SHOT_ISLAND_UNSTUBBED_REQUEST: 'the island requested something no state stub answers',
54
+ X_SHOT_ISLAND_MISSING: 'a declared island picture is not on disk',
55
+ X_SHOT_HOST_REFUSED: "x shot was asked to open a host outside the run's allow list",
56
+ X_SHOT_ELEMENT_MISSING: 'no element matched the selector before the deadline',
57
+ X_SHOT_ELEMENT_UNREADY: 'the element matched and never became ready to act on',
58
+ X_SHOT_KEY_INVALID: 'a key chord the browser cannot press',
59
+ };
@@ -0,0 +1,29 @@
1
+ // The X_* codes of the app tooling commands — `x client`, `x graph` / `x impact` and `x repro` —
2
+ // split out of `error-codes.ts` at its 500-line ceiling, as `error-codes-shot.ts` was. That file
3
+ // folds these into the CLI's one owned union and registers them in its one call.
4
+
5
+ /** Codes `x client`, `x graph`, `x impact` and `x repro` declare and own. */
6
+ export const CLI_TOOLING_ERROR_CODES = [
7
+ 'X_CLIENT_SPEC_UNSUPPORTED',
8
+ 'X_CLIENT_STALE',
9
+ 'X_CLIENT_EDITED',
10
+ 'X_GRAPH_NODE_UNKNOWN',
11
+ 'X_CLIENT_SPEC_INVALID',
12
+ 'X_REPRO_UNKNOWN',
13
+ 'X_REPRO_NOT_REPRODUCED',
14
+ 'X_REPRO_CAPTURE_OFF',
15
+ ] as const;
16
+
17
+ export type CliToolingErrorCode = (typeof CLI_TOOLING_ERROR_CODES)[number];
18
+
19
+ /** Their titles, typed over the list above so a code without one is a build error. */
20
+ export const CLI_TOOLING_ERROR_TITLES: Readonly<Record<CliToolingErrorCode, string>> = {
21
+ X_CLIENT_SPEC_UNSUPPORTED: 'x client met an OpenAPI construct its compiler refuses',
22
+ X_CLIENT_STALE: 'a generated client is older than the document it was compiled from',
23
+ X_CLIENT_EDITED: 'a generated client file was changed by hand',
24
+ X_GRAPH_NODE_UNKNOWN: 'x graph or x impact was asked about a node the app graph does not hold',
25
+ X_CLIENT_SPEC_INVALID: 'x client was handed a document that is not OpenAPI 3.0 or 3.1',
26
+ X_REPRO_UNKNOWN: 'x repro was given an id nothing holds',
27
+ X_REPRO_NOT_REPRODUCED: 'a written repro test did not fail the way the original did',
28
+ X_REPRO_CAPTURE_OFF: 'x repro was given a request id while failure capture is off',
29
+ };
@@ -3,6 +3,10 @@
3
3
  // the CLI is the surface an agent reads first — a failure here has to be actionable without a doc
4
4
  // lookup or a second round-trip. The classes that throw these codes live in `./errors`.
5
5
  import { registerErrorCodes } from '@ultimat3/core';
6
+ import type { CliShotErrorCode } from './error-codes-shot';
7
+ import { CLI_SHOT_ERROR_CODES, CLI_SHOT_ERROR_TITLES } from './error-codes-shot';
8
+ import type { CliToolingErrorCode } from './error-codes-tooling';
9
+ import { CLI_TOOLING_ERROR_CODES, CLI_TOOLING_ERROR_TITLES } from './error-codes-tooling';
6
10
 
7
11
  /** Codes this package declares and owns. */
8
12
  export const CLI_OWNED_ERROR_CODES = [
@@ -161,34 +165,6 @@ export const CLI_OWNED_ERROR_CODES = [
161
165
  // source-level fix, and an agent following it here edits a file that is already right — the
162
166
  // repair is an install, and only `duplicate-packages.ts` can see that.
163
167
  'X_PACKAGE_DUPLICATED',
164
- 'X_SHOT_BROWSER_MISSING',
165
- // `ui.shot` (the dev MCP server): a route it will not photograph, and why.
166
- 'X_UI_SHOT_ROUTE_UNKNOWN',
167
- 'X_UI_SHOT_ROUTE_UNBUDGETED',
168
- // `ui.interact`: the four ways a step list is refused. All four REFUSE rather than trim or skip
169
- // — a step dropped, a keystroke swallowed or a navigation followed changes what the picture is of.
170
- 'X_UI_INTERACT_STEPS_INVALID',
171
- 'X_UI_INTERACT_SECRET_FIELD',
172
- 'X_UI_INTERACT_LEFT_APP',
173
- 'X_UI_INTERACT_STEP_FAILED',
174
- // `ui.diff`: the three ways two captures fail to become a comparison. The path gate is what
175
- // lets a file-reading tool sit under `dev:read` — it reads `.x/shot/` and nothing else.
176
- 'X_UI_DIFF_PATH_OUTSIDE',
177
- 'X_UI_DIFF_FILE_MISSING',
178
- 'X_UI_DIFF_SIZE_MISMATCH',
179
- // `x shot --island` — one code per way a component's named state fails to become a picture.
180
- // The last of the four is the one that gates: it is checked against the expansion computed
181
- // before a browser existed, so a capture loop that swallowed a failure cannot exit 0.
182
- 'X_SHOT_ISLAND_STATES_EMPTY',
183
- 'X_SHOT_ISLAND_UNPHOTOGRAPHABLE',
184
- 'X_SHOT_ISLAND_UNSTUBBED_REQUEST',
185
- 'X_SHOT_ISLAND_MISSING',
186
- // The raw-CDP shot page's own refusals (`cdp-shot-errors.ts` holds the classes): which host it
187
- // may open, which element it was pointed at, and which key it was asked to press.
188
- 'X_SHOT_HOST_REFUSED',
189
- 'X_SHOT_ELEMENT_MISSING',
190
- 'X_SHOT_ELEMENT_UNREADY',
191
- 'X_SHOT_KEY_INVALID',
192
168
  'X_GH_UNAVAILABLE',
193
169
  'X_GH_NOT_AUTHENTICATED',
194
170
  'X_GH_COMMAND_FAILED',
@@ -237,6 +213,43 @@ export const CLI_OWNED_ERROR_CODES = [
237
213
  'X_CATALOG_SLOT_MISMATCH',
238
214
  'X_CATALOG_UNTRANSLATED',
239
215
  'X_WORKER_QUEUE_UNKNOWN',
216
+ 'X_UPGRADE_DIRTY_TREE',
217
+ 'X_UPGRADE_MIXED_PINS',
218
+ 'X_UPGRADE_HOP_TOO_WIDE',
219
+ 'X_UPGRADE_REGISTRY_UNREACHABLE',
220
+ 'X_UPGRADE_PIN_UNREADABLE',
221
+ 'X_UPGRADE_VERSION_UNPUBLISHED',
222
+ 'X_UPGRADE_INSTALL_FAILED',
223
+ 'X_UPGRADE_GATE_UNREADABLE',
224
+ 'X_DEPLOY_BUILD_UNSTAMPED',
225
+ 'X_DEPLOY_UNSTABLE',
226
+ 'X_DEPLOY_QUEUE_UNSERVED',
227
+ 'X_DEPLOY_BUILD_MISMATCH',
228
+ 'X_JOB_OUTLASTS_DRAIN',
229
+ 'X_FEATURE_REQUIRED',
230
+ 'X_BOUNDARY_PACKAGE_TO_APP',
231
+ 'X_APP_IMPORT_UNRESOLVED',
232
+ 'X_STREAM_ALREADY_FLUSHED',
233
+ 'X_QUERY_SLICE_ENTITY',
234
+ 'X_DEPLOY_ENV_MISSING',
235
+ 'X_ENV_FILE_EXISTS',
236
+ 'X_DEV_STOP_TIMEOUT',
237
+ 'X_DEV_STOP_DENIED',
238
+ 'X_ASSET_RETAIN_FAILED',
239
+ 'X_APP_MODULE_LOAD_FAILED',
240
+ 'X_CHART_MISSING',
241
+ 'X_ENTITY_VIEW_DRIFT',
242
+ 'X_ENTITY_RETAIN_UNSWEPT',
243
+ 'X_NEW_SHAPE_UNKNOWN',
244
+ 'X_NEW_SHAPE_CONFLICT',
245
+ 'X_SEO_CARD_ROUTE_UNKNOWN',
246
+ 'X_SEO_CARD_BROWSER_MISSING',
247
+ 'X_IMAGE_SOURCE_UNREADABLE',
248
+ 'X_VERIFY_SKIPS_OVER_CEILING',
249
+ 'X_ASSET_VARIANT_UNDECLARED',
250
+ 'X_NEW_SHAPE_GENERATE_FAILED',
251
+ 'X_ISLAND_DISPOSER_MISSING',
252
+ 'X_SOCKET_HAND_ROLLED',
240
253
  ] as const;
241
254
 
242
255
  /**
@@ -260,9 +273,8 @@ export const CLI_BORROWED_ERROR_CODES = [
260
273
  // nearest declared name all come from `permissionUnknown`. A CLI-owned twin would be a second
261
274
  // wording for the condition `can()` already refuses at run time, and the two would drift.
262
275
  'X_PERMISSION_UNKNOWN',
263
- // `@ultimat3/db`'s, reported by `x doctor`'s database probe with that package's own two-branch
264
- // fix. The CLI is the half that asks BEFORE a pool is opened; the condition and the remedy are
265
- // both db's, and a CLI twin would be one unreachable database with two names.
276
+ // `@ultimat3/db`'s, reported by `x doctor`'s database probe with db's own fix: the CLI only asks
277
+ // before a pool opens, and a CLI twin would be one unreachable database with two names.
266
278
  'X_DB_UNAVAILABLE',
267
279
  'X_CONFIG_INVALID',
268
280
  'X_ENV_MISSING',
@@ -271,17 +283,24 @@ export const CLI_BORROWED_ERROR_CODES = [
271
283
  ] as const;
272
284
 
273
285
  /** Every code the CLI can throw: the ones it owns plus the one it borrows. */
274
- export const CLI_ERROR_CODES = [...CLI_OWNED_ERROR_CODES, ...CLI_BORROWED_ERROR_CODES] as const;
286
+ export const CLI_ERROR_CODES = [
287
+ ...CLI_OWNED_ERROR_CODES,
288
+ ...CLI_SHOT_ERROR_CODES,
289
+ ...CLI_TOOLING_ERROR_CODES,
290
+ ...CLI_BORROWED_ERROR_CODES,
291
+ ] as const;
275
292
 
276
- export type CliOwnedErrorCode = (typeof CLI_OWNED_ERROR_CODES)[number];
293
+ /** The codes listed in THIS file — the ones `CLI_ERROR_TITLES` below must title. */
294
+ type CliListedErrorCode = (typeof CLI_OWNED_ERROR_CODES)[number];
295
+ export type CliOwnedErrorCode = CliListedErrorCode | CliShotErrorCode | CliToolingErrorCode;
277
296
  export type CliErrorCode = (typeof CLI_ERROR_CODES)[number];
278
297
 
279
298
  /**
280
299
  * Registered titles, so `x errors list` enumerates the CLI's codes alongside every other
281
300
  * package's instead of leaving a hole an agent has to read source to fill. Typed over
282
- * `CliOwnedErrorCode`, so adding a code without a title is a build error.
301
+ * `CliListedErrorCode`, so adding a code without a title is a build error.
283
302
  */
284
- export const CLI_ERROR_TITLES: Readonly<Record<CliOwnedErrorCode, string>> = {
303
+ export const CLI_ERROR_TITLES: Readonly<Record<CliListedErrorCode, string>> = {
285
304
  X_CLI_UNKNOWN_COMMAND: 'not a command in the registry',
286
305
  X_CLI_BAD_FLAG: 'unknown flag, missing value, or a value the command refuses',
287
306
  X_VERIFY_FAILED: 'at least one x verify step failed',
@@ -355,24 +374,6 @@ export const CLI_ERROR_TITLES: Readonly<Record<CliOwnedErrorCode, string>> = {
355
374
  X_SECRETS_EDIT_FAILED: 'the editor exited non-zero, so nothing was resealed',
356
375
  X_WORKSPACE_DEP_UNDECLARED: 'a workspace imports another workspace it does not declare',
357
376
  X_PACKAGE_DUPLICATED: 'two copies of one registry-holding framework package are installed',
358
- X_SHOT_BROWSER_MISSING: '`x shot` has no browser to launch',
359
- X_UI_SHOT_ROUTE_UNKNOWN: 'a ui.* tool was asked for a path no route answers',
360
- X_UI_SHOT_ROUTE_UNBUDGETED: 'a ui.* tool refused a route that declares no budget.js',
361
- X_UI_INTERACT_STEPS_INVALID: 'a ui.interact step list is over its bounds or malformed',
362
- X_UI_INTERACT_SECRET_FIELD: 'ui.interact refused to type into a password field',
363
- X_UI_INTERACT_LEFT_APP: 'a ui.interact step navigated off the dev server origin',
364
- X_UI_INTERACT_STEP_FAILED: 'a ui.interact step raised a scraping error',
365
- X_UI_DIFF_PATH_OUTSIDE: 'ui.diff was handed a path that does not resolve inside .x/shot/',
366
- X_UI_DIFF_FILE_MISSING: 'ui.diff was handed a capture that is not on disk',
367
- X_UI_DIFF_SIZE_MISMATCH: 'ui.diff was handed two captures of different sizes',
368
- X_SHOT_ISLAND_STATES_EMPTY: 'an island states file declares no manifest',
369
- X_SHOT_ISLAND_UNPHOTOGRAPHABLE: 'the island never reached a state worth photographing',
370
- X_SHOT_ISLAND_UNSTUBBED_REQUEST: 'the island requested something no state stub answers',
371
- X_SHOT_ISLAND_MISSING: 'a declared island picture is not on disk',
372
- X_SHOT_HOST_REFUSED: "x shot was asked to open a host outside the run's allow list",
373
- X_SHOT_ELEMENT_MISSING: 'no element matched the selector before the deadline',
374
- X_SHOT_ELEMENT_UNREADY: 'the element matched and never became ready to act on',
375
- X_SHOT_KEY_INVALID: 'a key chord the browser cannot press',
376
377
  X_GH_UNAVAILABLE: 'the GitHub CLI is not runnable from here',
377
378
  X_GH_NOT_AUTHENTICATED: 'gh holds no credentials for this host',
378
379
  X_GH_COMMAND_FAILED: 'a gh invocation exited non-zero',
@@ -430,12 +431,57 @@ export const CLI_ERROR_TITLES: Readonly<Record<CliOwnedErrorCode, string>> = {
430
431
  X_CATALOG_SLOT_MISMATCH: 'two locales define one key with different interpolation slots',
431
432
  X_CATALOG_UNTRANSLATED: "a locale still carries the reference locale's value",
432
433
  X_WORKER_QUEUE_UNKNOWN: 'WORKER_QUEUES names a queue no job and no jobs.queues entry declares',
434
+ X_UPGRADE_DIRTY_TREE: 'x upgrade refused a tree with uncommitted changes',
435
+ X_UPGRADE_MIXED_PINS: 'x upgrade found two @ultimat3/* versions across the workspaces',
436
+ X_UPGRADE_HOP_TOO_WIDE: 'x upgrade --to is outside the one hop a run may make',
437
+ X_UPGRADE_REGISTRY_UNREACHABLE: 'x upgrade could not read the published versions from npm',
438
+ X_UPGRADE_PIN_UNREADABLE: 'x upgrade found an @ultimat3/* dependency that names no version',
439
+ X_UPGRADE_VERSION_UNPUBLISHED: 'x upgrade --to names a version npm does not have',
440
+ X_UPGRADE_INSTALL_FAILED: 'bun install failed after x upgrade wrote the pins',
441
+ X_UPGRADE_GATE_UNREADABLE: 'the installed x verify answered with no JSON report',
442
+ X_DEPLOY_BUILD_UNSTAMPED: 'the deployment answers with no build header',
443
+ X_DEPLOY_UNSTABLE: 'the build answered but did not hold',
444
+ X_DEPLOY_QUEUE_UNSERVED: 'a registered queue has no serving worker of this build',
445
+ X_DEPLOY_BUILD_MISMATCH: 'production never served this build',
446
+ X_JOB_OUTLASTS_DRAIN: "a job's declared timeout outlasts the worker's budget",
447
+ X_FEATURE_REQUIRED: 'x g query or x g backfill named no slice to read',
448
+ X_BOUNDARY_PACKAGE_TO_APP: 'packages/ imported apps/ at runtime',
449
+ X_APP_IMPORT_UNRESOLVED: 'an app module imports what does not resolve',
450
+ X_STREAM_ALREADY_FLUSHED: 'a response header was written after the stream flushed',
451
+ X_QUERY_SLICE_ENTITY: 'x g query into a slice whose entity is not the one it scaffolds',
452
+ X_DEPLOY_ENV_MISSING: 'x deploy --method compose found no complete .env.production',
453
+ X_ENV_FILE_EXISTS: 'x env init --production found .env.production already written',
454
+ X_DEV_STOP_TIMEOUT: 'x stop sent SIGTERM and x dev did not exit in time',
455
+ X_DEV_STOP_DENIED: 'x stop may not signal the x dev the lock names',
456
+ X_ASSET_RETAIN_FAILED: 'Build assets were not retained',
457
+ X_APP_MODULE_LOAD_FAILED:
458
+ 'an app module failed to load, so the role refused to serve a partial app',
459
+ X_CHART_MISSING: 'x g chart-alerts found no Helm chart to write into',
460
+ X_ENTITY_VIEW_DRIFT: "a view entity's columns disagree with the view its migration created",
461
+ X_ENTITY_RETAIN_UNSWEPT: 'an entity declares retain and no purge() sweeps it',
462
+ X_NEW_SHAPE_UNKNOWN: 'x new --shape names no registered shape',
463
+ X_NEW_SHAPE_CONFLICT: 'x new --shape with --no-example',
464
+ X_SEO_CARD_ROUTE_UNKNOWN: 'og.capture names a path no site/ route answers',
465
+ X_SEO_CARD_BROWSER_MISSING:
466
+ 'a static build with social cards found no browser to photograph them',
467
+ X_IMAGE_SOURCE_UNREADABLE: 'a responsiveImage src is not a file the static export holds',
468
+ X_VERIFY_SKIPS_OVER_CEILING: 'a step skipped more tests than its skipCeiling allows',
469
+ X_ASSET_VARIANT_UNDECLARED: 'an /assets image variant responsiveImage() never mints',
470
+ X_NEW_SHAPE_GENERATE_FAILED: 'a shape generator failed in the new app',
471
+ X_ISLAND_DISPOSER_MISSING: "an island's mount returns no disposer",
472
+ X_SOCKET_HAND_ROLLED: 'an app upgrades a websocket itself',
433
473
  };
434
474
 
435
475
  // One unconditional call, so a second package claiming one of the CLI's codes throws
436
476
  // X_ERROR_CODE_DUPLICATE instead of losing silently to whichever module imported first.
437
477
  registerErrorCodes(
438
- Object.fromEntries(Object.entries(CLI_ERROR_TITLES).map(([code, title]) => [code, { title }])),
478
+ Object.fromEntries(
479
+ Object.entries({
480
+ ...CLI_ERROR_TITLES,
481
+ ...CLI_SHOT_ERROR_TITLES,
482
+ ...CLI_TOOLING_ERROR_TITLES,
483
+ }).map(([code, title]) => [code, { title }]),
484
+ ),
439
485
  );
440
486
 
441
487
  // This file exports NO `docsFor(code)`, and adding one back is the defect. A CLI error passes no
@@ -42,8 +42,8 @@ export const COMMAND_TOKENS: readonly RegExp[] = [
42
42
  // Built from `fix-path.ts`'s extension list, because the token that makes a fix count as an
43
43
  // instruction is exactly the token `citedPathProblem` then has to resolve.
44
44
  new RegExp(FILE_TOKEN_PATTERN),
45
- // A repo script, run as written: `bin/check --full`, `./bin/probe`. An app's gate is `bin/check`,
46
- // so without this the one command its fixes most often name read as the banned word "check".
45
+ // A repo script, run as written: `bin/check --full`, `./bin/probe`. An app's own wrapper is often
46
+ // `bin/check` (an older scaffold's gate), so without this its fixes read as the banned word "check".
47
47
  // Anchored like `x` above — `/usr/bin/env` and `robin/check` are not the repo's scripts.
48
48
  // (`scripts/<name>.ts` is already a file token.)
49
49
  /(?:^|[\s;|&("'`])(?:\.\/)?bin\/[\w.-]+/,
package/src/errors.ts CHANGED
@@ -123,11 +123,30 @@ export class VerifyFailedError extends UltimateError {
123
123
 
124
124
  /** The command needs an app root (a directory containing `app.config.ts`) and found none. */
125
125
  export class NotInAppError extends UltimateError {
126
- constructor(input: { command: string; from: string }) {
126
+ constructor(input: {
127
+ command: string;
128
+ from: string;
129
+ /** `app.config.ts` directories under `from`: an app below the cwd is one `cd` away. */
130
+ below?: readonly string[];
131
+ /** `from` already depends on the framework, so `x new` would be a second project, not a fix. */
132
+ adopter?: boolean;
133
+ }) {
134
+ const [first] = input.below ?? [];
135
+ const found = (input.below ?? []).join(', ');
127
136
  super({
128
137
  code: 'X_NOT_IN_APP',
129
- cause: `"x ${input.command}" must run inside an Ultimate app; no app.config.ts at or above ${input.from}`,
130
- fix: 'x new myapp && cd myapp',
138
+ cause:
139
+ first !== undefined
140
+ ? `"x ${input.command}" runs from the directory holding app.config.ts, and that is below ${input.from}: ${found}`
141
+ : input.adopter === true
142
+ ? `"x ${input.command}" must run inside an Ultimate app; ${input.from} depends on @ultimat3/* but no app.config.ts exists at or above it, or within three directories below`
143
+ : `"x ${input.command}" must run inside an Ultimate app; no app.config.ts at or above ${input.from}`,
144
+ fix:
145
+ first !== undefined
146
+ ? `cd ${quoteArg(first)} && x ${quoteArg(input.command)} --json`
147
+ : input.adopter === true
148
+ ? `x new scratch-app --dry-run --json # its file list carries app.config.ts; write one at the app root, then re-run: x ${quoteArg(input.command)} --json`
149
+ : 'bunx create-ultimate myapp && cd myapp',
131
150
  });
132
151
  }
133
152
  }
@@ -0,0 +1,99 @@
1
+ // Where `x exercise` runs: a test process, pinned before the app loads, on a scratch migrated
2
+ // database installed as the ambient `db()` and thrown away after — never the developer's own (the
3
+ // pass COMMITS its rows), on the engine the schema dump replays on (`db-replay-engine.ts`), so the
4
+ // gate needs nothing listening, exactly as the `drift` step does not.
5
+
6
+ import type { Environment } from '@ultimat3/core';
7
+ import { ENVIRONMENT_KEY, resolveEnvironment, UltimateError } from '@ultimat3/core';
8
+ import type { DbClient } from '@ultimat3/db';
9
+ import { migrate, raw, setDbClient } from '@ultimat3/db';
10
+ import { testSealKeyEnv } from '@ultimat3/testing';
11
+ import { chooseReplayEngine, withScratchDatabase } from './db-replay-engine';
12
+ import { applyFrameworkSchema } from './framework-schema';
13
+ import { readMigrations } from './migrations';
14
+ import { resolveServices } from './runtime-bindings';
15
+
16
+ type Env = Readonly<Record<string, string | undefined>>;
17
+
18
+ /**
19
+ * A deployed environment asked for the pass. `X_CONTRACT_EXERCISE_ENVIRONMENT`, core's code for
20
+ * the same refusal one level down — with this command's fix, never `bun test`'s.
21
+ */
22
+ export class ExerciseDeployRefusedError extends UltimateError {
23
+ constructor(environment: Environment) {
24
+ super({
25
+ code: 'X_CONTRACT_EXERCISE_ENVIRONMENT',
26
+ cause: `x exercise runs every handler on generated input, and ${ENVIRONMENT_KEY} resolved ${environment}: the pass runs in development or test only`,
27
+ fix: `${ENVIRONMENT_KEY}=development x exercise --json # from a checkout, never against a deploy`,
28
+ meta: { environment },
29
+ });
30
+ }
31
+ }
32
+
33
+ /**
34
+ * The environment the pass runs as: always `test`, once a deploy is refused. The pass is a test run
35
+ * on a scratch database thrown away after it, and an app picks its store from this answer at module
36
+ * load: a `development` app on its embedded memory store would read none of the rows seeded here.
37
+ */
38
+ export function exerciseEnvironment(env: Env): Environment {
39
+ const environment = resolveEnvironment({ env });
40
+ if (environment !== 'development' && environment !== 'test') {
41
+ throw new ExerciseDeployRefusedError(environment);
42
+ }
43
+ return 'test';
44
+ }
45
+
46
+ const PINNED = [ENVIRONMENT_KEY, 'NODE_ENV'] as const;
47
+
48
+ /**
49
+ * `work` — the app's load and the pass — in a test process: the environment pinned BEFORE any app
50
+ * module reads it, and the throwaway seal key `bun test` gets when the app supplied none (CI names
51
+ * no `ULTIMATE_SECRETS_KEY`, and every row sealed here dies with the scratch database). Every
52
+ * variable is put back as found: a command that exits must not leave the next one a test process.
53
+ */
54
+ export async function withExerciseProcess<T>(
55
+ root: string,
56
+ env: Env,
57
+ work: () => Promise<T>,
58
+ ): Promise<T> {
59
+ const environment = exerciseEnvironment(env);
60
+ const pinned: Record<string, string> = Object.fromEntries(
61
+ PINNED.map((key) => [key, environment]),
62
+ );
63
+ const added = { ...pinned, ...testSealKeyEnv({ root, env: { ...env, ...pinned } }) };
64
+ const previous = Object.keys(added).map((key) => [key, process.env[key]] as const);
65
+ Object.assign(process.env, added);
66
+ try {
67
+ return await work();
68
+ } finally {
69
+ for (const [key, value] of previous) {
70
+ if (value === undefined) delete process.env[key];
71
+ else process.env[key] = value;
72
+ }
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Framework tables, then every migration — the order every real boot runs them in — then `work`
78
+ * with the client installed. Released either way: a command that exits holding an installed
79
+ * client would hand the next caller in this process a closed pool.
80
+ */
81
+ export async function withExerciseDatabase<T>(
82
+ root: string,
83
+ env: Env,
84
+ work: (client: DbClient) => Promise<T>,
85
+ ): Promise<T> {
86
+ const migrations = await readMigrations(root);
87
+ const engine = await chooseReplayEngine(migrations, env);
88
+ const { stateDir } = resolveServices(root, env);
89
+ return withScratchDatabase(engine, stateDir, async (client) => {
90
+ await applyFrameworkSchema((statement) => client.execute(raw(statement)));
91
+ await migrate({ migrations, client });
92
+ setDbClient(client);
93
+ try {
94
+ return await work(client);
95
+ } finally {
96
+ setDbClient(undefined);
97
+ }
98
+ });
99
+ }
@@ -0,0 +1,159 @@
1
+ // What `x exercise` makes of a pass's records: one finding per `unreached` primitive, carrying the
2
+ // override to paste as its fix, and per crossed tenant and diverged crash proof; the counts; the
3
+ // table; and the one report document both `--json` and `.x/exercise.json` hold. Pure — records in, plain values out — so the gate step and the
4
+ // command cannot disagree about what is red.
5
+
6
+ // why: Bun ships no path joiner.
7
+ import { join } from 'node:path';
8
+ import {
9
+ ERROR_DOCS_URL,
10
+ renderCauseValue,
11
+ renderFixLiteral,
12
+ renderFixShellArg,
13
+ } from '@ultimat3/core';
14
+ import type { ExerciseRecord } from '@ultimat3/testing';
15
+ import { planSweepFindings, tenantProofFindings } from '@ultimat3/testing';
16
+ import type { Finding } from './output';
17
+ import { renderTable } from './table';
18
+
19
+ /** Where the pass's report is written, app-root-relative: state, gitignored with the rest of `.x/`. */
20
+ export const EXERCISE_REPORT_FILE = join('.x', 'exercise.json');
21
+
22
+ /** The file an override is pasted into — `defineExercise()`'s one home. */
23
+ export const EXERCISE_FILE = 'apps/web/exercise.ts';
24
+
25
+ type Outcome = ExerciseRecord['outcome'];
26
+
27
+ export interface ExerciseCounts {
28
+ readonly total: number;
29
+ readonly reached: number;
30
+ readonly refused: number;
31
+ readonly unreached: number;
32
+ readonly off: number;
33
+ }
34
+
35
+ export const exerciseCounts = (records: readonly ExerciseRecord[]): ExerciseCounts => {
36
+ const count = (outcome: Outcome): number =>
37
+ records.filter((one) => one.outcome === outcome).length;
38
+ return {
39
+ total: records.length,
40
+ reached: count('reached'),
41
+ refused: count('refused'),
42
+ unreached: count('unreached'),
43
+ off: count('off'),
44
+ };
45
+ };
46
+
47
+ /**
48
+ * `X_CONTRACT_EXERCISE_UNREACHED`, `@ultimat3/action`'s code for the same silence one level up: a
49
+ * primitive no run got to as an allowed actor. The fix is the override the pass printed, or the
50
+ * input shape to fill when it printed none — then the one-primitive rerun that proves it.
51
+ */
52
+ const unreachedFinding = (record: ExerciseRecord): Finding => {
53
+ const name = renderFixShellArg(record.name, '<name>');
54
+ const override = record.override ?? `${record.name}: { input: { /* a value it accepts */ } }`;
55
+ const stopped = record.code === undefined ? '' : ` — stopped on ${record.code}`;
56
+ const why = record.reason === undefined ? '' : ` (${renderCauseValue(record.reason)})`;
57
+ return {
58
+ code: 'X_CONTRACT_EXERCISE_UNREACHED',
59
+ cause: `${record.kind} ${renderCauseValue(record.name)} never reached its handler as an allowed actor in the exercise pass${stopped}${why}`,
60
+ fix: `defineExercise({ ${override} }) # paste the entry into ${EXERCISE_FILE}, or { off: '<why>' }; then: x exercise ${name} --json`,
61
+ docs: ERROR_DOCS_URL,
62
+ at: EXERCISE_FILE,
63
+ };
64
+ };
65
+
66
+ type CrashFinding = Extract<
67
+ NonNullable<ExerciseRecord['crash']>,
68
+ { readonly verdict: 'diverged' }
69
+ >['findings'][number];
70
+
71
+ const crashFindingsOf = (record: ExerciseRecord): readonly CrashFinding[] =>
72
+ record.crash?.verdict === 'diverged' ? record.crash.findings : [];
73
+
74
+ /**
75
+ * `X_JOB_CRASH_DIVERGED`, testing's code: a reached job, killed at a step boundary, made an effect
76
+ * more often than a clean run — it sits outside every `step.run`, so a retry makes it again. One
77
+ * finding per effect and the step it follows, every boundary that showed it named in the cause.
78
+ */
79
+ const crashFindings = (record: ExerciseRecord): readonly Finding[] => {
80
+ const groups = new Map<string, CrashFinding[]>();
81
+ for (const one of crashFindingsOf(record)) {
82
+ const key = `${one.effect}\u0000${one.after ?? ''}`;
83
+ groups.set(key, [...(groups.get(key) ?? []), one]);
84
+ }
85
+ const job = renderFixShellArg(record.name, '<job>');
86
+ return [...groups.values()].map((group): Finding => {
87
+ const [first] = group as [CrashFinding, ...CrashFinding[]];
88
+ const where =
89
+ first.after === undefined
90
+ ? 'before the first step'
91
+ : `after step ${JSON.stringify(first.after)}`;
92
+ const at = group.map((one) => JSON.stringify(one.boundary)).join(', ');
93
+ return {
94
+ code: 'X_JOB_CRASH_DIVERGED',
95
+ cause: `job ${renderCauseValue(record.name)} killed at ${at} made ${renderCauseValue(first.effect)} ${first.killed}× against ${first.clean}× in a clean run: it runs outside every step, ${where}, so the retry after a crash makes it again`,
96
+ fix: `x exercise ${job} --json # in job(${renderFixLiteral(record.name, '<job>')}): wrap the ${renderFixLiteral(first.effect, '<effect>')} made ${where} in step.run('<name>', async () => …)`,
97
+ docs: ERROR_DOCS_URL,
98
+ at: `${record.kind} ${record.name}`,
99
+ };
100
+ });
101
+ };
102
+
103
+ /** One line per killed boundary that diverged: `job chargeOrder · killed at mail · X_… effect 2×`. */
104
+ export const crashFindingLines = (records: readonly ExerciseRecord[]): readonly string[] =>
105
+ records.flatMap((record) =>
106
+ crashFindingsOf(record).map(
107
+ (one) =>
108
+ `${record.kind} ${record.name} · killed at ${one.boundary} · X_JOB_CRASH_DIVERGED ${one.effect} ${one.killed}× (clean ${one.clean}×)`,
109
+ ),
110
+ );
111
+
112
+ /**
113
+ * Unreached primitives, then the tenant proof's crossed ones: tenant B handed A's data or changed
114
+ * A's row; then the crash proof's diverged jobs. `unproven`, either proof's, is reported on the
115
+ * record and never red — the pass could not ask, which is not an answer.
116
+ */
117
+ export const exerciseFindings = (records: readonly ExerciseRecord[]): readonly Finding[] => [
118
+ ...records.filter((one) => one.outcome === 'unreached').map(unreachedFinding),
119
+ ...tenantProofFindings(records).map((one) => ({ ...one, docs: ERROR_DOCS_URL })),
120
+ ...records.flatMap(crashFindings),
121
+ // The plan and scale sweeps: a statement no index serves, a shape that grows with the rows.
122
+ ...planSweepFindings(records).map((one) => ({ ...one, docs: ERROR_DOCS_URL })),
123
+ ];
124
+
125
+ export interface ExerciseReport {
126
+ readonly seed: number | null;
127
+ readonly counts: ExerciseCounts;
128
+ readonly records: readonly ExerciseRecord[];
129
+ }
130
+
131
+ export const exerciseReport = (
132
+ records: readonly ExerciseRecord[],
133
+ seed: number | undefined,
134
+ ): ExerciseReport => ({ seed: seed ?? null, counts: exerciseCounts(records), records });
135
+
136
+ /** Unreached first — the rows that need a hand — then refused, off, reached; by name within. */
137
+ const RANK: readonly Outcome[] = ['unreached', 'refused', 'off', 'reached'];
138
+
139
+ export const renderExerciseTable = (records: readonly ExerciseRecord[]): readonly string[] =>
140
+ renderTable(
141
+ ['primitive', 'kind', 'outcome', 'tenant', 'crash', 'code', 'statements', 'effects', 'ms'],
142
+ records
143
+ .toSorted(
144
+ (left, right) =>
145
+ RANK.indexOf(left.outcome) - RANK.indexOf(right.outcome) ||
146
+ left.name.localeCompare(right.name),
147
+ )
148
+ .map((one) => [
149
+ one.name,
150
+ one.kind,
151
+ one.outcome,
152
+ one.tenant?.verdict ?? '-',
153
+ one.crash?.verdict ?? '-',
154
+ one.code ?? '-',
155
+ String(one.statements),
156
+ String(one.effects.length),
157
+ String(one.ms),
158
+ ]),
159
+ );
@@ -0,0 +1,42 @@
1
+ // `apps/web/exercise.ts`, read: the one `defineExercise()` export an app may declare, or none. A
2
+ // file with no such export, or with two, is refused — an override nobody reads is a silent skip,
3
+ // and two are two answers to one question.
4
+
5
+ // why: Bun ships no path joiner.
6
+ import { join } from 'node:path';
7
+ import { UltimateError } from '@ultimat3/core';
8
+ import type { ExerciseOverrides } from '@ultimat3/testing';
9
+ import { EXERCISE_FILE } from './exercise-findings';
10
+
11
+ const isOverrides = (value: unknown): value is ExerciseOverrides =>
12
+ typeof value === 'object' &&
13
+ value !== null &&
14
+ (value as { readonly kind?: unknown }).kind === 'exercise-overrides';
15
+
16
+ /**
17
+ * The file exists and declares no single `defineExercise()` export. `X_CONTRACT_EXERCISE_UNDECLARED`
18
+ * — `@ultimat3/action`'s code for an exercise option that says nothing — because that is what a
19
+ * file of overrides the pass cannot find is.
20
+ */
21
+ export class ExerciseOverridesUnreadableError extends UltimateError {
22
+ constructor(found: number) {
23
+ super({
24
+ code: 'X_CONTRACT_EXERCISE_UNDECLARED',
25
+ cause: `${EXERCISE_FILE} exports ${found === 0 ? 'no' : String(found)} defineExercise() value${found === 1 ? '' : 's'}, so x exercise cannot tell which overrides to apply`,
26
+ fix: `export const exercise = defineExercise({}); # one export in ${EXERCISE_FILE}, imported from @ultimat3/testing`,
27
+ });
28
+ }
29
+ }
30
+
31
+ /** The overrides, or `undefined` when the app has no `apps/web/exercise.ts`. */
32
+ export async function readExerciseOverrides(root: string): Promise<ExerciseOverrides | undefined> {
33
+ const path = join(root, EXERCISE_FILE);
34
+ if (!(await Bun.file(path).exists())) return undefined;
35
+ const module = (await import(path)) as Record<string, unknown>;
36
+ const found = Object.values(module).filter(isOverrides);
37
+ const [only] = found;
38
+ if (found.length !== 1 || only === undefined) {
39
+ throw new ExerciseOverridesUnreadableError(found.length);
40
+ }
41
+ return only;
42
+ }