@warlock.js/core 5.34.2 → 5.35.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 (259) hide show
  1. package/CHANGELOG.md +62 -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 +14 -0
  49. package/esm/dev-server/module-loader.mjs.map +1 -1
  50. package/esm/dev-server/special-file-patterns.mjs +4 -1
  51. package/esm/dev-server/special-file-patterns.mjs.map +1 -1
  52. package/esm/e2e/browser.d.mts.map +1 -1
  53. package/esm/e2e/browser.mjs +3 -1
  54. package/esm/e2e/browser.mjs.map +1 -1
  55. package/esm/e2e/index.d.mts +2 -2
  56. package/esm/e2e/page.d.mts +6 -1
  57. package/esm/e2e/page.d.mts.map +1 -1
  58. package/esm/e2e/page.mjs +36 -1
  59. package/esm/e2e/page.mjs.map +1 -1
  60. package/esm/e2e/types.d.mts +37 -0
  61. package/esm/e2e/types.d.mts.map +1 -1
  62. package/esm/e2e/types.mjs.map +1 -1
  63. package/esm/errors/error-codes.d.mts +48 -0
  64. package/esm/errors/error-codes.d.mts.map +1 -1
  65. package/esm/errors/error-codes.mjs +48 -0
  66. package/esm/errors/error-codes.mjs.map +1 -1
  67. package/esm/errors/index.d.mts +2 -2
  68. package/esm/errors/index.mjs +2 -2
  69. package/esm/generations/add-command.action.mjs +2 -0
  70. package/esm/generations/add-command.action.mjs.map +1 -1
  71. package/esm/generations/design-system-payload.mjs +53 -0
  72. package/esm/generations/design-system-payload.mjs.map +1 -0
  73. package/esm/generations/features/design-system.feature.mjs +38 -0
  74. package/esm/generations/features/design-system.feature.mjs.map +1 -0
  75. package/esm/generations/features/index.mjs +3 -1
  76. package/esm/generations/features/index.mjs.map +1 -1
  77. package/esm/generations/features/notifications.feature.mjs +13 -2
  78. package/esm/generations/features/notifications.feature.mjs.map +1 -1
  79. package/esm/generations/features/test.feature.mjs +48 -13
  80. package/esm/generations/features/test.feature.mjs.map +1 -1
  81. package/esm/generations/stubs.mjs +9 -5
  82. package/esm/generations/stubs.mjs.map +1 -1
  83. package/esm/http/error-mapper.d.mts +1 -1
  84. package/esm/http/error-mapper.mjs +3 -3
  85. package/esm/http/error-mapper.mjs.map +1 -1
  86. package/esm/http/error-response.d.mts +15 -0
  87. package/esm/http/error-response.d.mts.map +1 -0
  88. package/esm/http/error-response.mjs +47 -0
  89. package/esm/http/error-response.mjs.map +1 -0
  90. package/esm/http/errors/errors.d.mts +8 -0
  91. package/esm/http/errors/errors.d.mts.map +1 -1
  92. package/esm/http/errors/errors.mjs +13 -1
  93. package/esm/http/errors/errors.mjs.map +1 -1
  94. package/esm/http/index.d.mts +5 -3
  95. package/esm/http/index.mjs +3 -2
  96. package/esm/http/list-endpoint.d.mts +35 -0
  97. package/esm/http/list-endpoint.d.mts.map +1 -0
  98. package/esm/http/list-endpoint.mjs +28 -0
  99. package/esm/http/list-endpoint.mjs.map +1 -0
  100. package/esm/http/middleware/inject-request-context.d.mts.map +1 -1
  101. package/esm/http/middleware/inject-request-context.mjs +12 -2
  102. package/esm/http/middleware/inject-request-context.mjs.map +1 -1
  103. package/esm/http/response.d.mts +2 -0
  104. package/esm/http/response.d.mts.map +1 -1
  105. package/esm/http/response.mjs +2 -0
  106. package/esm/http/response.mjs.map +1 -1
  107. package/esm/http/server.d.mts.map +1 -1
  108. package/esm/http/server.mjs +2 -0
  109. package/esm/http/server.mjs.map +1 -1
  110. package/esm/http/types.d.mts +7 -0
  111. package/esm/http/types.d.mts.map +1 -1
  112. package/esm/index.d.mts +10 -7
  113. package/esm/index.mjs +3 -2
  114. package/esm/openapi/security.mjs +12 -4
  115. package/esm/openapi/security.mjs.map +1 -1
  116. package/esm/openapi/types.d.mts +2 -0
  117. package/esm/openapi/types.d.mts.map +1 -1
  118. package/esm/production/build-budgets.d.mts +46 -0
  119. package/esm/production/build-budgets.d.mts.map +1 -0
  120. package/esm/production/build-budgets.mjs +109 -0
  121. package/esm/production/build-budgets.mjs.map +1 -0
  122. package/esm/production/production-builder.mjs +13 -1
  123. package/esm/production/production-builder.mjs.map +1 -1
  124. package/esm/production/resolve-build-config.d.mts +1 -1
  125. package/esm/production/resolve-build-config.d.mts.map +1 -1
  126. package/esm/production/resolve-build-config.mjs.map +1 -1
  127. package/esm/restful/restful.d.mts +1 -1
  128. package/esm/tests/fakes/storage-fake.d.mts +3 -1
  129. package/esm/tests/fakes/storage-fake.d.mts.map +1 -1
  130. package/esm/tests/fakes/storage-fake.mjs +3 -3
  131. package/esm/tests/fakes/storage-fake.mjs.map +1 -1
  132. package/esm/tests/fakes/test-fakes.d.mts +3 -1
  133. package/esm/tests/fakes/test-fakes.d.mts.map +1 -1
  134. package/esm/tests/fakes/test-fakes.mjs +3 -2
  135. package/esm/tests/fakes/test-fakes.mjs.map +1 -1
  136. package/esm/tests/index.d.mts +4 -2
  137. package/esm/tests/index.mjs +3 -1
  138. package/esm/tests/load-unit-config.mjs +24 -0
  139. package/esm/tests/load-unit-config.mjs.map +1 -0
  140. package/esm/tests/response-contract.d.mts +32 -0
  141. package/esm/tests/response-contract.d.mts.map +1 -0
  142. package/esm/tests/response-contract.mjs +116 -0
  143. package/esm/tests/response-contract.mjs.map +1 -0
  144. package/esm/tests/start-http-development-server.d.mts.map +1 -1
  145. package/esm/tests/start-http-development-server.mjs +10 -2
  146. package/esm/tests/start-http-development-server.mjs.map +1 -1
  147. package/esm/tests/test-lifecycle-state.mjs.map +1 -1
  148. package/esm/tests/test-locales.mjs +33 -0
  149. package/esm/tests/test-locales.mjs.map +1 -0
  150. package/esm/tests/test-runtime-scope.d.mts +15 -0
  151. package/esm/tests/test-runtime-scope.d.mts.map +1 -0
  152. package/esm/tests/test-runtime-scope.mjs +80 -0
  153. package/esm/tests/test-runtime-scope.mjs.map +1 -0
  154. package/esm/tests/vitest-setup.d.mts +13 -3
  155. package/esm/tests/vitest-setup.d.mts.map +1 -1
  156. package/esm/tests/vitest-setup.mjs +146 -18
  157. package/esm/tests/vitest-setup.mjs.map +1 -1
  158. package/esm/tooling/check/affected-files.d.mts +5 -0
  159. package/esm/tooling/check/affected-files.d.mts.map +1 -0
  160. package/esm/tooling/check/affected-files.mjs +56 -0
  161. package/esm/tooling/check/affected-files.mjs.map +1 -0
  162. package/esm/tooling/check/changed-files.d.mts +2 -0
  163. package/esm/tooling/check/changed-files.d.mts.map +1 -1
  164. package/esm/tooling/check/changed-files.mjs +2 -2
  165. package/esm/tooling/check/changed-files.mjs.map +1 -1
  166. package/esm/tooling/check/conventions-step.d.mts +2 -1
  167. package/esm/tooling/check/conventions-step.d.mts.map +1 -1
  168. package/esm/tooling/check/conventions-step.mjs +48 -6
  169. package/esm/tooling/check/conventions-step.mjs.map +1 -1
  170. package/esm/tooling/check/fixes.d.mts +8 -0
  171. package/esm/tooling/check/fixes.d.mts.map +1 -0
  172. package/esm/tooling/check/fixes.mjs +31 -0
  173. package/esm/tooling/check/fixes.mjs.map +1 -0
  174. package/esm/tooling/check/index.d.mts +12 -0
  175. package/esm/tooling/check/index.mjs +3 -2
  176. package/esm/tooling/check/run-check.d.mts +2 -0
  177. package/esm/tooling/check/run-check.d.mts.map +1 -1
  178. package/esm/tooling/check/run-check.mjs +20 -4
  179. package/esm/tooling/check/run-check.mjs.map +1 -1
  180. package/esm/tooling/check/run-tests.mjs +1 -1
  181. package/esm/tooling/check/source-ast.d.mts +1 -0
  182. package/esm/tooling/check/web-conventions.d.mts +10 -0
  183. package/esm/tooling/check/web-conventions.d.mts.map +1 -0
  184. package/esm/tooling/check/web-conventions.mjs +126 -0
  185. package/esm/tooling/check/web-conventions.mjs.map +1 -0
  186. package/esm/tooling/index.d.mts +7 -2
  187. package/esm/tooling/index.mjs +4 -2
  188. package/esm/tooling/inspect/collect-files.mjs +172 -0
  189. package/esm/tooling/inspect/collect-files.mjs.map +1 -0
  190. package/esm/tooling/inspect/collect-pages.mjs +3 -2
  191. package/esm/tooling/inspect/collect-pages.mjs.map +1 -1
  192. package/esm/tooling/inspect/collect-routes.mjs +1 -1
  193. package/esm/tooling/inspect/collect-routes.mjs.map +1 -1
  194. package/esm/tooling/inspect/explain-file.d.mts +12 -0
  195. package/esm/tooling/inspect/explain-file.d.mts.map +1 -0
  196. package/esm/tooling/inspect/explain-file.mjs +45 -0
  197. package/esm/tooling/inspect/explain-file.mjs.map +1 -0
  198. package/esm/tooling/inspect/index.d.mts +3 -2
  199. package/esm/tooling/inspect/index.mjs +2 -1
  200. package/esm/tooling/inspect/inspect-app.d.mts.map +1 -1
  201. package/esm/tooling/inspect/inspect-app.mjs +9 -3
  202. package/esm/tooling/inspect/inspect-app.mjs.map +1 -1
  203. package/esm/tooling/inspect/inspect-report.schema.mjs +34 -1
  204. package/esm/tooling/inspect/inspect-report.schema.mjs.map +1 -1
  205. package/esm/tooling/inspect/inspect-report.types.d.mts +17 -1
  206. package/esm/tooling/inspect/inspect-report.types.d.mts.map +1 -1
  207. package/esm/tooling/inspect/inspect-report.types.mjs +2 -1
  208. package/esm/tooling/inspect/inspect-report.types.mjs.map +1 -1
  209. package/esm/tooling/mcp/mcp-tools.d.mts.map +1 -1
  210. package/esm/tooling/mcp/mcp-tools.mjs +8 -2
  211. package/esm/tooling/mcp/mcp-tools.mjs.map +1 -1
  212. package/esm/tooling/problem.d.mts +4 -0
  213. package/esm/tooling/problem.d.mts.map +1 -1
  214. package/esm/tooling/problem.mjs +1 -0
  215. package/esm/tooling/problem.mjs.map +1 -1
  216. package/esm/tooling/syntax/ast.d.mts +2 -0
  217. package/esm/tooling/typecheck/run-typecheck.mjs +5 -1
  218. package/esm/tooling/typecheck/run-typecheck.mjs.map +1 -1
  219. package/esm/upgrade/codemods/toolchain-templates.mjs +3 -1
  220. package/esm/upgrade/codemods/toolchain-templates.mjs.map +1 -1
  221. package/esm/vite/index.d.mts +3 -1
  222. package/esm/vite/index.mjs +3 -1
  223. package/esm/vite/test-setup-reporter.d.mts +16 -0
  224. package/esm/vite/test-setup-reporter.d.mts.map +1 -0
  225. package/esm/vite/test-setup-reporter.mjs +55 -0
  226. package/esm/vite/test-setup-reporter.mjs.map +1 -0
  227. package/esm/vite/test-workers.d.mts +5 -0
  228. package/esm/vite/test-workers.d.mts.map +1 -0
  229. package/esm/vite/test-workers.mjs +38 -0
  230. package/esm/vite/test-workers.mjs.map +1 -0
  231. package/esm/warlock-config/index.d.mts +2 -1
  232. package/esm/warlock-config/types.d.mts +3 -0
  233. package/esm/warlock-config/types.d.mts.map +1 -1
  234. package/llms-full.txt +883 -70
  235. package/llms.txt +58 -56
  236. package/package.json +15 -14
  237. package/schemas/inspect-report.schema.json +47 -1
  238. package/skills/build-budgets/SKILL.md +51 -0
  239. package/skills/build-restful/SKILL.md +56 -0
  240. package/skills/configure-app/SKILL.md +34 -0
  241. package/skills/create-module/SKILL.md +9 -0
  242. package/skills/generate-openapi/SKILL.md +1 -1
  243. package/skills/generate-web-ui/SKILL.md +71 -0
  244. package/skills/index.md +3 -3
  245. package/skills/run-app/SKILL.md +1 -1
  246. package/skills/send-mail/SKILL.md +25 -0
  247. package/skills/send-response/SKILL.md +77 -0
  248. package/skills/test-browser/SKILL.md +54 -0
  249. package/skills/test-http/SKILL.md +91 -0
  250. package/skills/test-service/SKILL.md +161 -3
  251. package/skills/upgrade-from-5-29/SKILL.md +22 -0
  252. package/skills/use-localization/SKILL.md +21 -0
  253. package/skills/warlock-check/SKILL.md +72 -1
  254. package/skills/warlock-inspect/SKILL.md +45 -2
  255. package/skills/warlock-mcp/SKILL.md +4 -0
  256. package/skills/warlock-trace/SKILL.md +2 -2
  257. package/skills/write-cli-command/SKILL.md +20 -2
  258. package/esm/production/build-app-production.mjs +0 -14
  259. package/esm/production/build-app-production.mjs.map +0 -1
package/llms-full.txt CHANGED
@@ -7,7 +7,7 @@
7
7
  ## Package orientation
8
8
 
9
9
  ---
10
- description: "The Warlock server framework: HTTP routes, controllers, responses, resources, repositories, CLI, mail, storage, security helpers. Exports `router`, `Request`, `Response`, `RequestHandler`, `useCase`, `RepositoryManager`, `defineResource`, `defineConfig`, `storage`, `sendMail`, `hashPassword`, `command`. Use for: add a route, write a controller, return a JSON response, hide a field from the response, upload a file, send an email, hash a password, add a CLI command, generate an OpenAPI document, type an API response, wire a socket, run or configure the app. Not this package: database models/queries → @warlock.js/cascade; input schemas → @warlock.js/seal; SSR pages → @warlock.js/web."
10
+ description: 'Warlock application runtime: `router`, `Request`, `Response`, `config`, `env`, `sendMail` and `defineConfig`. Use for "add an API route", controllers, middleware, CLI commands, config, uploads, mail, sockets, OpenAPI, testing or upgrades. Not this package → @warlock.js/web for React SSR pages; @warlock.js/cascade for database queries; @warlock.js/seal for standalone schemas; @warlock.js/auth for login.'
11
11
  ---
12
12
 
13
13
  # @warlock.js/core
@@ -26,11 +26,11 @@ When the framework is wrong or missing a capability, follow the `report-framewor
26
26
 
27
27
  ## Topics by area
28
28
 
29
- - **Foundations:** warlock-conventions, create-module, make-endpoint, configure-app, use-app-context, resolve-path, build-url, update-packages, lower-stage3-decorators, upgrade-from-5-29
29
+ - **Foundations:** warlock-conventions, create-module, make-endpoint, configure-app, build-budgets, use-app-context, resolve-path, build-url, update-packages, lower-stage3-decorators, upgrade-from-5-29
30
30
  - **HTTP & routing:** register-route, warlock-routes, create-controller, use-middleware, write-middleware, validate-input, use-request-locals, build-restful, wire-socket
31
31
  - **Responses & resources:** send-response, define-resource, use-model-transformers, use-localization
32
32
  - **Domain layers:** write-use-case, use-repository, write-seeder
33
- - **CLI & running the app:** run-app, write-cli-command, add-connector, health-checks, warlock-doctor, warlock-inspect, warlock-check, generate-openapi
33
+ - **CLI & running the app:** generate-web-ui, run-app, write-cli-command, add-connector, health-checks, warlock-doctor, warlock-inspect, warlock-check, generate-openapi
34
34
  - **Agent tooling:** warlock-mcp, warlock-trace, warlock-agents-md, warlock-board
35
35
  - **Storage, files & mail:** store-file, upload-file, process-image, send-mail
36
36
  - **Security & utilities:** hash-password, encrypt-data, store-secrets, retry-operation, benchmark-code, request-memo, request-tracing
@@ -45,7 +45,7 @@ When the framework is wrong or missing a capability, follow the `report-framewor
45
45
  - Prefer typed model getters over scattered `.get<T>("field")` casts.
46
46
 
47
47
 
48
- ## add-connector `@warlock.js/core/add-connector/SKILL.md`
48
+ ## add-connector `@warlock.js/core/skills/add-connector/SKILL.md`
49
49
 
50
50
  ---
51
51
  name: add-connector
@@ -277,7 +277,7 @@ See the [Registering a connector](#registering-a-connector) section above for th
277
277
  - The `warlock-conventions` topic — module layout, canonical imports.
278
278
 
279
279
 
280
- ## benchmark-code `@warlock.js/core/benchmark-code/SKILL.md`
280
+ ## benchmark-code `@warlock.js/core/skills/benchmark-code/SKILL.md`
281
281
 
282
282
  ---
283
283
  name: benchmark-code
@@ -543,7 +543,62 @@ The `latency` is the _total_ time including retries — useful for the SLO you a
543
543
  - The `configure-app` topic — wiring `src/config/benchmark.ts`.
544
544
 
545
545
 
546
- ## build-restful `@warlock.js/core/build-restful/SKILL.md`
546
+ ## build-budgets `@warlock.js/core/skills/build-budgets/SKILL.md`
547
+
548
+ ---
549
+ name: build-budgets
550
+ description: "Configure build.budgets in warlock.config.ts, inspect per-page gzip JS/CSS and deferred assets, compare the last build, and enforce limits with warlock build --json. Use for client bundle size regressions and CI transfer-size gates. For field metrics use web measure-web-vitals."
551
+ ---
552
+
553
+ # Client build budgets
554
+
555
+ ```ts title="warlock.config.ts"
556
+ import { defineConfig } from "@warlock.js/core";
557
+
558
+ export default defineConfig({
559
+ build: {
560
+ budgets: {
561
+ page: { js: "150kb", css: "50kb" },
562
+ pages: { "/admin/**": { js: "220kb" } },
563
+ mode: "error",
564
+ },
565
+ },
566
+ });
567
+ ```
568
+
569
+ Run `npx warlock build` for the sorted table, or `npx warlock build --json`
570
+ for `{ success, budgets, error? }`. Progress goes to stderr in JSON mode.
571
+ `budgets` contains `version: 1`, `compression: "gzip"`, `pages` and
572
+ `violations`. API-only builds report an empty page list.
573
+
574
+ Page limits cover the hydration entry, page, layouts, root, setup registrations
575
+ and error boundary, plus their transitive static imports. Files are counted once
576
+ per page and gzipped individually. Dynamic imports beyond those required roots
577
+ are reported separately and do not consume the budget. Shared files count in
578
+ each page's first visit. This measures emitted JS/CSS, not HTML, fonts or images.
579
+
580
+ `pages` keys match route paths with `*` (within a segment), `**` (across
581
+ segments), or `?` (one non-slash character). Defaults apply first; matching rules
582
+ merge in insertion order and the last supplied field wins. Unsupported glob
583
+ syntax fails with an actionable error. Units are case-insensitive `b`, `kb` and
584
+ `mb`, with 1024 bytes per kb; equality passes. Omitted fields are unlimited.
585
+
586
+ Existing apps without budgets receive a report only. Configured budgets default
587
+ to `mode: "error"`; `"warn"` logs violations and completes. New project templates
588
+ configure 150kb JS / 50kb CSS. A failure names the page, limit, actual size and
589
+ the ten largest rendered modules. Those module sizes are Rollup attribution
590
+ estimates in uncompressed rendered bytes, not gzip shares; compression is not
591
+ additive across modules inside a file.
592
+
593
+ The successful build stores `build-budgets.json` in `build.outdir` (normally
594
+ `dist`). Preserve that directory before rebuilding to obtain deltas. A failed
595
+ build rolls back to the previous artifact and baseline. JSON output still
596
+ contains its attempted report. Changing output directories or deleting dist
597
+ starts a fresh baseline; new routes display no delta. Vite's private
598
+ `client/.vite/budget-modules.json` contains the full module attribution.
599
+
600
+
601
+ ## build-restful `@warlock.js/core/skills/build-restful/SKILL.md`
547
602
 
548
603
  ---
549
604
  name: build-restful
@@ -814,8 +869,64 @@ router.restfulResource("/orders", new OrdersRestful());
814
869
  - The `define-resource` topic — wire shape for the records the chain returns.
815
870
  - The `warlock-conventions` topic — controller/service/repository layering rules.
816
871
 
872
+ ## Bounded list endpoints
873
+
874
+ Use `listEndpoint(request, listCallback, options)` from `@warlock.js/core` to
875
+ produce `{ <pluralKey>: data, pagination }` from a page-based repository list.
876
+ The callback receives repository options, with sorting translated into the
877
+ repository's `[column, direction]` tuple. Wrap repository methods in an arrow
878
+ to preserve their receiver. Both `list` and `listCached` work.
879
+
880
+ ```ts
881
+ import { listEndpoint, type Request, type RequestHandler } from "@warlock.js/core";
882
+ import { v, type Infer } from "@warlock.js/seal";
883
+ import { productsRepository } from "../repositories/products.repository";
884
+
885
+ const querySchema = v.object({
886
+ page: v.int().min(1).default(1),
887
+ limit: v.int().min(1).default(20),
888
+ orderBy: v.literal("createdAt", "name").optional(),
889
+ orderDir: v.literal("asc", "desc").optional(),
890
+ status: v.literal("active", "archived").optional(),
891
+ });
892
+ type Query = Infer<typeof querySchema>;
893
+
894
+ export const listProducts: RequestHandler<Request<Query>> = async ({ request, response }) =>
895
+ response.success(
896
+ await listEndpoint(request, (options) => productsRepository.listCached(options), {
897
+ pluralKey: "products",
898
+ maxLimit: 100,
899
+ orderBy: ["createdAt", "name"],
900
+ filters: ["status"],
901
+ }),
902
+ );
903
+
904
+ listProducts.validation = { schema: querySchema, validating: ["query"] };
905
+ // Attach responseSchema with your ProductResource and pagination fields too.
906
+ ```
907
+
908
+ `request.validated()` must already contain schema-validated query values. Seal
909
+ handles coercion and filter validation; the helper does not read raw query or
910
+ body values. Configure the repository's `filterBy` for the chosen filter fields.
911
+ Only explicitly listed `filters` are forwarded; pagination and sort controls
912
+ cannot be declared as filters in the typed API. Filters remain flat repository
913
+ options, rather than a nested `filters` object.
914
+
915
+ Defaults are page 1, limit 20, maxLimit 100. The helper clamps a positive integer
916
+ limit to maxLimit (use `.max(100)` in the query schema instead if you prefer a
917
+ 422 for excessive requests). Invalid page/limit or unlisted sort columns throw
918
+ `AppError("invalidListQuery", 422, ...)`. Sort directions are restricted to
919
+ `asc`/`desc`, defaulting to `asc` when a column is provided. With no column,
920
+ repository default ordering applies. Invalid helper configuration throws a
921
+ `TypeError`. The plural key must be nonempty and cannot be `pagination`.
922
+
923
+ The helper forces page pagination and preserves the repository's pagination
924
+ metadata without recalculating it. Cursor and unpaginated endpoints need their
925
+ own controller. Add authorization constraints inside the callback using trusted
926
+ server values; never accept them as client filters.
927
+
817
928
 
818
- ## build-url `@warlock.js/core/build-url/SKILL.md`
929
+ ## build-url `@warlock.js/core/skills/build-url/SKILL.md`
819
930
 
820
931
  ---
821
932
  name: build-url
@@ -999,7 +1110,7 @@ Don't call `setBaseUrl` per request — it's process-global and races every othe
999
1110
  - The `configure-app` topic — `src/config/app.ts` and the `baseUrl` env wiring.
1000
1111
 
1001
1112
 
1002
- ## configure-app `@warlock.js/core/configure-app/SKILL.md`
1113
+ ## configure-app `@warlock.js/core/skills/configure-app/SKILL.md`
1003
1114
 
1004
1115
  ---
1005
1116
  name: configure-app
@@ -1425,8 +1536,42 @@ Keep the domain error class in an app module imported by both the service and
1425
1536
  this config. Return `undefined` for errors you do not recognize; a mapped 5xx
1426
1537
  is opaque unless the mapper explicitly chooses a public message.
1427
1538
 
1539
+ ## Format every JSON error response
1540
+
1541
+ `http.errorMapper` classifies thrown non-`HttpError` errors as `{ status, message?, payload? }`. It does not see existing `HttpError` subclasses, returned validation errors, helper responses, unmatched routes, or global rate-limit failures.
1542
+
1543
+ Use `http.errorResponse` when the application needs one JSON error envelope across those paths:
1544
+
1545
+ ```ts title="src/config/http.ts"
1546
+ import type { HttpConfigurations } from "@warlock.js/core";
1547
+
1548
+ export default {
1549
+ errorMapper: () => ({ status: 500 }),
1550
+ errorResponse({ status, requestId }) {
1551
+ return {
1552
+ error: {
1553
+ code: status >= 500 ? "serverError" : `http${status}`,
1554
+ message: status >= 500 ? "Internal server error." : "Request failed.",
1555
+ },
1556
+ requestId,
1557
+ };
1558
+ },
1559
+ } satisfies HttpConfigurations;
1560
+ ```
1561
+
1562
+ The synchronous formatter receives `HttpErrorResponseContext`: `{ status: number, body: unknown, requestId: string }`. Narrow `body` before reading fields; it is the parsed, serialized JSON body. Return a JSON-serializable object or `undefined` to keep the original. The formatter changes only the body, preserving the HTTP status and headers except for recalculated content length, a `private, no-store` cache policy, and the correlation header. If it throws or cannot serialize its result, core logs the formatter error and retains the original body.
1563
+
1564
+ The hook covers status 400–599 with `application/json` or an `application/*+json` content type, including Fastify parsing errors, unmatched-route 404s, validation 422s, rate-limit 429s, and 500s. Warlock responses use the ID already stamped from `request.id`. Before Warlock creates a Request, the formatter receives Fastify's `request.id` and echoes that same value under `http.requestId.header` (default `X-Request-Id`). Setting `http.requestId.enabled: false` suppresses that header; without a stamped Warlock ID, the formatter falls back to Fastify's request ID even for routed responses. Keep request-ID headers enabled when the body must correlate with Warlock request logs. HTML pages, files, raw text, streams, and errors after headers have been sent are not reformatted. With no formatter configured, existing response shapes are unchanged.
1565
+
1566
+ ## Co-located configuration tests
1428
1567
 
1429
- ## create-controller `@warlock.js/core/create-controller/SKILL.md`
1568
+ `src/config/mail.spec.ts` and `src/config/mail.test.tsx` are never imported as
1569
+ configuration. Discovery and reload also exclude `test`, `tests`, `__tests__`,
1570
+ `__mocks__`, `__fixtures__`, and `fixtures` directories at any depth. A real
1571
+ `src/config/tests.ts` remains ordinary configuration.
1572
+
1573
+
1574
+ ## create-controller `@warlock.js/core/skills/create-controller/SKILL.md`
1430
1575
 
1431
1576
  ---
1432
1577
  name: create-controller
@@ -1725,7 +1870,7 @@ export const remoteCatalogController: RequestHandler = async ({ request, respons
1725
1870
  ```
1726
1871
 
1727
1872
 
1728
- ## create-module `@warlock.js/core/create-module/SKILL.md`
1873
+ ## create-module `@warlock.js/core/skills/create-module/SKILL.md`
1729
1874
 
1730
1875
  ---
1731
1876
  name: create-module
@@ -1925,8 +2070,17 @@ built-in import sorting.
1925
2070
  After editing generated code, run `npm run format` and `npm run lint:check`.
1926
2071
  Formatting retains unused imports; the linter reports them.
1927
2072
 
2073
+ ## Notification translation defaults
2074
+
2075
+ After configuring `app.localeCodes`, run `npx warlock add notifications`.
2076
+ New notification dictionaries include `notFound` as a string in every configured
2077
+ locale. English ("Notification not found") is the explicit fallback for each
2078
+ locale without a bundled translation, including locale sets without English.
2079
+ Replace fallback text with your translations before publishing. Existing module
2080
+ files and existing translation keys are preserved.
2081
+
1928
2082
 
1929
- ## define-resource `@warlock.js/core/define-resource/SKILL.md`
2083
+ ## define-resource `@warlock.js/core/skills/define-resource/SKILL.md`
1930
2084
 
1931
2085
  ---
1932
2086
  name: define-resource
@@ -2333,7 +2487,7 @@ export const CommentResource = defineResource({
2333
2487
  - The `create-controller` topic — calling `new XResource(record).toJSON()` from a controller.
2334
2488
 
2335
2489
 
2336
- ## encrypt-data `@warlock.js/core/encrypt-data/SKILL.md`
2490
+ ## encrypt-data `@warlock.js/core/skills/encrypt-data/SKILL.md`
2337
2491
 
2338
2492
  ---
2339
2493
  name: encrypt-data
@@ -2547,11 +2701,11 @@ Password hashing (`hashPassword`) needs `bcryptjs`, which is its own install —
2547
2701
  - The `warlock-conventions` topic — service layering, where the encryption boundary should sit.
2548
2702
 
2549
2703
 
2550
- ## generate-openapi `@warlock.js/core/generate-openapi/SKILL.md`
2704
+ ## generate-openapi `@warlock.js/core/skills/generate-openapi/SKILL.md`
2551
2705
 
2552
2706
  ---
2553
2707
  name: generate-openapi
2554
- description: 'Generate an OpenAPI 3.1 document from the registered routes with `warlock generate.openapi` (default `storage/openapi/openapi.json`), write a Postman collection from it with `warlock generate.postman` (default `storage/postman/collection.json`), and read it live in the dev-only API docs page at `/__warlock/docs`. Documents paths, path/query parameters, request bodies from Seal validation, responses from `handler.responseSchema`, 401/422 and bearer/cookie security from `authMiddleware()`. Triggers: `generate.openapi`, `openApiGeneratorCommand`, `buildOpenApiDocument`, `getDevelopmentOpenApiDocument`, `generate.postman`, `openApiToPostmanCollection`, `getDevelopmentPostmanCollection`, `responseSchema`, `--include-pages`, `/__warlock/docs`; "OpenAPI", "Swagger", "API docs", "import my API into Postman or Insomnia", "document my routes". Skip: declaring response types on a handler — the `create-controller` topic; the devtools dashboard — the `devtools-overview` topic of the `warlock-js-devtools` skill; listing routes — the `warlock-routes` topic.'
2708
+ description: 'Generate an OpenAPI 3.1 document from the registered routes with `warlock generate.openapi` (default `storage/openapi/openapi.json`), write a Postman collection from it with `warlock generate.postman` (default `storage/postman/collection.json`), and read it live in the dev-only API docs page at `/__warlock/docs`. Documents paths, path/query parameters, request bodies from Seal validation, responses from `handler.responseSchema`, 401/422 and bearer/cookie security from `authMiddleware()`. Triggers: `generate.openapi`, `openApiGeneratorCommand`, `buildOpenApiDocument`, `getDevelopmentOpenApiDocument`, `generate.postman`, `openApiToPostmanCollection`, `getDevelopmentPostmanCollection`, `responseSchema`, `--include-pages`, `/__warlock/docs`; "OpenAPI", "Swagger", "API docs", "import my API into Postman or Insomnia", "document my routes". Skip: declaring response types on a handler — the `create-controller` topic; the devtools dashboard — the `warlock-js-devtools` skill; listing routes — the `warlock-routes` topic.'
2555
2709
  ---
2556
2710
 
2557
2711
  # Warlock — generate an OpenAPI document
@@ -2725,7 +2879,82 @@ const { document, warnings } = buildOpenApiDocument(router.list(), {
2725
2879
  - The `warlock-routes` topic — list the routes the document is built from.
2726
2880
 
2727
2881
 
2728
- ## hash-password `@warlock.js/core/hash-password/SKILL.md`
2882
+ ## generate-web-ui `@warlock.js/core/skills/generate-web-ui/SKILL.md`
2883
+
2884
+ ---
2885
+ name: generate-web-ui
2886
+ description: "Generate web pages, schema-backed forms and CRUD UI with warlock generate page, form and crud-ui. Includes setup companions, design-system controls, useSubmitForm, optional guarded endpoints and module CRUD prerequisites."
2887
+ ---
2888
+
2889
+ # Generate web UI
2890
+
2891
+ Run these commands from an app with `@warlock.js/web` installed and the app's
2892
+ design system in `src/web/design-system/components/`. Controls are default
2893
+ exports, imported individually through `web/design-system/components/<name>`;
2894
+ they implement Mongez `useFormControl` and accept `name`, `label`, `hint` and
2895
+ `error`. Forms import `Form` directly from `@mongez/react-form`.
2896
+
2897
+ ```sh
2898
+ npx warlock generate page account/settings
2899
+ npx warlock generate page dashboard --interactive
2900
+ npx warlock generate form contact
2901
+ npx warlock generate form feedback --module posts --endpoint
2902
+ npx warlock generate crud-ui posts --dry-run --json
2903
+ npx warlock generate crud-ui posts
2904
+ ```
2905
+
2906
+ `page account/settings` creates `src/web/account/settings.page.tsx` and
2907
+ `settings.setup.ts`, with `/account/settings`, a named route, a loader and
2908
+ `config.metadata`. `--interactive` also creates a counter feature hook under
2909
+ `hooks/`; replace that example with the feature's behavior. Executable page
2910
+ exports belong in `.setup.ts`, UI in `.page.tsx`, state and effects in hooks.
2911
+
2912
+ `form contact` creates `src/web/forms/contact-form.tsx`, a props file, a
2913
+ `use-contact-form` hook using `useSubmitForm`, and
2914
+ `src/shared/schema/contact.schema.ts`. The starter schema has one `name`
2915
+ string field; edit that shared schema and the controls together. Its default
2916
+ submit target is `/contact`; supply a real endpoint or set the form's `path`.
2917
+ With `--module posts`, output lives under `src/web/posts/`, the shared schema
2918
+ under `src/shared/schema/posts/`, and the target is `/posts/contact`.
2919
+ The module must exist. `--endpoint` requires `--module`, adds a controller
2920
+ with `validation` and `responseSchema`, and appends a guarded POST route.
2921
+ This initial endpoint validates and echoes the input; replace that body with a
2922
+ service call to persist it. It does not create database persistence for you.
2923
+
2924
+ For CRUD UI, first generate a full module with
2925
+ `npx warlock generate module posts`. Define its create/update schemas and
2926
+ list query schema, then run `npx warlock generate crud-ui posts`.
2927
+ The generator loads the real Seal schemas using the app loader, so their
2928
+ imports must resolve. These schema modules must be safe to import on both
2929
+ client and server. Plain string fields map to TextInput, password names to
2930
+ PasswordInput, email/date/time/date-time formats to the corresponding controls,
2931
+ and booleans to CheckboxInput. Unions, nullable fields, enums, numeric fields,
2932
+ nested objects and arrays require custom control decisions and are rejected
2933
+ before writing output. An empty module schema produces an empty form.
2934
+
2935
+ CRUD output contains list/create/edit pages and setup companions, separate
2936
+ create/update forms, their hooks and props, list/edit/delete hooks, and a
2937
+ delete action component. Lists use the existing GET endpoint and its
2938
+ `data`/`pagination` contract (`page` and `pages`). Literal fields in
2939
+ `listPostsQuerySchema` become filter inputs; `page` is controlled by navigation
2940
+ and `limit` remains the endpoint default. Edit reads GET `/posts/:id`, create
2941
+ submits POST `/posts`, update uses PUT `/posts/:id`, and delete uses DELETE
2942
+ `/posts/:id`. Successful deletion refreshes the list. Mutation forms show
2943
+ validation errors, pending state and success feedback.
2944
+
2945
+ CRUD generation accepts the conventional full module route file, allowing
2946
+ formatting and comments. It fails closed for custom routes/guards instead of
2947
+ inferring authorization. Pages require `requireUser({ userType: "user" })`,
2948
+ matching the starter's `guarded` helper. Configure `web.session` with the auth
2949
+ page-session resolver before visiting these pages. Endpoint guards still
2950
+ run on every API request; customized authorization requires a hand-written UI.
2951
+
2952
+ All three commands support `--dry-run --json` through the normal generation
2953
+ report. Existing generated files are skipped unless `--force` is explicit.
2954
+ Route registration is idempotent and retains existing module content.
2955
+
2956
+
2957
+ ## hash-password `@warlock.js/core/skills/hash-password/SKILL.md`
2729
2958
 
2730
2959
  ---
2731
2960
  name: hash-password
@@ -2912,7 +3141,7 @@ There is no `warlock add` feature for password hashing — `bcryptjs` is a plain
2912
3141
  - The `warlock-conventions` topic — service layering, where the password boundary should sit.
2913
3142
 
2914
3143
 
2915
- ## health-checks `@warlock.js/core/health-checks/SKILL.md`
3144
+ ## health-checks `@warlock.js/core/skills/health-checks/SKILL.md`
2916
3145
 
2917
3146
  ---
2918
3147
  name: health-checks
@@ -3054,7 +3283,7 @@ For an even smoother handoff, give the load balancer time to observe the 503 bef
3054
3283
  - The `use-middleware` topic — maintenance mode and other built-in middleware.
3055
3284
 
3056
3285
 
3057
- ## lower-stage3-decorators `@warlock.js/core/lower-stage3-decorators/SKILL.md`
3286
+ ## lower-stage3-decorators `@warlock.js/core/skills/lower-stage3-decorators/SKILL.md`
3058
3287
 
3059
3288
  ---
3060
3289
  name: lower-stage3-decorators
@@ -3114,7 +3343,7 @@ It's the **same esbuild transform the production `tsx`/build path uses**, so a f
3114
3343
  - The `write-migration` topic of the `warlock-js-cascade` skill — the decorated models this plugin lets you load.
3115
3344
 
3116
3345
 
3117
- ## make-endpoint `@warlock.js/core/make-endpoint/SKILL.md`
3346
+ ## make-endpoint `@warlock.js/core/skills/make-endpoint/SKILL.md`
3118
3347
 
3119
3348
  ---
3120
3349
  name: make-endpoint
@@ -3228,7 +3457,7 @@ Every `generate.*` command, `make`, and `add` take both. `--dry-run` writes (and
3228
3457
  - The `generate-openapi` topic — what `responseSchema` documents.
3229
3458
 
3230
3459
 
3231
- ## model-factories `@warlock.js/core/model-factories/SKILL.md`
3460
+ ## model-factories `@warlock.js/core/skills/model-factories/SKILL.md`
3232
3461
 
3233
3462
  ---
3234
3463
  name: model-factories
@@ -3324,7 +3553,7 @@ it("lists users", async () => {
3324
3553
  - The `write-seeder` topic — seeding a development database.
3325
3554
 
3326
3555
 
3327
- ## process-image `@warlock.js/core/process-image/SKILL.md`
3556
+ ## process-image `@warlock.js/core/skills/process-image/SKILL.md`
3328
3557
 
3329
3558
  ---
3330
3559
  name: process-image
@@ -3501,7 +3730,7 @@ See the `upload-file` topic for the multipart entry point and the `store-file` t
3501
3730
  - The `warlock-conventions` topic — where image-processing services live.
3502
3731
 
3503
3732
 
3504
- ## register-route `@warlock.js/core/register-route/SKILL.md`
3733
+ ## register-route `@warlock.js/core/skills/register-route/SKILL.md`
3505
3734
 
3506
3735
  ---
3507
3736
  name: register-route
@@ -3786,7 +4015,7 @@ router.get("/webhooks/status", webhookStatusController, { rateLimit: false });
3786
4015
  - The `warlock-conventions` topic — module layout, the `guarded` convention, path aliases.
3787
4016
 
3788
4017
 
3789
- ## report-framework-issue `@warlock.js/core/report-framework-issue/SKILL.md`
4018
+ ## report-framework-issue `@warlock.js/core/skills/report-framework-issue/SKILL.md`
3790
4019
 
3791
4020
  ---
3792
4021
  name: report-framework-issue
@@ -3949,7 +4178,7 @@ GitHub CLI flags: [list](https://cli.github.com/manual/gh_issue_list),
3949
4178
  [authentication](https://cli.github.com/manual/gh_auth_status).
3950
4179
 
3951
4180
 
3952
- ## request-memo `@warlock.js/core/request-memo/SKILL.md`
4181
+ ## request-memo `@warlock.js/core/skills/request-memo/SKILL.md`
3953
4182
 
3954
4183
  ---
3955
4184
  name: request-memo
@@ -4095,7 +4324,7 @@ Reusing one key for different result types is a caller bug: TypeScript cannot co
4095
4324
  - The `create-controller` topic — consuming request-scoped values from controllers.
4096
4325
 
4097
4326
 
4098
- ## request-tracing `@warlock.js/core/request-tracing/SKILL.md`
4327
+ ## request-tracing `@warlock.js/core/skills/request-tracing/SKILL.md`
4099
4328
 
4100
4329
  ---
4101
4330
  name: request-tracing
@@ -4333,7 +4562,7 @@ export default httpConfig;
4333
4562
  - The `logger-basics` topic of the `warlock-js-logger` skill — structured logging; tracing hooks are the place to bridge phase timing into your log channel.
4334
4563
 
4335
4564
 
4336
- ## resolve-path `@warlock.js/core/resolve-path/SKILL.md`
4565
+ ## resolve-path `@warlock.js/core/skills/resolve-path/SKILL.md`
4337
4566
 
4338
4567
  ---
4339
4568
  name: resolve-path
@@ -4508,7 +4737,7 @@ If you find yourself reaching for `path.join(process.cwd(), ...)` — that's a s
4508
4737
  - The `upload-file` topic — the `UploadedFile` shape that pairs with `uploadsPath`.
4509
4738
 
4510
4739
 
4511
- ## retry-operation `@warlock.js/core/retry-operation/SKILL.md`
4740
+ ## retry-operation `@warlock.js/core/skills/retry-operation/SKILL.md`
4512
4741
 
4513
4742
  ---
4514
4743
  name: retry-operation
@@ -4630,7 +4859,7 @@ const result = await measure("publish-event", () =>
4630
4859
  - The `write-use-case` topic — the use-case `retry` option.
4631
4860
 
4632
4861
 
4633
- ## run-app `@warlock.js/core/run-app/SKILL.md`
4862
+ ## run-app `@warlock.js/core/skills/run-app/SKILL.md`
4634
4863
 
4635
4864
  ---
4636
4865
  name: run-app
@@ -4816,7 +5045,7 @@ Omit switches to run TypeScript and installed/configured Oxlint. Set `oxlint: fa
4816
5045
 
4817
5046
  ## `warlock build` — production bundle
4818
5047
 
4819
- esbuild bundle of the app down to a single JS file in `dist/`. No flags — every setting comes from `warlock.config.ts > build`.
5048
+ esbuild bundle of the app down to a single JS file in `dist/`. `--json` prints the client budget report; build settings come from `warlock.config.ts > build`. See the `build-budgets` topic for per-page gzip limits and deltas.
4820
5049
 
4821
5050
  ### `build.*` config knobs
4822
5051
 
@@ -5350,7 +5579,7 @@ directly. Regression tests cover both paths without inherited loader hooks,
5350
5579
  including configured declaration roots, source updates and deleted declarations.
5351
5580
 
5352
5581
 
5353
- ## send-mail `@warlock.js/core/send-mail/SKILL.md`
5582
+ ## send-mail `@warlock.js/core/skills/send-mail/SKILL.md`
5354
5583
 
5355
5584
  ---
5356
5585
  name: send-mail
@@ -5755,8 +5984,33 @@ await Mail.to(user.email)
5755
5984
  - The `configure-app` topic — `src/config/mail.ts` shape and `env()` patterns.
5756
5985
  - The `warlock-conventions` topic — where mail-sending services live.
5757
5986
 
5987
+ ## Preview development mail in devtools
5758
5988
 
5759
- ## send-response `@warlock.js/core/send-response/SKILL.md`
5989
+ Run `npx warlock add devtools`, start development, and open `/__warlock` (or your
5990
+ configured devtools path). In the mail panel, select a captured message to read
5991
+ its recipients, subject, text, and sandboxed HTML preview. Send while the panel's
5992
+ collector is attached; captures are held in memory and disappear on restart.
5993
+
5994
+ Use development mail mode so previews do not deliver real email:
5995
+
5996
+ ```ts
5997
+ import { setMailMode, sendMail } from "@warlock.js/core";
5998
+
5999
+ setMailMode("development");
6000
+ await sendMail({
6001
+ to: "reader@example.com",
6002
+ subject: "Preview",
6003
+ html: "<p>Your code is 123456</p>",
6004
+ });
6005
+ ```
6006
+
6007
+ Development sends emit `mailEvents.onCaptured`, which devtools consumes; console
6008
+ logs show metadata rather than sensitive bodies. Devtools is mounted only for
6009
+ the development runtime and is not mounted in production. Keep production mail
6010
+ mode configured for actual delivery. Test-mode assertions use the test mailbox.
6011
+
6012
+
6013
+ ## send-response `@warlock.js/core/skills/send-response/SKILL.md`
5760
6014
 
5761
6015
  ---
5762
6016
  name: send-response
@@ -6242,8 +6496,85 @@ export function requireProduct<T>(product: T | null | undefined): T {
6242
6496
  }
6243
6497
  ```
6244
6498
 
6499
+ ## Format every JSON error response
6500
+
6501
+ `http.errorMapper` classifies thrown non-`HttpError` errors as `{ status, message?, payload? }`. It does not see existing `HttpError` subclasses, returned validation errors, helper responses, unmatched routes, or global rate-limit failures.
6502
+
6503
+ Use `http.errorResponse` when the application needs one JSON error envelope across those paths:
6504
+
6505
+ ```ts title="src/config/http.ts"
6506
+ import type { HttpConfigurations } from "@warlock.js/core";
6507
+
6508
+ export default {
6509
+ errorMapper: () => ({ status: 500 }),
6510
+ errorResponse({ status, requestId }) {
6511
+ return {
6512
+ error: {
6513
+ code: status >= 500 ? "serverError" : `http${status}`,
6514
+ message: status >= 500 ? "Internal server error." : "Request failed.",
6515
+ },
6516
+ requestId,
6517
+ };
6518
+ },
6519
+ } satisfies HttpConfigurations;
6520
+ ```
6521
+
6522
+ The synchronous formatter receives `HttpErrorResponseContext`: `{ status: number, body: unknown, requestId: string }`. Narrow `body` before reading fields; it is the parsed, serialized JSON body. Return a JSON-serializable object or `undefined` to keep the original. The formatter changes only the body, preserving the HTTP status and headers except for recalculated content length, a `private, no-store` cache policy, and the correlation header. If it throws or cannot serialize its result, core logs the formatter error and retains the original body.
6523
+
6524
+ The hook covers status 400–599 with `application/json` or an `application/*+json` content type, including Fastify parsing errors, unmatched-route 404s, validation 422s, rate-limit 429s, and 500s. Warlock responses use the ID already stamped from `request.id`. Before Warlock creates a Request, the formatter receives Fastify's `request.id` and echoes that same value under `http.requestId.header` (default `X-Request-Id`). Setting `http.requestId.enabled: false` suppresses that header; without a stamped Warlock ID, the formatter falls back to Fastify's request ID even for routed responses. Keep request-ID headers enabled when the body must correlate with Warlock request logs. HTML pages, files, raw text, streams, and errors after headers have been sent are not reformatted. With no formatter configured, existing response shapes are unchanged.
6525
+
6526
+ ## Typed domain failures
6527
+
6528
+ `AppError<Details, Code>` is available from `@warlock.js/core` and the lightweight
6529
+ `@warlock.js/core/errors` entry. Its constructor is
6530
+ `new AppError(code, status, publicMessage, details?)`. Status must be an integer
6531
+ from 400 to 599. The message and details are explicitly public, including for
6532
+ 5xx errors: never put diagnostics, credentials or private records there.
6533
+
6534
+ ```ts
6535
+ import { AppError } from "@warlock.js/core/errors";
6536
+
6537
+ throw new AppError("quotaExceeded", 429, "Try again later.", { retryAfter: 30 });
6538
+ ```
6539
+
6540
+ The default response is
6541
+ `{ error: { code: "quotaExceeded", message: "Try again later.", details: { retryAfter: 30 } } }`.
6542
+ Absent details are omitted; `false`, `0` and `null` details are preserved. Errors
6543
+ retain the private, no-store cache policy, and server failures are logged.
6245
6544
 
6246
- ## store-file `@warlock.js/core/store-file/SKILL.md`
6545
+ `AppError` and `HttpError` are separate `Error` subclasses. Existing `HttpError`
6546
+ constructors, inheritance, `payload` and legacy `{ error: message, payload? }`
6547
+ bodies remain unchanged. Both bypass `http.errorMapper`; ordinary errors retain
6548
+ the existing opaque 500 fallback. Choose `AppError` for new domain failures
6549
+ that need stable codes, and keep existing HTTP error subclasses as they are.
6550
+
6551
+ `http.errorResponse` receives an optional typed `appError` alongside `body`,
6552
+ `status` and `requestId`. No per-error mapper is needed:
6553
+
6554
+ ```ts
6555
+ import type { HttpErrorResponseFormatter } from "@warlock.js/core";
6556
+
6557
+ const errorResponse: HttpErrorResponseFormatter = ({ appError, requestId }) => {
6558
+ if (!appError) return undefined; // keep other JSON errors unchanged
6559
+ return {
6560
+ error: {
6561
+ code: appError.code,
6562
+ message: appError.publicMessage,
6563
+ ...(appError.details === undefined ? {} : { details: appError.details }),
6564
+ },
6565
+ requestId,
6566
+ };
6567
+ };
6568
+ // Set this function as http.errorResponse in your HTTP configuration.
6569
+ ```
6570
+
6571
+ The formatter sees the original instance only for thrown `AppError`s handled
6572
+ by the request lifecycle. Early Fastify rejections and returned helper responses
6573
+ have no `appError`; use `body` and `status` for those cases. Do not spread an
6574
+ error instance into the response.
6575
+
6576
+
6577
+ ## store-file `@warlock.js/core/skills/store-file/SKILL.md`
6247
6578
 
6248
6579
  ---
6249
6580
  name: store-file
@@ -6691,7 +7022,7 @@ Client `PUT`s the bytes straight to R2 — your server never sees them.
6691
7022
  - The `send-response` topic — `response.sendFile(absPath)` for serving local files.
6692
7023
 
6693
7024
 
6694
- ## store-secrets `@warlock.js/core/store-secrets/SKILL.md`
7025
+ ## store-secrets `@warlock.js/core/skills/store-secrets/SKILL.md`
6695
7026
 
6696
7027
  ---
6697
7028
  name: store-secrets
@@ -6855,7 +7186,7 @@ The normal test harness still installs `secretsFake`; explicit `backend`
6855
7186
  instances and that fake continue to bypass configured backend selection.
6856
7187
 
6857
7188
 
6858
- ## test-browser `@warlock.js/core/test-browser/SKILL.md`
7189
+ ## test-browser `@warlock.js/core/skills/test-browser/SKILL.md`
6859
7190
 
6860
7191
  ---
6861
7192
  name: test-browser
@@ -6962,6 +7293,7 @@ port-file handshake above when the app supports binding `0` itself.
6962
7293
  document. Selector helpers do not enter frames or closed shadow roots.
6963
7294
 
6964
7295
  Use `--browser /path/to/chrome` or `WARLOCK_E2E_BROWSER` if discovery fails.
7296
+ Set `browser.startupTimeoutMs` (default 30 seconds) when the browser is slow to start, for example on shared CI runners.
6965
7297
  `browser: { args: ["--no-sandbox"] }` is an explicit option for controlled root
6966
7298
  containers; it is not a default. On Windows, command arrays should invoke JS tools
6967
7299
  through `process.execPath`, avoiding `.cmd` executables that require a shell.
@@ -6977,8 +7309,61 @@ recorded process group (Windows: verified PID tree). It retains disposable files
6977
7309
  if ownership verification fails and reports the failure in evidence. Do not add
6978
7310
  process-name or port-based kills to a spec.
6979
7311
 
7312
+ ## Accessibility checks
7313
+
7314
+ Call `await page.assertAccessible()` after the content is visible and again after
7315
+ opening menus, dialogs or validation feedback. The harness reads its pinned local
7316
+ axe-core distribution and injects it with CDP `Runtime.evaluate`; it never fetches
7317
+ a CDN script or adds axe to the app bundle. A missing dependency, injection error,
7318
+ unmatched selector or scan exception fails the step.
7319
+
7320
+ ```ts
7321
+ await page.assertAccessible();
7322
+ await page.assertAccessible({
7323
+ selector: "main",
7324
+ tags: ["wcag2a", "wcag2aa", "wcag21aa"],
7325
+ allow: [{ id: "color-contrast", selector: ".legacy-caption" }],
7326
+ timeoutMs: 30_000,
7327
+ });
7328
+ ```
7329
+
7330
+ Defaults run all enabled axe rules on the document. `tags` uses axe's `runOnly`
7331
+ tag filter; `selector` limits the scanned region. Allowances match exact rule IDs
7332
+ and, when supplied, an exact single-element target selector. Omitting `selector`
7333
+ from an allowance permits that rule everywhere; keep exceptions narrow. Nested
7334
+ frame/shadow targets cannot match a simple selector allowance. Allowed nodes stay
7335
+ in evidence with `allowed: true`; other nodes of the same rule still fail.
7336
+
7337
+ Evidence's `accessibility` array contains URL, viewport, timestamp, violations
7338
+ with rule IDs and target selectors, and incomplete checks for manual review.
7339
+ Incomplete checks do not fail automatically. A passing automated scan does not
7340
+ replace keyboard, screen-reader, focus-order or contrast review. See the
7341
+ [axe API](https://github.com/dequelabs/axe-core/blob/develop/doc/API.md) for scan
7342
+ semantics. Run at both 390 and 1280 pixels with `assertViewport()` as well.
7343
+
7344
+ ## Watched development checks
7345
+
7346
+ Use an owned app copy with `app.env: { NODE_ENV: "development" }` and
7347
+ `app.start: [process.execPath, "node_modules/@warlock.js/core/bin/warlock.js", "dev"]`.
7348
+ Do not attach to an existing server. The fixture's `directory` is available in
7349
+ `run`; use `node:fs/promises` to edit only files inside that copy.
6980
7350
 
6981
- ## test-fakes `@warlock.js/core/test-fakes/SKILL.md`
7351
+ Wait for a visible revision marker after each edit; a successful file write alone
7352
+ does not prove watcher invalidation or browser hydration. Exercise several edits,
7353
+ including setup-file creation, changes and removal. After each revision, request
7354
+ the page again, exercise a hydrated control, check imported `shared` in loaders
7355
+ and middleware, and verify concurrent requests keep distinct values. Do not
7356
+ allowlist request-store or hydration errors to make these checks pass.
7357
+
7358
+ For forms beneath layouts, compare server-rendered IDs with the hydrated hook
7359
+ values (for example expose the hook value in a click handler), check repeated
7360
+ instances, label `for` and ARIA references, then navigate away and back. Comparing
7361
+ DOM IDs alone can miss a mismatch: React may retain the server attribute while
7362
+ client handlers capture another ID. Capture evidence and screenshots at every
7363
+ revision. Do not replace React's `useId()` globally or hide hydration warnings.
7364
+
7365
+
7366
+ ## test-fakes `@warlock.js/core/skills/test-fakes/SKILL.md`
6982
7367
 
6983
7368
  ---
6984
7369
  name: test-fakes
@@ -7107,7 +7492,7 @@ An unknown fake name or a non-boolean value fails setup with the list of known f
7107
7492
  - The `model-factories` topic — deterministic fixtures.
7108
7493
 
7109
7494
 
7110
- ## test-http `@warlock.js/core/test-http/SKILL.md`
7495
+ ## test-http `@warlock.js/core/skills/test-http/SKILL.md`
7111
7496
 
7112
7497
  ---
7113
7498
  name: test-http
@@ -7570,8 +7955,99 @@ This is fine for normal test flow. It bites when you're inside a transaction the
7570
7955
  - The `warlock-conventions` topic — module layout, where `tests/*.test.ts` files live.
7571
7956
  - The `write-cli-command` topic — `warlock add test` scaffolds both global + worker setup files.
7572
7957
 
7958
+ ## Worker dictionaries and owned runtime resources
7959
+
7960
+ The generated `src/test-setup.ts` calls:
7961
+
7962
+ ```ts
7963
+ await setupTest({ importModule: (file) => import(file) });
7964
+ ```
7965
+
7966
+ `setupTest()` reads `src/app/*/utils/locales.json` after configuration in each
7967
+ Vitest worker. Server translation helpers therefore see all configured locales
7968
+ without manual registration. Invalid dictionaries fail setup. `teardownTest()`
7969
+ unregisters those dictionaries; legacy executable locale files still require
7970
+ explicit imports in worker setup.
7971
+
7972
+ Global HTTP fixtures should use `await startHttpTestServer({ port: 0, web: false })`
7973
+ for API-only suites and `await stopHttpTestServer()` in teardown. Stop releases
7974
+ connectors and the file runtime's loader channel, queued work, watchers, health
7975
+ workers, and registered module cleanup hooks, including after partial startup.
7976
+ A Node loader registration is process-scoped; its message channel is closed at
7977
+ teardown. Module-owned timers/listeners must register their cleanup hooks.
7978
+
7979
+ A module that owns a long-running handle can export an awaited cleanup:
7980
+
7981
+ ```ts
7982
+ // src/app/messages/main.ts
7983
+ import { createServer } from "node:net";
7984
+ const listener = createServer();
7985
+ listener.listen(0, "127.0.0.1");
7986
+ export async function cleanup(): Promise<void> {
7987
+ await new Promise<void>((resolve, reject) => {
7988
+ listener.close((error) => (error ? reject(error) : resolve()));
7989
+ });
7990
+ }
7991
+ ```
7573
7992
 
7574
- ## test-service `@warlock.js/core/test-service/SKILL.md`
7993
+ HTTP test teardown awaits these cleanup functions and reports failures after
7994
+ attempting every module's cleanup. Avoid leaving module-owned handles running
7995
+ outside this lifecycle.
7996
+
7997
+ ## Response contracts from declared schemas
7998
+
7999
+ `assertResponseContracts` from `@warlock.js/core/tests` sends HTTP requests and
8000
+ asserts the serialized JSON body against the handler's `responseSchema` for the
8001
+ actual status. It reuses Core's OpenAPI schema translation, including resource
8002
+ classes, nested objects, arrays, nullable casts and resource self references.
8003
+ It checks required fields and primitive types without coercion; extra fields
8004
+ are allowed. URL/date format annotations are not checked beyond their declared
8005
+ JSON shape. Builders, computed resolvers and resources without a static shape
8006
+ fail with a diagnostic rather than silently passing.
8007
+
8008
+ ```ts
8009
+ import { it } from "vitest";
8010
+ import { router } from "@warlock.js/core";
8011
+ import { assertResponseContracts } from "@warlock.js/core/tests";
8012
+
8013
+ it("matches product response declarations", async () => {
8014
+ await assertResponseContracts({
8015
+ routes: router.list().filter((route) => route.path === "/products"),
8016
+ cases: () => [{ path: "/products?page=1&limit=10", status: 200 }],
8017
+ requireAllStatuses: true,
8018
+ });
8019
+ });
8020
+ ```
8021
+
8022
+ With the shared test server, retain `startHttpTestServer({ port: 0 })` in global
8023
+ setup and `setupTest` in the worker. The routes and their handler schemas must
8024
+ also be loaded in the worker running this assertion (import their routes module
8025
+ there); global setup runs in a different process. Omit `routes` to discover all
8026
+ registered routes with `responseSchema` in that worker. Empty discovery fails.
8027
+ Explicit routes lacking a schema also fail.
8028
+
8029
+ Static GET routes default to one request without headers. Parameterized routes
8030
+ and non-GET methods require explicit `cases(route)` fixtures; an empty case
8031
+ list fails. Each case can supply a concrete `path`, `init` (headers, serialized
8032
+ body, etc.) and expected `status`. Supply fixtures with valid IDs, seeded data,
8033
+ authentication and invalid inputs to exercise the desired outcomes. An `all` route also needs a concrete fixture `method`. Redirects
8034
+ are not followed. POST/PUT/PATCH/DELETE fixtures execute real side effects;
8035
+ use isolated test data and clean it up.
8036
+
8037
+ Undeclared response statuses fail. Only statuses actually returned are checked
8038
+ by default; `requireAllStatuses: true` additionally fails if any declared status
8039
+ has no exercised fixture. HEAD, 204 and 304 responses must have an empty body; declare it with
8040
+ `{ body: {} }`. Other statuses must carry matching JSON.
8041
+
8042
+ For an in-process server, pass `client: app` from `createTestHttpServer()` and
8043
+ close it in teardown. For signed-in requests, pass `client: await actingAs(user)`
8044
+ and dispose that client afterward. These clients use the same assertion and
8045
+ run the real HTTP middleware and serialization paths.
8046
+
8047
+ 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.
8048
+
8049
+
8050
+ ## test-service `@warlock.js/core/skills/test-service/SKILL.md`
7575
8051
 
7576
8052
  ---
7577
8053
  name: test-service
@@ -7588,9 +8064,9 @@ For unit tests, you import the thing under test and call it directly. No HTTP, n
7588
8064
 
7589
8065
  ⛔ **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.**
7590
8066
 
7591
- **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.**
8067
+ **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.**
7592
8068
 
7593
- ⚠ **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.
8069
+ 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.
7594
8070
 
7595
8071
  ⚠ **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.**
7596
8072
 
@@ -7992,7 +8468,7 @@ Node process, preserve the source package's module mode. ESM fixtures need a
7992
8468
  CommonJS and named exports fail before the behavior under test runs. Assert the
7993
8469
  child process exit status with its captured stderr before checking stdout.
7994
8470
 
7995
- - **`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.
8471
+ - **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.
7996
8472
  - ⛔ **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.
7997
8473
  - ⛔ **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.
7998
8474
  - **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.
@@ -8020,8 +8496,166 @@ setupTest({ importModule: (file) => import(file) });
8020
8496
 
8021
8497
  The generated `src/test-setup.ts` already does this. Without it, `setupTest()` under Vitest warns once.
8022
8498
 
8499
+ ## Runtime lifetime, unit projects and setup profiling
8500
+
8501
+ `warlock add test` (also used by `create-warlock --features=test`) generates two
8502
+ Vitest projects. `unit` discovers `src/**/*.spec.ts` outside `tests/` directories
8503
+ and has no Warlock setup or HTTP global setup. `integration` discovers
8504
+ `src/**/*.test.ts`, specs inside `src/**/tests/`, and `tests/**/*.{test,spec}.ts`.
8505
+ It uses the framework setup, HTTP global setup and a generated `src/test-runner.ts`.
8506
+ Keep pure service specs beside their source and mock repositories at the boundary.
8507
+ Existing config files are preserved; copy the projects layout into them manually.
8508
+ Vitest 4.1.10 or newer is required for the generated runner.
8509
+
8510
+ For a unit spec that needs configuration values, use:
8511
+
8512
+ ```ts
8513
+ import { afterAll, expect } from "vitest";
8514
+ import { setupTest, teardownTest } from "@warlock.js/core/tests";
8515
+
8516
+ await setupTest({
8517
+ mode: "unit",
8518
+ importModule: (file) => import(file),
8519
+ file: expect.getState().testPath,
8520
+ });
8521
+ afterAll(teardownTest);
8522
+ ```
8523
+
8524
+ Unit mode loads `warlock.config.ts` and configuration values once per module graph.
8525
+ It skips bootstrap/env/prestart hooks, connectors, config special handlers,
8526
+ `filesOrchestrator.init()`, the native ESM loader, and fake installation. Keep
8527
+ configuration imports free of side effects; arbitrary code imported by a config
8528
+ file can still perform I/O. A plain unit spec usually needs no `setupTest()` at all.
8529
+ `connectors: false` still bootstraps; it is not the cheap unit mode. Use separate
8530
+ projects for unit and integration runtimes.
8531
+
8532
+ ### Opt into one runtime per fork
8023
8533
 
8024
- ## update-packages `@warlock.js/core/update-packages/SKILL.md`
8534
+ The default remains `tests.lifetime: "file"`. To reuse a runtime, change the
8535
+ integration project's `isolate` to `false`, keep `pool: "forks"` and
8536
+ `runner: "./src/test-runner.ts"`, then configure:
8537
+
8538
+ ```ts title="src/config/tests.ts"
8539
+ import { resetWorkerDatabase } from "../test-database";
8540
+
8541
+ export default {
8542
+ lifetime: "worker",
8543
+ // Application-owned callback: truncate only this worker's test database.
8544
+ // Called before every file's fixtures, including the first file.
8545
+ resetBetweenFiles: async () => {
8546
+ await resetWorkerDatabase();
8547
+ },
8548
+ profileSetup: true,
8549
+ };
8550
+ ```
8551
+
8552
+ Run with `npx vitest run --project integration`. Worker lifetime refuses watch,
8553
+ threads, isolated module graphs, or a missing runner with a message explaining
8554
+ the required settings. Keep `afterAll(teardownTest)` in the setup file: in worker
8555
+ mode it marks the end of the file; Vitest's awaited worker cleanup closes the
8556
+ runtime once. The runner uses `VitestTestRunner.onCleanupWorkerContext`, verified
8557
+ in Vitest 4.1.10's `runners.d.ts`, runner implementation and worker cleanup source.
8558
+ For an existing project, generate this runner without overwriting your config:
8559
+
8560
+ ```ts title="src/test-runner.ts"
8561
+ import { configureTestWorker } from "@warlock.js/core/tests";
8562
+ import { VitestTestRunner } from "vitest/runners";
8563
+
8564
+ export default class WarlockTestRunner extends VitestTestRunner {
8565
+ public constructor(config: ConstructorParameters<typeof VitestTestRunner>[0]) {
8566
+ super(config);
8567
+ configureTestWorker(
8568
+ { pool: this.pool, watch: config.watch, isolate: config.isolate },
8569
+ (cleanup) => this.onCleanupWorkerContext(cleanup),
8570
+ );
8571
+ }
8572
+ }
8573
+ ```
8574
+
8575
+ The first file bootstraps; subsequent files reuse connectors and config. Between
8576
+ files, selected fakes are restored and reinstalled, resetting mail/queue records,
8577
+ cache, secrets, clock, IDs and a private storage directory. Worker storage is
8578
+ separate from the global HTTP server's storage: use an in-process test HTTP server
8579
+ when assertions need to see request-side fake state. The hook runs after fake
8580
+ reset and before fixtures. It must finish before returning. Within a file,
8581
+ repeated `setupTest()` calls remain idempotent; use `beforeEach`/`afterEach` for
8582
+ per-test cleanup.
8583
+
8584
+ This trades module isolation for startup cost: application singletons, config
8585
+ mutations, mocks and module side effects can survive between files. Reset your
8586
+ own state in `resetBetweenFiles`, restore mocks, and keep connector selection
8587
+ consistent. Give each worker a distinct database/schema using
8588
+ `process.env.VITEST_POOL_ID` when preparing test connections. Never drop a shared
8589
+ database from this hook while other workers are using it. The framework does not
8590
+ create a memory replica set or a cross-worker lock; those belong to the application's
8591
+ global setup. A shared HTTP server and a single replica set can contend on I/O,
8592
+ locks and shared fixtures even though workers run concurrently. Scope rows and
8593
+ HTTP fixtures per worker, or run tests that require global mutation separately
8594
+ with `maxWorkers: 1`. No test-server teardown ownership changes are needed here.
8595
+
8596
+ `getTestRuntimeStats()` from `@warlock.js/core/tests` returns process-local
8597
+ `{ bootstraps, files }`. For example, after setup in worker mode:
8598
+
8599
+ ```ts
8600
+ import { expect, it } from "vitest";
8601
+ import { getTestRuntimeStats } from "@warlock.js/core/tests";
8602
+
8603
+ it("reuses this worker's runtime", () => {
8604
+ expect(getTestRuntimeStats().bootstraps).toBe(1);
8605
+ });
8606
+ ```
8607
+
8608
+ Across one integration project, a successful run with no worker recycling needs
8609
+ at most `maxWorkers` bootstraps. Multiple projects, worker crashes and memory-limit
8610
+ recycling can create additional processes; count distinct PIDs in profiles.
8611
+
8612
+ ### Measure and size CI workers
8613
+
8614
+ Set `tests.profileSetup: true` or run
8615
+ `WARLOCK_TEST_PROFILE=1 npx vitest run --project integration`. Pass
8616
+ `file: expect.getState().testPath` to `setupTest()` for file labels (the generated
8617
+ setup does this). Each file emits one line with milliseconds for environment, config,
8618
+ bootstrap, orchestrator, app config, connectors, fakes, reset and teardown as
8619
+ applicable. Reused files report reset instead of startup steps. A summary per
8620
+ worker reports file/bootstrap counts, summed step timings and final shutdown.
8621
+ The generated config also installs `TestSetupReporter` from `@warlock.js/core/vite`
8622
+ (`reporters: ["default", new TestSetupReporter()]`), which aggregates file counts,
8623
+ bootstrap counts and timings across PIDs into a run summary. Summed timings are
8624
+ not wall time. Worker shutdown is included when its summary reaches the reporter
8625
+ before the run-end callback; individual worker summaries always report it.
8626
+
8627
+ The generated config sets `maxWorkers: recommendedTestWorkers()` from
8628
+ `@warlock.js/core/vite`. It takes the smaller of CPU affinity and the tightest
8629
+ visible cgroup v2 quota, rounding fractional CPUs down with a minimum of one.
8630
+ For example `cpu.max = 1200000 100000` recommends 12 workers on a 48-CPU host.
8631
+ The format follows the [kernel cgroup v2 CPU interface](https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html#cpu-interface-files).
8632
+ It reads `/proc/self/cgroup` and visible ancestors under `/sys/fs/cgroup`; hosts
8633
+ without readable v2 quotas use affinity. Set `maxWorkers` explicitly on cgroup v1,
8634
+ custom cgroup mounts, or when database/memory limits require fewer workers.
8635
+
8636
+ ### Release gate
8637
+
8638
+ ```bash
8639
+ npx warlock test --changed --since "$LAST_RELEASE_REF"
8640
+ npx vitest run --project integration
8641
+ # Nightly (all projects):
8642
+ npx vitest run
8643
+ ```
8644
+
8645
+ The first command includes affected tests, using the existing changed-test CLI;
8646
+ the second always covers integration wiring. Keep the full suite nightly and
8647
+ compare cold and reused setup profiles on the same revision, worker count and
8648
+ database fixture before claiming a speedup.
8649
+
8650
+ For a manual fake harness outside `setupTest`, the same private storage behavior
8651
+ is available with `await installTestFakes(true, { isolatedStorage: true })`, paired
8652
+ with `await restoreTestFakes()`. Both are exported from `@warlock.js/core/tests`.
8653
+ `await storageFake.install({ isolated: true })` / `await storageFake.restore()`
8654
+ provides it for a storage-only harness. Existing installs share the HTTP root by
8655
+ default; the isolated option never replaces or deletes that shared root.
8656
+
8657
+
8658
+ ## update-packages `@warlock.js/core/skills/update-packages/SKILL.md`
8025
8659
 
8026
8660
  ---
8027
8661
  name: update-packages
@@ -8169,7 +8803,7 @@ Two small zero-dependency utilities back the tooling and are exported from `@war
8169
8803
  - The `configure-app` topic — `warlock.config.ts` shape and `defineConfig`.
8170
8804
 
8171
8805
 
8172
- ## upgrade-from-5-29 `@warlock.js/core/upgrade-from-5-29/SKILL.md`
8806
+ ## upgrade-from-5-29 `@warlock.js/core/skills/upgrade-from-5-29/SKILL.md`
8173
8807
 
8174
8808
  ---
8175
8809
  name: upgrade-from-5-29
@@ -8649,6 +9283,10 @@ It replaces standard scripts/dependencies, pins TypeScript 7.0.2, updates genera
8649
9283
  editor settings, removes `healthCheckers.eslint` and selects Oxlint. A generated
8650
9284
  `.prettierignore` becomes `.oxfmtignore`; Oxfmt scripts read it explicitly.
8651
9285
 
9286
+ Generated migration configs enable `typescript/no-explicit-any`, `curly`, and
9287
+ `no-console` as errors, matching new apps. Fix those diagnostics before running
9288
+ `npm run lint:check` and `npx warlock check` again.
9289
+
8652
9290
  The `oxc-toolchain` codemod also updates generated VS Code settings and literal
8653
9291
  ESLint/Oxlint health-check switches. Custom editor settings, checker arrays,
8654
9292
  computed options and nonstandard scripts require review. Oxc diagnostics keep
@@ -8704,8 +9342,26 @@ existing Oxc configs retain their categories. A real lockfile beside a workspace
8704
9342
  marker determines the installer: `package-lock.json` plus `pnpm-workspace.yaml`
8705
9343
  without `pnpm-lock.yaml` now produces `npm install`.
8706
9344
 
9345
+ ## 5.34 to 5.35 resource hidden input
9346
+
9347
+ Model resources now receive hidden input columns (subject to `resourceColumns`)
9348
+ to support derivations such as `has_secret: Boolean(data.secret)`. Model
9349
+ serialization removes `hidden` top-level keys from the final resource result.
9350
+ Review resources that copy input under renamed or nested keys and return explicit
9351
+ public projections. Direct resource construction does not invoke model filtering.
8707
9352
 
8708
- ## upload-file `@warlock.js/core/upload-file/SKILL.md`
9353
+ ## 5.34 to 5.35 document hydration
9354
+
9355
+ Initial Web hydration now includes `root.tsx`, matching the document ancestry
9356
+ used by SSR so `useId()` and generated Mongez form IDs agree beneath layouts.
9357
+ Root components and providers execute in both realms. Move server-only work into
9358
+ `root.setup.ts` loaders and pass browser-safe results as `data`; retain exactly
9359
+ one `#vessel` around `{children}`. Client navigation still swaps layout/page
9360
+ children while preserving the document root. Remove temporary explicit form IDs
9361
+ only when the application does not otherwise depend on those names.
9362
+
9363
+
9364
+ ## upload-file `@warlock.js/core/skills/upload-file/SKILL.md`
8709
9365
 
8710
9366
  ---
8711
9367
  name: upload-file
@@ -9018,7 +9674,7 @@ The reverse direction — sending a stored file back via `response.sendFile(...)
9018
9674
  - The `warlock-conventions` topic — storage configuration and driver selection.
9019
9675
 
9020
9676
 
9021
- ## use-app-context `@warlock.js/core/use-app-context/SKILL.md`
9677
+ ## use-app-context `@warlock.js/core/skills/use-app-context/SKILL.md`
9022
9678
 
9023
9679
  ---
9024
9680
  name: use-app-context
@@ -9329,7 +9985,7 @@ cors: {
9329
9985
  - The `warlock-conventions` topic — module layout, where paths point to.
9330
9986
 
9331
9987
 
9332
- ## use-localization `@warlock.js/core/use-localization/SKILL.md`
9988
+ ## use-localization `@warlock.js/core/skills/use-localization/SKILL.md`
9333
9989
 
9334
9990
  ---
9335
9991
  name: use-localization
@@ -9670,8 +10326,29 @@ This produces `account.title` and `account.actions.save`. Keep every leaf comple
9670
10326
 
9671
10327
  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.
9672
10328
 
10329
+ ## Worker dictionaries and owned runtime resources
10330
+
10331
+ The generated `src/test-setup.ts` calls:
10332
+
10333
+ ```ts
10334
+ await setupTest({ importModule: (file) => import(file) });
10335
+ ```
10336
+
10337
+ `setupTest()` reads `src/app/*/utils/locales.json` after configuration in each
10338
+ Vitest worker. Server translation helpers therefore see all configured locales
10339
+ without manual registration. Invalid dictionaries fail setup. `teardownTest()`
10340
+ unregisters those dictionaries; legacy executable locale files still require
10341
+ explicit imports in worker setup.
9673
10342
 
9674
- ## use-middleware `@warlock.js/core/use-middleware/SKILL.md`
10343
+ Global HTTP fixtures should use `await startHttpTestServer({ port: 0, web: false })`
10344
+ for API-only suites and `await stopHttpTestServer()` in teardown. Stop releases
10345
+ connectors and the file runtime's loader channel, queued work, watchers, health
10346
+ workers, and registered module cleanup hooks, including after partial startup.
10347
+ A Node loader registration is process-scoped; its message channel is closed at
10348
+ teardown. Module-owned timers/listeners must register their cleanup hooks.
10349
+
10350
+
10351
+ ## use-middleware `@warlock.js/core/skills/use-middleware/SKILL.md`
9675
10352
 
9676
10353
  ---
9677
10354
  name: use-middleware
@@ -9969,7 +10646,7 @@ router.post("/private/preview", ({ response }) => response.success({ ready: true
9969
10646
  ```
9970
10647
 
9971
10648
 
9972
- ## use-model-transformers `@warlock.js/core/use-model-transformers/SKILL.md`
10649
+ ## use-model-transformers `@warlock.js/core/skills/use-model-transformers/SKILL.md`
9973
10650
 
9974
10651
  ---
9975
10652
  name: use-model-transformers
@@ -10202,7 +10879,7 @@ Rule of thumb: transformers are for **pure, deterministic** transforms of the ro
10202
10879
  - The `warlock-conventions` topic — schema files live in `src/app/<module>/models/<entity>/<entity>.model.ts`.
10203
10880
 
10204
10881
 
10205
- ## use-repository `@warlock.js/core/use-repository/SKILL.md`
10882
+ ## use-repository `@warlock.js/core/skills/use-repository/SKILL.md`
10206
10883
 
10207
10884
  ---
10208
10885
  name: use-repository
@@ -10636,7 +11313,7 @@ The third arg is a context object — `{ allValues, dateFormat, ... }` — for f
10636
11313
  - The `warlock-conventions` topic — controller → service → repository → model layering.
10637
11314
 
10638
11315
 
10639
- ## use-request-locals `@warlock.js/core/use-request-locals/SKILL.md`
11316
+ ## use-request-locals `@warlock.js/core/skills/use-request-locals/SKILL.md`
10640
11317
 
10641
11318
  ---
10642
11319
  name: use-request-locals
@@ -10807,7 +11484,7 @@ Writing `request.locals.organization` does not affect `request.all()`, `request.
10807
11484
  - The `create-controller` topic — reading locals in downstream controllers.
10808
11485
 
10809
11486
 
10810
- ## validate-input `@warlock.js/core/validate-input/SKILL.md`
11487
+ ## validate-input `@warlock.js/core/skills/validate-input/SKILL.md`
10811
11488
 
10812
11489
  ---
10813
11490
  name: validate-input
@@ -11165,7 +11842,7 @@ updateProductController.validation = {
11165
11842
  - The `warlock-conventions` topic — `Request<Schema>` type-alias convention.
11166
11843
 
11167
11844
 
11168
- ## warlock-agents-md `@warlock.js/core/warlock-agents-md/SKILL.md`
11845
+ ## warlock-agents-md `@warlock.js/core/skills/warlock-agents-md/SKILL.md`
11169
11846
 
11170
11847
  ---
11171
11848
  name: warlock-agents-md
@@ -11265,7 +11942,7 @@ refresher.dispose();
11265
11942
  - The `warlock-conventions` topic — the conventions listed in the section.
11266
11943
 
11267
11944
 
11268
- ## warlock-board `@warlock.js/core/warlock-board/SKILL.md`
11945
+ ## warlock-board `@warlock.js/core/skills/warlock-board/SKILL.md`
11269
11946
 
11270
11947
  ---
11271
11948
  name: warlock-board
@@ -11392,7 +12069,7 @@ const { messages } = await watchBoard({ agent: "implementer", timeoutMs: 30 * 60
11392
12069
  - The `warlock-inspect` topic — the app's facts as JSON before you change it.
11393
12070
 
11394
12071
 
11395
- ## warlock-check `@warlock.js/core/warlock-check/SKILL.md`
12072
+ ## warlock-check `@warlock.js/core/skills/warlock-check/SKILL.md`
11396
12073
 
11397
12074
  ---
11398
12075
  name: warlock-check
@@ -11471,7 +12148,7 @@ npx warlock typecheck --changed # changed files + the files importing them
11471
12148
  npx warlock typecheck --json --project tsconfig.app.json
11472
12149
  ```
11473
12150
 
11474
- `--changed` checks the changed files and their direct importers only; a full run (cheap after the first, it is incremental) catches anything further away.
12151
+ `--changed` checks diagnostics for changed files and their transitive importers. The native compiler still performs an incremental program compilation.
11475
12152
 
11476
12153
  ## `warlock explain`
11477
12154
 
@@ -11532,8 +12209,79 @@ Migrate lint and format tooling with
11532
12209
  `npx warlock upgrade --from 5.32 --to 5.33.0 --dry-run`, then remove `--dry-run`
11533
12210
  and work through the manual-review list. Use `--lint=oxc` for new apps.
11534
12211
 
12212
+ ## Web and response contract conventions
12213
+
12214
+ The following codes default to **warnings**, so upgrading does not fail existing projects.
12215
+ They parse real TypeScript/JSX syntax with the same oxc-parser adapter as the existing conventions.
12216
+
12217
+ | Code | Convention | Remedy |
12218
+ | -------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
12219
+ | `WL9013` | A component file exceeds 150 lines | Split components and extract hooks; configure `componentLineLimit`. |
12220
+ | `WL9014` | More than one component in a file | Move each component into its own TSX file. |
12221
+ | `WL9015` | State/effects or event-handler functions inside a component | Move `useState`, `useReducer`, `useEffect`, `useLayoutEffect` and handlers into `use-*.ts` hooks. |
12222
+ | `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/**`. |
12223
+ | `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. |
12224
+ | `WL9018` | Exported `RequestHandler` in `src/app/**/controllers` lacks `responseSchema` | Assign `handler.responseSchema` to document each response status. |
12225
+
12226
+ Web rules apply to `src/web/**/*.tsx`. Components are top-level JSX-returning functions
12227
+ with a capitalized name (or an anonymous default export). Hook files and ordinary helpers
12228
+ are excluded. Handler checks recognize inline JSX callbacks, `handle*`/`on*` functions,
12229
+ and local function values referenced by event props. They do not execute code or infer
12230
+ runtime spreads, factory contracts or values imported from other files.
12231
+
12232
+ Create `.warlock-check.json` in the project root:
12233
+
12234
+ ```json
12235
+ {
12236
+ "componentLineLimit": 180,
12237
+ "rules": {
12238
+ "WL9013": "warning",
12239
+ "WL9015": "error",
12240
+ "WL9016": "off"
12241
+ }
12242
+ }
12243
+ ```
12244
+
12245
+ `rules` accepts `"off"`, `"warning"`, or `"error"` for convention codes, including
12246
+ existing codes. Per-line `// warlock-check-ignore WL9015 -- reason` works as before.
12247
+ Use `npx warlock explain WL9015` for the registry explanation.
12248
+
12249
+ ```bash
12250
+ npx warlock check --changed --fix --json
12251
+ ```
12252
+
12253
+ `--changed` selects tracked changes against HEAD plus untracked source files and their
12254
+ transitive import dependents (including re-exports, type imports and related tests).
12255
+ Resolution uses the project's compiler configuration, with relative/app imports as a
12256
+ fallback for files outside that configuration. Lint and conventions receive that selection;
12257
+ Vitest uses related mode. TypeScript still compiles incrementally as a program, then filters
12258
+ diagnostics to the selection and its importers; this is not a partial compilation.
12259
+
12260
+ `--fix` applies safe convention edits before the pipeline. Currently it removes `.js`
12261
+ suffixes from relative imports (`WL9002`). Ignored/disabled findings are not fixed.
12262
+ Component extraction, control replacement and response schemas require human decisions.
12263
+ Problems that have a safe edit include optional `fix: { start, end, text }` in JSON;
12264
+ `start` and `end` are zero-based source string offsets with an exclusive end.
12265
+ The report otherwise keeps schema version 1 and its existing shape. Applied fixes disappear
12266
+ from the final report; unresolved findings remain. Review the resulting source diff.
12267
+
12268
+ Programmatic usage: `await runCheck({ changed: true, fix: true })` from
12269
+ `@warlock.js/core/tooling`.
12270
+
12271
+ ## Explain a source file
11535
12272
 
11536
- ## warlock-conventions `@warlock.js/core/warlock-conventions/SKILL.md`
12273
+ `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.
12274
+
12275
+ Starter controllers declare a response contract for each returned status. For example:
12276
+
12277
+ ```ts
12278
+ contactController.responseSchema = { 200: { body: { message: "string" } } };
12279
+ ```
12280
+
12281
+ 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.
12282
+
12283
+
12284
+ ## warlock-conventions `@warlock.js/core/skills/warlock-conventions/SKILL.md`
11537
12285
 
11538
12286
  ---
11539
12287
  name: warlock-conventions
@@ -11789,7 +12537,7 @@ The comment covers the codes it names on its own line and the next; with no code
11789
12537
  - The `send-response` topic — the full Response helper surface.
11790
12538
 
11791
12539
 
11792
- ## warlock-doctor `@warlock.js/core/warlock-doctor/SKILL.md`
12540
+ ## warlock-doctor `@warlock.js/core/skills/warlock-doctor/SKILL.md`
11793
12541
 
11794
12542
  ---
11795
12543
  name: warlock-doctor
@@ -11972,7 +12720,7 @@ formatting. Oxlint/Oxfmt are the only supported tools in 5.34.0. Use the
11972
12720
  Migrate with `npx warlock upgrade --from 5.32 --to 5.33.0` (preview with `--dry-run`).
11973
12721
 
11974
12722
 
11975
- ## warlock-inspect `@warlock.js/core/warlock-inspect/SKILL.md`
12723
+ ## warlock-inspect `@warlock.js/core/skills/warlock-inspect/SKILL.md`
11976
12724
 
11977
12725
  ---
11978
12726
  name: warlock-inspect
@@ -11989,9 +12737,9 @@ npx warlock inspect --json > app.json # the full report
11989
12737
  npx warlock inspect --json --section routes,models # only some sections
11990
12738
  ```
11991
12739
 
11992
- 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.
12740
+ 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.
11993
12741
 
11994
- Sections: `routes`, `models`, `resources`, `config`, `events`, `pages`, `jobs`, `schedules`, `packages`. An unknown section fails before the app boots.
12742
+ Sections: `routes`, `models`, `resources`, `config`, `events`, `pages`, `jobs`, `schedules`, `packages`, `files`. An unknown section fails before the app boots.
11995
12743
 
11996
12744
  ## Answer questions with jq
11997
12745
 
@@ -12109,8 +12857,51 @@ const valid = new Ajv2020({ strict: false }).validate(INSPECT_REPORT_JSON_SCHEMA
12109
12857
  - The `warlock-routes` topic — the human route table.
12110
12858
  - The `generate-openapi` topic — the same route schemas as an OpenAPI document.
12111
12859
 
12860
+ ## Session middleware in groups
12861
+
12862
+ `sessionMiddleware({ userTypes: "user" })` publishes the same `warlock.auth`
12863
+ descriptor as `authMiddleware()`. Group middleware is inherited by nested
12864
+ routes, so `inspect` and the generated `agents-md` summary recognize these guards.
12865
+ `{ optional: true }` reports `auth.required: false`, with sources and user types
12866
+ retained. Such routes remain in the unguarded-write list. Legacy descriptors
12867
+ without `required` still mean required authentication.
12868
+
12869
+ ```ts
12870
+ import { router } from "@warlock.js/core";
12871
+ import { sessionMiddleware } from "@warlock.js/auth";
12872
+
12873
+ router.group({ middleware: [sessionMiddleware({ userTypes: "user" })] }, () => {
12874
+ router.post("/settings", async ({ response }) => response.success({ saved: true }));
12875
+ });
12876
+ ```
12877
+
12878
+ ## Source files and reachability
12879
+
12880
+ ```bash
12881
+ npx warlock inspect --section files --json
12882
+ npx warlock explain src/app/posts/services/list.service.ts
12883
+ npx warlock explain src/web/components/Post.tsx --json
12884
+ npx warlock explain WL3001 --json
12885
+ ```
12886
+
12887
+ 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.
12888
+
12889
+ 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`.
12112
12890
 
12113
- ## warlock-mcp `@warlock.js/core/warlock-mcp/SKILL.md`
12891
+ 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.
12892
+
12893
+ ```ts
12894
+ import { inspectApp, explainFile } from "@warlock.js/core/tooling";
12895
+
12896
+ const report = await inspectApp({ sections: ["files"] });
12897
+ const source = explainFile("src/app/posts/services/list.service.ts", report);
12898
+ console.log(source.role, source.exports, source.importedBy);
12899
+ ```
12900
+
12901
+ 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.
12902
+
12903
+
12904
+ ## warlock-mcp `@warlock.js/core/skills/warlock-mcp/SKILL.md`
12114
12905
 
12115
12906
  ---
12116
12907
  name: warlock-mcp
@@ -12233,8 +13024,12 @@ await server.close();
12233
13024
  - The `warlock-trace` topic — send a request and read its timeline.
12234
13025
  - The `expose-as-mcp-server` topic of @warlock.js/ai-tools — the transport underneath.
12235
13026
 
13027
+ ## Source file graph
13028
+
13029
+ 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.
12236
13030
 
12237
- ## warlock-routes `@warlock.js/core/warlock-routes/SKILL.md`
13031
+
13032
+ ## warlock-routes `@warlock.js/core/skills/warlock-routes/SKILL.md`
12238
13033
 
12239
13034
  ---
12240
13035
  name: warlock-routes
@@ -12374,11 +13169,11 @@ and exits non-zero.
12374
13169
  - The `write-cli-command` topic — the command + `preload` shape `routesCommand` is built from.
12375
13170
 
12376
13171
 
12377
- ## warlock-trace `@warlock.js/core/warlock-trace/SKILL.md`
13172
+ ## warlock-trace `@warlock.js/core/skills/warlock-trace/SKILL.md`
12378
13173
 
12379
13174
  ---
12380
13175
  name: warlock-trace
12381
- description: 'Verify runtime behaviour without a browser: `warlock trace <method> <url> [--body json|@file] [--header k:v] [--json]` sends one request to the running `warlock dev` server and prints what devtools recorded for it — middleware chain with timings, validation result and errors, handler time, queries with N+1 flags, logs, cache operations, captured mails, the response and timings — as a versioned report (`schemaVersion: 1`); `traceRequest()` returns the same object. Triggers: `warlock trace`, `traceRequest`, `TraceReport`, `buildTraceReport`, `x-request-id`, `.warlock/dev-server.json`, `WL8008`, `WL8009`, `WL5004`, `--id-header`; "did validation pass for this request", "which queries does this endpoint run", "is there an N+1 on this route", "what middleware runs on this route", "check the endpoint after my change", "request timeline as JSON". Skip: the devtools dashboard in a browser — the `devtools-overview` skill of @warlock.js/devtools; static route facts (guard, rate limit, schemas) — the `warlock-inspect` topic; typecheck/lint/tests — the `warlock-check` topic.'
13176
+ description: 'Verify runtime behaviour without a browser: `warlock trace <method> <url> [--body json|@file] [--header k:v] [--json]` sends one request to the running `warlock dev` server and prints what devtools recorded for it — middleware chain with timings, validation result and errors, handler time, queries with N+1 flags, logs, cache operations, captured mails, the response and timings — as a versioned report (`schemaVersion: 1`); `traceRequest()` returns the same object. Triggers: `warlock trace`, `traceRequest`, `TraceReport`, `buildTraceReport`, `x-request-id`, `.warlock/dev-server.json`, `WL8008`, `WL8009`, `WL5004`, `--id-header`; "did validation pass for this request", "which queries does this endpoint run", "is there an N+1 on this route", "what middleware runs on this route", "check the endpoint after my change", "request timeline as JSON". Skip: the devtools dashboard in a browser — the `warlock-js-devtools` skill; static route facts (guard, rate limit, schemas) — the `warlock-inspect` topic; typecheck/lint/tests — the `warlock-check` topic.'
12382
13177
  ---
12383
13178
 
12384
13179
  # Warlock — `warlock trace`
@@ -12456,10 +13251,10 @@ Devtools is checked before anything is sent. With `--json` a failure prints `{ "
12456
13251
 
12457
13252
  - The `warlock-inspect` topic — the static facts of a route (guard, rate limit, schemas).
12458
13253
  - The `warlock-check` topic — typecheck, lint, tests and conventions in one run.
12459
- - The `devtools-overview` skill of `@warlock.js/devtools` — the dashboard behind the timeline.
13254
+ - The `warlock-js-devtools` skill — the dashboard behind the timeline.
12460
13255
 
12461
13256
 
12462
- ## wire-socket `@warlock.js/core/wire-socket/SKILL.md`
13257
+ ## wire-socket `@warlock.js/core/skills/wire-socket/SKILL.md`
12463
13258
 
12464
13259
  ---
12465
13260
  name: wire-socket
@@ -12770,7 +13565,7 @@ CLI commands, scheduled jobs, queue workers — anything running outside an HTTP
12770
13565
  - The `warlock-conventions` topic — where socket-related code lives (a setup function for handler registration, `services/` for emit sites).
12771
13566
 
12772
13567
 
12773
- ## write-cli-command `@warlock.js/core/write-cli-command/SKILL.md`
13568
+ ## write-cli-command `@warlock.js/core/skills/write-cli-command/SKILL.md`
12774
13569
 
12775
13570
  ---
12776
13571
  name: write-cli-command
@@ -13111,11 +13906,29 @@ Set a unique owner and registry per app instance, then import `processes` from
13111
13906
  the generated module and await `processes.spawn({ kind: "worker", command,
13112
13907
  args, labels })`. Consume output and await `handle.exited`. Use handle stops or
13113
13908
  label filters; never stop processes by command or name. `spawnPty()` lazily
13114
- loads separately installed optional peer `node-pty`. The `supervise-processes`
13115
- skill in `@warlock.js/process` contains complete working examples.
13909
+ loads separately installed optional peer `node-pty`. The `warlock-js-process`
13910
+ skill contains complete working examples.
13911
+
13912
+ ## Add the shared design system
13913
+
13914
+ ```bash
13915
+ npx warlock add tailwind
13916
+ npx warlock add design-system
13917
+ ```
13918
+
13919
+ `design-system` refuses before dependency installation when Tailwind is absent
13920
+ from `package.json` or `src/web/app.css`. It adds pinned shadcn primitives and
13921
+ Mongez controls to `src/web/design-system`, preserving existing source files.
13922
+ Use `--no-install` to record dependencies only, or `--dry-run --json` to preview
13923
+ files and dependencies without writing. For example, a generated control is
13924
+ `import TextInput from "web/design-system/components/text-input";` with
13925
+ `<TextInput id="profile-name" name="name" label="Name" />`.
13926
+ See Web's `build-accessible-controls` topic for the complete control contract.
13927
+
13928
+ Built-in `npx warlock add notifications` preloads `config: ["app"]` so generated locale stubs respect `app.localeCodes`, without bootstrap hooks or connectors.
13116
13929
 
13117
13930
 
13118
- ## write-middleware `@warlock.js/core/write-middleware/SKILL.md`
13931
+ ## write-middleware `@warlock.js/core/skills/write-middleware/SKILL.md`
13119
13932
 
13120
13933
  ---
13121
13934
  name: write-middleware
@@ -13343,7 +14156,7 @@ export const optionalAuth: Middleware = async ({ request, response }) => {
13343
14156
  - The `warlock-conventions` topic — the `guarded()` / `guardedAdmin()` / `publicRoutes()` convention.
13344
14157
 
13345
14158
 
13346
- ## write-seeder `@warlock.js/core/write-seeder/SKILL.md`
14159
+ ## write-seeder `@warlock.js/core/skills/write-seeder/SKILL.md`
13347
14160
 
13348
14161
  ---
13349
14162
  name: write-seeder
@@ -13738,7 +14551,7 @@ warlock seed --transaction # (default true) — pass --transaction=false to s
13738
14551
  - The `warlock-conventions` topic — `seeds/` is the canonical folder name (singular `seed/` is incorrect).
13739
14552
 
13740
14553
 
13741
- ## write-use-case `@warlock.js/core/write-use-case/SKILL.md`
14554
+ ## write-use-case `@warlock.js/core/skills/write-use-case/SKILL.md`
13742
14555
 
13743
14556
  ---
13744
14557
  name: write-use-case