@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
@@ -14,6 +14,7 @@ export const JOBS_SUBCOMMANDS = [
14
14
  'cancel',
15
15
  'delete',
16
16
  'promote',
17
+ 'resolve-step',
17
18
  'pause',
18
19
  'resume',
19
20
  'drain',
@@ -24,9 +25,9 @@ export const jobsSpec: CommandSpec = {
24
25
  // Never ENDING in "(planned)": that suffix is how `x help` and the planned-table tests tell a
25
26
  // planned COMMAND from a shipped one, and `x jobs` ships — only its `drain` does not.
26
27
  summary:
27
- 'list, show, retry, cancel, delete and promote jobs; pause and resume a queue (drain is planned)',
28
+ 'list, show, retry, cancel, delete and promote jobs; resolve a step; pause and resume a queue (drain is planned)',
28
29
  usage:
29
- 'x jobs [list|show <id>|retry <id>|cancel <id>|delete <id>|promote <id>|pause <queue>|resume <queue>|drain] [--queue q] [--state s] [--name n] [--limit n] [--after cursor] [--from-step name] [--reason text] [--json]',
30
+ 'x jobs [list|show <id>|retry <id>|cancel <id>|delete <id>|promote <id>|resolve-step <id> <step>|pause <queue>|resume <queue>|drain] [--queue q] [--state s] [--name n] [--limit n] [--after cursor] [--from-step name] [--reason text] [--ran|--not-ran] [--output json] [--json]',
30
31
  requiresApp: true,
31
32
  subcommands: JOBS_SUBCOMMANDS,
32
33
  // The bare `x jobs` lists; it never retries, cancels or drains anything.
@@ -57,5 +58,23 @@ export const jobsSpec: CommandSpec = {
57
58
  summary: 'cancel: why, recorded on the job',
58
59
  subcommands: ['cancel'],
59
60
  },
61
+ {
62
+ name: 'ran',
63
+ type: 'boolean',
64
+ summary: 'resolve-step: the step happened — record --output as its answer',
65
+ subcommands: ['resolve-step'],
66
+ },
67
+ {
68
+ name: 'not-ran',
69
+ type: 'boolean',
70
+ summary: 'resolve-step: the step did not happen — the next run calls it again',
71
+ subcommands: ['resolve-step'],
72
+ },
73
+ {
74
+ name: 'output',
75
+ type: 'string',
76
+ summary: 'resolve-step: with --ran, the JSON a replay of the step returns (default null)',
77
+ subcommands: ['resolve-step'],
78
+ },
60
79
  ],
61
80
  };
package/src/cmd-jobs.ts CHANGED
@@ -1,8 +1,8 @@
1
- // `x jobs list|show|retry|cancel|delete|promote|pause|resume` — introspect and recover the job queue,
2
- // bound to `@ultimat3/jobs`'s own introspection so the CLI, `/_x` and MCP report identically;
3
- // `drain` is planned (`cmd-planned.ts`): Postgres is the one durable driver. CLI wiring only:
4
- // the driver-injected logic is `jobs-report.ts`, the `--json` shapes `jobs-json.ts`, the table
5
- // `jobs-table.ts`, and getting hold of the queue at all is `jobs-driver.ts` — shared with `x db`.
1
+ // `x jobs list|show|retry|cancel|delete|promote|resolve-step|pause|resume` — introspect and
2
+ // recover the job queue, bound to `@ultimat3/jobs`'s own introspection so the CLI, `/_x` and MCP
3
+ // report identically; `drain` is planned (`cmd-planned.ts`): Postgres is the one durable driver.
4
+ // CLI wiring only: the driver-injected logic is `jobs-report.ts`, the `--json` shapes
5
+ // `jobs-json.ts`, the table `jobs-table.ts`, the queue `jobs-driver.ts` — shared with `x db`.
6
6
 
7
7
  import { renderFixShellArg } from '@ultimat3/core';
8
8
  import type { JobDriver } from '@ultimat3/jobs';
@@ -16,6 +16,7 @@ import {
16
16
  } from '@ultimat3/jobs';
17
17
  import { loadApp } from './app-load';
18
18
  import { requireAppRoot } from './app-root';
19
+ import { runResolveStep } from './cmd-jobs-resolve';
19
20
  import { jobsSpec } from './cmd-jobs-spec';
20
21
  import { plannedSubcommand } from './cmd-planned';
21
22
  import type { CliCommand, CommandContext } from './command';
@@ -250,7 +251,7 @@ async function runPause(
250
251
  }
251
252
 
252
253
  /** The subcommands that answer a `JobTrace`. `list`, `delete`, `promote` and the rest read rows only. */
253
- const TRACE_SUBCOMMANDS: ReadonlySet<string> = new Set(['show', 'retry', 'cancel']);
254
+ const TRACE_SUBCOMMANDS: ReadonlySet<string> = new Set(['show', 'retry', 'cancel', 'resolve-step']);
254
255
 
255
256
  export const jobsCommand: CliCommand = {
256
257
  spec: jobsSpec,
@@ -274,6 +275,9 @@ export const jobsCommand: CliCommand = {
274
275
  if (sub === 'cancel') return runCancel(driver, ctx);
275
276
  if (sub === 'delete') return runDelete(driver, ctx);
276
277
  if (sub === 'promote') return runPromote(driver, ctx);
278
+ if (sub === 'resolve-step') {
279
+ return runResolveStep(driver, ctx, requireIdPositional(ctx, sub, driver));
280
+ }
277
281
  if (sub === 'pause' || sub === 'resume') return runPause(driver, ctx, sub);
278
282
  return runList(driver, ctx);
279
283
  });
@@ -0,0 +1,28 @@
1
+ // `x mail`'s declaration, apart from its body: the parser, `x help` and the `errors` step read it
2
+ // without loading `cmd-mail.ts`, which `registry.ts` imports only when the command runs.
3
+
4
+ import type { CommandSpec } from './parse';
5
+
6
+ export const MAIL_SUBCOMMANDS = ['preview'] as const;
7
+
8
+ export const mailSpec: CommandSpec = {
9
+ name: 'mail',
10
+ summary: "render a mail's declared preview in every locale, nothing sent",
11
+ usage: 'x mail preview <id> [--locale <tag>] [--tz <zone>] [--out <dir>] [--json]',
12
+ requiresApp: true,
13
+ subcommands: MAIL_SUBCOMMANDS,
14
+ defaultSubcommand: 'preview',
15
+ flags: [
16
+ { name: 'locale', type: 'string', summary: 'one locale; default every locale the app routes' },
17
+ {
18
+ name: 'tz',
19
+ type: 'string',
20
+ summary: 'IANA zone every date in the body renders in; default UTC, as the /_x panel',
21
+ },
22
+ {
23
+ name: 'out',
24
+ type: 'string',
25
+ summary: 'write <id>.<locale>.html and .txt into this directory',
26
+ },
27
+ ],
28
+ };
@@ -0,0 +1,70 @@
1
+ // `x mail preview <id>` — a template checked in every locale without triggering the flow that
2
+ // sends it: `@ultimat3/mail`'s `previewMail` over the mail's declared `preview` data. A key a
3
+ // locale cannot resolve is named in `--json` and fails the command, as `send` would refuse it.
4
+
5
+ // why: Bun exposes no path-join primitive; `--out` is a directory the files are joined under.
6
+ import { join, resolve } from 'node:path';
7
+ import { appLocaleSet } from '@ultimat3/i18n/app-catalogs';
8
+ import { previewMail } from '@ultimat3/mail';
9
+ import { assertTimeZone } from '@ultimat3/time';
10
+ import { loadApp } from './app-load';
11
+ import { requireAppRoot } from './app-root';
12
+ import { mailSpec } from './cmd-mail-spec';
13
+ import type { CliCommand, CommandContext } from './command';
14
+ import { MissingPositionalError } from './errors';
15
+ import { msg } from './messages';
16
+ import type { CommandResult, JsonValue } from './output';
17
+ import { flagString } from './parse';
18
+
19
+ /** A sample has no reader, so no reader's zone: UTC unless `--tz` names one — never the ambient. */
20
+ const PREVIEW_ZONE = 'UTC';
21
+
22
+ export const mailCommand: CliCommand = {
23
+ spec: mailSpec,
24
+ async run(ctx: CommandContext): Promise<CommandResult> {
25
+ const root = requireAppRoot('mail', ctx.cwd).dir;
26
+ const id = ctx.args.positionals[0];
27
+ if (id === undefined) {
28
+ throw new MissingPositionalError({
29
+ command: 'mail preview',
30
+ positional: 'id',
31
+ example: 'x mail preview welcome --locale es',
32
+ });
33
+ }
34
+ await loadApp(root);
35
+ const asked = flagString(ctx.args, 'locale');
36
+ const locales = asked === undefined ? (await appLocaleSet(root)).locales : [asked];
37
+ const tz = assertTimeZone(flagString(ctx.args, 'tz') ?? PREVIEW_ZONE);
38
+ const out = flagString(ctx.args, 'out');
39
+ const previews: Record<string, JsonValue>[] = [];
40
+ for (const locale of locales) {
41
+ const preview = previewMail(id, { locale, tz });
42
+ const files: string[] = [];
43
+ if (out !== undefined) {
44
+ const base = join(resolve(ctx.cwd, out), `${id}.${preview.locale}`);
45
+ await Bun.write(`${base}.html`, preview.html);
46
+ await Bun.write(`${base}.txt`, preview.text);
47
+ files.push(`${base}.html`, `${base}.txt`);
48
+ }
49
+ previews.push({
50
+ locale: preview.locale,
51
+ subject: preview.subject,
52
+ missing: preview.misses.map((miss) => miss.key),
53
+ files,
54
+ });
55
+ }
56
+ const missing = previews.filter(
57
+ (each) => Array.isArray(each['missing']) && each['missing'].length > 0,
58
+ );
59
+ return {
60
+ ok: missing.length === 0,
61
+ command: 'mail',
62
+ summary: msg(missing.length === 0 ? 'cli.mail.preview.ok' : 'cli.mail.preview.missing', {
63
+ id,
64
+ missing: missing.length,
65
+ locales: previews.length,
66
+ }),
67
+ data: { id, previews, tz },
68
+ };
69
+ },
70
+ };
@@ -0,0 +1,69 @@
1
+ // `x mcp lock <name>` — fetch a remote MCP server's tools named by the app's `mcpTools({ name,
2
+ // allow })` and write what a reviewer accepts into `tools.lock.json`. `--check` writes nothing and
3
+ // fails on a drift (`X_MCP_REMOTE_TOOL_CHANGED`): the convention's build error, for `verify`.
4
+
5
+ // why: Bun exposes no path-join primitive; the lock sits at the app root.
6
+ import { join } from 'node:path';
7
+ import { renderFixShellArg } from '@ultimat3/core';
8
+ import {
9
+ assertLocked,
10
+ declaredMcpServers,
11
+ McpRemoteToolChangedError,
12
+ readToolsLock,
13
+ TOOLS_LOCK_FILE,
14
+ writeToolsLock,
15
+ } from '@ultimat3/mcp';
16
+ import { loadApp } from './app-load';
17
+ import type { CommandContext } from './command';
18
+ import { MissingPositionalError } from './errors';
19
+ import { msg } from './messages';
20
+ import type { CommandResult } from './output';
21
+ import { flagBool } from './parse';
22
+
23
+ export async function mcpLock(ctx: CommandContext, root: string): Promise<CommandResult> {
24
+ const name = ctx.args.positionals[0];
25
+ if (name === undefined) {
26
+ throw new MissingPositionalError({
27
+ command: 'mcp lock',
28
+ positional: 'name',
29
+ example: 'x mcp lock <name> --json # the name given to mcpTools({ name })',
30
+ });
31
+ }
32
+ // The app's own modules evaluate every `mcpTools()`; a missing lock is a load finding, not a stop.
33
+ await loadApp(root);
34
+ const server = declaredMcpServers().get(name);
35
+ if (server === undefined) {
36
+ throw new McpRemoteToolChangedError({
37
+ server: name,
38
+ changes: [
39
+ `no mcpTools({ name: ${renderFixShellArg(name, '<name>')} }) is declared (declared: ${[...declaredMcpServers().keys()].join(', ') || 'none'})`,
40
+ ],
41
+ });
42
+ }
43
+ const file = join(root, TOOLS_LOCK_FILE);
44
+ const handle = Bun.file(file);
45
+ const previous: unknown = (await handle.exists())
46
+ ? ((await handle.json()) as unknown)
47
+ : undefined;
48
+ const live = await server.client.listTools();
49
+ if (flagBool(ctx.args, 'check')) {
50
+ assertLocked(name, readToolsLock(previous).get(name), server.url, server.allow, live);
51
+ return {
52
+ ok: true,
53
+ command: 'mcp',
54
+ summary: msg('cli.mcp.lock_current', { server: name, file: TOOLS_LOCK_FILE }),
55
+ data: { server: name, file: TOOLS_LOCK_FILE, changed: false },
56
+ };
57
+ }
58
+ await Bun.write(file, writeToolsLock(previous, name, server.url, server.allow, live));
59
+ return {
60
+ ok: true,
61
+ command: 'mcp',
62
+ summary: msg('cli.mcp.locked', {
63
+ server: name,
64
+ tools: server.allow.length,
65
+ file: TOOLS_LOCK_FILE,
66
+ }),
67
+ data: { server: name, file: TOOLS_LOCK_FILE, tools: [...server.allow] },
68
+ };
69
+ }
@@ -8,14 +8,33 @@ export const DEFAULT_PORT = 9229;
8
8
  export const mcpSpec: CommandSpec = {
9
9
  name: 'mcp',
10
10
  summary: 'serve the dev tools: routes, schema, policies, db, queues, logs, tests, verify',
11
- usage: 'x mcp tools | x mcp serve [--transport stdio|http] [--port 9229] [--json]',
11
+ usage:
12
+ 'x mcp tools | x mcp serve [--transport stdio|http] [--port 9229] | x mcp lock <name> [--check] [--json]',
12
13
  requiresApp: true,
13
- subcommands: ['serve', 'tools'],
14
+ subcommands: ['serve', 'tools', 'lock'],
14
15
  // No default, deliberately: `wiki/CLI-Reference.md` already says to write `x mcp serve` rather
15
16
  // than a bare `x mcp`, and the parser's old first-element guess made the bare form START A
16
17
  // SERVER — the one thing a word typed by mistake must not do. Refusing enforces the guidance.
17
18
  flags: [
18
- { name: 'transport', type: 'string', summary: 'stdio | http', default: 'stdio' },
19
- { name: 'port', type: 'string', summary: 'HTTP port', default: String(DEFAULT_PORT) },
19
+ {
20
+ name: 'transport',
21
+ type: 'string',
22
+ summary: 'stdio | http',
23
+ default: 'stdio',
24
+ subcommands: ['serve', 'tools'],
25
+ },
26
+ {
27
+ name: 'port',
28
+ type: 'string',
29
+ summary: 'HTTP port',
30
+ default: String(DEFAULT_PORT),
31
+ subcommands: ['serve', 'tools'],
32
+ },
33
+ {
34
+ name: 'check',
35
+ type: 'boolean',
36
+ summary: 'compare the remote server to tools.lock.json, write nothing',
37
+ subcommands: ['lock'],
38
+ },
20
39
  ],
21
40
  };
package/src/cmd-mcp.ts CHANGED
@@ -10,13 +10,16 @@ import {
10
10
  timingSafeEqual,
11
11
  UltimateError,
12
12
  } from '@ultimat3/core';
13
- import { mcpHttpRoute, type StdioTransportInput, serveStdio } from '@ultimat3/mcp';
13
+ import { jsonResponse, methodNotAllowed, toProblem } from '@ultimat3/http';
14
+ import { mcpHostRefused, mcpHttpRoute, type StdioTransportInput, serveStdio } from '@ultimat3/mcp';
14
15
  import { requireAppRoot } from './app-root';
16
+ import { mcpLock } from './cmd-mcp-lock';
15
17
  import { DEFAULT_PORT, mcpSpec } from './cmd-mcp-spec';
16
18
  import type { CliCommand, CommandContext } from './command';
17
19
  import { BadFlagError } from './errors';
18
20
  import { intFlagOr, neighbouringPort, PORT_RANGE } from './flag-number';
19
21
  import { holdUntilShutdown } from './hold';
22
+ import { isLoopbackHostname } from './loopback-host';
20
23
  import type { CliMcpServer } from './mcp-host';
21
24
  import { createDevMcpServer, DEV_TOOL_SCOPES } from './mcp-host';
22
25
  import { msg } from './messages';
@@ -119,8 +122,18 @@ export async function startMcpHttp(host: CliMcpServer, port: number): Promise<Mc
119
122
  idleTimeout: 0,
120
123
  fetch: (request: Request, server): Response | Promise<Response> => {
121
124
  const url = new URL(request.url);
122
- if (request.method !== route.method || url.pathname !== route.path) {
123
- return notFound(route.path);
125
+ // Where this server IS reached — every refusal's fix names it, never the Host a request sent.
126
+ const endpoint = `${server.url.origin}${route.path}`;
127
+ // Before anything else, as `/_x` does: this server serves this machine, and a rebound name
128
+ // reaches it with a Host and an Origin that agree, which no Origin rule can tell apart.
129
+ if (!isLoopbackHostname(url.hostname)) return mcpHostRefused(endpoint);
130
+ if (url.pathname !== route.path) return notFound(route.path);
131
+ // `@ultimat3/http`'s own 405, the one the pipeline answers on an app's mounted `/mcp`.
132
+ if (request.method !== route.method) {
133
+ return jsonResponse(toProblem(methodNotAllowed(request.method, route.path, ['POST'])), {
134
+ status: 405,
135
+ headers: { allow: 'POST' },
136
+ });
124
137
  }
125
138
  // The socket's peer: failed tokens are metered per address, and only the server knows it.
126
139
  return route.handle(request, { address: server.requestIP(request)?.address });
@@ -211,6 +224,7 @@ export const mcpCommand: CliCommand = {
211
224
  spec: mcpSpec,
212
225
  async run(ctx: CommandContext): Promise<CommandResult> {
213
226
  const root = requireAppRoot('mcp', ctx.cwd).dir;
227
+ if (ctx.args.subcommand === 'lock') return mcpLock(ctx, root);
214
228
  const transport = flagString(ctx.args, 'transport') ?? 'stdio';
215
229
  // Validated before the app is loaded: a typo must not cost a boot to report.
216
230
  if (!isTransport(transport)) {
@@ -1,6 +1,7 @@
1
1
  // `x new`'s declaration, apart from its body: the parser, `x help` and the `errors` step
2
2
  // read it without loading `cmd-new.ts`, which `registry.ts` imports only when the command runs.
3
3
 
4
+ import { NEW_SHAPES } from './new-shapes';
4
5
  import type { CommandSpec } from './parse';
5
6
 
6
7
  export const newSpec: CommandSpec = {
@@ -9,7 +10,7 @@ export const newSpec: CommandSpec = {
9
10
  // Every flag the table below declares, in the spelling that turns it off where the default is
10
11
  // on: the usage line offered `--no-example` while the table listed `--example`, and a reader
11
12
  // had to reconcile the two to answer "which one do I get if I type neither".
12
- usage: 'x new <name> [--dir path] [--no-example] [--no-git] [--dry-run] [--force] [--json]',
13
+ usage: `x new <name> [--dir path] [--shape ${NEW_SHAPES.join('|')}] [--no-example] [--no-git] [--dry-run] [--force] [--json]`,
13
14
  flags: [
14
15
  { name: 'dir', type: 'string', summary: 'parent directory (default: cwd)' },
15
16
  {
@@ -21,6 +22,12 @@ export const newSpec: CommandSpec = {
21
22
  summary: 'include the example feature slice (default: on; --no-example for an empty app/)',
22
23
  default: true,
23
24
  },
25
+ {
26
+ // The list is `NEW_SHAPES`, so `x help new` cannot name a shape `x new` refuses.
27
+ name: 'shape',
28
+ type: 'string',
29
+ summary: `start from an app shape: ${NEW_SHAPES.join(', ')} (default: none — the dashboard and example slice; never with --no-example)`,
30
+ },
24
31
  {
25
32
  name: 'git',
26
33
  type: 'boolean',
package/src/cmd-new.ts CHANGED
@@ -14,17 +14,23 @@ import { AppNameEmptyError, AppNameIsPathError, MissingPositionalError } from '.
14
14
  import type { Runner } from './exec';
15
15
  import { reproducedFlags } from './invocation-flags';
16
16
  import { msg } from './messages';
17
+ import { type NewShape, readShape } from './new-shapes';
18
+ import { runShapeGenerators } from './new-shapes-generate';
17
19
  import type { CommandResult } from './output';
18
20
  import { flagBool, flagString } from './parse';
19
21
  import { quoteArg } from './shell-quote';
20
22
  import type { GeneratedFile } from './templates';
21
23
  import { appFiles, names, repoFiles, resourceFiles } from './templates';
24
+ import { applyOverlay, SHAPE_OVERLAYS } from './templates/shapes';
22
25
  import { loadVersion } from './version-loader';
26
+ import { writeGenerated } from './write-generated';
23
27
 
24
28
  export interface NewAppOptions {
25
29
  readonly name: string;
26
30
  /** The example feature slice. `--no-example` gives the same shape with an empty app/. */
27
31
  readonly example: boolean;
32
+ /** `--shape`: an overlay over the scaffold below (`templates/shapes/`); absent is the default. */
33
+ readonly shape?: NewShape | undefined;
28
34
  }
29
35
 
30
36
  /** `--no-git`'s `problem`, matched rather than re-spelled where the report decides on a line. */
@@ -102,6 +108,21 @@ export async function initRepository(runner: Runner, dir: string): Promise<Repos
102
108
  return { initialized: true, committed: true, problem: null };
103
109
  }
104
110
 
111
+ /** git's refusal of a commit on a box with no `user.name` / `user.email` configured. */
112
+ const NO_IDENTITY = /Author identity unknown|Please tell me who you are|empty ident/i;
113
+
114
+ /**
115
+ * The commands that finish what `initRepository` could not: no `git init` once `.git` exists, and
116
+ * the identity first when that is what refused, or the pasted commit fails the same way.
117
+ */
118
+ export function repositoryFix(git: RepositoryInit): string {
119
+ const commit = "git add -A && git commit -m 'x new'";
120
+ if (!git.initialized) return `git init && ${commit}`;
121
+ return NO_IDENTITY.test(git.problem ?? '')
122
+ ? `git config user.name '<your name>' && git config user.email '<you@example.com>' && ${commit}`
123
+ : commit;
124
+ }
125
+
105
126
  /** `path` relative to `cwd` when it lies under it, else absolute — never a `../../` walk. */
106
127
  const displayPath = (cwd: string, path: string): string => {
107
128
  const rel = relative(cwd, path);
@@ -111,8 +132,18 @@ const displayPath = (cwd: string, path: string): string => {
111
132
  /** The `cd` operand of the next-steps line: the target as typed from `cwd`, pasteable verbatim. */
112
133
  const cdTarget = (cwd: string, target: string): string => quoteArg(displayPath(cwd, target));
113
134
 
135
+ /** The `x g` lines a shape runs once its files are written, as a reader would type them. */
136
+ const shapeGenerateLines = (shape: NewShape): readonly string[] =>
137
+ (SHAPE_OVERLAYS[shape].generate ?? []).map((argv) => ['x', 'g', ...argv].join(' '));
138
+
114
139
  /** Pure: the complete file list for a new app, so `--dry-run` and the test see the same thing. */
115
140
  export function planNewApp(options: NewAppOptions): readonly GeneratedFile[] {
141
+ if (options.shape !== undefined) {
142
+ const overlay = SHAPE_OVERLAYS[options.shape];
143
+ const base = planNewApp({ name: options.name, example: overlay.example });
144
+ // Merge-aware, as below: a shape's catalog keys land in the one catalog the base wrote.
145
+ return dedupe(applyOverlay(base, overlay, names(options.name)));
146
+ }
116
147
  const app = names(options.name);
117
148
  const files: GeneratedFile[] = [
118
149
  ...repoFiles(app, loadVersion(), options.example),
@@ -159,7 +190,8 @@ export interface WrittenApp {
159
190
  */
160
191
  export async function writeNewApp(target: string, options: NewAppOptions): Promise<WrittenApp> {
161
192
  const files = planNewApp(options);
162
- for (const file of files) await Bun.write(join(target, file.path), file.contents);
193
+ // In order, so a link's target (`CLAUDE.md` before `AGENTS.md`) is on disk when it lands.
194
+ for (const file of files) await writeGenerated(target, file);
163
195
  return { dir: target, files: files.map((file) => file.path) };
164
196
  }
165
197
 
@@ -231,7 +263,13 @@ export const newCommand: CliCommand = {
231
263
  });
232
264
  }
233
265
  const target = resolve(parentDir(ctx.cwd, flagString(ctx.args, 'dir')), app.kebab);
234
- const options: NewAppOptions = { name: raw, example: ctx.args.flags.get('example') !== false };
266
+ // Before the target is touched: a refused shape writes nothing, `--dry-run` included.
267
+ const shape = readShape(ctx.args, invocationOf(ctx, 'new'), app.kebab);
268
+ const options: NewAppOptions = {
269
+ name: raw,
270
+ example: ctx.args.flags.get('example') !== false,
271
+ shape,
272
+ };
235
273
 
236
274
  if (flagBool(ctx.args, 'dry-run')) {
237
275
  const files = planNewApp(options);
@@ -243,7 +281,13 @@ export const newCommand: CliCommand = {
243
281
  dir: target,
244
282
  cd: cdTarget(ctx.cwd, target),
245
283
  }),
246
- data: { dir: target, files: files.map((file) => file.path), dryRun: true },
284
+ data: {
285
+ dir: target,
286
+ files: files.map((file) => file.path),
287
+ // What a real run then runs in the app, so a dry run names the files it cannot plan.
288
+ ...(shape === undefined ? {} : { generate: shapeGenerateLines(shape) }),
289
+ dryRun: true,
290
+ },
247
291
  // Where each file WOULD land, as the caller would reach it: relative to the cwd, `--dir`
248
292
  // included — `${app.kebab}/…` named the wrong place whenever `--dir` was set.
249
293
  lines: files.map((file) =>
@@ -273,26 +317,34 @@ export const newCommand: CliCommand = {
273
317
  // `git add -A && git commit` them — they are the user's, and `x new` did not write them.
274
318
  const existed = existsSync(target);
275
319
  const written = await writeNewApp(target, options);
320
+ // Before the first commit, so the repository starts with what the shape's generators wrote.
321
+ const generated =
322
+ shape === undefined
323
+ ? []
324
+ : await runShapeGenerators(ctx, shape, target, SHAPE_OVERLAYS[shape].generate ?? []);
276
325
  const git =
277
326
  ctx.args.flags.get('git') === false
278
327
  ? NO_GIT
279
328
  : existed
280
329
  ? PRE_EXISTING
281
330
  : await initRepository(ctx.runner, target);
282
- const lines = [msg('cli.new.wrote', { count: written.files.length, dir: target })];
331
+ const files = [...new Set([...written.files, ...generated])];
332
+ const lines = [msg('cli.new.wrote', { count: files.length, dir: target })];
283
333
  if (git.problem !== null && git.problem !== SKIPPED && git !== PRE_EXISTING) {
284
- lines.push(msg('cli.new.noRepository', { problem: git.problem }));
285
- // Raw, unlike the two prose lines around it: this one is an instruction to run verbatim, and
286
- // a translated command is a broken one (`packages/cli/CLAUDE.md`).
287
334
  lines.push(
288
- ` run: cd ${quoteArg(target)} && git init && git add -A && git commit -m 'x new'`,
335
+ msg(git.initialized ? 'cli.new.uncommitted' : 'cli.new.noRepository', {
336
+ problem: git.problem,
337
+ }),
289
338
  );
339
+ // Raw, unlike the two prose lines around it: this one is an instruction to run verbatim, and
340
+ // a translated command is a broken one (`packages/cli/CLAUDE.md`).
341
+ lines.push(` run: cd ${quoteArg(target)} && ${repositoryFix(git)}`);
290
342
  }
291
343
  return {
292
344
  ok: true,
293
345
  command: 'new',
294
346
  summary: msg('cli.new.done', { name: app.kebab, cd: cdTarget(ctx.cwd, target) }),
295
- data: { dir: written.dir, files: written.files, git },
347
+ data: { dir: written.dir, files, git },
296
348
  lines,
297
349
  };
298
350
  },
@@ -42,12 +42,6 @@ export const PLANNED_COMMANDS: readonly PlannedCommand[] = [
42
42
  usage: 'x status [--json]',
43
43
  fix: 'x doctor --json',
44
44
  },
45
- {
46
- name: 'upgrade',
47
- summary: 'move every @ultimat3/* in lockstep, with codemods',
48
- usage: 'x upgrade [--dry-run] [--json]',
49
- fix: 'bun update --latest && x verify',
50
- },
51
45
  {
52
46
  name: 'logs',
53
47
  summary: 'structured logs and OTel spans, filterable',
@@ -0,0 +1,31 @@
1
+ // `x repro`'s declaration, apart from its body: the parser, `x help` and the `errors` step read it
2
+ // without loading `cmd-repro.ts`, which `registry.ts` imports only when the command runs.
3
+
4
+ import type { CommandSpec } from './parse';
5
+
6
+ export const reproSpec: CommandSpec = {
7
+ name: 'repro',
8
+ summary: 'a dead job or a captured 5xx, as a failing test: x repro <id> --write --verify',
9
+ usage:
10
+ 'x repro <jobId|requestId|traceId|fingerprint> [--write] [--verify] | x repro --list [--code X_…] | x repro --from <file> [--write] [--verify] [--json]',
11
+ requiresApp: true,
12
+ flags: [
13
+ {
14
+ name: 'write',
15
+ type: 'boolean',
16
+ summary: 'write the test to repro/repro-<id>.<kind>.test.ts',
17
+ },
18
+ {
19
+ name: 'verify',
20
+ type: 'boolean',
21
+ summary: 'run the written test and answer { reproduced, code, fingerprint }',
22
+ },
23
+ { name: 'list', type: 'boolean', summary: 'the captured bundles, newest first' },
24
+ { name: 'code', type: 'string', summary: 'list: only bundles of this X_* code' },
25
+ {
26
+ name: 'from',
27
+ type: 'string',
28
+ summary: 'a bundle file (x repro <id> --json) instead of the database',
29
+ },
30
+ ],
31
+ };