@ultimat3/cli 29.4.0 → 30.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 (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 +1313 -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,129 @@
1
+ // `x exercise [<name>] [--seed n]` — every action, query and job run once against generated rows,
2
+ // with no test written: load the app, read `apps/web/exercise.ts`, run `@ultimat3/testing`'s pass
3
+ // on a scratch database, write `.x/exercise.json`. CLI wiring only: the pass is testing's, the
4
+ // verdict is `exercise-findings.ts`'s, so the gate step and this command read one rule.
5
+
6
+ // why: Bun ships no path joiner.
7
+ import { join } from 'node:path';
8
+ import { describeEntities, registeredEntities } from '@ultimat3/entity';
9
+ import { registeredSubjects } from '@ultimat3/jobs';
10
+ import type { NamedActor } from '@ultimat3/policy';
11
+ import type { ExerciseRecord, SeedEntity } from '@ultimat3/testing';
12
+ import {
13
+ entitySeedWriter,
14
+ planSweepLines,
15
+ runExercisePass,
16
+ tenantFindingLines,
17
+ } from '@ultimat3/testing';
18
+ import { loadApp } from './app-load';
19
+ import { requireAppRoot } from './app-root';
20
+ import { exerciseSpec } from './cmd-exercise-spec';
21
+ import type { CliCommand, CommandContext } from './command';
22
+ import { registeredEntityCores } from './db-seed-generate';
23
+ import { devActors } from './dev-policy';
24
+ import { withExerciseDatabase, withExerciseProcess } from './exercise-database';
25
+ import {
26
+ crashFindingLines,
27
+ EXERCISE_REPORT_FILE,
28
+ exerciseFindings,
29
+ exerciseReport,
30
+ renderExerciseTable,
31
+ } from './exercise-findings';
32
+ import { readExerciseOverrides } from './exercise-overrides';
33
+ import { entityTenantFindings } from './exercise-static';
34
+ import { subjectFindings, subjectProofFindings } from './exercise-subject';
35
+ import { readIntFlag } from './flag-number';
36
+ import { msg } from './messages';
37
+ import type { CommandResult, JsonValue } from './output';
38
+
39
+ /**
40
+ * The actors `x policy explain` evaluates — `devActors()`, the `/_x` panel's own set — each a
41
+ * member of `tenant`. Never a second "every role" builder: the pass picks among exactly these.
42
+ */
43
+ export const exerciseRoles = (tenant: string): readonly NamedActor[] =>
44
+ devActors().map((named) =>
45
+ named.actor === null ? named : { ...named, actor: { ...named.actor, orgId: tenant } },
46
+ );
47
+
48
+ const readSeed = (ctx: CommandContext): number | undefined =>
49
+ readIntFlag(ctx.args, {
50
+ name: 'seed',
51
+ command: 'exercise',
52
+ min: 0,
53
+ max: 2_147_483_647,
54
+ example: 'x exercise --seed 1 --json',
55
+ });
56
+
57
+ export const exerciseCommand: CliCommand = {
58
+ spec: exerciseSpec,
59
+ async run(ctx: CommandContext): Promise<CommandResult> {
60
+ const root = requireAppRoot('exercise', ctx.cwd).dir;
61
+ const seed = readSeed(ctx);
62
+ return withExerciseProcess(root, ctx.env, () => exercise(ctx, root, seed));
63
+ },
64
+ };
65
+
66
+ async function exercise(
67
+ ctx: CommandContext,
68
+ root: string,
69
+ seed: number | undefined,
70
+ ): Promise<CommandResult> {
71
+ const only = ctx.args.positionals[0];
72
+ const loaded = await loadApp(root);
73
+ const overrides = await readExerciseOverrides(root);
74
+ const cores = registeredEntityCores();
75
+ const entities: readonly SeedEntity[] = Object.values(cores);
76
+ // Static before any row: a subject whose plan is refused is not proved, it is reported.
77
+ const subjectStatic =
78
+ only === undefined ? subjectFindings(registeredSubjects(), registeredEntities()) : [];
79
+ const proved = registeredSubjects().filter(
80
+ (subject) => only === undefined && !subjectStatic.some((f) => f.at === subject.entity),
81
+ );
82
+ const records: readonly ExerciseRecord[] = await withExerciseDatabase(root, ctx.env, (client) =>
83
+ runExercisePass({
84
+ entities,
85
+ writer: entitySeedWriter({ entities: cores, client }),
86
+ ...(seed === undefined ? {} : { seed }),
87
+ ...(overrides === undefined ? {} : { overrides }),
88
+ ...(only === undefined ? {} : { only }),
89
+ roles: exerciseRoles,
90
+ }),
91
+ );
92
+ // The subject proof (`exercise-subject.ts`) on a scratch database of its own: the pass's rows
93
+ // are committed, and a second seed graph beside them collides on every small unique domain.
94
+ const subjectProofs =
95
+ proved.length === 0
96
+ ? []
97
+ : await withExerciseDatabase(root, ctx.env, (client) =>
98
+ subjectProofFindings(proved, cores, client),
99
+ );
100
+ const report = exerciseReport(records, seed);
101
+ // The whole pass, not a one-primitive run's slice of it, is what the file is for — but a
102
+ // one-primitive run is still a fact worth keeping, so it is written either way.
103
+ await Bun.write(join(root, EXERCISE_REPORT_FILE), `${JSON.stringify(report, null, 2)}\n`);
104
+ const findings = [
105
+ ...loaded.findings,
106
+ // The static half of the tenant proof (`exercise-static.ts`): the pass's own question,
107
+ // asked of the declarations before any row is written.
108
+ ...(only === undefined ? entityTenantFindings(describeEntities()) : []),
109
+ ...subjectStatic,
110
+ ...subjectProofs,
111
+ ...exerciseFindings(records),
112
+ ];
113
+ return {
114
+ ok: findings.length === 0,
115
+ command: 'exercise',
116
+ summary:
117
+ records.length === 0
118
+ ? msg('cli.exercise.none')
119
+ : msg('cli.exercise.summary', { ...report.counts, file: EXERCISE_REPORT_FILE }),
120
+ lines: [
121
+ ...renderExerciseTable(records),
122
+ ...tenantFindingLines(records),
123
+ ...crashFindingLines(records),
124
+ ...planSweepLines(records),
125
+ ].map((line) => ` ${line}`),
126
+ findings,
127
+ data: report as unknown as JsonValue,
128
+ };
129
+ }
@@ -11,7 +11,8 @@ export const generateSpec: CommandSpec = {
11
11
  // Projected from `GENERATORS`, never restated: the literal that used to live here had already
12
12
  // drifted — it omitted `backfill` — and a usage line that can disagree with the list it
13
13
  // describes is exactly the second source of truth axiom 2 forbids.
14
- usage: `x g ${GENERATORS.join('|')} <name> [--feature f]`,
14
+ // `sign-in` and `chart-alerts` take no `<name>`, so each is spelled once, in its own clause.
15
+ usage: `x g ${GENERATORS.filter((kind) => kind !== 'sign-in' && kind !== 'chart-alerts').join('|')} <name> [--feature f] · x g sign-in --password|--oidc <provider> · x g chart-alerts [--print]`,
15
16
  // Declared from the SAME constant `readKind` validates against: without it `fix-command.ts`
16
17
  // has no set to judge the word after `x g`, and two shipped `@ultimat3/admin` fix lines said
17
18
  // `x g migration` — a generator that has never existed — straight through the `errors` gate.
@@ -25,6 +26,13 @@ export const generateSpec: CommandSpec = {
25
26
  { name: 'locales', type: 'string', summary: 'comma-separated locales, default en' },
26
27
  { name: 'at', type: 'string', summary: 'island, admin:page: directory to write into' },
27
28
  { name: 'permission', type: 'string', summary: 'admin:page: the permission it needs' },
29
+ { name: 'oidc', type: 'string', summary: 'sign-in: an OpenID Connect provider id' },
30
+ { name: 'password', type: 'boolean', summary: 'sign-in: an address and a password' },
31
+ {
32
+ name: 'print',
33
+ type: 'boolean',
34
+ summary: 'chart-alerts: print plain rule groups, write nothing',
35
+ },
28
36
  { name: 'force', type: 'boolean', summary: 'overwrite existing files' },
29
37
  { name: 'dry-run', type: 'boolean', summary: 'print the file list, write nothing' },
30
38
  ],
@@ -11,21 +11,25 @@ import { requireAppRoot } from './app-root';
11
11
  import { generateSpec } from './cmd-generate-spec';
12
12
  import type { CliCommand, CommandContext } from './command';
13
13
  import { invocationOf } from './command';
14
- import { localiseCatalogs } from './generate-catalog-locales';
14
+ import { localiseCatalogs, namedOrAppLocales } from './generate-catalog-locales';
15
+ import { refuseChartAlertsFlags, runChartAlerts } from './generate-chart-alerts';
15
16
  import { appDisk, plannedDisk } from './generate-disk';
16
17
  import { assertFeatureExists } from './generate-feature';
18
+ import { assertSliceToRead } from './generate-feature-required';
17
19
  import { generate, sliceDir } from './generate-files';
18
20
  import { followUpEdits } from './generate-follow-ups';
21
+ import { formatWritten } from './generate-format-written';
19
22
  import type { Generator } from './generate-kinds';
20
23
  import { readFeature, readKind, readName, readPermission, readSurface } from './generate-kinds';
21
24
  import { refusePluralTable } from './generate-plural';
22
25
  import { followReexports } from './generate-reexports';
23
26
  import { refuseShadowedTypes, refuseShadowedValues } from './generate-shadow';
24
27
  import { declaresOnlyOtherEntities } from './generate-sibling';
28
+ import { runSignIn } from './generate-sign-in';
29
+ import { refuseSignInFlags } from './generate-sign-in-mode';
25
30
  import { containedPath, planWrites, writeFiles } from './generate-write';
26
31
  import { resolveDbModule } from './handle-registration';
27
32
  import { resolveCatalogModule } from './i18n-audit';
28
- import { catalogLocales } from './i18n-index';
29
33
  import { reproducedFlags } from './invocation-flags';
30
34
  import { msg } from './messages';
31
35
  import type { CommandResult, Finding } from './output';
@@ -68,6 +72,12 @@ export const generateCommand: CliCommand = {
68
72
  async run(ctx: CommandContext): Promise<CommandResult> {
69
73
  const root = requireAppRoot('g', ctx.cwd).dir;
70
74
  const kind = readKind(ctx.args.positionals[0]);
75
+ refuseSignInFlags(kind, ctx.args);
76
+ refuseChartAlertsFlags(kind, ctx.args);
77
+ // One sign-in per app, named by a flag rather than a `<name>`, and edits no other kind makes.
78
+ if (kind === 'sign-in') return await runSignIn(ctx, root);
79
+ // One chart per app, named by its `Chart.yaml`: the same shape, for the same reason.
80
+ if (kind === 'chart-alerts') return await runChartAlerts(ctx, root);
71
81
  const name = readName(ctx.args.positionals[1], kind);
72
82
  const featureFlag = readFeature(flagString(ctx.args, 'feature'), kind);
73
83
  // Both flags are resolved before a single file is planned: a bad surface or a locale that is
@@ -89,7 +99,15 @@ export const generateCommand: CliCommand = {
89
99
  // Read for the same reason: which errors the slice declares is written on THIS app's disk.
90
100
  const slice = sliceDir(surface, kebab(featureFlag ?? name));
91
101
  // A named slice that is not there is refused, never invented (X_FEATURE_UNKNOWN).
92
- assertFeatureExists(root, kind, featureFlag, slice);
102
+ assertFeatureExists(root, kind, featureFlag, slice, name);
103
+ // And a read with no table to read, never handed one (X_FEATURE_REQUIRED).
104
+ assertSliceToRead(root, {
105
+ kind,
106
+ name,
107
+ feature: featureFlag,
108
+ slice,
109
+ flags: reproducedFlags(generateCommand.spec, ctx.args),
110
+ });
93
111
  const sliceErrors = await readSliceErrors(root, kind, slice);
94
112
  // And its `policy.ts`, for the same two kinds and the same reason: the action imports from it.
95
113
  const slicePolicy = await followReexports(
@@ -162,7 +180,7 @@ export const generateCommand: CliCommand = {
162
180
  const force = flagBool(ctx.args, 'force');
163
181
  if (flagBool(ctx.args, 'dry-run')) {
164
182
  // The write plan, never the bare file list: what the real run would write, skip and refuse.
165
- const plan = await planWrites(root, files, force, invocation);
183
+ const plan = await planWrites(root, files, force, invocation, name);
166
184
  const refused = [...plan.conflicts, ...bindings];
167
185
  // And the edits OUTSIDE the slice — role map, handle, API index, manifests, catalog index —
168
186
  // run for real against the plan held in memory: the same code, so the same list. A dry run
@@ -206,12 +224,15 @@ export const generateCommand: CliCommand = {
206
224
  const report =
207
225
  bindings.length > 0
208
226
  ? { written: [], conflicts: bindings }
209
- : await writeFiles(root, files, force, invocation);
227
+ : await writeFiles(root, files, force, invocation, name);
210
228
  // The edits a generated primitive needs outside its own slice, performed rather than left as
211
229
  // findings (`generate-follow-ups.ts`). Before the manifest load below, so the projection sees
212
230
  // them all — a repo reading a table the handle lacks would not load at all.
213
231
  const follow = await followUpEdits(root, report.written, dbModule, appDisk(root));
214
232
  const edited = follow.edited;
233
+ // Before the manifest reads a byte of it. Only what this run created: an edited file is its
234
+ // author's, and a run that reformatted one would hand them a diff they never asked for.
235
+ await formatWritten(root, report.written, ctx.runner);
215
236
  // Facts, not prose: every `x g` run leaves the route/action/entity/job/policy table current,
216
237
  // the same guarantee `x manifest` makes on its own — an agent reading it after `x g` never
217
238
  // sees a resource that exists on disk but not in the manifest.
@@ -267,25 +288,6 @@ export const generateCommand: CliCommand = {
267
288
  },
268
289
  };
269
290
 
270
- /**
271
- * `--locales` when the caller named any, and otherwise every locale the app already has a catalog
272
- * for: a key written to `en` alone in an app that also ships `es` renders ⟦key⟧ for every Spanish
273
- * reader. A stem that is not a locale tag is some other JSON file, and not a catalog to write to.
274
- */
275
- async function namedOrAppLocales(
276
- root: string,
277
- named: readonly string[],
278
- ): Promise<readonly string[]> {
279
- if (named.length > 0) return named;
280
- return (await catalogLocales(root)).filter((stem) => {
281
- try {
282
- return Intl.getCanonicalLocales(stem).length === 1;
283
- } catch {
284
- return false;
285
- }
286
- });
287
- }
288
-
289
291
  const isEntityKind = (kind: Generator): boolean => kind === 'entity' || kind === 'resource';
290
292
 
291
293
  /**
@@ -309,13 +311,16 @@ export function nextSteps(
309
311
  'bunx x db migrate',
310
312
  // A page that mounts an island has a byte budget only a build can weigh — measured on `x new`'s
311
313
  // app (`RESOURCE_PAGE_BUDGET`), and an app that charges more per document is over it on this
312
- // run rather than on the next `bin/check`.
314
+ // run rather than on the next full gate. The step builds itself: the page is newer than the stats.
313
315
  ...(written.some((path) => path.endsWith('/page.tsx')) ? [MEASURE_PAGE] : []),
314
316
  ];
315
317
  }
316
318
 
317
- /** The two commands that weigh a page against its budget: the build writes the bytes it reads. */
318
- export const MEASURE_PAGE = 'bunx x build --target static && bunx x verify --only budgets';
319
+ /**
320
+ * The command that weighs a page against its budget. One command: `budgets` runs the static build
321
+ * itself when the stats are older than the source, and a just-written page is (plan 101, 21).
322
+ */
323
+ export const MEASURE_PAGE = 'bunx x verify --only budgets';
319
324
 
320
325
  /** The frame `x new` writes: a resource's page renders inside it when the app has one. */
321
326
  export const SHELL_MODULE = 'apps/web/shared/shell.tsx';
@@ -0,0 +1,97 @@
1
+ // The app graph `x graph`, `x impact` and the dev MCP server's `graph.query` read: the app loaded
2
+ // once (`appManifest`), its module graph scanned, and the last `x exercise` run's effects added as
3
+ // `observed` edges. Rebuilt per call — a graph committed anywhere would churn on every import.
4
+
5
+ // why: Bun ships no path-join primitive.
6
+ import { join } from 'node:path';
7
+ import { renderCauseValue, UltimateError } from '@ultimat3/core';
8
+ import type { AppGraph, ObservedEdge } from '@ultimat3/manifest';
9
+ import { buildGraph, findNode, MANIFEST_FILENAME } from '@ultimat3/manifest';
10
+ import { appManifest } from './app-manifest';
11
+ import { moduleFiles, primitiveFiles } from './app-module-graph';
12
+ import type { Finding } from './output';
13
+
14
+ /** Where `x exercise` writes what each primitive did. */
15
+ export const EXERCISE_REPORT = '.x/exercise.json';
16
+
17
+ interface ExerciseRecord {
18
+ readonly kind?: unknown;
19
+ readonly name?: unknown;
20
+ readonly effects?: unknown;
21
+ }
22
+
23
+ /**
24
+ * `.x/exercise.json` as edges: a primitive that wrote or read an entity's table, or enqueued a
25
+ * job. Ignored when it is older than `x.manifest.json` — the run described an earlier app — or
26
+ * unreadable: observed edges are a bonus over declared ones, never a requirement.
27
+ */
28
+ export async function observedEdges(
29
+ root: string,
30
+ tables: ReadonlyMap<string, string>,
31
+ ): Promise<readonly ObservedEdge[]> {
32
+ const report = Bun.file(join(root, EXERCISE_REPORT));
33
+ const manifest = Bun.file(join(root, MANIFEST_FILENAME));
34
+ if (!(await report.exists())) return [];
35
+ if ((await manifest.exists()) && report.lastModified < manifest.lastModified) return [];
36
+ let parsed: unknown;
37
+ try {
38
+ parsed = await report.json();
39
+ } catch {
40
+ // A torn write from a run still in progress: no observed edges this time.
41
+ return [];
42
+ }
43
+ const records = (parsed as { readonly records?: unknown }).records;
44
+ if (!Array.isArray(records)) return [];
45
+ const edges: ObservedEdge[] = [];
46
+ for (const record of records as readonly ExerciseRecord[]) {
47
+ if (typeof record.kind !== 'string' || typeof record.name !== 'string') continue;
48
+ const from = `${record.kind}:${record.name}`;
49
+ for (const effect of Array.isArray(record.effects) ? record.effects : []) {
50
+ const { kind, entity, op, job } = effect as Readonly<Record<string, unknown>>;
51
+ if (kind === 'row' && typeof entity === 'string') {
52
+ const to = `entity:${tables.get(entity) ?? entity}`;
53
+ edges.push({ from, to, rel: op === 'select' ? 'reads' : 'writes' });
54
+ } else if (kind === 'enqueue' && typeof job === 'string') {
55
+ edges.push({ from, to: `job:${job}`, rel: 'enqueues' });
56
+ }
57
+ }
58
+ }
59
+ return edges;
60
+ }
61
+
62
+ export interface LoadedGraph {
63
+ readonly graph: AppGraph;
64
+ /** Modules that would not load: the graph describes what did. */
65
+ readonly findings: readonly Finding[];
66
+ }
67
+
68
+ export async function loadAppGraph(root: string): Promise<LoadedGraph> {
69
+ const { manifest, findings } = await appManifest(root);
70
+ const files = await moduleFiles(root);
71
+ const primitives = await primitiveFiles(
72
+ root,
73
+ files.map((file) => file.path),
74
+ );
75
+ const tables = new Map(manifest.entities.map((entity) => [entity.table, entity.name]));
76
+ const graph = buildGraph(manifest, { files, primitives }, await observedEdges(root, tables));
77
+ return { graph, findings };
78
+ }
79
+
80
+ /** A name that is no node of the graph, with the three nearest to pick from. */
81
+ export class GraphNodeUnknownError extends UltimateError {
82
+ constructor(input: { readonly asked: string; readonly candidates: readonly string[] }) {
83
+ super({
84
+ code: 'X_GRAPH_NODE_UNKNOWN',
85
+ cause: `${renderCauseValue(input.asked)} is no node of the app graph (nearest: ${input.candidates.join(', ') || 'none'})`,
86
+ fix: 'x graph --json # every node id is in data.nodes; pass one of them, a primitive name or a file path',
87
+ meta: { asked: input.asked, candidates: [...input.candidates] },
88
+ });
89
+ }
90
+ }
91
+
92
+ /** The node `text` names, or the refusal listing the nearest three. */
93
+ export function resolveNode(graph: AppGraph, text: string): string {
94
+ const { id, candidates } = findNode(graph, text.replace(/^\.\//, ''));
95
+ if (id === undefined) throw new GraphNodeUnknownError({ asked: text, candidates });
96
+ return id;
97
+ }
@@ -0,0 +1,16 @@
1
+ // `x graph`'s declaration, apart from its body: the parser, `x help` and the `errors` step read it
2
+ // without loading `cmd-graph.ts`, which `registry.ts` imports only when the command runs.
3
+
4
+ import type { CommandSpec } from './parse';
5
+
6
+ export const graphSpec: CommandSpec = {
7
+ name: 'graph',
8
+ summary:
9
+ 'the app as one typed graph: routes, primitives, tags, MCP tools, files and tests, each edge with its provenance',
10
+ usage: 'x graph [--from <node> --to <node>] [--json]',
11
+ requiresApp: true,
12
+ flags: [
13
+ { name: 'from', type: 'string', summary: 'a node id, primitive name or file path to start at' },
14
+ { name: 'to', type: 'string', summary: 'the node to explain a link to; needs --from' },
15
+ ],
16
+ };
@@ -0,0 +1,73 @@
1
+ // `x graph` — the app as one typed graph, or with `--from`/`--to` the chain of edges linking two
2
+ // nodes. CLI wiring only: the graph is `@ultimat3/manifest`'s `buildGraph`, loaded by
3
+ // `cmd-graph-load.ts`.
4
+
5
+ import type { GraphNodeKind } from '@ultimat3/manifest';
6
+ import { GRAPH_NODE_KINDS, pathBetween } from '@ultimat3/manifest';
7
+ import { requireAppRoot } from './app-root';
8
+ import { loadAppGraph, resolveNode } from './cmd-graph-load';
9
+ import { graphSpec } from './cmd-graph-spec';
10
+ import type { CliCommand, CommandContext } from './command';
11
+ import { BadFlagError } from './errors';
12
+ import { msg } from './messages';
13
+ import type { CommandResult, JsonValue } from './output';
14
+ import { flagString } from './parse';
15
+
16
+ const asJson = (value: unknown): JsonValue => JSON.parse(JSON.stringify(value)) as JsonValue;
17
+
18
+ export const graphCommand: CliCommand = {
19
+ spec: graphSpec,
20
+ async run(ctx: CommandContext): Promise<CommandResult> {
21
+ const root = requireAppRoot('graph', ctx.cwd).dir;
22
+ const from = flagString(ctx.args, 'from');
23
+ const to = flagString(ctx.args, 'to');
24
+ if ((from === undefined) !== (to === undefined)) {
25
+ throw new BadFlagError({
26
+ flag: from === undefined ? 'from' : 'to',
27
+ command: 'graph',
28
+ reason: '--from and --to name the two ends of one path, so each needs the other',
29
+ fix: 'x graph --from query:postById --to entity:posts --json',
30
+ });
31
+ }
32
+ const { graph, findings } = await loadAppGraph(root);
33
+ if (from !== undefined && to !== undefined) {
34
+ const path = pathBetween(graph, resolveNode(graph, from), resolveNode(graph, to));
35
+ return {
36
+ ok: true,
37
+ command: 'graph',
38
+ summary:
39
+ path.edges === null
40
+ ? msg('cli.graph.noPath', {
41
+ from: path.from,
42
+ to: path.to,
43
+ searched: path.searched.join(', '),
44
+ })
45
+ : msg('cli.graph.path', { from: path.from, to: path.to, steps: path.edges.length }),
46
+ lines: (path.edges ?? []).map((edge) =>
47
+ msg('cli.graph.pathStep', { ...edge, provenance: edge.provenance }),
48
+ ),
49
+ data: asJson(path),
50
+ };
51
+ }
52
+ const count = (kind: GraphNodeKind): number =>
53
+ graph.nodes.filter((n) => n.kind === kind).length;
54
+ const provenance = [...new Set(graph.edges.map((edge) => edge.provenance))].sort();
55
+ return {
56
+ ok: true,
57
+ command: 'graph',
58
+ summary: msg('cli.graph.summary', {
59
+ nodes: graph.nodes.length,
60
+ edges: graph.edges.length,
61
+ provenance: provenance.join(', '),
62
+ opaque: graph.opaque.length,
63
+ }),
64
+ lines: [
65
+ ...GRAPH_NODE_KINDS.filter((kind) => count(kind) > 0).map((kind) =>
66
+ msg('cli.graph.kind', { kind, count: count(kind) }),
67
+ ),
68
+ ...findings.map((finding) => ` ${finding.code} ${finding.at ?? ''}`),
69
+ ],
70
+ data: asJson(graph),
71
+ };
72
+ },
73
+ };
package/src/cmd-i18n.ts CHANGED
@@ -260,7 +260,10 @@ async function untranslatedKeys(
260
260
  locale: string,
261
261
  ): Promise<readonly string[]> {
262
262
  const extraction = await scanSource(root);
263
- const report = withPlaceholdersMissing(auditCatalogs({ extraction, catalogs }), catalogs);
263
+ // `reference` as `auditApp` passes it: an app whose default is not `en` is told to sync its default
264
+ // over a key `en` holds alone, and this list is all that command can add.
265
+ const audit = auditCatalogs({ extraction, catalogs, reference: DEFAULT_LOCALE });
266
+ const report = withPlaceholdersMissing(audit, catalogs);
264
267
  return report.locales.find((audit) => audit.locale === locale)?.missing ?? [];
265
268
  }
266
269
 
@@ -0,0 +1,13 @@
1
+ // `x impact`'s declaration, apart from its body: the parser, `x help` and the `errors` step read it
2
+ // without loading `cmd-impact.ts`, which `registry.ts` imports only when the command runs.
3
+
4
+ import type { CommandSpec } from './parse';
5
+
6
+ export const impactSpec: CommandSpec = {
7
+ name: 'impact',
8
+ summary:
9
+ 'what an edit to a file, primitive or entity reaches: routes, actions, queries, jobs, MCP tools, published operations, tests, budgets',
10
+ usage: 'x impact <file|primitive|entity> [--json]',
11
+ requiresApp: true,
12
+ flags: [],
13
+ };
@@ -0,0 +1,64 @@
1
+ // `x impact <file|primitive|entity>` — what an edit reaches, before it is made: routes, actions,
2
+ // queries, jobs, MCP tools, published operations, tests and budgets, each with the provenance of
3
+ // the edges that reached it. The question an agent asks before an edit, answered in one call.
4
+
5
+ // why: Bun ships no path API; a path argument is relative to where the command ran.
6
+ import { join } from 'node:path';
7
+ import type { Impact, ImpactEntry } from '@ultimat3/manifest';
8
+ import { impactOf } from '@ultimat3/manifest';
9
+ import { requireAppRoot } from './app-root';
10
+ import { loadAppGraph, resolveNode } from './cmd-graph-load';
11
+ import { impactSpec } from './cmd-impact-spec';
12
+ import type { CliCommand, CommandContext } from './command';
13
+ import { MissingPositionalError } from './errors';
14
+ import { msg } from './messages';
15
+ import type { CommandResult, JsonValue } from './output';
16
+ import { posixRelative } from './posix-path';
17
+
18
+ const SECTIONS = ['routes', 'actions', 'queries', 'jobs', 'mcpTools', 'tests', 'budgets'] as const;
19
+
20
+ const names = (entries: readonly ImpactEntry[]): string =>
21
+ entries.map((entry) => `${entry.name} [${entry.provenance.join('+')}]`).join(', ');
22
+
23
+ const render = (impact: Impact): readonly string[] => [
24
+ ...(impact.publishedOperations.length === 0
25
+ ? []
26
+ : [msg('cli.impact.published', { operations: names(impact.publishedOperations) })]),
27
+ ...SECTIONS.filter((section) => impact[section].length > 0).map((section) =>
28
+ msg('cli.impact.section', { section, names: names(impact[section]) }),
29
+ ),
30
+ ];
31
+
32
+ export const impactCommand: CliCommand = {
33
+ spec: impactSpec,
34
+ async run(ctx: CommandContext): Promise<CommandResult> {
35
+ const root = requireAppRoot('impact', ctx.cwd).dir;
36
+ const asked = ctx.args.positionals[0];
37
+ if (asked === undefined) {
38
+ throw new MissingPositionalError({
39
+ command: 'impact',
40
+ positional: 'file|primitive|entity',
41
+ example: 'x impact apps/web/app/posts/queries/post-by-id.ts --json',
42
+ });
43
+ }
44
+ const { graph } = await loadAppGraph(root);
45
+ // A path is read from where the command ran, and the graph keys files from the app root.
46
+ const onDisk = join(ctx.cwd, asked);
47
+ const named = (await Bun.file(onDisk).exists()) ? posixRelative(root, onDisk) : asked;
48
+ const impact = impactOf(graph, resolveNode(graph, named));
49
+ return {
50
+ ok: true,
51
+ command: 'impact',
52
+ summary: msg('cli.impact.summary', {
53
+ node: impact.node,
54
+ routes: impact.routes.length,
55
+ actions: impact.actions.length,
56
+ queries: impact.queries.length,
57
+ jobs: impact.jobs.length,
58
+ tests: impact.tests.length,
59
+ }),
60
+ lines: render(impact),
61
+ data: JSON.parse(JSON.stringify(impact)) as JsonValue,
62
+ };
63
+ },
64
+ };
@@ -0,0 +1,76 @@
1
+ // `x jobs resolve-step <id> <step> --ran [--output <json>] | --not-ran` — a person's answer to
2
+ // `X_STEP_OUTCOME_UNKNOWN`: a `once` step that started and never completed either happened (its
3
+ // output is recorded, a replay returns it) or did not (`cleared`, the next run calls it again).
4
+ // Then `x jobs retry <id>`. Split from `cmd-jobs.ts`, which is the dispatch.
5
+
6
+ import type { JobDriver, StepResolution } from '@ultimat3/jobs';
7
+ import { resolveJobStep } from '@ultimat3/jobs';
8
+ import type { CommandContext } from './command';
9
+ import { BadFlagError, MissingPositionalError } from './errors';
10
+ import { jobTraceToJson } from './jobs-json';
11
+ import { showJob } from './jobs-report';
12
+ import { msg } from './messages';
13
+ import type { CommandResult } from './output';
14
+ import { flagBool, flagString } from './parse';
15
+
16
+ const EXAMPLE = "x jobs resolve-step <id> <step> --ran --output '{}' --json";
17
+
18
+ /** Exactly one of `--ran` and `--not-ran`; `--output` is JSON, and only with `--ran`. */
19
+ function resolutionOf(ctx: CommandContext): StepResolution {
20
+ const ran = flagBool(ctx.args, 'ran');
21
+ const notRan = flagBool(ctx.args, 'not-ran');
22
+ const output = flagString(ctx.args, 'output');
23
+ if (ran === notRan) {
24
+ throw new BadFlagError({
25
+ flag: ran ? 'not-ran' : 'ran',
26
+ command: 'jobs resolve-step',
27
+ reason: 'say whether the step ran: pass exactly one of --ran and --not-ran',
28
+ fix: EXAMPLE,
29
+ });
30
+ }
31
+ if (notRan) {
32
+ if (output !== undefined) {
33
+ throw new BadFlagError({
34
+ flag: 'output',
35
+ command: 'jobs resolve-step',
36
+ reason: 'a step that did not run has no output to record',
37
+ fix: 'x jobs resolve-step <id> <step> --not-ran --json',
38
+ });
39
+ }
40
+ return { ran: false };
41
+ }
42
+ if (output === undefined) return { ran: true, output: null };
43
+ try {
44
+ return { ran: true, output: JSON.parse(output) as unknown };
45
+ } catch {
46
+ throw new BadFlagError({
47
+ flag: 'output',
48
+ command: 'jobs resolve-step',
49
+ reason: 'the output is not JSON — it is what a replay of the step will return',
50
+ fix: EXAMPLE,
51
+ });
52
+ }
53
+ }
54
+
55
+ export async function runResolveStep(
56
+ driver: JobDriver,
57
+ ctx: CommandContext,
58
+ id: string,
59
+ ): Promise<CommandResult> {
60
+ const step = ctx.args.positionals[1];
61
+ if (step === undefined) {
62
+ throw new MissingPositionalError({
63
+ command: 'jobs resolve-step',
64
+ positional: 'step',
65
+ example: `x jobs show ${id} --json`,
66
+ });
67
+ }
68
+ const resolved = await resolveJobStep(driver, id, step, resolutionOf(ctx));
69
+ const trace = await showJob(driver, id);
70
+ return {
71
+ ok: true,
72
+ command: 'jobs',
73
+ summary: msg('cli.jobs.stepResolved', { id, step, status: resolved.status }),
74
+ data: jobTraceToJson(trace),
75
+ };
76
+ }