@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
@@ -8,7 +8,7 @@ import { APP_CONFIG_FILE, ERROR_DOCS_URL } from '@ultimat3/core';
8
8
  import type { Manifest } from '@ultimat3/manifest';
9
9
  import { AGENTS_MD_FILENAME, MANIFEST_FILENAME, verifyContract } from '@ultimat3/manifest';
10
10
  import type { MetaIssue } from '@ultimat3/seo';
11
- import { validateMeta } from '@ultimat3/seo';
11
+ import { llmsDescriptionIssues, validateMeta } from '@ultimat3/seo';
12
12
  import { checkAgentsMd } from './app-agents-md';
13
13
  import { readAppSources } from './app-boundaries';
14
14
  import { envExampleFindings } from './app-env';
@@ -17,14 +17,18 @@ import { policyFindings } from './app-permissions';
17
17
  import { asyncPageFindings } from './async-pages';
18
18
  import { appBoundaryFindings } from './boundary-findings';
19
19
  import { checkBudgets, readBuildStats } from './budgets';
20
+ import { checkClientDrift } from './client-emit-drift';
20
21
  import { checkDestructiveMigrations } from './db-destructive';
22
+ import { checkRollingMigrations } from './db-rolling';
21
23
  import { checkUngeneratableMigrations } from './db-ungeneratable';
22
24
  import { checkDocumentStyles, documentSurfaces } from './document-styles';
23
25
  import { checkErrorCodeResolution, checkErrorFixReport } from './error-contract';
24
26
  import { guardFindings } from './guards';
25
27
  import { catalogFindings } from './i18n-registration';
26
28
  import { isrQueryAdvice } from './isr-query-advice';
29
+ import { readJobBudgetFindings } from './job-budget';
27
30
  import { unregisteredJobFindings } from './job-registration';
31
+ import { LINT_ARGV, lintOutcome } from './lint-findings';
28
32
  import { liveRouteFindings } from './live-routes';
29
33
  import { withLoadFindings, withoutLoadFindings } from './load-findings';
30
34
  import {
@@ -32,14 +36,21 @@ import {
32
36
  manifestMissingFindings,
33
37
  manifestStaleness,
34
38
  } from './manifest-staleness';
39
+ import { mcpLockFindings } from './mcp-lock-findings';
35
40
  import { msg } from './messages';
36
41
  import type { Finding } from './output';
37
42
  import { findingFrom } from './output';
43
+ import { retainUnsweptFindings } from './retain-sweep';
38
44
  import { checkMigrationDrift } from './schema-drift';
39
45
  import { checkSchemaDump } from './schema-dump-drift';
46
+ import { cardRouteFindings } from './seo-cards';
40
47
  import { scanSiteMeta } from './seo-meta';
41
48
  import { quoteArg } from './shell-quote';
49
+ import { loadSiteSettings } from './site-config';
42
50
  import { readStaticReport } from './static-report';
51
+ import { unknownToolGateFindings } from './tool-gate-names';
52
+ import { measureIfStale } from './verify-budgets-build';
53
+ import { exerciseStep } from './verify-exercise';
43
54
  import { floorProblemFindings, readVerifyFloor } from './verify-floor';
44
55
  import { roleLoadFindings } from './verify-role-load';
45
56
  import type { VerifyStep } from './verify-step';
@@ -52,6 +63,9 @@ import { checkWorkspaceDependencies } from './workspace-graph';
52
63
  /** The one file that makes the `roadmap` step answerable, and therefore what `applies` reads. */
53
64
  const ROADMAP_FILE = join('docs', 'idea', '14-roadmap.md');
54
65
 
66
+ /** Where `exercise` joins the suites: directly after `job`, as `VERIFY_STEP_NAMES` holds it. */
67
+ const AFTER_JOB = TEST_STEPS.findIndex((step) => step.name === 'job') + 1;
68
+
55
69
  /** The whole contract, in cost order. Every check the framework knows how to make lives here. */
56
70
  export const VERIFY_STEPS: readonly VerifyStep[] = [
57
71
  {
@@ -82,12 +96,7 @@ export const VERIFY_STEPS: readonly VerifyStep[] = [
82
96
  // the colour rule is `packages/ui/src/tokens/tokens.test.ts`, and rides on the `unit` step.
83
97
  summary: 'biome: format, no any, no default exports, no unused imports',
84
98
  async run(ctx) {
85
- const result = await ctx.runner(['bunx', 'biome', 'check', '.'], { cwd: ctx.root });
86
- return fromExec(result, {
87
- code: 'X_LINT_FAILED',
88
- cause: 'biome reported problems',
89
- fix: 'bunx biome check --write .',
90
- });
99
+ return lintOutcome(await ctx.runner(LINT_ARGV, { cwd: ctx.root }));
91
100
  },
92
101
  },
93
102
  {
@@ -176,7 +185,9 @@ export const VERIFY_STEPS: readonly VerifyStep[] = [
176
185
  };
177
186
  },
178
187
  },
179
- ...TEST_STEPS,
188
+ ...TEST_STEPS.slice(0, AFTER_JOB),
189
+ exerciseStep,
190
+ ...TEST_STEPS.slice(AFTER_JOB),
180
191
  {
181
192
  name: 'drift',
182
193
  summary:
@@ -202,6 +213,12 @@ export const VERIFY_STEPS: readonly VerifyStep[] = [
202
213
  // itself, so CI still needs nothing listening: the committed `packages/db/schema/` against
203
214
  // what replaying the migrations produces, and that dump loaded back (`X_SCHEMA_DUMP_DRIFT`).
204
215
  ...(await checkSchemaDump(ctx.root, ctx.env ?? Bun.env)),
216
+ // The fifth: what the previous release would trip over during a roll. Off until the app
217
+ // sets `rollingSince` in `x.verify.json`, since an applied migration cannot gain a marker.
218
+ ...(await checkRollingMigrations(ctx.root)),
219
+ // The sixth: a generated client (`x client`) against its own stamp — a source document that
220
+ // moved (`X_CLIENT_STALE`) or a body edited by hand (`X_CLIENT_EDITED`). No database.
221
+ ...(await checkClientDrift(ctx.root)),
205
222
  ]),
206
223
  },
207
224
  {
@@ -235,17 +252,21 @@ export const VERIFY_STEPS: readonly VerifyStep[] = [
235
252
  // no stylesheet registry to read; there is nothing for either half to weigh.
236
253
  applies: async (ctx) => existsSync(join(ctx.root, APP_CONFIG_FILE)),
237
254
  async run(ctx) {
238
- // No stats file is NOT "nothing to weigh" — it is every declared budget unmeasured, which is
239
- // the case `checkBudgets` already names per route (`X_BUDGET_UNMEASURED`). Skipping the half
240
- // entirely is how a step that has never run once reported green: `.x/` is gitignored, so no
241
- // CI run and neither gated app has ever had a `build-stats.json` for it to read.
255
+ // No stats file is NOT "nothing to weigh" — it is every declared budget unmeasured. So the
256
+ // step MEASURES first, whenever the stats are missing, of an earlier rule, or older than the
257
+ // newest app source (`verify-budgets-build.ts`): `x verify` alone is the gate, on a fresh
258
+ // tree too, and a heavier island is never weighed at the bytes of a build before it.
242
259
  //
243
- // Handed over as `undefined` and never as `?? { routes: [] }`: "no build has run here" and
244
- // "a build ran and could not weigh this route" are two different instructions, and only the
245
- // caller knows which of them is true. The step still does not BUILD — measuring here would
246
- // make `x verify` a static build on every run (8.2s on `dummy/social-media-clone`, and 5.9s
247
- // to a hard `X_PRERENDER_FAILED` on `examples/dummy`), and it would be a second builder
248
- // beside `apps/web/prerender.ts`, which is where an app reads `SITE_ORIGIN`.
260
+ // Only then, never on every run: an unedited tree pays nothing, and a rebuild after an edit costs
261
+ // one static build (8.2 s on `dummy/social-media-clone`, As of 2026-11). The builder is
262
+ // `x build`, spawned through `ctx.runner` so it is a tagged child under this step's deadline
263
+ // (`stepTimeoutMs.budgets` overrides). A failing build — `examples/dummy`'s old 5.9 s was a
264
+ // hard `X_PRERENDER_FAILED` — is red under the build's own findings, unchanged. `SITE_ORIGIN`
265
+ // shapes canonical and og:url, never script bytes, and the build writes `.x/verify-static`,
266
+ // so a deployable `.x/static` built with the real origin is never replaced.
267
+ // A failed build's findings stand in for the per-route weighing below, which would only
268
+ // repeat them as `X_BUDGET_UNMEASURED` per route; the other riders still answer.
269
+ const measured = await measureIfStale(ctx);
249
270
  const stats = await readBuildStats(ctx.root);
250
271
  // The report beside the stats, for the one thing `checkBudgets` reads off it: a route the
251
272
  // build rendered and could not weigh because its island was handed props over the cap is
@@ -265,13 +286,17 @@ export const VERIFY_STEPS: readonly VerifyStep[] = [
265
286
  ...(await liveRouteFindings(ctx.root)),
266
287
  // The fourth: a data read in an async Page rather than in `load` (`async-pages.ts`).
267
288
  ...asyncPageFindings(),
268
- ...checkBudgets(manifest, stats, report?.unmeasured),
289
+ ...(measured.findings.length > 0
290
+ ? measured.findings
291
+ : checkBudgets(manifest, stats, report?.unmeasured)),
269
292
  ]);
270
293
  // What no build can weigh, said out loud rather than dropped (`measure-paths.ts`).
271
294
  const notes = (report?.unmeasured ?? [])
272
295
  .filter((one) => one.weighable === false)
273
296
  .map((one) => `not weighed: ${one.reason}`);
274
- const output = [outcome.output, ...notes].filter((line) => line !== undefined).join('\n');
297
+ const output = [measured.output, outcome.output, ...notes]
298
+ .filter((line) => line !== undefined)
299
+ .join('\n');
275
300
  return output === '' ? outcome : { ...outcome, output };
276
301
  },
277
302
  },
@@ -293,12 +318,19 @@ export const VERIFY_STEPS: readonly VerifyStep[] = [
293
318
  // such key), so canonical checks are skipped rather than run against an origin this file
294
319
  // invented. `seo-meta.ts` spells out why that is the honest half.
295
320
  const report = validateMeta(scan.records);
321
+ // `/llms.txt` lists these pages by description: while it is on, a page without one is red.
322
+ const llms =
323
+ (await loadSiteSettings(ctx.root)).llms === false
324
+ ? []
325
+ : llmsDescriptionIssues(scan.records);
296
326
  // The scan filled the route table, so the advice costs no second load. Rides here because
297
327
  // this is the step about public, shared-cache pages; it never fails it.
298
328
  const warnings = await isrQueryAdvice(ctx.root);
329
+ // A declared `og.capture` that names no site/ route: the card convention's build error.
330
+ const cards = cardRouteFindings(scan.records);
299
331
  return {
300
- ok: scan.findings.length === 0 && report.ok,
301
- findings: [...scan.findings, ...report.issues.map(seoFinding)],
332
+ ok: scan.findings.length === 0 && report.ok && llms.length === 0 && cards.length === 0,
333
+ findings: [...scan.findings, ...[...report.issues, ...llms].map(seoFinding), ...cards],
302
334
  ...(warnings.length === 0 ? {} : { warnings }),
303
335
  };
304
336
  },
@@ -358,11 +390,19 @@ export const VERIFY_STEPS: readonly VerifyStep[] = [
358
390
  // Started first and awaited last: it is a child process, and it runs beside the rest.
359
391
  const roleLoad = app ? roleLoadFindings(ctx.root, ctx.runner) : Promise.resolve([]);
360
392
  const drift = await driftFindings(ctx.root);
393
+ // Every `mcpTools()` against `tools.lock.json`: the `x mcp lock --check` of each, at once.
394
+ const remote = app ? await mcpLockFindings(ctx.root) : { findings: [], warnings: [] };
361
395
  const findings = [
362
396
  ...(app ? await withLoadFindings(ctx.root, drift) : drift),
363
397
  // An app only: the framework monorepo declares no `defineApi`, so it has no list to be in.
364
398
  ...(existsSync(join(ctx.root, APP_CONFIG_FILE))
365
- ? await unregisteredJobFindings(ctx.root)
399
+ ? [
400
+ ...(await unregisteredJobFindings(ctx.root)),
401
+ ...(await retainUnsweptFindings(ctx.root)),
402
+ ...(await readJobBudgetFindings(ctx.root)),
403
+ ...(await unknownToolGateFindings(ctx.root)),
404
+ ...remote.findings,
405
+ ]
366
406
  : []),
367
407
  // What a worker never registers: a declaration beside a component the API index does not
368
408
  // import (`verify-role-load.ts`). The boot checks jobs and tasks only, and only at boot.
@@ -374,7 +414,10 @@ export const VERIFY_STEPS: readonly VerifyStep[] = [
374
414
  ];
375
415
  // Warnings are not findings: `AGENTS.md` tabulating a route table is a smell a human
376
416
  // judges, not a build error. They ride in `output`, which `--json` carries verbatim.
377
- const output = agents.warnings.map((warning) => `${AGENTS_MD_FILENAME}: ${warning}`);
417
+ const output = [
418
+ ...agents.warnings.map((warning) => `${AGENTS_MD_FILENAME}: ${warning}`),
419
+ ...remote.warnings,
420
+ ];
378
421
  return {
379
422
  ok: findings.length === 0,
380
423
  findings,
@@ -13,7 +13,7 @@
13
13
  // ORDER, NOT MEMBERSHIP, FOLLOWS THE CLOCK. Each slice also writes a junit report; its per-file
14
14
  // durations go into the run's timings cache (`TEST_TIMINGS_FILE`, `test-timings.ts`) and the NEXT
15
15
  // run starts the slices longest-first from it. The slices themselves stay a pure function of the
16
- // file list — the coverage a slice measures never depends on a cache. Measured on bank-integrations
16
+ // file list — the coverage a slice measures never depends on a cache. Measured on one adopter's app
17
17
  // (1 192 files, 102 slices, 8 processes): sorted order 64.1 s, longest-first 56.3 s, the floor
18
18
  // 56.1 s — the pool used to start its 50 s slice last.
19
19
 
@@ -0,0 +1,41 @@
1
+ // The gate's second serial lane: `e2e` beside `live` in a package monorepo, isolated from it by
2
+ // construction. Split from `verify-run.ts`, which owns the order; this file owns the one condition
3
+ // under which two serial suites may share the machine, and the environment that makes it safe.
4
+
5
+ // why: Bun ships no synchronous existence check or path join.
6
+ import { existsSync } from 'node:fs';
7
+ // why: Bun ships no path-join primitive.
8
+ import { join } from 'node:path';
9
+ import { APP_CONFIG_FILE } from '@ultimat3/core';
10
+ import type { Runner } from './exec';
11
+ import type { VerifyContext } from './verify-step';
12
+
13
+ /**
14
+ * What a service URL is called: every `TEST_*_URL` the suites read (`docker/test-services.env`) and
15
+ * `DATABASE_URL`, which `@ultimat3/testing`'s template database falls back to. A child of the lane
16
+ * sees none of them, so whatever it runs, it cannot reach the server `live` is using.
17
+ */
18
+ export const isServiceUrlKey = (key: string): boolean =>
19
+ key === 'DATABASE_URL' || /^TEST_[A-Z0-9_]*_URL$/.test(key);
20
+
21
+ /**
22
+ * Whether `e2e` may run beside `live`. Both serial, and serial for reasons that do not meet: `live`
23
+ * holds a cluster-wide replication slot, `e2e` one browser profile and the built output. In a
24
+ * package monorepo the e2e suite boots throwaway apps on free ports and strips every service URL
25
+ * itself (`packages/cli/e2e/*`), and `besideLive` below makes that true of every file. An APP's
26
+ * e2e boots the app against the services its own live suite uses, so an app keeps them in order.
27
+ */
28
+ export const e2eBesideLive = (ctx: Pick<VerifyContext, 'root'>): boolean =>
29
+ !existsSync(join(ctx.root, APP_CONFIG_FILE));
30
+
31
+ /** `ctx` with every child its runner starts stripped of each service URL in `env`. */
32
+ export function besideLive(ctx: VerifyContext): VerifyContext {
33
+ // Both: a child inherits the process environment, so a URL only `Bun.env` holds reaches it even
34
+ // when the caller handed `ctx.env`.
35
+ const keys = new Set([...Object.keys(Bun.env), ...Object.keys(ctx.env ?? {})]);
36
+ const strip: Record<string, undefined> = {};
37
+ for (const key of keys) if (isServiceUrlKey(key)) strip[key] = undefined;
38
+ const runner: Runner = (command, options) =>
39
+ ctx.runner(command, { ...options, env: { ...options.env, ...strip } });
40
+ return { ...ctx, runner };
41
+ }
@@ -0,0 +1,64 @@
1
+ // The `exercise` gate step: `x exercise --json` as a child under the step's deadline, its findings
2
+ // the step's. A child, never in process: the pass freezes the clock, seals the network and installs
3
+ // its own database, and the steps beside it in this process must see none of that.
4
+
5
+ // why: Bun has no directory probe for the app config, and no path joiner.
6
+ import { existsSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import { APP_CONFIG_FILE, ERROR_DOCS_URL, isCompiledBundle } from '@ultimat3/core';
9
+ import { t } from '@ultimat3/schema';
10
+ import { execOutput } from './exec';
11
+ import type { Finding } from './output';
12
+ import { documentIn } from './upgrade-gate';
13
+ import type { VerifyStep } from './verify-step';
14
+
15
+ /** `x exercise`'s argv, spawned as this process's own CLI: the source entry, or a compiled binary. */
16
+ export function verifyExerciseCommand(): readonly string[] {
17
+ const self = isCompiledBundle(import.meta.dir) ? [] : [join(import.meta.dir, 'bin.ts')];
18
+ return [process.execPath, ...self, 'exercise', '--json'];
19
+ }
20
+
21
+ const FINDING = t.object({
22
+ code: t.string,
23
+ cause: t.string,
24
+ fix: t.string,
25
+ docs: t.optional(t.string),
26
+ at: t.optional(t.string),
27
+ });
28
+ const DOCUMENT = t.object({ ok: t.boolean, findings: t.optional(t.array(FINDING)) });
29
+
30
+ interface ExerciseDocument {
31
+ readonly ok: boolean;
32
+ readonly findings?: readonly Finding[];
33
+ }
34
+
35
+ export const exerciseStep: VerifyStep = {
36
+ name: 'exercise',
37
+ summary: 'every action, query and job runs once against seeded rows, with no test written',
38
+ // A repo with no `app.config.ts` registers no primitive: SKIPPED, for the reason `policy` gives.
39
+ applies: async (ctx) => existsSync(join(ctx.root, APP_CONFIG_FILE)),
40
+ async run(ctx) {
41
+ const result = await ctx.runner(verifyExerciseCommand(), { cwd: ctx.root });
42
+ const parsed = DOCUMENT.safeParse(documentIn(result.stdout));
43
+ const document = parsed.issues === undefined ? (parsed.value as ExerciseDocument) : undefined;
44
+ const reported = document?.findings ?? [];
45
+ // The exit code is the verdict, as `fromExec` reads every child's; the document adds the
46
+ // findings, and a document that says red is red whatever the exit. A red with nothing to say
47
+ // is still red, under the code for a CLI that failed without saying why.
48
+ if (result.ok && document?.ok !== false && reported.length === 0) {
49
+ return { ok: true, findings: [], output: execOutput(result) };
50
+ }
51
+ const findings: readonly Finding[] =
52
+ reported.length > 0
53
+ ? reported
54
+ : [
55
+ {
56
+ code: 'X_CLI_UNEXPECTED',
57
+ cause: `x exercise --json exited ${String(result.code)} with no finding, so x verify could not say which primitive failed`,
58
+ fix: 'x exercise --json',
59
+ docs: ERROR_DOCS_URL,
60
+ },
61
+ ];
62
+ return { ok: false, findings, output: execOutput(result) };
63
+ },
64
+ };
@@ -14,6 +14,8 @@ import { COVERAGE_FIELD, readCoverageFloor } from './coverage-floor';
14
14
  import type { Finding } from './output';
15
15
  import type { StepTimeouts } from './verify-deadline';
16
16
  import { readStepTimeouts, STEP_TIMEOUT_FIELD } from './verify-deadline';
17
+ import type { SkipCeiling } from './verify-skip-ceiling';
18
+ import { readSkipCeiling, SKIP_CEILING_FIELD } from './verify-skip-ceiling';
17
19
  import { GATE_COMMAND, VERIFY_STEP_NAMES } from './verify-step';
18
20
 
19
21
  /** Hand-written and committed, beside the generated `x.manifest.json` the same gate reads. */
@@ -62,6 +64,17 @@ export interface VerifyFloor {
62
64
  * Absent in an app is `X_COVERAGE_FLOOR_UNSTATED`: no app is held to a number it did not write.
63
65
  */
64
66
  readonly coverage?: CoverageFloor;
67
+ /**
68
+ * The migration id the rolling-deploy rail judges after (`db-rolling.ts`), exclusive: an applied
69
+ * migration cannot gain a marker, so what it already holds is not judged. `"0"` judges everything.
70
+ * Absent is off, and `x doctor` says which id turns it on.
71
+ */
72
+ readonly rollingSince?: string;
73
+ /**
74
+ * The most tests a step may skip (`verify-skip-ceiling.ts`), judged by `x verify merge` and by a
75
+ * whole `x verify` under `CI`. Absent is no ceiling, and a CI run names the number to write.
76
+ */
77
+ readonly skipCeiling?: SkipCeiling;
65
78
  /** Why part of the file is not a floor. The `manifest` step reports these; nothing swallows them. */
66
79
  readonly problems: readonly string[];
67
80
  }
@@ -72,6 +85,9 @@ export const BUDGET_FIELD = 'agentsMdMaxBytes';
72
85
  /** The floor's typecheck-binary key. Named once: the problem quotes it and the fix repairs it. */
73
86
  export const TYPECHECK_BIN_FIELD = 'typecheckBin';
74
87
 
88
+ /** The floor's rolling-rail bound. Named once: the problem quotes it and the fix repairs it. */
89
+ export const ROLLING_SINCE_FIELD = 'rollingSince';
90
+
75
91
  /** The floor's isolation key. */
76
92
  export const ISOLATE_FIELD = 'isolate';
77
93
 
@@ -130,6 +146,19 @@ function readTypecheckBin(payload: Record<string, unknown> | undefined): {
130
146
  return { bin: raw, problems: [] };
131
147
  }
132
148
 
149
+ function readRollingSince(payload: Record<string, unknown> | undefined): {
150
+ since?: string;
151
+ problems: readonly string[];
152
+ } {
153
+ const raw = payload?.[ROLLING_SINCE_FIELD];
154
+ if (raw === undefined) return { problems: [] };
155
+ if (typeof raw !== 'string' || raw.trim().length === 0)
156
+ return {
157
+ problems: [`"${ROLLING_SINCE_FIELD}" is ${JSON.stringify(raw)}, which is not a migration id`],
158
+ };
159
+ return { since: raw, problems: [] };
160
+ }
161
+
133
162
  const asRecord = (value: unknown): Record<string, unknown> | undefined =>
134
163
  typeof value === 'object' && value !== null && !Array.isArray(value)
135
164
  ? (value as Record<string, unknown>)
@@ -163,12 +192,16 @@ export function parseVerifyFloor(
163
192
  const isolate = readIsolate(record);
164
193
  const timeouts = readStepTimeouts(record, declared);
165
194
  const coverage = readCoverageFloor(record);
195
+ const rolling = readRollingSince(record);
196
+ const skips = readSkipCeiling(record, declared);
166
197
  const rest = {
167
198
  ...(budget.budget === undefined ? {} : { agentsMdMaxBytes: budget.budget }),
168
199
  ...(typecheckBin.bin === undefined ? {} : { typecheckBin: typecheckBin.bin }),
169
200
  ...(isolate.isolate === undefined ? {} : { isolate: isolate.isolate }),
170
201
  ...(timeouts.timeouts === undefined ? {} : { stepTimeoutMs: timeouts.timeouts }),
171
202
  ...(coverage.coverage === undefined ? {} : { coverage: coverage.coverage }),
203
+ ...(rolling.since === undefined ? {} : { rollingSince: rolling.since }),
204
+ ...(skips.ceiling === undefined ? {} : { skipCeiling: skips.ceiling }),
172
205
  };
173
206
  const steps = record?.['steps'];
174
207
  if (!Array.isArray(steps)) {
@@ -182,6 +215,8 @@ export function parseVerifyFloor(
182
215
  ...isolate.problems,
183
216
  ...timeouts.problems,
184
217
  ...coverage.problems,
218
+ ...rolling.problems,
219
+ ...skips.problems,
185
220
  ],
186
221
  };
187
222
  }
@@ -200,6 +235,8 @@ export function parseVerifyFloor(
200
235
  ...budget.problems,
201
236
  ...timeouts.problems,
202
237
  ...coverage.problems,
238
+ ...rolling.problems,
239
+ ...skips.problems,
203
240
  ],
204
241
  };
205
242
  }
@@ -289,5 +326,9 @@ const fixFor = (problem: string): string => {
289
326
  return `x verify --json # then write "${STEP_TIMEOUT_FIELD}" in ${VERIFY_FLOOR_FILE} as { "unit": 900000 } — a step it runs, a whole number of milliseconds`;
290
327
  if (problem.includes(`"${COVERAGE_FIELD}`))
291
328
  return `x verify --only unit --json # then write "${COVERAGE_FIELD}" in ${VERIFY_FLOOR_FILE} as { "lines": 95, "funcs": 95 }, every "exclude" entry as { "glob": "…", "why": "…" }`;
329
+ if (problem.includes(`"${ROLLING_SINCE_FIELD}"`))
330
+ return `x doctor --json # it names your newest migration id: set "${ROLLING_SINCE_FIELD}" in ${VERIFY_FLOOR_FILE} to that, "0" to judge them all, or drop the key`;
331
+ if (problem.includes(`"${SKIP_CEILING_FIELD}"`))
332
+ return `x verify --json # then write "${SKIP_CEILING_FIELD}" in ${VERIFY_FLOOR_FILE} as { "contract": 9 } — a step it runs, a whole number of tests`;
292
333
  return `x verify --json # then write ${VERIFY_FLOOR_FILE} as {"steps":["unit","contract"]}, naming only steps it ran`;
293
334
  };
@@ -29,6 +29,7 @@ import type { PartStep } from './verify-part-step';
29
29
  import { readPartStep } from './verify-part-step';
30
30
  import { verifySummary } from './verify-run';
31
31
  import { corpusHash } from './verify-shard';
32
+ import { judgeSkips } from './verify-skip-ceiling';
32
33
  import { GATE_COMMAND, VERIFY_STEP_NAMES } from './verify-step';
33
34
 
34
35
  /**
@@ -71,8 +72,8 @@ const isRecord = (value: unknown): value is Record<string, unknown> =>
71
72
  typeof value === 'object' && value !== null && !Array.isArray(value);
72
73
 
73
74
  /**
74
- * The LAST non-empty line, and only it: `bin/check --json` prints the build's document and then
75
- * the gate's. Never "the last line that parses" — a part whose tail is not the gate's document is
75
+ * The LAST non-empty line, and only it: an app's own wrapper script (an older scaffold's
76
+ * `bin/check --json`) prints the build's document and then the gate's. Never "the last line that parses" — a part whose tail is not the gate's document is
76
77
  * a truncated or appended upload, and falling back to an earlier line would believe it.
77
78
  */
78
79
  export function parsePart(file: string, text: string, command: string = GATE_COMMAND): VerifyPart {
@@ -322,8 +323,19 @@ function mergeStep(
322
323
  const nothingRan = sharded.length > 0 && tests !== undefined && tests.ran === 0;
323
324
  const required = floorRequires(floor, name);
324
325
  const floorGap = nothingRan && required ? [skippedSuiteFinding(name, tests.skipped)] : [];
326
+ // The ceiling on the SUM, always: a merge is the CI verdict, and one shard's skips say nothing.
327
+ const skips =
328
+ sharded.length > 0
329
+ ? judgeSkips(name, tests, floor?.skipCeiling)
330
+ : { findings: [], warnings: [] };
325
331
  const riding = rider?.findings ?? [];
326
- const findings = [...steps.flatMap((step) => step.findings), ...gaps, ...floorGap, ...riding];
332
+ const findings = [
333
+ ...steps.flatMap((step) => step.findings),
334
+ ...gaps,
335
+ ...floorGap,
336
+ ...skips.findings,
337
+ ...riding,
338
+ ];
327
339
  const output = [
328
340
  ...steps
329
341
  .filter((step) => !step.ok && step.output !== undefined && step.output.length > 0)
@@ -331,7 +343,9 @@ function mergeStep(
331
343
  ...(rider?.output === undefined ? [] : [rider.output]),
332
344
  ].join('\n');
333
345
  // Every part's advice, once each and in the order the parts reported it.
334
- const warnings = [...new Set(steps.flatMap((step) => step.warnings ?? []))];
346
+ const warnings = [
347
+ ...new Set([...steps.flatMap((step) => step.warnings ?? []), ...skips.warnings]),
348
+ ];
335
349
  const skipped =
336
350
  sharded.length > 0 ? nothingRan && !required : steps.every((step) => step.skipped);
337
351
  return {
@@ -340,6 +354,7 @@ function mergeStep(
340
354
  steps.every((step) => step.ok) &&
341
355
  gaps.length === 0 &&
342
356
  floorGap.length === 0 &&
357
+ skips.findings.length === 0 &&
343
358
  riding.length === 0,
344
359
  // The slowest job: the parts ran side by side, and wall time is what a CI run waits on.
345
360
  durationMs: Math.max(...steps.map((step) => step.durationMs)),
@@ -6,6 +6,7 @@
6
6
 
7
7
  // why: Bun ships no path API; the fix names the import relative to the API index.
8
8
  import { dirname, join } from 'node:path';
9
+ import { gatedToolNames } from '@ultimat3/action';
9
10
  import { ERROR_DOCS_URL, registeredServiceNames, singleLine } from '@ultimat3/core';
10
11
  import { entityNames } from '@ultimat3/entity';
11
12
  import { catalogDeclarationCount } from '@ultimat3/i18n';
@@ -67,6 +68,7 @@ export function registrations(): readonly Registration[] {
67
68
  'channel',
68
69
  registeredChannels().map((channel) => channel.name),
69
70
  ),
71
+ ...of('tool gate', gatedToolNames()),
70
72
  ];
71
73
  }
72
74
 
package/src/verify-run.ts CHANGED
@@ -9,12 +9,14 @@ import { msg } from './messages';
9
9
  import type { CommandResult, Finding, JsonValue, StepResult } from './output';
10
10
  import type { StepDeadline, StepGuard } from './verify-deadline';
11
11
  import { guardStep, raceDeadline, stepDeadline } from './verify-deadline';
12
+ import { besideLive, e2eBesideLive } from './verify-e2e-lane';
12
13
  import {
13
14
  floorRequires,
14
15
  readVerifyFloor,
15
16
  skippedSuiteFinding,
16
17
  vanishedSuiteFinding,
17
18
  } from './verify-floor';
19
+ import { inCi, judgeSkips } from './verify-skip-ceiling';
18
20
  import { stalledOutput, stepTimeoutFinding } from './verify-stalled';
19
21
  import type {
20
22
  CoverageNumbers,
@@ -24,6 +26,7 @@ import type {
24
26
  VerifyStepName,
25
27
  } from './verify-step';
26
28
  import { GATE_COMMAND } from './verify-step';
29
+ import { resetTestDiscovery } from './verify-tests';
27
30
 
28
31
  /**
29
32
  * Run every step, never bailing early: an agent fixing three things at once needs all
@@ -38,6 +41,9 @@ export async function runVerify(
38
41
  steps: readonly VerifyStep[],
39
42
  ctx: VerifyContext,
40
43
  ): Promise<CommandResult> {
44
+ // The file list is one run's: the dev MCP host calls this again in one process, and a suite
45
+ // written or deleted since the last call must be what this one judges.
46
+ resetTestDiscovery();
41
47
  const floor = await readVerifyFloor(ctx.root);
42
48
  const only = onlyList(ctx.only);
43
49
  const selected = only === undefined ? steps : steps.filter((step) => only.includes(step.name));
@@ -76,7 +82,14 @@ export async function runVerify(
76
82
  // The static steps wait for the serial suites and then run BESIDE them — only when `live` is in
77
83
  // the list, so a one-step run (`--only`) and a list with no serial suite keep today's order.
78
84
  const overlapping = selected.some((step) => step.name === SERIAL_SUITES[0]);
79
- const beside = overlapping ? selected.filter((step) => BESIDE_SERIAL_SUITES.has(step.name)) : [];
85
+ // The second serial lane (`verify-e2e-lane.ts`): `e2e` beside `live`, its children stripped of
86
+ // every service URL, in a package monorepo only.
87
+ const e2eLane = overlapping && e2eBesideLive(ctx);
88
+ const beside = overlapping
89
+ ? selected.filter(
90
+ (step) => BESIDE_SERIAL_SUITES.has(step.name) || (e2eLane && step.name === 'e2e'),
91
+ )
92
+ : [];
80
93
  let pending: Promise<void> | undefined;
81
94
  const join = async (): Promise<void> => {
82
95
  await pending;
@@ -93,7 +106,9 @@ export async function runVerify(
93
106
  for (const step of selected) {
94
107
  if (beside.includes(step)) continue;
95
108
  if (step.name === SERIAL_SUITES[0]) {
96
- pending = Promise.all(beside.map((other) => run(other, base))).then(() => undefined);
109
+ pending = Promise.all(
110
+ beside.map((other) => run(other, other.name === 'e2e' ? besideLive(base) : base)),
111
+ ).then(() => undefined);
97
112
  } else if (!SERIAL_SUITES.includes(step.name)) {
98
113
  await join();
99
114
  }
@@ -199,7 +214,7 @@ export const BESIDE_SERIAL_SUITES: ReadonlySet<VerifyStepName> = new Set<VerifyS
199
214
  ]);
200
215
 
201
216
  /** The consecutive run of steps the static group overlaps, in the order `VERIFY_STEP_NAMES` holds. */
202
- export const SERIAL_SUITES: readonly VerifyStepName[] = ['live', 'job', 'e2e', 'eval'];
217
+ export const SERIAL_SUITES: readonly VerifyStepName[] = ['live', 'job', 'exercise', 'e2e', 'eval'];
203
218
 
204
219
  async function runStep(
205
220
  step: VerifyStep,
@@ -279,20 +294,25 @@ async function runStep(
279
294
  // with no files at all — the run just had to finish before it could be seen. Appended to the
280
295
  // step's own findings so `data.failed`, the counts and every gate reading this table carry it.
281
296
  const vanished = nothingRan && required ? [skippedSuiteFinding(step.name, tests.skipped)] : [];
297
+ // A mostly skipped suite, judged where the verdict is: CI, and only a whole run (a shard's
298
+ // share is judged by `x verify merge`, on the sum). A laptop without services stays a dev loop.
299
+ const skips =
300
+ outcome.shard === undefined && inCi(ctx.env ?? Bun.env)
301
+ ? judgeSkips(step.name, tests, floor?.skipCeiling)
302
+ : { findings: [], warnings: [] };
303
+ const warnings = [...(outcome.warnings ?? []), ...skips.warnings];
282
304
  return {
283
305
  result: {
284
306
  name: step.name,
285
- ok: outcome.ok && vanished.length === 0,
307
+ ok: outcome.ok && vanished.length === 0 && skips.findings.length === 0,
286
308
  durationMs: Math.round(performance.now() - started),
287
309
  // Without a floor to require it, a suite that ran nothing is a SKIP and not a pass (#434):
288
310
  // the `e2e` step printed `✓ e2e 46ms` over its one skipped test, which is the one thing a
289
311
  // step table may never do — a reader cannot tell a lane that ran from a lane that did not.
290
312
  skipped: nothingRan && !required,
291
- findings: [...outcome.findings, ...vanished],
313
+ findings: [...outcome.findings, ...vanished, ...skips.findings],
292
314
  ...(outcome.output === undefined ? {} : { output: outcome.output }),
293
- ...(outcome.warnings === undefined || outcome.warnings.length === 0
294
- ? {}
295
- : { warnings: outcome.warnings }),
315
+ ...(warnings.length === 0 ? {} : { warnings }),
296
316
  ...(outcome.workers === undefined ? {} : { workers: outcome.workers }),
297
317
  ...(outcome.widthReason === undefined ? {} : { widthReason: outcome.widthReason }),
298
318
  ...(outcome.slowest === undefined || outcome.slowest.length === 0
@@ -23,8 +23,18 @@ export class VerifyShardInvalidError extends UltimateError {
23
23
  }
24
24
  }
25
25
 
26
- /** The steps a shard may split: the parallel suites. `live` and `e2e` are serial by design. */
27
- export const SHARDABLE_STEPS: readonly VerifyStepName[] = ['unit', 'contract', 'job'];
26
+ /**
27
+ * The steps a shard may split: every suite that runs test files. `live` and `e2e` stay ONE process
28
+ * inside a shard — serial is about one machine's replication slot and browser, and each CI runner
29
+ * starts its own — so their slices split across runners, never across workers.
30
+ */
31
+ export const SHARDABLE_STEPS: readonly VerifyStepName[] = [
32
+ 'unit',
33
+ 'contract',
34
+ 'live',
35
+ 'job',
36
+ 'e2e',
37
+ ];
28
38
 
29
39
  export interface ShardSpec {
30
40
  /** 1-based, as a CI matrix counts. */