@warlock.js/core 5.34.3 → 5.36.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 (281) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/esm/cli/cli-commands.manager.mjs +1 -1
  3. package/esm/cli/cli-commands.manager.mjs.map +1 -1
  4. package/esm/cli/commands/add.command.mjs +1 -0
  5. package/esm/cli/commands/add.command.mjs.map +1 -1
  6. package/esm/cli/commands/build.command.mjs +30 -3
  7. package/esm/cli/commands/build.command.mjs.map +1 -1
  8. package/esm/cli/commands/check/check-command.action.mjs +1 -0
  9. package/esm/cli/commands/check/check-command.action.mjs.map +1 -1
  10. package/esm/cli/commands/check.command.mjs +5 -0
  11. package/esm/cli/commands/check.command.mjs.map +1 -1
  12. package/esm/cli/commands/explain/explain-command.action.mjs +21 -0
  13. package/esm/cli/commands/explain/explain-command.action.mjs.map +1 -1
  14. package/esm/cli/commands/explain.command.mjs +14 -4
  15. package/esm/cli/commands/explain.command.mjs.map +1 -1
  16. package/esm/cli/commands/generate/generate.command.mjs +19 -1
  17. package/esm/cli/commands/generate/generate.command.mjs.map +1 -1
  18. package/esm/cli/commands/generate/generators/endpoint.generator.mjs +1 -1
  19. package/esm/cli/commands/generate/generators/endpoint.generator.mjs.map +1 -1
  20. package/esm/cli/commands/generate/generators/web.generator.mjs +169 -0
  21. package/esm/cli/commands/generate/generators/web.generator.mjs.map +1 -0
  22. package/esm/cli/commands/generate/templates/web-stubs.mjs +231 -0
  23. package/esm/cli/commands/generate/templates/web-stubs.mjs.map +1 -0
  24. package/esm/cli/commands/inspect/inspect-command.action.mjs +4 -3
  25. package/esm/cli/commands/inspect/inspect-command.action.mjs.map +1 -1
  26. package/esm/cli/commands/inspect.command.mjs +13 -8
  27. package/esm/cli/commands/inspect.command.mjs.map +1 -1
  28. package/esm/cli/commands/mcp/mcp-command.action.mjs +1 -1
  29. package/esm/cli/commands/mcp/mcp-command.action.mjs.map +1 -1
  30. package/esm/config/config-loader.d.mts.map +1 -1
  31. package/esm/config/config-loader.mjs +2 -0
  32. package/esm/config/config-loader.mjs.map +1 -1
  33. package/esm/config/load-config-files.mjs +4 -0
  34. package/esm/config/load-config-files.mjs.map +1 -1
  35. package/esm/dev-server/file-event-handler.mjs +11 -1
  36. package/esm/dev-server/file-event-handler.mjs.map +1 -1
  37. package/esm/dev-server/file-manager.d.mts +1 -1
  38. package/esm/dev-server/file-manager.d.mts.map +1 -1
  39. package/esm/dev-server/file-manager.mjs.map +1 -1
  40. package/esm/dev-server/files-orchestrator.mjs +16 -1
  41. package/esm/dev-server/files-orchestrator.mjs.map +1 -1
  42. package/esm/dev-server/files-watcher.mjs +13 -4
  43. package/esm/dev-server/files-watcher.mjs.map +1 -1
  44. package/esm/dev-server/loader/loader-port-acks.mjs +21 -9
  45. package/esm/dev-server/loader/loader-port-acks.mjs.map +1 -1
  46. package/esm/dev-server/loader/register-loader.mjs +31 -12
  47. package/esm/dev-server/loader/register-loader.mjs.map +1 -1
  48. package/esm/dev-server/module-loader.mjs +15 -1
  49. package/esm/dev-server/module-loader.mjs.map +1 -1
  50. package/esm/dev-server/parse-imports.mjs +1 -1
  51. package/esm/dev-server/special-file-patterns.mjs +4 -1
  52. package/esm/dev-server/special-file-patterns.mjs.map +1 -1
  53. package/esm/dev-server/type-generator.mjs +1 -1
  54. package/esm/e2e/cdp.d.mts.map +1 -1
  55. package/esm/e2e/cdp.mjs +9 -2
  56. package/esm/e2e/cdp.mjs.map +1 -1
  57. package/esm/e2e/index.d.mts +2 -2
  58. package/esm/e2e/page.d.mts +14 -1
  59. package/esm/e2e/page.d.mts.map +1 -1
  60. package/esm/e2e/page.mjs +81 -8
  61. package/esm/e2e/page.mjs.map +1 -1
  62. package/esm/e2e/types.d.mts +49 -0
  63. package/esm/e2e/types.d.mts.map +1 -1
  64. package/esm/e2e/types.mjs.map +1 -1
  65. package/esm/errors/error-codes.d.mts +72 -0
  66. package/esm/errors/error-codes.d.mts.map +1 -1
  67. package/esm/errors/error-codes.mjs +72 -0
  68. package/esm/errors/error-codes.mjs.map +1 -1
  69. package/esm/errors/index.d.mts +2 -2
  70. package/esm/errors/index.mjs +2 -2
  71. package/esm/generations/add-command.action.mjs +2 -0
  72. package/esm/generations/add-command.action.mjs.map +1 -1
  73. package/esm/generations/design-system-payload.mjs +53 -0
  74. package/esm/generations/design-system-payload.mjs.map +1 -0
  75. package/esm/generations/features/design-system.feature.mjs +38 -0
  76. package/esm/generations/features/design-system.feature.mjs.map +1 -0
  77. package/esm/generations/features/index.mjs +3 -1
  78. package/esm/generations/features/index.mjs.map +1 -1
  79. package/esm/generations/features/notifications.feature.mjs +13 -2
  80. package/esm/generations/features/notifications.feature.mjs.map +1 -1
  81. package/esm/generations/features/test.feature.mjs +48 -13
  82. package/esm/generations/features/test.feature.mjs.map +1 -1
  83. package/esm/generations/features/web.feature.mjs +3 -3
  84. package/esm/generations/features/web.feature.mjs.map +1 -1
  85. package/esm/generations/stubs.mjs +9 -5
  86. package/esm/generations/stubs.mjs.map +1 -1
  87. package/esm/http/error-mapper.d.mts +1 -1
  88. package/esm/http/error-mapper.mjs +3 -3
  89. package/esm/http/error-mapper.mjs.map +1 -1
  90. package/esm/http/error-response.d.mts +3 -0
  91. package/esm/http/error-response.d.mts.map +1 -1
  92. package/esm/http/error-response.mjs +8 -2
  93. package/esm/http/error-response.mjs.map +1 -1
  94. package/esm/http/errors/errors.d.mts +8 -0
  95. package/esm/http/errors/errors.d.mts.map +1 -1
  96. package/esm/http/errors/errors.mjs +13 -1
  97. package/esm/http/errors/errors.mjs.map +1 -1
  98. package/esm/http/index.d.mts +3 -2
  99. package/esm/http/index.mjs +3 -2
  100. package/esm/http/list-endpoint.d.mts +35 -0
  101. package/esm/http/list-endpoint.d.mts.map +1 -0
  102. package/esm/http/list-endpoint.mjs +28 -0
  103. package/esm/http/list-endpoint.mjs.map +1 -0
  104. package/esm/http/middleware/inject-request-context.d.mts.map +1 -1
  105. package/esm/http/middleware/inject-request-context.mjs +12 -2
  106. package/esm/http/middleware/inject-request-context.mjs.map +1 -1
  107. package/esm/http/server.mjs +1 -1
  108. package/esm/index.d.mts +10 -6
  109. package/esm/index.mjs +5 -2
  110. package/esm/localization/index.mjs +3 -1
  111. package/esm/localization/translation-usage-graph.d.mts +49 -0
  112. package/esm/localization/translation-usage-graph.d.mts.map +1 -0
  113. package/esm/localization/translation-usage-graph.mjs +128 -0
  114. package/esm/localization/translation-usage-graph.mjs.map +1 -0
  115. package/esm/localization/translation-usage.d.mts +85 -0
  116. package/esm/localization/translation-usage.d.mts.map +1 -0
  117. package/esm/localization/translation-usage.mjs +307 -0
  118. package/esm/localization/translation-usage.mjs.map +1 -0
  119. package/esm/openapi/security.mjs +12 -4
  120. package/esm/openapi/security.mjs.map +1 -1
  121. package/esm/openapi/types.d.mts +2 -0
  122. package/esm/openapi/types.d.mts.map +1 -1
  123. package/esm/production/build-budgets.d.mts +64 -0
  124. package/esm/production/build-budgets.d.mts.map +1 -0
  125. package/esm/production/build-budgets.mjs +109 -0
  126. package/esm/production/build-budgets.mjs.map +1 -0
  127. package/esm/production/production-builder.mjs +13 -1
  128. package/esm/production/production-builder.mjs.map +1 -1
  129. package/esm/production/resolve-build-config.d.mts +1 -1
  130. package/esm/production/resolve-build-config.d.mts.map +1 -1
  131. package/esm/production/resolve-build-config.mjs.map +1 -1
  132. package/esm/production/route-registration-snapshot.mjs +5 -3
  133. package/esm/production/route-registration-snapshot.mjs.map +1 -1
  134. package/esm/restful/restful.d.mts +1 -1
  135. package/esm/router/router.d.mts +2 -0
  136. package/esm/router/router.d.mts.map +1 -1
  137. package/esm/router/router.mjs +4 -1
  138. package/esm/router/router.mjs.map +1 -1
  139. package/esm/router/types.d.mts +19 -0
  140. package/esm/router/types.d.mts.map +1 -1
  141. package/esm/tests/fakes/storage-fake.d.mts +3 -1
  142. package/esm/tests/fakes/storage-fake.d.mts.map +1 -1
  143. package/esm/tests/fakes/storage-fake.mjs +3 -3
  144. package/esm/tests/fakes/storage-fake.mjs.map +1 -1
  145. package/esm/tests/fakes/test-fakes.d.mts +3 -1
  146. package/esm/tests/fakes/test-fakes.d.mts.map +1 -1
  147. package/esm/tests/fakes/test-fakes.mjs +3 -2
  148. package/esm/tests/fakes/test-fakes.mjs.map +1 -1
  149. package/esm/tests/index.d.mts +4 -2
  150. package/esm/tests/index.mjs +3 -1
  151. package/esm/tests/load-unit-config.mjs +24 -0
  152. package/esm/tests/load-unit-config.mjs.map +1 -0
  153. package/esm/tests/response-contract.d.mts +32 -0
  154. package/esm/tests/response-contract.d.mts.map +1 -0
  155. package/esm/tests/response-contract.mjs +116 -0
  156. package/esm/tests/response-contract.mjs.map +1 -0
  157. package/esm/tests/start-http-development-server.d.mts.map +1 -1
  158. package/esm/tests/start-http-development-server.mjs +10 -2
  159. package/esm/tests/start-http-development-server.mjs.map +1 -1
  160. package/esm/tests/test-lifecycle-state.mjs.map +1 -1
  161. package/esm/tests/test-locales.mjs +33 -0
  162. package/esm/tests/test-locales.mjs.map +1 -0
  163. package/esm/tests/test-runtime-scope.d.mts +15 -0
  164. package/esm/tests/test-runtime-scope.d.mts.map +1 -0
  165. package/esm/tests/test-runtime-scope.mjs +80 -0
  166. package/esm/tests/test-runtime-scope.mjs.map +1 -0
  167. package/esm/tests/vitest-setup.d.mts +13 -3
  168. package/esm/tests/vitest-setup.d.mts.map +1 -1
  169. package/esm/tests/vitest-setup.mjs +146 -18
  170. package/esm/tests/vitest-setup.mjs.map +1 -1
  171. package/esm/tooling/check/affected-files.d.mts +5 -0
  172. package/esm/tooling/check/affected-files.d.mts.map +1 -0
  173. package/esm/tooling/check/affected-files.mjs +56 -0
  174. package/esm/tooling/check/affected-files.mjs.map +1 -0
  175. package/esm/tooling/check/api-route-references.d.mts +16 -0
  176. package/esm/tooling/check/api-route-references.d.mts.map +1 -0
  177. package/esm/tooling/check/api-route-references.mjs +146 -0
  178. package/esm/tooling/check/api-route-references.mjs.map +1 -0
  179. package/esm/tooling/check/changed-files.d.mts +2 -0
  180. package/esm/tooling/check/changed-files.d.mts.map +1 -1
  181. package/esm/tooling/check/changed-files.mjs +2 -2
  182. package/esm/tooling/check/changed-files.mjs.map +1 -1
  183. package/esm/tooling/check/conventions-step.d.mts +7 -3
  184. package/esm/tooling/check/conventions-step.d.mts.map +1 -1
  185. package/esm/tooling/check/conventions-step.mjs +59 -8
  186. package/esm/tooling/check/conventions-step.mjs.map +1 -1
  187. package/esm/tooling/check/fixes.d.mts +8 -0
  188. package/esm/tooling/check/fixes.d.mts.map +1 -0
  189. package/esm/tooling/check/fixes.mjs +31 -0
  190. package/esm/tooling/check/fixes.mjs.map +1 -0
  191. package/esm/tooling/check/index.d.mts +12 -0
  192. package/esm/tooling/check/index.mjs +3 -2
  193. package/esm/tooling/check/run-check.d.mts +2 -0
  194. package/esm/tooling/check/run-check.d.mts.map +1 -1
  195. package/esm/tooling/check/run-check.mjs +23 -5
  196. package/esm/tooling/check/run-check.mjs.map +1 -1
  197. package/esm/tooling/check/run-tests.mjs +1 -1
  198. package/esm/tooling/check/source-ast.d.mts +1 -0
  199. package/esm/tooling/check/translation-conventions.mjs +32 -0
  200. package/esm/tooling/check/translation-conventions.mjs.map +1 -0
  201. package/esm/tooling/check/web-conventions.d.mts +10 -0
  202. package/esm/tooling/check/web-conventions.d.mts.map +1 -0
  203. package/esm/tooling/check/web-conventions.mjs +126 -0
  204. package/esm/tooling/check/web-conventions.mjs.map +1 -0
  205. package/esm/tooling/index.d.mts +7 -2
  206. package/esm/tooling/index.mjs +4 -2
  207. package/esm/tooling/inspect/collect-files.mjs +172 -0
  208. package/esm/tooling/inspect/collect-files.mjs.map +1 -0
  209. package/esm/tooling/inspect/collect-pages.mjs +3 -2
  210. package/esm/tooling/inspect/collect-pages.mjs.map +1 -1
  211. package/esm/tooling/inspect/collect-routes.mjs +1 -1
  212. package/esm/tooling/inspect/collect-routes.mjs.map +1 -1
  213. package/esm/tooling/inspect/explain-file.d.mts +12 -0
  214. package/esm/tooling/inspect/explain-file.d.mts.map +1 -0
  215. package/esm/tooling/inspect/explain-file.mjs +45 -0
  216. package/esm/tooling/inspect/explain-file.mjs.map +1 -0
  217. package/esm/tooling/inspect/index.d.mts +3 -2
  218. package/esm/tooling/inspect/index.mjs +2 -1
  219. package/esm/tooling/inspect/inspect-app.d.mts.map +1 -1
  220. package/esm/tooling/inspect/inspect-app.mjs +9 -3
  221. package/esm/tooling/inspect/inspect-app.mjs.map +1 -1
  222. package/esm/tooling/inspect/inspect-report.schema.mjs +34 -1
  223. package/esm/tooling/inspect/inspect-report.schema.mjs.map +1 -1
  224. package/esm/tooling/inspect/inspect-report.types.d.mts +17 -1
  225. package/esm/tooling/inspect/inspect-report.types.d.mts.map +1 -1
  226. package/esm/tooling/inspect/inspect-report.types.mjs +2 -1
  227. package/esm/tooling/inspect/inspect-report.types.mjs.map +1 -1
  228. package/esm/tooling/mcp/mcp-tools.d.mts.map +1 -1
  229. package/esm/tooling/mcp/mcp-tools.mjs +8 -2
  230. package/esm/tooling/mcp/mcp-tools.mjs.map +1 -1
  231. package/esm/tooling/problem.d.mts +4 -0
  232. package/esm/tooling/problem.d.mts.map +1 -1
  233. package/esm/tooling/problem.mjs +1 -0
  234. package/esm/tooling/problem.mjs.map +1 -1
  235. package/esm/tooling/syntax/ast.d.mts +2 -0
  236. package/esm/tooling/typecheck/run-typecheck.mjs +5 -1
  237. package/esm/tooling/typecheck/run-typecheck.mjs.map +1 -1
  238. package/esm/upgrade/codemods/index.mjs +2 -0
  239. package/esm/upgrade/codemods/index.mjs.map +1 -1
  240. package/esm/upgrade/codemods/vite-vitest.mjs +170 -0
  241. package/esm/upgrade/codemods/vite-vitest.mjs.map +1 -0
  242. package/esm/upgrade/notes.mjs +4 -0
  243. package/esm/upgrade/notes.mjs.map +1 -1
  244. package/esm/vite/index.d.mts +3 -1
  245. package/esm/vite/index.mjs +3 -1
  246. package/esm/vite/test-setup-reporter.d.mts +16 -0
  247. package/esm/vite/test-setup-reporter.d.mts.map +1 -0
  248. package/esm/vite/test-setup-reporter.mjs +55 -0
  249. package/esm/vite/test-setup-reporter.mjs.map +1 -0
  250. package/esm/vite/test-workers.d.mts +5 -0
  251. package/esm/vite/test-workers.d.mts.map +1 -0
  252. package/esm/vite/test-workers.mjs +38 -0
  253. package/esm/vite/test-workers.mjs.map +1 -0
  254. package/esm/warlock-config/index.d.mts +2 -1
  255. package/esm/warlock-config/types.d.mts +3 -0
  256. package/esm/warlock-config/types.d.mts.map +1 -1
  257. package/llms-full.txt +874 -9
  258. package/llms.txt +2 -0
  259. package/package.json +15 -14
  260. package/schemas/inspect-report.schema.json +47 -1
  261. package/skills/build-budgets/SKILL.md +51 -0
  262. package/skills/build-restful/SKILL.md +56 -0
  263. package/skills/configure-app/SKILL.md +7 -0
  264. package/skills/create-module/SKILL.md +9 -0
  265. package/skills/generate-web-ui/SKILL.md +71 -0
  266. package/skills/index.md +2 -2
  267. package/skills/register-route/SKILL.md +13 -0
  268. package/skills/run-app/SKILL.md +1 -1
  269. package/skills/send-mail/SKILL.md +25 -0
  270. package/skills/send-response/SKILL.md +50 -0
  271. package/skills/test-browser/SKILL.md +53 -0
  272. package/skills/test-http/SKILL.md +91 -0
  273. package/skills/test-service/SKILL.md +161 -3
  274. package/skills/upgrade-from-5-29/SKILL.md +109 -0
  275. package/skills/use-localization/SKILL.md +25 -0
  276. package/skills/warlock-check/SKILL.md +75 -1
  277. package/skills/warlock-inspect/SKILL.md +45 -2
  278. package/skills/warlock-mcp/SKILL.md +4 -0
  279. package/skills/write-cli-command/SKILL.md +18 -0
  280. package/esm/production/build-app-production.mjs +0 -14
  281. package/esm/production/build-app-production.mjs.map +0 -1
@@ -458,3 +458,94 @@ This is fine for normal test flow. It bites when you're inside a transaction the
458
458
  - The `create-controller` topic — the controller shape your HTTP tests are exercising.
459
459
  - The `warlock-conventions` topic — module layout, where `tests/*.test.ts` files live.
460
460
  - The `write-cli-command` topic — `warlock add test` scaffolds both global + worker setup files.
461
+
462
+ ## Worker dictionaries and owned runtime resources
463
+
464
+ The generated `src/test-setup.ts` calls:
465
+
466
+ ```ts
467
+ await setupTest({ importModule: (file) => import(file) });
468
+ ```
469
+
470
+ `setupTest()` reads `src/app/*/utils/locales.json` after configuration in each
471
+ Vitest worker. Server translation helpers therefore see all configured locales
472
+ without manual registration. Invalid dictionaries fail setup. `teardownTest()`
473
+ unregisters those dictionaries; legacy executable locale files still require
474
+ explicit imports in worker setup.
475
+
476
+ Global HTTP fixtures should use `await startHttpTestServer({ port: 0, web: false })`
477
+ for API-only suites and `await stopHttpTestServer()` in teardown. Stop releases
478
+ connectors and the file runtime's loader channel, queued work, watchers, health
479
+ workers, and registered module cleanup hooks, including after partial startup.
480
+ A Node loader registration is process-scoped; its message channel is closed at
481
+ teardown. Module-owned timers/listeners must register their cleanup hooks.
482
+
483
+ A module that owns a long-running handle can export an awaited cleanup:
484
+
485
+ ```ts
486
+ // src/app/messages/main.ts
487
+ import { createServer } from "node:net";
488
+ const listener = createServer();
489
+ listener.listen(0, "127.0.0.1");
490
+ export async function cleanup(): Promise<void> {
491
+ await new Promise<void>((resolve, reject) => {
492
+ listener.close((error) => (error ? reject(error) : resolve()));
493
+ });
494
+ }
495
+ ```
496
+
497
+ HTTP test teardown awaits these cleanup functions and reports failures after
498
+ attempting every module's cleanup. Avoid leaving module-owned handles running
499
+ outside this lifecycle.
500
+
501
+ ## Response contracts from declared schemas
502
+
503
+ `assertResponseContracts` from `@warlock.js/core/tests` sends HTTP requests and
504
+ asserts the serialized JSON body against the handler's `responseSchema` for the
505
+ actual status. It reuses Core's OpenAPI schema translation, including resource
506
+ classes, nested objects, arrays, nullable casts and resource self references.
507
+ It checks required fields and primitive types without coercion; extra fields
508
+ are allowed. URL/date format annotations are not checked beyond their declared
509
+ JSON shape. Builders, computed resolvers and resources without a static shape
510
+ fail with a diagnostic rather than silently passing.
511
+
512
+ ```ts
513
+ import { it } from "vitest";
514
+ import { router } from "@warlock.js/core";
515
+ import { assertResponseContracts } from "@warlock.js/core/tests";
516
+
517
+ it("matches product response declarations", async () => {
518
+ await assertResponseContracts({
519
+ routes: router.list().filter((route) => route.path === "/products"),
520
+ cases: () => [{ path: "/products?page=1&limit=10", status: 200 }],
521
+ requireAllStatuses: true,
522
+ });
523
+ });
524
+ ```
525
+
526
+ With the shared test server, retain `startHttpTestServer({ port: 0 })` in global
527
+ setup and `setupTest` in the worker. The routes and their handler schemas must
528
+ also be loaded in the worker running this assertion (import their routes module
529
+ there); global setup runs in a different process. Omit `routes` to discover all
530
+ registered routes with `responseSchema` in that worker. Empty discovery fails.
531
+ Explicit routes lacking a schema also fail.
532
+
533
+ Static GET routes default to one request without headers. Parameterized routes
534
+ and non-GET methods require explicit `cases(route)` fixtures; an empty case
535
+ list fails. Each case can supply a concrete `path`, `init` (headers, serialized
536
+ body, etc.) and expected `status`. Supply fixtures with valid IDs, seeded data,
537
+ authentication and invalid inputs to exercise the desired outcomes. An `all` route also needs a concrete fixture `method`. Redirects
538
+ are not followed. POST/PUT/PATCH/DELETE fixtures execute real side effects;
539
+ use isolated test data and clean it up.
540
+
541
+ Undeclared response statuses fail. Only statuses actually returned are checked
542
+ by default; `requireAllStatuses: true` additionally fails if any declared status
543
+ has no exercised fixture. HEAD, 204 and 304 responses must have an empty body; declare it with
544
+ `{ body: {} }`. Other statuses must carry matching JSON.
545
+
546
+ For an in-process server, pass `client: app` from `createTestHttpServer()` and
547
+ close it in teardown. For signed-in requests, pass `client: await actingAs(user)`
548
+ and dispose that client afterward. These clients use the same assertion and
549
+ run the real HTTP middleware and serialization paths.
550
+
551
+ Cleanup hooks that own asynchronous handles should return a promise for completed closure. For MessagePorts, subscribe to `close` before calling `close()` and await the event before checking reference state.
@@ -13,9 +13,9 @@ For unit tests, you import the thing under test and call it directly. No HTTP, n
13
13
 
14
14
  ⛔ **If your project was generated before 4.14.0, fix BOTH the comment and the call.** The comment is false, **and** the generated `setupTest({ connectors: true })` is now an _explicit_ value that overrides your `src/config/tests.ts`. **Bare `setupTest()` is the correct call.**
15
15
 
16
- **The lifetime is FILE-SCOPED, on purpose.** Your setup file bootstraps the framework and its `afterAll(teardownTest)` closes it, once per test file. **One owner, one pairing — correct under every pool, every isolation setting, and watch mode.**
16
+ **The default lifetime is FILE-SCOPED.** Your setup file bootstraps the framework and its `afterAll(teardownTest)` closes it, once per test file. **One owner, one pairing — correct under every pool, every isolation setting, and watch mode.**
17
17
 
18
- ⚠ **A worker-scoped lifetime is possible and is deliberately not shipped yet.** Lifecycle state now lives in the worker runtime, so leaving the framework running would let every file in a worker share one bootstrap. Two things block claiming it: under `pool: "threads"` we cannot observe whether Node reclaims a torn-down thread's sockets and pools, and **in watch mode Vitest reuses workers between reruns, so there is no recycle and no cleanup owner.** It gets taken when the real cost is measured and the integration is chosen, not inherited. See _Lifecycle and repeated calls_ below.
18
+ Worker lifetime is available as an opt-in in 5.35.0. See the runtime lifetime section below for its runner, pool and isolation requirements.
19
19
 
20
20
  ⚠ **Changed in 4.13.0 — the import is a subpath now.** `setupTest` used to be re-exported from the package root; it is not any more, because that put the test helpers into every application's production module graph. `import { setupTest } from "@warlock.js/core"` now fails with _"has no exported member"_ — **add `/tests` to the specifier and nothing else changes.**
21
21
 
@@ -417,7 +417,7 @@ Node process, preserve the source package's module mode. ESM fixtures need a
417
417
  CommonJS and named exports fail before the behavior under test runs. Assert the
418
418
  child process exit status with its captured stderr before checking stdout.
419
419
 
420
- - **`setupTest` runs per test file, not per worker** — every version of this skill through 4.13.0 said otherwise. **You pay one framework bootstrap per test file**, which is what 4.13.0 already cost; the difference is that it is now a chosen lifetime rather than a side effect of a module flag resetting.
420
+ - **By default `setupTest` boots per test file** — every version of this skill through 4.13.0 said otherwise. **You pay one framework bootstrap per test file**, which is what 4.13.0 already cost; the difference is that it is now a chosen lifetime rather than a side effect of a module flag resetting.
421
421
  - ⛔ **Never delete the `afterAll(teardownTest)` from your setup file.** Without it nothing closes what `setupTest` opened, and the connectors outlive the file that started them.
422
422
  - ⛔ **You can't swap the connector set by calling `setupTest` again — it rejects now.** Through 4.13.0 the second call was silently ignored. Tear down first, or don't call it.
423
423
  - **These connections are separate from the HTTP test server's.** A row inserted by a service-level test is on this connection; the HTTP test server has its own. They only see each other if both point at the same physical DB **and** the inserting test has committed.
@@ -444,3 +444,161 @@ setupTest({ importModule: (file) => import(file) });
444
444
  ```
445
445
 
446
446
  The generated `src/test-setup.ts` already does this. Without it, `setupTest()` under Vitest warns once.
447
+
448
+ ## Runtime lifetime, unit projects and setup profiling
449
+
450
+ `warlock add test` (also used by `create-warlock --features=test`) generates two
451
+ Vitest projects. `unit` discovers `src/**/*.spec.ts` outside `tests/` directories
452
+ and has no Warlock setup or HTTP global setup. `integration` discovers
453
+ `src/**/*.test.ts`, specs inside `src/**/tests/`, and `tests/**/*.{test,spec}.ts`.
454
+ It uses the framework setup, HTTP global setup and a generated `src/test-runner.ts`.
455
+ Keep pure service specs beside their source and mock repositories at the boundary.
456
+ Existing config files are preserved; copy the projects layout into them manually.
457
+ Vitest 5 or newer is required for the generated runner (Vitest 5 removed `vitest/runners`; the runner extends `TestRunner` from `vitest`).
458
+
459
+ For a unit spec that needs configuration values, use:
460
+
461
+ ```ts
462
+ import { afterAll, expect } from "vitest";
463
+ import { setupTest, teardownTest } from "@warlock.js/core/tests";
464
+
465
+ await setupTest({
466
+ mode: "unit",
467
+ importModule: (file) => import(file),
468
+ file: expect.getState().testPath,
469
+ });
470
+ afterAll(teardownTest);
471
+ ```
472
+
473
+ Unit mode loads `warlock.config.ts` and configuration values once per module graph.
474
+ It skips bootstrap/env/prestart hooks, connectors, config special handlers,
475
+ `filesOrchestrator.init()`, the native ESM loader, and fake installation. Keep
476
+ configuration imports free of side effects; arbitrary code imported by a config
477
+ file can still perform I/O. A plain unit spec usually needs no `setupTest()` at all.
478
+ `connectors: false` still bootstraps; it is not the cheap unit mode. Use separate
479
+ projects for unit and integration runtimes.
480
+
481
+ ### Opt into one runtime per fork
482
+
483
+ The default remains `tests.lifetime: "file"`. To reuse a runtime, change the
484
+ integration project's `isolate` to `false`, keep `pool: "forks"` and
485
+ `runner: "./src/test-runner.ts"`, then configure:
486
+
487
+ ```ts title="src/config/tests.ts"
488
+ import { resetWorkerDatabase } from "../test-database";
489
+
490
+ export default {
491
+ lifetime: "worker",
492
+ // Application-owned callback: truncate only this worker's test database.
493
+ // Called before every file's fixtures, including the first file.
494
+ resetBetweenFiles: async () => {
495
+ await resetWorkerDatabase();
496
+ },
497
+ profileSetup: true,
498
+ };
499
+ ```
500
+
501
+ Run with `npx vitest run --project integration`. Worker lifetime refuses watch,
502
+ threads, isolated module graphs, or a missing runner with a message explaining
503
+ the required settings. Keep `afterAll(teardownTest)` in the setup file: in worker
504
+ mode it marks the end of the file; Vitest's awaited worker cleanup closes the
505
+ runtime once. The runner uses `TestRunner.onCleanupWorkerContext`, verified
506
+ in Vitest 5.0.3's `TestRunner` declaration, runner implementation and worker cleanup source.
507
+ For an existing project, generate this runner without overwriting your config:
508
+
509
+ ```ts title="src/test-runner.ts"
510
+ import { configureTestWorker } from "@warlock.js/core/tests";
511
+ import { TestRunner } from "vitest";
512
+
513
+ export default class WarlockTestRunner extends TestRunner {
514
+ public constructor(config: ConstructorParameters<typeof TestRunner>[0]) {
515
+ super(config);
516
+ configureTestWorker(
517
+ { pool: this.pool, watch: config.watch, isolate: config.isolate },
518
+ (cleanup) => this.onCleanupWorkerContext?.(cleanup),
519
+ );
520
+ }
521
+ }
522
+ ```
523
+
524
+ The first file bootstraps; subsequent files reuse connectors and config. Between
525
+ files, selected fakes are restored and reinstalled, resetting mail/queue records,
526
+ cache, secrets, clock, IDs and a private storage directory. Worker storage is
527
+ separate from the global HTTP server's storage: use an in-process test HTTP server
528
+ when assertions need to see request-side fake state. The hook runs after fake
529
+ reset and before fixtures. It must finish before returning. Within a file,
530
+ repeated `setupTest()` calls remain idempotent; use `beforeEach`/`afterEach` for
531
+ per-test cleanup.
532
+
533
+ This trades module isolation for startup cost: application singletons, config
534
+ mutations, mocks and module side effects can survive between files. Reset your
535
+ own state in `resetBetweenFiles`, restore mocks, and keep connector selection
536
+ consistent. Give each worker a distinct database/schema using
537
+ `process.env.VITEST_POOL_ID` (1-based since Vitest 5) when preparing test connections. Never drop a shared
538
+ database from this hook while other workers are using it. The framework does not
539
+ create a memory replica set or a cross-worker lock; those belong to the application's
540
+ global setup. A shared HTTP server and a single replica set can contend on I/O,
541
+ locks and shared fixtures even though workers run concurrently. Scope rows and
542
+ HTTP fixtures per worker, or run tests that require global mutation separately
543
+ with `maxWorkers: 1`. No test-server teardown ownership changes are needed here.
544
+
545
+ `getTestRuntimeStats()` from `@warlock.js/core/tests` returns process-local
546
+ `{ bootstraps, files }`. For example, after setup in worker mode:
547
+
548
+ ```ts
549
+ import { expect, it } from "vitest";
550
+ import { getTestRuntimeStats } from "@warlock.js/core/tests";
551
+
552
+ it("reuses this worker's runtime", () => {
553
+ expect(getTestRuntimeStats().bootstraps).toBe(1);
554
+ });
555
+ ```
556
+
557
+ Across one integration project, a successful run with no worker recycling needs
558
+ at most `maxWorkers` bootstraps. Multiple projects, worker crashes and memory-limit
559
+ recycling can create additional processes; count distinct PIDs in profiles.
560
+
561
+ ### Measure and size CI workers
562
+
563
+ Set `tests.profileSetup: true` or run
564
+ `WARLOCK_TEST_PROFILE=1 npx vitest run --project integration`. Pass
565
+ `file: expect.getState().testPath` to `setupTest()` for file labels (the generated
566
+ setup does this). Each file emits one line with milliseconds for environment, config,
567
+ bootstrap, orchestrator, app config, connectors, fakes, reset and teardown as
568
+ applicable. Reused files report reset instead of startup steps. A summary per
569
+ worker reports file/bootstrap counts, summed step timings and final shutdown.
570
+ The generated config also installs `TestSetupReporter` from `@warlock.js/core/vite`
571
+ (`reporters: ["default", new TestSetupReporter()]`), which aggregates file counts,
572
+ bootstrap counts and timings across PIDs into a run summary. Summed timings are
573
+ not wall time. Worker shutdown is included when its summary reaches the reporter
574
+ before the run-end callback; individual worker summaries always report it.
575
+
576
+ The generated config sets `maxWorkers: recommendedTestWorkers()` from
577
+ `@warlock.js/core/vite`. It takes the smaller of CPU affinity and the tightest
578
+ visible cgroup v2 quota, rounding fractional CPUs down with a minimum of one.
579
+ For example `cpu.max = 1200000 100000` recommends 12 workers on a 48-CPU host.
580
+ The format follows the [kernel cgroup v2 CPU interface](https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html#cpu-interface-files).
581
+ It reads `/proc/self/cgroup` and visible ancestors under `/sys/fs/cgroup`; hosts
582
+ without readable v2 quotas use affinity. Set `maxWorkers` explicitly on cgroup v1,
583
+ custom cgroup mounts, or when database/memory limits require fewer workers.
584
+
585
+ ### Release gate
586
+
587
+ ```bash
588
+ npx warlock test --changed --since "$LAST_RELEASE_REF"
589
+ npx vitest run --project integration
590
+ # Nightly (all projects):
591
+ npx vitest run
592
+ ```
593
+
594
+ The first command includes affected tests, using the existing changed-test CLI;
595
+ the second always covers integration wiring. Keep the full suite nightly and
596
+ compare cold and reused setup profiles on the same revision, worker count and
597
+ database fixture before claiming a speedup.
598
+
599
+ For a manual fake harness outside `setupTest`, the same private storage behavior
600
+ is available with `await installTestFakes(true, { isolatedStorage: true })`, paired
601
+ with `await restoreTestFakes()`. Both are exported from `@warlock.js/core/tests`.
602
+ `await storageFake.install({ isolated: true })` / `await storageFake.restore()`
603
+ provides it for a storage-only harness. Existing installs share the HTTP root by
604
+ default; the isolated option never replaces or deletes that shared root.
@@ -534,3 +534,112 @@ editor settings and nonstandard scripts can also require review.
534
534
  existing Oxc configs retain their categories. A real lockfile beside a workspace
535
535
  marker determines the installer: `package-lock.json` plus `pnpm-workspace.yaml`
536
536
  without `pnpm-lock.yaml` now produces `npm install`.
537
+
538
+ ## 5.34 to 5.35 resource hidden input
539
+
540
+ Model resources now receive hidden input columns (subject to `resourceColumns`)
541
+ to support derivations such as `has_secret: Boolean(data.secret)`. Model
542
+ serialization removes `hidden` top-level keys from the final resource result.
543
+ Review resources that copy input under renamed or nested keys and return explicit
544
+ public projections. Direct resource construction does not invoke model filtering.
545
+
546
+ ## 5.34 to 5.35 document hydration
547
+
548
+ Initial Web hydration now includes `root.tsx`, matching the document ancestry
549
+ used by SSR so `useId()` and generated Mongez form IDs agree beneath layouts.
550
+ Root components and providers execute in both realms. Move server-only work into
551
+ `root.setup.ts` loaders and pass browser-safe results as `data`; retain exactly
552
+ one `#vessel` around `{children}`. Client navigation still swaps layout/page
553
+ children while preserving the document root. Remove temporary explicit form IDs
554
+ only when the application does not otherwise depend on those names.
555
+
556
+ ## 5.35 to 5.36 per-page API routes
557
+
558
+ A page document now carries only the named API routes its client code can reach. Before 5.36 every page carried every named route, so a browser call to a name the build cannot see worked on every page by accident. Check each `apiHref()` and `useSubmitForm({ route })` call:
559
+
560
+ - A name written as a string literal, a constant or a template with a static prefix is found automatically.
561
+ - A name computed at run time, or taken from a module the build cannot follow, now throws on the page that calls it. Declare the names in that page's setup file, for example `export const config = { apiRoutes: ["orders.*"] };`, a layout or root `config` covers every page that renders it. Or call the endpoint with an explicit `path` and `method`.
562
+ - `npx warlock check` reports the call sites that need a declaration as `WL9019`.
563
+
564
+ Routes registered with `clientVisible: false` are never embedded, and a browser call to one is an error (`WL9020`). Keep endpoints the browser must not reach on that option. Authorization is unchanged: the server still decides each request.
565
+
566
+ To measure a page's inline payload, set `build.budgets.payload` (default `64kb`). An over-budget route logs one line per process and never fails a request.
567
+
568
+ ## 5.35 to 5.36 translation payloads
569
+
570
+ A page's hydration payload now carries only the translations its client code
571
+ can reach, in route-dictionary (`locales.json`) and global-registry (`extend()`)
572
+ apps alike. The groups every page uses (default `app` and `common`) arrive once
573
+ per locale in a hashed, cached file that the page preloads. Nothing in a page's
574
+ render changes: server output is the full dictionary, as before.
575
+
576
+ Run `warlock build`, then `warlock check`. A `WL9104` warning marks a page that
577
+ renders a translation key computed at runtime. Declare the keys it renders in
578
+ the page's setup file, for example
579
+ `export const config = { translations: ["statuses.*", "members.title"] }`, so the
580
+ page ships only what it uses.
581
+
582
+ In development, a warning names a key the page reads but its payload lacks, and
583
+ resolves it from the full table so the page keeps working. In production the
584
+ key renders as its own text and the miss is reported once through
585
+ `onClientError` with `kind: "translations"`. Declare the key, or set
586
+ `build: { translations: { prune: false } }` in the web connector options while migrating to
587
+ restore the previous per-page payload.
588
+
589
+ Global-registry apps are covered too. Their shared table is served at
590
+ `/__warlock/translations/<locale>.<hash>.json`, which is cached immutable in
591
+ production. A direct `trans()` call from `@mongez/localization` is not checked
592
+ for a pruning miss; the calls through `useTrans()` are.
593
+
594
+ ## 5.35 to 5.36 Vite 8 and Vitest 5
595
+
596
+ `@warlock.js/web` requires Vite 8 (`^8.0.0`) and `@vitejs/plugin-react` 6, and the
597
+ test kit requires Vitest 5. Vite 8 bundles with Rolldown and transforms
598
+ TypeScript with Oxc. Vite 7 is not supported; plugin-react 6 needs Vite 8, so the
599
+ family moves together. Vite 8 needs Node.js 20.19 or 22.12 and newer, Vitest 5
600
+ needs 22.12 or newer; Warlock already requires 22.15.
601
+
602
+ ```bash
603
+ npx warlock upgrade --from 5.35 --to 5.36.0 --dry-run
604
+ npx warlock upgrade --from 5.35 --to 5.36.0
605
+ npm install
606
+ npx warlock check
607
+ ```
608
+
609
+ The `vite-8-vitest-5` codemod:
610
+
611
+ - raises `vite` (8.3.3), `vitest` and `@vitest/*` (5.0.3) and `@vitejs/plugin-react`
612
+ (6.1.2) in `dependencies`, `devDependencies`, `overrides` and `resolutions`,
613
+ keeping `^`, `~` or exact style;
614
+ - rewrites a generated `src/test-runner.ts` from `VitestTestRunner` in
615
+ `vitest/runners` to `TestRunner` from `vitest`, and calls
616
+ `this.onCleanupWorkerContext?.(cleanup)` (the hook is absent in vm pools);
617
+ - lists, without editing, imports of the removed `vitest/coverage`,
618
+ `vitest/reporters`, `vitest/environments`, `vitest/snapshot`, `vitest/suite`,
619
+ `vitest/mocker` and `vitest/internal/module-runner` entry points, other names
620
+ imported from `vitest/runners`, a top-level `esbuild` option in a Vite or
621
+ Vitest config, and object-form `output.manualChunks`.
622
+
623
+ Do these by hand when the codemod lists them:
624
+
625
+ - Move a config's `esbuild: { jsx: "automatic" }` to
626
+ `oxc: { jsx: { runtime: "automatic" } }`. `esbuild.target` is not converted, and
627
+ Oxc does not lower native decorators: keep `lowerStage3Decorators()` from
628
+ `@warlock.js/core/vite` first in `plugins` for decorated modules, as in the
629
+ generated test config.
630
+ - Replace object `manualChunks` with
631
+ `build.rolldownOptions.output.codeSplitting.groups`; `build.rollupOptions` still
632
+ works but is deprecated in favour of `build.rolldownOptions`.
633
+ - A custom bundler `external` list that names Node builtins is still refused by the
634
+ client build.
635
+
636
+ Vitest 5 changes defaults that can alter a green suite: `clearMocks` is true,
637
+ `json` and `junit` reporters write to `.vitest/` instead of stdout, `-t` joins a
638
+ test's names with `>`, `VITEST_POOL_ID` and `VITEST_WORKER_ID` start at 1, nested
639
+ `vi.mock`/`vi.hoisted` calls throw, and an unawaited `resolves` or `rejects`
640
+ assertion fails the test. Vitest also no longer searches parent directories for a
641
+ config file; run it from the package that owns the config. Official guides:
642
+ https://vite.dev/guide/migration and https://vitest.dev/guide/migration.
643
+
644
+ A project that uses `@warlock.js/ai`'s custom matchers needs Vitest 5 for their
645
+ TypeScript types.
@@ -336,3 +336,28 @@ GET /products/42?locale=ar
336
336
  This produces `account.title` and `account.actions.save`. Keep every leaf complete for the configured locale list. The page pipeline selects an immutable snapshot for its route source and locale. `useTrans()` and `t()` use that snapshot; a missing scoped key returns the key itself. With no route-locales manifest, the existing global `@mongez/localization` registry remains the legacy fallback path.
337
337
 
338
338
  When a route-locales snapshot applies, `t()` (through the request binding) and `request.transFrom(locale, key, placeholders?)` resolve through it; without one, they retain the existing global-registry behavior. The underlying `transFromKeywords(locale, keywords, key, placeholders?, converter?)` is supplied by `@mongez/localization` 3.5.0. Error pages rebind to their own snapshot, while framework fallback uses the app/root snapshot. Development HMR add/edit/delete publishes a new snapshot, and the snapshot revision keeps page-cache entries from reusing old JSON. Generated `.warlock` typings contain the flattened route keys; do not maintain a second handwritten key union.
339
+
340
+ ## Worker dictionaries and owned runtime resources
341
+
342
+ The generated `src/test-setup.ts` calls:
343
+
344
+ ```ts
345
+ await setupTest({ importModule: (file) => import(file) });
346
+ ```
347
+
348
+ `setupTest()` reads `src/app/*/utils/locales.json` after configuration in each
349
+ Vitest worker. Server translation helpers therefore see all configured locales
350
+ without manual registration. Invalid dictionaries fail setup. `teardownTest()`
351
+ unregisters those dictionaries; legacy executable locale files still require
352
+ explicit imports in worker setup.
353
+
354
+ Global HTTP fixtures should use `await startHttpTestServer({ port: 0, web: false })`
355
+ for API-only suites and `await stopHttpTestServer()` in teardown. Stop releases
356
+ connectors and the file runtime's loader channel, queued work, watchers, health
357
+ workers, and registered module cleanup hooks, including after partial startup.
358
+ A Node loader registration is process-scoped; its message channel is closed at
359
+ teardown. Module-owned timers/listeners must register their cleanup hooks.
360
+
361
+ ## Translation keys in pages (5.36)
362
+
363
+ `warlock check` reports `WL9104` when a page renders a translation key computed at runtime (`trans(key)` with no static prefix, or a translator passed on as a value). Such a page ships every translation group. Declare its keys in the page's setup file, `export const config = { translations: ["statuses.*", "members.title"] }`, to ship only what it uses. The `web` skill `localize-pages` covers the browser side.
@@ -75,7 +75,7 @@ npx warlock typecheck --changed # changed files + the files importing them
75
75
  npx warlock typecheck --json --project tsconfig.app.json
76
76
  ```
77
77
 
78
- `--changed` checks the changed files and their direct importers only; a full run (cheap after the first, it is incremental) catches anything further away.
78
+ `--changed` checks diagnostics for changed files and their transitive importers. The native compiler still performs an incremental program compilation.
79
79
 
80
80
  ## `warlock explain`
81
81
 
@@ -135,3 +135,77 @@ const types = runTypecheck({ files: ["src/app/posts/services/publish.service.ts"
135
135
  Migrate lint and format tooling with
136
136
  `npx warlock upgrade --from 5.32 --to 5.33.0 --dry-run`, then remove `--dry-run`
137
137
  and work through the manual-review list. Use `--lint=oxc` for new apps.
138
+
139
+ ## Web and response contract conventions
140
+
141
+ The following codes default to **warnings**, so upgrading does not fail existing projects.
142
+ They parse real TypeScript/JSX syntax with the same oxc-parser adapter as the existing conventions.
143
+
144
+ | Code | Convention | Remedy |
145
+ | -------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
146
+ | `WL9013` | A component file exceeds 150 lines | Split components and extract hooks; configure `componentLineLimit`. |
147
+ | `WL9014` | More than one component in a file | Move each component into its own TSX file. |
148
+ | `WL9015` | State/effects or event-handler functions inside a component | Move `useState`, `useReducer`, `useEffect`, `useLayoutEffect` and handlers into `use-*.ts` hooks. |
149
+ | `WL9016` | Native select, checkbox, radio, date, time or datetime-local input | Use the design-system control. Native controls are allowed in `src/web/design-system/**`. |
150
+ | `WL9017` | DOM input/textarea/select native validation | Use shared schema validation instead of `required`, `pattern`, `min`, `max`, `minLength`, `maxLength` or input `type="email"`. Custom component props are allowed. |
151
+ | `WL9018` | Exported `RequestHandler` in `src/app/**/controllers` lacks `responseSchema` | Assign `handler.responseSchema` to document each response status. |
152
+ | `WL9019` | Browser route call (`apiHref`, `useSubmitForm({ route })`) takes a run-time name | Pass a string literal or a template with a static prefix, or declare the names with `config.apiRoutes` in a page setup file. |
153
+ | `WL9020` | Browser code names a route registered with `clientVisible: false` (error) | Call the endpoint from the server, or remove `clientVisible: false` from the route. |
154
+ | `WL9104` | Translation key computed at runtime in `src/web` (the page ships every group) | Declare the keys with `config.translations` in the page's setup file, for example `["statuses.*"]`. |
155
+
156
+ Web rules apply to `src/web/**/*.tsx`. Components are top-level JSX-returning functions
157
+ with a capitalized name (or an anonymous default export). Hook files and ordinary helpers
158
+ are excluded. Handler checks recognize inline JSX callbacks, `handle*`/`on*` functions,
159
+ and local function values referenced by event props. They do not execute code or infer
160
+ runtime spreads, factory contracts or values imported from other files.
161
+
162
+ Create `.warlock-check.json` in the project root:
163
+
164
+ ```json
165
+ {
166
+ "componentLineLimit": 180,
167
+ "rules": {
168
+ "WL9013": "warning",
169
+ "WL9015": "error",
170
+ "WL9016": "off"
171
+ }
172
+ }
173
+ ```
174
+
175
+ `rules` accepts `"off"`, `"warning"`, or `"error"` for convention codes, including
176
+ existing codes. Per-line `// warlock-check-ignore WL9015 -- reason` works as before.
177
+ Use `npx warlock explain WL9015` for the registry explanation.
178
+
179
+ ```bash
180
+ npx warlock check --changed --fix --json
181
+ ```
182
+
183
+ `--changed` selects tracked changes against HEAD plus untracked source files and their
184
+ transitive import dependents (including re-exports, type imports and related tests).
185
+ Resolution uses the project's compiler configuration, with relative/app imports as a
186
+ fallback for files outside that configuration. Lint and conventions receive that selection;
187
+ Vitest uses related mode. TypeScript still compiles incrementally as a program, then filters
188
+ diagnostics to the selection and its importers; this is not a partial compilation.
189
+
190
+ `--fix` applies safe convention edits before the pipeline. Currently it removes `.js`
191
+ suffixes from relative imports (`WL9002`). Ignored/disabled findings are not fixed.
192
+ Component extraction, control replacement and response schemas require human decisions.
193
+ Problems that have a safe edit include optional `fix: { start, end, text }` in JSON;
194
+ `start` and `end` are zero-based source string offsets with an exclusive end.
195
+ The report otherwise keeps schema version 1 and its existing shape. Applied fixes disappear
196
+ from the final report; unresolved findings remain. Review the resulting source diff.
197
+
198
+ Programmatic usage: `await runCheck({ changed: true, fix: true })` from
199
+ `@warlock.js/core/tooling`.
200
+
201
+ ## Explain a source file
202
+
203
+ `npx warlock explain src/app/posts/services/list.service.ts --json` reports the file's module, convention role, exports, imports, importers and reaching routes/pages. It boots the app for route/page registrations; error-code explanations remain static. See the `warlock-inspect` topic for graph semantics and the boot-free `npx warlock inspect --section files --json` command.
204
+
205
+ Starter controllers declare a response contract for each returned status. For example:
206
+
207
+ ```ts
208
+ contactController.responseSchema = { 200: { body: { message: "string" } } };
209
+ ```
210
+
211
+ Use `npx warlock inspect --section files --json` for static inspection without booting the app; `npx warlock explain src/app/contact/controllers/contact.controller.ts` explains its entry points.
@@ -13,9 +13,9 @@ npx warlock inspect --json > app.json # the full report
13
13
  npx warlock inspect --json --section routes,models # only some sections
14
14
  ```
15
15
 
16
- It boots like `warlock doctor`: connectors boot (routes register) but none starts — no database, cache or socket connection, no port. With `--json`, stdout is only the JSON.
16
+ Except for `--section files` (static source inspection without app boot), it boots like `warlock doctor`: connectors boot (routes register) but none starts — no database, cache or socket connection, no port. With `--json`, stdout is only the JSON.
17
17
 
18
- Sections: `routes`, `models`, `resources`, `config`, `events`, `pages`, `jobs`, `schedules`, `packages`. An unknown section fails before the app boots.
18
+ Sections: `routes`, `models`, `resources`, `config`, `events`, `pages`, `jobs`, `schedules`, `packages`, `files`. An unknown section fails before the app boots.
19
19
 
20
20
  ## Answer questions with jq
21
21
 
@@ -132,3 +132,46 @@ const valid = new Ajv2020({ strict: false }).validate(INSPECT_REPORT_JSON_SCHEMA
132
132
  - The `warlock-agents-md` topic — the app summary `warlock dev` keeps in AGENTS.md.
133
133
  - The `warlock-routes` topic — the human route table.
134
134
  - The `generate-openapi` topic — the same route schemas as an OpenAPI document.
135
+
136
+ ## Session middleware in groups
137
+
138
+ `sessionMiddleware({ userTypes: "user" })` publishes the same `warlock.auth`
139
+ descriptor as `authMiddleware()`. Group middleware is inherited by nested
140
+ routes, so `inspect` and the generated `agents-md` summary recognize these guards.
141
+ `{ optional: true }` reports `auth.required: false`, with sources and user types
142
+ retained. Such routes remain in the unguarded-write list. Legacy descriptors
143
+ without `required` still mean required authentication.
144
+
145
+ ```ts
146
+ import { router } from "@warlock.js/core";
147
+ import { sessionMiddleware } from "@warlock.js/auth";
148
+
149
+ router.group({ middleware: [sessionMiddleware({ userTypes: "user" })] }, () => {
150
+ router.post("/settings", async ({ response }) => response.success({ saved: true }));
151
+ });
152
+ ```
153
+
154
+ ## Source files and reachability
155
+
156
+ ```bash
157
+ npx warlock inspect --section files --json
158
+ npx warlock explain src/app/posts/services/list.service.ts
159
+ npx warlock explain src/web/components/Post.tsx --json
160
+ npx warlock explain WL3001 --json
161
+ ```
162
+
163
+ The `files` section reads every file under `src/`, without importing app modules or booting connectors. Each entry has `file`, `module`, `role`, `exports`, `imports` and `importedBy`. Paths are relative to the project root. Modules use `src/app/<module>` or the containing top-level `src` directory. Roles follow file names and directories: controller, service, repository, model, migration, schema, resource, routes, page, setup, layout, root, hook, component, util, type, config and spec; unmatched files use `source`. Non-code assets have empty exports and imports.
164
+
165
+ Each code file is parsed once with OXC per inspection. Export names include types, aliases, destructured bindings, `default` and `*` for star re-exports; star exports are recorded rather than expanded. Imports include type imports, side effects, re-exports and literal dynamic imports. Each import is `{ specifier, file }`: `file` is the resolved local path, or `null` for external/unresolved imports. Resolution follows inherited tsconfig path aliases, fallback targets, baseUrl, extensionless files and directory indexes within `src/`. Computed dynamic imports and CommonJS `require()` calls are not graph edges. Parse/config failures appear in `warnings`.
166
+
167
+ File explanations accept a project-relative or absolute path. They boot the app to read the existing routes/pages sections, then walk the static graph to find entry points reaching the file, including page layouts and loaders. Type-only edges participate; this is source reachability, not proof that runtime execution calls a function. Every route declared in a reaching route module is listed. Boot warnings remain in the JSON explanation and human output, so missing registrations are visible. `warlock explain <WLxxxx>` keeps its static error-code behavior.
168
+
169
+ ```ts
170
+ import { inspectApp, explainFile } from "@warlock.js/core/tooling";
171
+
172
+ const report = await inspectApp({ sections: ["files"] });
173
+ const source = explainFile("src/app/posts/services/list.service.ts", report);
174
+ console.log(source.role, source.exports, source.importedBy);
175
+ ```
176
+
177
+ For route/page reachability in code, call `inspectApp({ sections: ["files", "routes", "pages"] })` in an already booted app. `inspect_files` exposes the section through MCP with a case-insensitive `filter` on file, module or role.
@@ -118,3 +118,7 @@ await server.close();
118
118
  - The `warlock-check` topic — what `check_run` and `test_run` report.
119
119
  - The `warlock-trace` topic — send a request and read its timeline.
120
120
  - The `expose-as-mcp-server` topic of @warlock.js/ai-tools — the transport underneath.
121
+
122
+ ## Source file graph
123
+
124
+ Call `inspect_files` with `{ "filter": "service" }` for file paths, modules, convention roles, export names, resolved imports and reverse `importedBy` edges. This section scans `src/` without executing source modules; files-only requests skip the lazy app boot. `inspect_app` includes a `files` count. See `npx warlock explain src/app/posts/services/list.service.ts --json` for reaching routes and pages.
@@ -339,3 +339,21 @@ args, labels })`. Consume output and await `handle.exited`. Use handle stops or
339
339
  label filters; never stop processes by command or name. `spawnPty()` lazily
340
340
  loads separately installed optional peer `node-pty`. The `warlock-js-process`
341
341
  skill contains complete working examples.
342
+
343
+ ## Add the shared design system
344
+
345
+ ```bash
346
+ npx warlock add tailwind
347
+ npx warlock add design-system
348
+ ```
349
+
350
+ `design-system` refuses before dependency installation when Tailwind is absent
351
+ from `package.json` or `src/web/app.css`. It adds pinned shadcn primitives and
352
+ Mongez controls to `src/web/design-system`, preserving existing source files.
353
+ Use `--no-install` to record dependencies only, or `--dry-run --json` to preview
354
+ files and dependencies without writing. For example, a generated control is
355
+ `import TextInput from "web/design-system/components/text-input";` with
356
+ `<TextInput id="profile-name" name="name" label="Name" />`.
357
+ See Web's `build-accessible-controls` topic for the complete control contract.
358
+
359
+ Built-in `npx warlock add notifications` preloads `config: ["app"]` so generated locale stubs respect `app.localeCodes`, without bootstrap hooks or connectors.
@@ -1,14 +0,0 @@
1
- import { ProductionBuilder } from "./production-builder.mjs";
2
-
3
- //#region ../../packages/core/src/production/build-app-production.ts
4
- /**
5
- * Build the application for production
6
- * Options are loaded from warlock.config.ts
7
- */
8
- async function buildAppProduction() {
9
- await new ProductionBuilder().build();
10
- }
11
-
12
- //#endregion
13
- export { buildAppProduction };
14
- //# sourceMappingURL=build-app-production.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"build-app-production.mjs","names":[],"sources":["../../../../../../../../packages/core/src/production/build-app-production.ts"],"sourcesContent":["import { ProductionBuilder } from \"./production-builder\";\n\n/**\n * Build the application for production\n * Options are loaded from warlock.config.ts\n */\nexport async function buildAppProduction() {\n const builder = new ProductionBuilder();\n await builder.build();\n}\n"],"mappings":";;;;;;;AAMA,eAAsB,qBAAqB;CAEzC,MAAM,IADc,kBACR,CAAC,CAAC,MAAM;AACtB"}