@ultimat3/cli 29.3.2 → 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 +13 -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 +28 -8
  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 +42 -138
  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 +17 -27
  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,268 @@
1
+ // What both `x g sign-in` shapes write: where a signed-in browser lands, the sign-out action, the
2
+ // boot guard that refuses a people-facing role with sign-in unconfigured, and the page's styles.
3
+ // The two shapes differ only in how a person proves who they are (`sign-in-password.ts`,
4
+ // `sign-in-oidc.ts`); the session after that is one session, so its edges are written once.
5
+
6
+ import type { GeneratedFile } from './naming';
7
+ import { authModule } from './scaffold-auth';
8
+ import { destroySessionTest } from './sign-in-action-tests';
9
+
10
+ /** Every generated file lives under the scaffold's own auth slice, beside `auth.ts`. */
11
+ export const AUTH_DIR = 'apps/web/app/auth';
12
+ /** The page `x g sign-in` writes, and what `auth.signInPath` names. */
13
+ export const SIGN_IN_PATH = '/auth/sign-in';
14
+
15
+ /** How the generated page reaches a string: the app's own catalog module, or the framework's. */
16
+ export const catalogImport = (module: string | undefined): string =>
17
+ module === undefined
18
+ ? "import { t } from '@ultimat3/i18n';"
19
+ : `import { useT } from '${module}';`;
20
+
21
+ export const translatorBinding = (module: string | undefined): string =>
22
+ module === undefined ? '' : '\n const t = useT();';
23
+
24
+ /** `text` as `//` comment lines of at most 100 columns, split on spaces. */
25
+ const commentLines = (text: string): string => {
26
+ const lines: string[] = [];
27
+ let line = '//';
28
+ for (const word of text.split(' ')) {
29
+ if (line.length + 1 + word.length > 100) {
30
+ lines.push(line);
31
+ line = '//';
32
+ }
33
+ line = `${line} ${word}`;
34
+ }
35
+ return [...lines, line].join('\n');
36
+ };
37
+
38
+ /**
39
+ * A string-array literal the way Biome prints it after `prefix`: on one line when it fits in 100
40
+ * columns, otherwise one item per line with a trailing comma.
41
+ */
42
+ export const arrayLiteral = (prefix: string, items: readonly string[], suffix: string): string => {
43
+ const quoted = items.map((item) => `'${item}'`);
44
+ const inline = `${prefix}[${quoted.join(', ')}]${suffix}`;
45
+ if (inline.length <= 100) return inline;
46
+ const indent = /^\s*/.exec(prefix)?.[0] ?? '';
47
+ const rows = quoted.map((item) => `${indent} ${item},`).join('\n');
48
+ return `${prefix}[\n${rows}\n${indent}]${suffix}`;
49
+ };
50
+
51
+ const flowSource =
52
+ (): string => `// Where a signed-in browser goes, and the one browser/agent split every sign-in action makes. The
53
+ // destination is a FIXED path or a same-origin \`?next=\`: the endpoint that hands out a session is
54
+ // the classic open redirect, and \`nextAfterSignIn\` is the framework's one check for it.
55
+ import { NEXT_PARAM, nextAfterSignIn, setRedirect, useRequestHeader } from '@ultimat3/http';
56
+
57
+ /** Where a browser lands with no \`?next=\`. The app's to move. */
58
+ export const AFTER_SIGN_IN = '/dashboard';
59
+ /** The page this app signs in on — \`auth.signInPath\` in \`app.config.ts\` names the same one. */
60
+ export const SIGN_IN_PATH = '${SIGN_IN_PATH}';
61
+ export { NEXT_PARAM };
62
+
63
+ /** A same-origin path to go to next, or the fallback. Never another origin. */
64
+ export const nextPath = (raw: string | undefined, fallback = AFTER_SIGN_IN): string =>
65
+ nextAfterSignIn(raw, fallback);
66
+
67
+ /**
68
+ * A browser that posted the native form gets a 303 to where it was going; an agent that posted
69
+ * JSON gets the same path as data. One operation, two audiences — \`setRedirect\` is that seam.
70
+ */
71
+ export const landAt = (path: string): string => {
72
+ if ((useRequestHeader('accept') ?? '').includes('text/html')) setRedirect(path);
73
+ return path;
74
+ };
75
+ `;
76
+
77
+ const flowTest =
78
+ (): string => `// The landing rule: a same-origin path is kept, anything else is the fixed destination.
79
+ import { expect, unitTest } from '@ultimat3/testing';
80
+ import { AFTER_SIGN_IN, nextPath, SIGN_IN_PATH } from './sign-in-flow';
81
+
82
+ unitTest('a same-origin next is kept, another origin is the fixed landing page', () => {
83
+ expect(nextPath('/posts')).toBe('/posts');
84
+ expect(nextPath('https://evil.example/steal')).toBe(AFTER_SIGN_IN);
85
+ expect(nextPath('//evil.example')).toBe(AFTER_SIGN_IN);
86
+ expect(nextPath(undefined)).toBe(AFTER_SIGN_IN);
87
+ expect(nextPath(undefined, SIGN_IN_PATH)).toBe(SIGN_IN_PATH);
88
+ });
89
+ `;
90
+
91
+ const destroySessionSource =
92
+ (): string => `// Sign out: the session row revoked, the cookie expired and the browser's stored state cleared —
93
+ // \`signOutHeaders()\` is the one list of what a sign-out response carries. Public, because a
94
+ // signed-out caller signing out is a no-op rather than a 401, and a stale tab must not see a 500.
95
+ import { action, t } from '@ultimat3/action';
96
+ import { logout, signOutHeaders } from '@ultimat3/auth';
97
+ import { useRequestContext, useRequestCookie } from '@ultimat3/http';
98
+ import { allow } from '@ultimat3/policy';
99
+ import { appAuth } from '../auth';
100
+ import { landAt } from '../sign-in-flow';
101
+
102
+ export const destroySession = action({
103
+ // A native form with no field posts no body, and an action's input is an object: the form
104
+ // declares one field rather than depending on how a browser encodes nothing.
105
+ input: t.object({ confirm: t.literal('sign-out') }),
106
+ output: t.object({ ok: t.boolean, next: t.string, revoked: t.boolean }),
107
+ policy: allow('public'),
108
+ mcp: { expose: false },
109
+ async handle() {
110
+ const auth = appAuth();
111
+ const token = useRequestCookie(auth.sessions.policy.cookieName);
112
+ const revoked = token === null || token === '' ? false : await logout(auth, token);
113
+ const response = useRequestContext('destroySession').headers;
114
+ for (const [name, value] of signOutHeaders({ session: auth.sessions.policy })) {
115
+ response.append(name, value);
116
+ }
117
+ return { ok: true, next: landAt('/'), revoked };
118
+ },
119
+ });
120
+ `;
121
+
122
+ /**
123
+ * The guard reads the environment the way the scaffold's authenticator does — `fallback:
124
+ * 'production'` never applies, because only a NAMED deployed environment is refused: \`x manifest\`
125
+ * and \`x verify\` import every module with none named, and must not trip it.
126
+ */
127
+ const guardSource = (
128
+ keys: readonly string[],
129
+ why: string,
130
+ ): string => `// Refuses to start a people-facing role with sign-in unconfigured. Outside \`development\` and \`test\`, a
131
+ // \`web\` or \`sync\` process with these keys unset would serve every policy-protected route as a 401
132
+ // that nobody can sign in past. Written by \`x g sign-in\` and owned by this app — an API-only app
133
+ // has no sign-in, so this is not a framework rule.
134
+ ${commentLines(why)}
135
+ import { signInUnconfigured } from '@ultimat3/auth';
136
+
137
+ /** Set in the role's environment, or sign-in cannot run. \`x env example\` lists them. */
138
+ ${arrayLiteral('export const SIGN_IN_ENV: readonly string[] = ', keys, ';')}
139
+
140
+ /** The roles a person reaches. \`worker\`, \`scheduler\`, \`replicator\` and \`migrate\` sign nobody in. */
141
+ const PEOPLE_FACING = new Set(['web', 'sync']);
142
+
143
+ type Env = Readonly<Record<string, string | undefined>>;
144
+
145
+ const named = (env: Env, key: string): string => (env[key] ?? '').trim();
146
+
147
+ /**
148
+ * Throws \`X_AUTH_SIGN_IN_UNCONFIGURED\` naming every unset key, or returns. Only a NAMED deployed
149
+ * environment is judged: \`x manifest\` and \`x verify\` load the app with none named. Called by
150
+ * \`apps/web/runtime.ts\`, which the boot imports before it binds a port — a throw there stops the
151
+ * role, where a throw in a scanned module would only be logged.
152
+ */
153
+ export function assertSignInConfigured(env: Env): void {
154
+ const environment = named(env, 'ULTIMATE_ENV') || named(env, 'NODE_ENV');
155
+ if (environment === '' || environment === 'development' || environment === 'test') return;
156
+ // \`ROLE\` unset is \`web\`, exactly as the boot reads it.
157
+ const role = named(env, 'ROLE') || 'web';
158
+ if (!PEOPLE_FACING.has(role)) return;
159
+ const missing = SIGN_IN_ENV.filter((key) => named(env, key) === '');
160
+ if (missing.length > 0) throw signInUnconfigured(role, missing);
161
+ }
162
+ `;
163
+
164
+ const guardTest = (
165
+ keys: readonly string[],
166
+ ): string => `// The guard refuses a deployed people-facing role with sign-in unset, and nothing else.
167
+ import { expect, unitTest } from '@ultimat3/testing';
168
+ import { assertSignInConfigured, SIGN_IN_ENV } from './sign-in-guard';
169
+
170
+ const configured = Object.fromEntries(SIGN_IN_ENV.map((key) => [key, 'set']));
171
+
172
+ const refusal = (env: Readonly<Record<string, string>>): unknown => {
173
+ try {
174
+ assertSignInConfigured(env);
175
+ } catch (error) {
176
+ return error;
177
+ }
178
+ return undefined;
179
+ };
180
+
181
+ unitTest('a deployed web or sync role with sign-in unset refuses to start', () => {
182
+ for (const ROLE of ['web', 'sync']) {
183
+ const error = refusal({ ULTIMATE_ENV: 'production', ROLE });
184
+ expect(error).toBeUltimateError('X_AUTH_SIGN_IN_UNCONFIGURED');
185
+ }
186
+ // \`ROLE\` unset is \`web\`, and \`NODE_ENV\` names the environment as well.
187
+ expect(refusal({ NODE_ENV: 'production' })).toBeUltimateError('X_AUTH_SIGN_IN_UNCONFIGURED');
188
+ expect(refusal({ ULTIMATE_ENV: 'staging', ROLE: 'web' })).toBeUltimateError(
189
+ 'X_AUTH_SIGN_IN_UNCONFIGURED',
190
+ );
191
+ });
192
+
193
+ unitTest('configured, local, unnamed, or a role nobody signs in to: it starts', () => {
194
+ ${arrayLiteral(' expect(SIGN_IN_ENV).toEqual(', keys, ');')}
195
+ expect(refusal({ ULTIMATE_ENV: 'production', ROLE: 'web', ...configured })).toBeUndefined();
196
+ expect(refusal({ ULTIMATE_ENV: 'development', ROLE: 'web' })).toBeUndefined();
197
+ expect(refusal({ NODE_ENV: 'test', ROLE: 'web' })).toBeUndefined();
198
+ expect(refusal({ ROLE: 'web' })).toBeUndefined();
199
+ expect(refusal({ ULTIMATE_ENV: 'production', ROLE: 'worker' })).toBeUndefined();
200
+ });
201
+ `;
202
+
203
+ const styleSource =
204
+ (): string => `// Semantic tokens only: a raw colour here is refused by guards/raw-colour.ts.
205
+ @use '@ultimat3/ui/tokens' as tokens;
206
+
207
+ .page {
208
+ display: grid;
209
+ gap: tokens.space(4);
210
+ max-width: 28rem;
211
+ margin-inline: auto;
212
+ padding: tokens.space(6);
213
+ color: tokens.role('fg');
214
+ }
215
+ `;
216
+
217
+ /** The files both shapes write, with the guard over this shape's settings. */
218
+ /** Where the generated runtime lives: `apps/<app>/runtime.ts` is what both boots read first. */
219
+ export const RUNTIME_MODULE = 'apps/web/runtime.ts';
220
+
221
+ /** The plain routes a shape mounts: an import line, and the expression `routes:` takes. */
222
+ export interface RuntimeRoutes {
223
+ readonly importLine: string;
224
+ readonly expression: string;
225
+ }
226
+
227
+ const runtimeSource = (
228
+ routes: RuntimeRoutes | undefined,
229
+ ): string => `// What this app hands the framework's boot — \`x dev\` and the container both import it before a
230
+ // port is bound. The sign-in guard runs HERE for that reason: a throw in this module stops the
231
+ // role, where the same throw in a module the app scan imports is logged and the boot goes on.
232
+ import { assertSignInConfigured } from './app/auth/sign-in-guard';${routes === undefined ? '' : `\n${routes.importLine}`}
233
+
234
+ assertSignInConfigured(process.env);
235
+
236
+ export const runtime = {${routes === undefined ? '' : ` routes: ${routes.expression} `}};
237
+ `;
238
+
239
+ const runtimeTest = (
240
+ routes: RuntimeRoutes | undefined,
241
+ ): string => `// The runtime loads under test (the guard judges no local environment)${routes === undefined ? '' : ', and mounts the sign-in routes'}.
242
+ import { expect, unitTest } from '@ultimat3/testing';
243
+ ${routes === undefined ? '' : `${routes.importLine}\n`}import { runtime } from './runtime';
244
+
245
+ unitTest('the runtime loads${routes === undefined ? '' : ' and mounts the sign-in routes'}', () => {
246
+ ${routes === undefined ? 'expect(runtime).toEqual({});' : `expect(runtime.routes).toBe(${routes.expression});`}
247
+ });
248
+ `;
249
+
250
+ export function sharedSignInFiles(
251
+ keys: readonly string[],
252
+ why: string,
253
+ routes?: RuntimeRoutes,
254
+ ): readonly GeneratedFile[] {
255
+ return [
256
+ { path: RUNTIME_MODULE, contents: runtimeSource(routes) },
257
+ { path: 'apps/web/runtime.test.ts', contents: runtimeTest(routes) },
258
+ { path: `${AUTH_DIR}/sign-in-flow.ts`, contents: flowSource() },
259
+ { path: `${AUTH_DIR}/sign-in-flow.test.ts`, contents: flowTest() },
260
+ { path: `${AUTH_DIR}/sign-in-guard.ts`, contents: guardSource(keys, why) },
261
+ { path: `${AUTH_DIR}/sign-in-guard.test.ts`, contents: guardTest(keys) },
262
+ { path: `${AUTH_DIR}/actions/destroy-session.ts`, contents: destroySessionSource() },
263
+ { path: `${AUTH_DIR}/actions/destroy-session.test.ts`, contents: destroySessionTest() },
264
+ { path: `${AUTH_DIR}/sign-in/page.module.scss`, contents: styleSource() },
265
+ // Every action above imports `../auth`; an app from before `x new` wrote it gets it here.
266
+ { path: `${AUTH_DIR}/auth.ts`, contents: authModule(), merge: 'if-absent' },
267
+ ];
268
+ }
@@ -38,19 +38,17 @@ const errorsSource = (feature: NameSet): string => {
38
38
  return `// The ${feature.kebab} feature's X_* codes. Never throw a bare Error: an agent reading the failure
39
39
  // needs the code, the cause and the exact command that fixes it.
40
40
 
41
- import { UltimateError } from '@ultimat3/core';
42
-
43
- // No \`docs:\`. \`UltimateError\` resolves it from the code's registered descriptor, so the link has
44
- // one home; a per-code URL written here is a page that does not exist.
45
- export class ${feature.pascal}NotFoundError extends UltimateError {
46
- constructor(input: { id: string }) {
47
- super({
48
- code: '${errorCode}',
49
- cause: \`no ${feature.kebab} with id \${input.id}\`,
50
- fix: 'x queries list --json, then pass an id the ${feature.kebab} read returns',
51
- });
52
- }
53
- }
41
+ import { defineError } from '@ultimat3/core';
42
+
43
+ // One declaration: the title, the 404 a response answers, and the class a service throws. No
44
+ // \`docs:\` — it resolves from the code's registered descriptor, so the link has one home.
45
+ export const ${feature.pascal}NotFoundError = defineError<{ readonly id: string }>({
46
+ code: '${errorCode}',
47
+ title: 'no such ${feature.kebab}',
48
+ status: 404,
49
+ cause: (input) => \`no ${feature.kebab} with id \${input.id}\`,
50
+ fix: 'x queries list --json, then pass an id the ${feature.kebab} read returns',
51
+ });
54
52
  `;
55
53
  };
56
54
 
@@ -77,13 +75,15 @@ export function sliceFoundation(
77
75
  needs: readonly SliceModule[],
78
76
  /** Every locale the planted entity's admin labels ship for. Defaults to `['en']`. */
79
77
  locales?: readonly string[],
78
+ /** The slice's entity names no tenant, so a planted policy holds no org rule. */
79
+ shape: { readonly singleTenant?: boolean } = {},
80
80
  ): readonly GeneratedFile[] {
81
81
  const feature = names(target.feature);
82
82
  const dir = `${target.surfaceDir}/${target.feature}`;
83
83
  return [
84
84
  ...ifAbsent([
85
85
  ...(needs.includes('entity') ? entityFiles(target.feature, target) : []),
86
- ...(needs.includes('policy') ? policyFiles(target.feature, target) : []),
86
+ ...(needs.includes('policy') ? policyFiles(target.feature, target, shape) : []),
87
87
  ...(needs.includes('errors')
88
88
  ? [{ path: `${dir}/errors.ts`, contents: errorsSource(feature) }]
89
89
  : []),
@@ -95,6 +95,61 @@ export function sliceFoundation(
95
95
  ];
96
96
  }
97
97
 
98
+ /** One `export const <binding> = entity('<table>', …)` an `entity.ts` declares. */
99
+ export interface DeclaredEntity {
100
+ readonly binding: string;
101
+ readonly table: string;
102
+ }
103
+
104
+ /** Every entity a slice's `entity.ts` exports, in source order, comments masked. */
105
+ export const declaredEntities = (source: string): readonly DeclaredEntity[] =>
106
+ [
107
+ ...stripComments(source).matchAll(
108
+ /\bexport\s+const\s+([A-Za-z_$][\w$]*)\s*=\s*entity\(\s*['"]([^'"]+)['"]/g,
109
+ ),
110
+ ].map((match) => ({ binding: match[1] ?? '', table: match[2] ?? '' }));
111
+
112
+ /**
113
+ * The entity a slice's `entity.ts` declares when it is NOT the one this generator would write for
114
+ * `feature` — `shortLink`/`short_links` in a `shortlink/` slice, whose scaffold is
115
+ * `shortlink`/`shortlinks`. A foundation planted around it is a `repo.ts` reading a table and a row
116
+ * type nothing declares. `undefined` when there is no file, the file declares the scaffold's, or it
117
+ * declares nothing this can read.
118
+ */
119
+ export function foreignSliceEntity(
120
+ feature: string,
121
+ sliceEntity: string | undefined,
122
+ ): DeclaredEntity | undefined {
123
+ if (sliceEntity === undefined) return undefined;
124
+ const own = names(feature);
125
+ const declared = declaredEntities(sliceEntity);
126
+ if (declared.some((one) => one.binding === own.camel && one.table === own.table))
127
+ return undefined;
128
+ // Nothing readable is "cannot tell", and a slice is never refused on a guess.
129
+ return declared[0];
130
+ }
131
+
132
+ /**
133
+ * Whether the slice's `entity.ts` is there and names no tenant: no `tenant:` key but `'none'`, no
134
+ * `.tenant()` column, and no column named `orgId` — the three ways `resolveTenantColumn` turns
135
+ * tenancy on, inference included, so deleting `tenant: 'orgId'` alone is still a tenant-scoped row.
136
+ * Absent is NOT this: a slice with no entity has decided nothing, and keeps the default shape.
137
+ */
138
+ export function sliceHasNoTenant(sliceEntity: string | undefined): boolean {
139
+ if (sliceEntity === undefined) return false;
140
+ const code = stripComments(sliceEntity);
141
+ if (/\btenant\s*:\s*'none'/.test(code)) return true;
142
+ return !/\btenant\s*:|\.tenant\(|\borgId\s*:/.test(code);
143
+ }
144
+
145
+ /**
146
+ * Whether a slice's `policy.ts` decides on an org the input names — `input.orgId`, or the
147
+ * `<Feature>Scope` a scaffolded `can<…>` types its input with. An action dropping `orgId` from its
148
+ * input would not compile against that rule, and at runtime the rule could only deny.
149
+ */
150
+ export const policyReadsOrg = (source: string): boolean =>
151
+ /\binput\??\.orgId\b|\bcan<\s*\w+Scope\b/.test(stripComments(source));
152
+
98
153
  /** The exported names an `export { a, b as c }` list declares — `c`, never `b`. */
99
154
  const listedExports = (code: string): readonly string[] =>
100
155
  [...code.matchAll(/\bexport\s*\{([^}]*)\}/g)].flatMap((match) =>
@@ -113,7 +168,7 @@ const listedExports = (code: string): readonly string[] =>
113
168
 
114
169
  /**
115
170
  * Whether `source` — a slice module as it stands on the app's disk — exports the VALUE `name`.
116
- * Read at generation time because assuming it was measured: `x g action` into ai-maxxing's
171
+ * Read at generation time because assuming it was measured: `x g action` into one adopter's
117
172
  * `fleet` slice wrote `import { FleetNotFoundError } from '../errors'` into a slice whose
118
173
  * `errors.ts` declared `HostNotFoundError` and `SessionNotFoundError` and no `FleetNotFoundError`.
119
174
  * The file failed at import — and because `x db gen` and `x manifest` load every module, one
@@ -5,7 +5,7 @@
5
5
  // WHY THERE IS MORE THAN ONE PROCESS, `As of 2026-09-27`. A `--parallel` worker is not freed file
6
6
  // by file: it keeps its heap for the life of the `bun test` process and it GROWS with every file it
7
7
  // is handed (JIT, module-resolution caches, PGlite heaps the collector has not yet returned). On
8
- // notificado.co's unit corpus (768 files, PGlite per file, 12-core box, Bun 1.4.0), whole-tree RSS
8
+ // one adopter's unit corpus (768 files, PGlite per file, 12-core box, Bun 1.4.0), whole-tree RSS
9
9
  // at 12 workers:
10
10
  //
11
11
  // | form | peak tree RSS | wall |
@@ -72,7 +72,7 @@ async function buildTemplate(
72
72
  await applyFrameworkSchema((statement) => client.execute(raw(statement)));
73
73
  await migrate({ migrations: await readMigrations(root), client, lock: false });
74
74
  // Without a checkpoint every boot from the template replays the migrations' WAL as crash
75
- // recovery (~130 ms of each ~600 ms boot, measured on notificado.co).
75
+ // recovery (~130 ms of each ~600 ms boot, measured on one adopter's app).
76
76
  await client.execute(raw('CHECKPOINT'));
77
77
  const captured = driver as DumpablePglite | undefined;
78
78
  assert(
@@ -87,7 +87,7 @@ export function testArgs(input: {
87
87
  * A fresh global per FILE. Off by default since 22.7 — `--parallel` implies it, so the default
88
88
  * run says `--no-isolate` — and on for a repository whose `x.verify.json` says `"isolate": true`
89
89
  * or a caller's `--isolate`. Measured, 4 workers: the framework's unit corpus 168s isolated,
90
- * 77s not; a notificado.co third 85-107s isolated, 20s not (`test-workers.ts`).
90
+ * 77s not; a third of one adopter's corpus 85-107s isolated, 20s not (`test-workers.ts`).
91
91
  */
92
92
  readonly isolate?: boolean;
93
93
  /**
@@ -140,6 +140,8 @@ export interface ReproduceOptions {
140
140
  readonly passthrough?: readonly string[];
141
141
  /** The run was isolated per file; the rerun must be too. */
142
142
  readonly isolate?: boolean;
143
+ /** The `simulateJobs` seed the run replayed: without it the rerun draws the default seeds. */
144
+ readonly seed?: number;
143
145
  }
144
146
 
145
147
  /**
@@ -162,6 +164,7 @@ export function reproduceFor(options: ReproduceOptions): string {
162
164
  '--workers',
163
165
  String(options.workers),
164
166
  ...(options.isolate === true ? ['--isolate'] : []),
167
+ ...(options.seed === undefined ? [] : ['--seed', String(options.seed)]),
165
168
  // Last, and after a `--` of its own, because that is where the caller typed it and where the
166
169
  // parser will find it again. Quoted for `shell-quote.ts`'s reason: a reproduce line is pasted.
167
170
  ...(options.passthrough === undefined || options.passthrough.length === 0
@@ -195,6 +198,8 @@ export interface RunShardsOptions {
195
198
  readonly env?: Readonly<Record<string, string | undefined>>;
196
199
  /** A fresh global per file (`x.verify.json` `"isolate": true`, or `--isolate`). Default off. */
197
200
  readonly isolate?: boolean;
201
+ /** `--seed`: handed to the children by the caller's runner, printed back in the reproduce. */
202
+ readonly seed?: number;
198
203
  /** The machine-pool lease (`test-slots.ts`), for a default-width run. */
199
204
  readonly lease?: (want: number) => Promise<{ readonly count: number; release(): void }>;
200
205
  }
@@ -221,6 +226,7 @@ const planOf = (
221
226
  ...(options.sample === undefined ? {} : { sample: options.sample.kept }),
222
227
  ...(options.affected === undefined ? {} : { affected: options.affected }),
223
228
  ...(options.isolate === true ? { isolate: true } : {}),
229
+ ...(options.seed === undefined ? {} : { seed: options.seed }),
224
230
  ...(options.passthrough === undefined || options.passthrough.length === 0
225
231
  ? {}
226
232
  : { passthrough: options.passthrough }),
@@ -52,19 +52,25 @@ export function availableCpus(): number {
52
52
  * registry per file. Without isolation (the default since 22.7, below) a worker is CPU-bound,
53
53
  * and oversubscribing only multiplies memory.
54
54
  *
55
- * So: `min(GATE_BUDGET_CAP, max(GATE_BUDGET_FLOOR, GATE_BUDGET_SHARE x TOTAL memory))` — total, never free, so the plan
55
+ * So: `max(GATE_BUDGET_FLOOR, GATE_BUDGET_SHARE x TOTAL memory)` — total, never free, so the plan
56
56
  * is the same on every run of the same machine — divided by `WORKER_BYTES`, clamped to 1..cpus.
57
57
  * `ULTIMATE_TEST_MEMORY_BUDGET` (e.g. `3g`) replaces the budget, `ULTIMATE_TEST_MAX_WORKERS` caps
58
58
  * the width, and an explicit `--workers` wins over both.
59
+ *
60
+ * Until 2026-11-07 the budget was also capped at a fixed 4 GiB, which held a 16-core 62 GB box to
61
+ * the 3 workers a 16 GB laptop gets: the framework's unit step took 374 s there at 3 workers and
62
+ * 149 s at 12. The cap is gone because the guard it stood for is elsewhere now — every run on a
63
+ * machine leases its workers from ONE pool sized by this plan (`test-slots.ts`), so a second gate
64
+ * shares the quarter rather than doubling it. A 16 GB runner's quarter is exactly the old cap, so
65
+ * CI plans what it always planned.
59
66
  */
60
- export const GATE_BUDGET_CAP = 4 * 1024 * 1024 * 1024;
61
67
 
62
68
  /** A quarter of the machine: an editor, a language server and another agent's gate need the rest. */
63
69
  export const GATE_BUDGET_SHARE = 0.25;
64
70
 
65
71
  /**
66
72
  * The budget never plans below this: enough for TWO workers at `WORKER_BYTES`. A quarter of an
67
- * 8 GB laptop (2 GiB) planned one worker, ~4.7 min for notificado.co's unit tier; two stay inside
73
+ * 8 GB laptop (2 GiB) planned one worker, ~4.7 min for one adopter's unit tier; two stay inside
68
74
  * the owner's 1-4 GB gate target (~2.6 GB measured).
69
75
  */
70
76
  export const GATE_BUDGET_FLOOR = 2.75 * 1024 * 1024 * 1024;
@@ -76,9 +82,9 @@ export const GATE_BUDGET_FLOOR = 2.75 * 1024 * 1024 * 1024;
76
82
  * | corpus | workers | batch | peak tree | tree / worker |
77
83
  * |-----------------------------------------|---------|-------|-----------|---------------|
78
84
  * | framework unit (1620 files) | 4 | 24 | 3.61-3.78 GB | 0.90-0.95 GB |
79
- * | notificado.co unit (768 files) | 4 | 24 | 4.26-4.58 GB | 1.07-1.15 GB |
80
- * | notificado.co unit (768 files) | 4 | 48 | 4.35 GB | 1.09 GB |
81
- * | notificado.co unit (768 files) | 3 | 24 | 3.28-3.61 GB | 1.09-1.20 GB |
85
+ * | one adopter's unit (768 files) | 4 | 24 | 4.26-4.58 GB | 1.07-1.15 GB |
86
+ * | one adopter's unit (768 files) | 4 | 48 | 4.35 GB | 1.09 GB |
87
+ * | one adopter's unit (768 files) | 3 | 24 | 3.28-3.61 GB | 1.09-1.20 GB |
82
88
  *
83
89
  * The planning figure is what the TREE costs per worker at its peak — the `x` parent and Bun's
84
90
  * coordinator included — rounded up from the heaviest corpus, not the largest single worker:
@@ -103,7 +109,7 @@ export const BATCH_FILES_PER_WORKER = 24;
103
109
  * The recycle point WITHOUT isolation (the default since 22.7). A shared worker's peak is set by
104
110
  * its live PGlite, not by how many files it has run, and a batch boundary is pure cost: every
105
111
  * worker restarts, re-imports the app and re-boots its database, and the batch waits on its
106
- * slowest file. Measured on notificado.co's unit tier (768 files, 281 s of file time, 3 workers,
112
+ * slowest file. Measured on one adopter's unit tier (768 files, 281 s of file time, 3 workers,
107
113
  * no isolation): 6 batches of 48/worker 150-172 s, ONE batch 92-94 s at 3.9 GB peak — the work
108
114
  * floor (281 s / 3). 256 a worker keeps a recycle point for a corpus far past that size.
109
115
  */
@@ -167,7 +173,10 @@ export interface WorkerPlan {
167
173
 
168
174
  const gb = (bytes: number): string => (bytes / 1024 ** 3).toFixed(1);
169
175
 
170
- /** The budget a default run plans on: the env override, or `min(4 GiB, max(2.75 GiB, 25% of RAM))`. */
176
+ /** `memoryBudget`'s default, as every help line and page prints it — derived, never restated. */
177
+ export const BUDGET_RULE = `max(${String(GATE_BUDGET_FLOOR / 1024 ** 3)} GiB, ${String(GATE_BUDGET_SHARE * 100)}% of RAM)`;
178
+
179
+ /** The budget a default run plans on: the env override, or `BUDGET_RULE`. */
171
180
  export function memoryBudget(total: number = totalMemory(), env: Env = Bun.env): number {
172
181
  const raw = env[MEMORY_BUDGET_ENV];
173
182
  if (raw !== undefined && raw.trim() !== '') {
@@ -175,10 +184,7 @@ export function memoryBudget(total: number = totalMemory(), env: Env = Bun.env):
175
184
  if (bytes === undefined) throw new TestBudgetInvalidError(MEMORY_BUDGET_ENV, raw);
176
185
  return bytes;
177
186
  }
178
- return Math.min(
179
- GATE_BUDGET_CAP,
180
- Math.max(GATE_BUDGET_FLOOR, Math.floor(Math.max(0, total) * GATE_BUDGET_SHARE)),
181
- );
187
+ return Math.max(GATE_BUDGET_FLOOR, Math.floor(Math.max(0, total) * GATE_BUDGET_SHARE));
182
188
  }
183
189
 
184
190
  const maxWorkersOf = (env: Env): number | undefined => {
@@ -0,0 +1,34 @@
1
+ // The `manifest` step's tool-gate half: a name in `mcpConfirmations({ tools })` that no tool answers
2
+ // to. It holds nothing, so an agent's `refundOrder` runs past a gate spelled `refundOrdr`.
3
+ // `defineAppMcp({ confirmations })` refuses that at boot for its own server; this is every other layout.
4
+
5
+ import { gatedToolNames, knownToolNames, listActions } from '@ultimat3/action';
6
+ import { ERROR_DOCS_URL, isMcpExposed } from '@ultimat3/core';
7
+ import { listQueries } from '@ultimat3/query';
8
+ import { loadApp } from './app-load';
9
+ import type { AppLoader } from './app-permissions';
10
+ import type { Finding } from './output';
11
+
12
+ /** Every name a gate could hold: what an agent can be offered, on either surface. */
13
+ function toolNames(): readonly string[] {
14
+ const exposed = [...listActions(), ...listQueries()].filter((one) => isMcpExposed(one.mcp));
15
+ return [...new Set([...exposed.map((one) => one.name), ...knownToolNames()])].sort();
16
+ }
17
+
18
+ /** Says nothing when the load failed: a short registry is the load's finding. */
19
+ export async function unknownToolGateFindings(
20
+ root: string,
21
+ load: AppLoader = loadApp,
22
+ ): Promise<readonly Finding[]> {
23
+ if ((await load(root)).findings.length > 0) return [];
24
+ const known = toolNames();
25
+ const shown = known.length === 0 ? '(none)' : known.join(', ');
26
+ return gatedToolNames()
27
+ .filter((name) => !known.includes(name))
28
+ .map((name) => ({
29
+ code: 'X_MCP_CONFIRMATION_TOOL_UNKNOWN',
30
+ cause: `mcpConfirmations({ tools }) names ${name}, which no exposed action, exposed query, MCP tool or in-process tool answers to, so the gate holds nothing and the tool it meant runs unconfirmed`,
31
+ fix: `mcpConfirmations({ tools: [...] }) # name tools this app declares: ${shown}`,
32
+ docs: ERROR_DOCS_URL,
33
+ }));
34
+ }
@@ -0,0 +1,49 @@
1
+ // `x upgrade`'s one context-file edit: `x new` writes `AGENTS.md` as a symlink to `CLAUDE.md`, and an
2
+ // app scaffolded before that holds two regular files. Byte-identical, the copy becomes the link;
3
+ // different, it is reported with the merge to do by hand — which half is right is the author's call.
4
+
5
+ // why: Bun has no lstat, the one call that tells a link from the file it points at.
6
+ import { lstat } from 'node:fs/promises';
7
+ import { join } from 'node:path';
8
+ import { writeGenerated } from './write-generated';
9
+
10
+ export const AGENTS_FILE = 'AGENTS.md';
11
+ export const CLAUDE_FILE = 'CLAUDE.md';
12
+
13
+ /**
14
+ * `link` — two identical regular files, so the hop replaces `AGENTS.md` with the link;
15
+ * `linked` — already a symlink; `differs` — two regular files with different text, left alone;
16
+ * `none` — one of the two is missing, so there is no pair to join.
17
+ */
18
+ export type AgentsLinkState = 'link' | 'linked' | 'differs' | 'none';
19
+
20
+ const kindOf = async (path: string): Promise<'file' | 'link' | 'missing'> => {
21
+ try {
22
+ const stat = await lstat(path);
23
+ return stat.isSymbolicLink() ? 'link' : stat.isFile() ? 'file' : 'missing';
24
+ } catch {
25
+ return 'missing';
26
+ }
27
+ };
28
+
29
+ /** Read-only: what the hop would do to `<root>/AGENTS.md`. `--dry-run` reports exactly this. */
30
+ export async function planAgentsLink(root: string): Promise<AgentsLinkState> {
31
+ const agents = await kindOf(join(root, AGENTS_FILE));
32
+ if (agents === 'link') return 'linked';
33
+ if (agents === 'missing' || (await kindOf(join(root, CLAUDE_FILE))) !== 'file') return 'none';
34
+ const [held, source] = await Promise.all([
35
+ Bun.file(join(root, AGENTS_FILE)).bytes(),
36
+ Bun.file(join(root, CLAUDE_FILE)).bytes(),
37
+ ]);
38
+ return Buffer.from(held).equals(Buffer.from(source)) ? 'link' : 'differs';
39
+ }
40
+
41
+ /**
42
+ * Performs a `link` plan. The contents are `CLAUDE.md`'s own, so where Windows refuses the link the
43
+ * fallback rewrites the identical copy — the file is unchanged and the next hop asks again.
44
+ */
45
+ export async function applyAgentsLink(root: string, state: AgentsLinkState): Promise<void> {
46
+ if (state !== 'link') return;
47
+ const contents = await Bun.file(join(root, CLAUDE_FILE)).bytes();
48
+ await writeGenerated(root, { path: AGENTS_FILE, contents, link: CLAUDE_FILE });
49
+ }