@jterrazz/test 16.1.1 → 18.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (309) hide show
  1. package/README.md +45 -171
  2. package/dist/browser/index.js +1 -2980
  3. package/dist/browser/services.js +1 -0
  4. package/dist/browser/stubs.js +1 -0
  5. package/dist/browser-shared-index.browser.js +3233 -0
  6. package/dist/checker.d.ts +6 -1
  7. package/dist/checker.js +3721 -2624
  8. package/dist/config.d.ts +40 -0
  9. package/dist/config.js +309 -0
  10. package/dist/in-place.global-setup.d.ts +12 -0
  11. package/dist/in-place.global-setup.js +21 -0
  12. package/dist/index.d.ts +1641 -3004
  13. package/dist/index.js +5095 -3835
  14. package/dist/mobile-session.global-setup.d.ts +15 -0
  15. package/dist/mobile-session.global-setup.js +31 -0
  16. package/dist/oxlint.cjs +6069 -5634
  17. package/dist/oxlint.d.cts +43 -7212
  18. package/dist/oxlint.d.ts +43 -7212
  19. package/dist/oxlint.js +3979 -5799
  20. package/dist/route-coverage.global-setup.d.ts +43 -0
  21. package/dist/route-coverage.global-setup.js +1 -0
  22. package/dist/services.d.ts +3 -0
  23. package/dist/services.global-setup.d.ts +43 -0
  24. package/dist/services.global-setup.js +1 -0
  25. package/dist/services.js +597 -0
  26. package/dist/shared-appium.adapter.js +428 -0
  27. package/dist/shared-artifacts.js +34 -0
  28. package/dist/shared-chapter.js +7 -0
  29. package/dist/shared-container.port.d.ts +31 -0
  30. package/dist/shared-derived-projects.js +73 -0
  31. package/dist/shared-device.port.d.ts +189 -0
  32. package/dist/shared-exchange.js +447 -0
  33. package/dist/shared-in-place.js +172 -0
  34. package/dist/shared-index.services.d.ts +346 -0
  35. package/dist/shared-index.stubs.d.ts +287 -0
  36. package/dist/shared-json-shape.js +17 -0
  37. package/dist/shared-landmarks.js +71 -0
  38. package/dist/shared-ledger.js +68 -0
  39. package/dist/shared-listened-port.js +19 -0
  40. package/dist/shared-literate-plugin.js +184 -0
  41. package/dist/shared-manifest.js +1321 -0
  42. package/dist/shared-pause.js +22 -0
  43. package/dist/shared-peer.js +88 -0
  44. package/dist/shared-playwright.adapter.js +328 -0
  45. package/dist/shared-process-alive.js +13 -0
  46. package/dist/shared-process.adapter.js +74 -0
  47. package/dist/shared-record-key.js +13 -0
  48. package/dist/shared-refusal.js +35 -0
  49. package/dist/shared-resolve.js +24 -0
  50. package/dist/shared-route-coverage.global-setup.js +187 -0
  51. package/dist/shared-scenario-failure.js +198 -0
  52. package/dist/shared-serve.js +407 -0
  53. package/dist/shared-service-images.js +10 -0
  54. package/dist/shared-services.global-setup.js +174 -0
  55. package/dist/shared-session-ledger.js +305 -0
  56. package/dist/shared-setup-entry.js +12 -0
  57. package/dist/shared-source-scan.js +133 -0
  58. package/dist/shared-spec-document.js +303 -0
  59. package/dist/shared-spec-grammar.js +66 -0
  60. package/dist/shared-specifications.js +26 -0
  61. package/dist/shared-specs-modules.js +1033 -0
  62. package/dist/shared-stub-reply.js +167 -0
  63. package/dist/shared-stub-server.js +142 -0
  64. package/dist/shared-temp-root.global-setup.js +73 -0
  65. package/dist/shared-test-baseline.js +26 -0
  66. package/dist/shared-test-section.js +58 -0
  67. package/dist/shared-testcontainers.adapter.js +306 -0
  68. package/dist/shared-tokens.js +55 -0
  69. package/dist/shared-yaml-document.js +119 -0
  70. package/dist/stubs.d.ts +2 -0
  71. package/dist/stubs.js +657 -0
  72. package/dist/temp-root.global-setup.d.ts +7 -0
  73. package/dist/temp-root.global-setup.js +1 -0
  74. package/dist/vitest.d.ts +93 -164
  75. package/dist/vitest.js +382 -550
  76. package/docs/README.md +82 -36
  77. package/docs/concepts/architecture.md +118 -0
  78. package/docs/concepts/conventions.md +333 -0
  79. package/docs/guides/api-specs.md +374 -0
  80. package/docs/guides/cli-specs.md +566 -0
  81. package/docs/guides/component-specs.md +252 -0
  82. package/docs/guides/develop.md +97 -0
  83. package/docs/guides/integration-specs.md +181 -0
  84. package/docs/guides/job-specs.md +180 -0
  85. package/docs/guides/lint-specs.md +174 -0
  86. package/docs/guides/mobile-specs.md +319 -0
  87. package/docs/guides/module-tests.md +149 -0
  88. package/docs/guides/release.md +44 -0
  89. package/docs/guides/spec-documents.md +189 -0
  90. package/docs/guides/website-specs.md +413 -0
  91. package/docs/reference/assertions.md +345 -0
  92. package/docs/reference/glossary.md +20 -0
  93. package/docs/reference/linting.md +483 -0
  94. package/docs/reference/services.md +236 -0
  95. package/docs/reference/stubs.md +500 -0
  96. package/docs/reference/time-and-doubles.md +137 -0
  97. package/docs/reference/tokens.md +258 -0
  98. package/docs/reference/visitor.md +221 -0
  99. package/docs/start/getting-started.md +187 -0
  100. package/docs/start/set-up-a-project.md +228 -0
  101. package/package.json +56 -36
  102. package/schema/spec.schema.json +43 -17
  103. package/dist/appium.adapter.js +0 -489
  104. package/dist/artifacts.js +0 -32
  105. package/dist/fixtures.js +0 -110
  106. package/dist/literate.js +0 -1747
  107. package/dist/match.js +0 -804
  108. package/dist/playwright.adapter.js +0 -438
  109. package/dist/server.js +0 -160
  110. package/dist/stream.js +0 -321
  111. package/dist/substring-warning.js +0 -282
  112. package/docs/01-architecture.md +0 -95
  113. package/docs/02-developing.md +0 -351
  114. package/docs/03-testing.md +0 -495
  115. package/docs/04-operating.md +0 -101
  116. package/docs/05-module-tests.md +0 -152
  117. package/docs/06-integration.md +0 -180
  118. package/docs/07-component.md +0 -246
  119. package/docs/08-website.md +0 -404
  120. package/docs/09-mobile.md +0 -270
  121. package/docs/10-api.md +0 -264
  122. package/docs/11-jobs.md +0 -157
  123. package/docs/12-cli.md +0 -677
  124. package/docs/13-elements.md +0 -248
  125. package/docs/14-assertions.md +0 -314
  126. package/docs/15-tokens.md +0 -253
  127. package/docs/16-contracts.md +0 -438
  128. package/docs/17-services.md +0 -188
  129. package/docs/18-conventions.md +0 -332
  130. package/docs/19-linting.md +0 -512
  131. package/docs/decisions/001-v9-conventions-on-three-channels.md +0 -50
  132. package/docs/decisions/002-the-statique-channel-holds-only-what-upstream-does-not.md +0 -49
  133. package/docs/decisions/003-a-rendered-component-is-a-facet-on-vitest-browser-mode.md +0 -84
  134. package/docs/decisions/004-a-module-against-real-services-or-a-golden-is-the-integration-facet.md +0 -81
  135. package/docs/decisions/005-the-doubles-ladder-is-closed.md +0 -86
  136. package/docs/decisions/006-the-catalogue-reaches-a-file-by-its-role.md +0 -245
  137. package/docs/decisions/007-compose-mode-leaves-the-package.md +0 -53
  138. package/docs/decisions/008-vitest-5-node-24-and-the-native-seams-as-optional-peers.md +0 -75
  139. package/docs/decisions/_template.md +0 -22
  140. package/docs/reference/classes/Matcher.md +0 -120
  141. package/docs/reference/classes/ProcessHandle.md +0 -368
  142. package/docs/reference/functions/defineContract.md +0 -39
  143. package/docs/reference/functions/defineContracts.md +0 -36
  144. package/docs/reference/functions/disabled.md +0 -28
  145. package/docs/reference/functions/enabled.md +0 -20
  146. package/docs/reference/functions/focused.md +0 -26
  147. package/docs/reference/functions/mockOf.md +0 -79
  148. package/docs/reference/functions/postgres.md +0 -31
  149. package/docs/reference/functions/process.md +0 -33
  150. package/docs/reference/functions/redis.md +0 -28
  151. package/docs/reference/functions/required.md +0 -41
  152. package/docs/reference/functions/selected.md +0 -26
  153. package/docs/reference/functions/sqlite.md +0 -33
  154. package/docs/reference/functions/testId.md +0 -20
  155. package/docs/reference/functions/text.md +0 -40
  156. package/docs/reference/functions/valued.md +0 -28
  157. package/docs/reference/functions/waitUntil.md +0 -46
  158. package/docs/reference/functions/within.md +0 -29
  159. package/docs/reference/index.md +0 -196
  160. package/docs/reference/interfaces/BaseResult.md +0 -87
  161. package/docs/reference/interfaces/CallResult.md +0 -160
  162. package/docs/reference/interfaces/CaptureScope.md +0 -82
  163. package/docs/reference/interfaces/CliResult.md +0 -253
  164. package/docs/reference/interfaces/ContainerAccessor.md +0 -166
  165. package/docs/reference/interfaces/DirectoryAccessor.md +0 -33
  166. package/docs/reference/interfaces/FetchResult.md +0 -180
  167. package/docs/reference/interfaces/FilesystemAccessor.md +0 -46
  168. package/docs/reference/interfaces/HttpResult.md +0 -126
  169. package/docs/reference/interfaces/JsonAccessor.md +0 -46
  170. package/docs/reference/interfaces/PageResult.md +0 -356
  171. package/docs/reference/interfaces/RenderResult.md +0 -72
  172. package/docs/reference/interfaces/RenderedText.md +0 -110
  173. package/docs/reference/interfaces/ResponseAccessor.md +0 -47
  174. package/docs/reference/interfaces/ScreenResult.md +0 -128
  175. package/docs/reference/interfaces/TableAccessor.md +0 -26
  176. package/docs/reference/interfaces/TextAccessor.md +0 -96
  177. package/docs/reference/type-aliases/ApiHandle.md +0 -68
  178. package/docs/reference/type-aliases/ApiSpecification.md +0 -213
  179. package/docs/reference/type-aliases/ApiSpecificationOptions.md +0 -68
  180. package/docs/reference/type-aliases/BrowserConsoleMessage.md +0 -30
  181. package/docs/reference/type-aliases/BrowserLinkElement.md +0 -50
  182. package/docs/reference/type-aliases/BrowserMetaElement.md +0 -40
  183. package/docs/reference/type-aliases/BrowserOpenOptions.md +0 -87
  184. package/docs/reference/type-aliases/BrowserPage.md +0 -132
  185. package/docs/reference/type-aliases/BrowserPort.md +0 -51
  186. package/docs/reference/type-aliases/CliEnv.md +0 -11
  187. package/docs/reference/type-aliases/CliHandle.md +0 -80
  188. package/docs/reference/type-aliases/CliInput.md +0 -36
  189. package/docs/reference/type-aliases/CliOutput.md +0 -46
  190. package/docs/reference/type-aliases/CliPort.md +0 -58
  191. package/docs/reference/type-aliases/CliSpecification.md +0 -144
  192. package/docs/reference/type-aliases/CliSpecificationOptions.md +0 -134
  193. package/docs/reference/type-aliases/ComponentChain.md +0 -156
  194. package/docs/reference/type-aliases/ComponentScenario.md +0 -20
  195. package/docs/reference/type-aliases/ComponentUi.md +0 -47
  196. package/docs/reference/type-aliases/ComponentVisitor.md +0 -10
  197. package/docs/reference/type-aliases/ContainerPort.md +0 -113
  198. package/docs/reference/type-aliases/Contract.md +0 -68
  199. package/docs/reference/type-aliases/ContractInput.md +0 -13
  200. package/docs/reference/type-aliases/ContractRequest.md +0 -95
  201. package/docs/reference/type-aliases/ContractResponder.md +0 -23
  202. package/docs/reference/type-aliases/ContractResponse.md +0 -78
  203. package/docs/reference/type-aliases/ContractResponseValue.md +0 -13
  204. package/docs/reference/type-aliases/Contracts.md +0 -48
  205. package/docs/reference/type-aliases/DatabaseKeys.md +0 -16
  206. package/docs/reference/type-aliases/DatabasePort.md +0 -72
  207. package/docs/reference/type-aliases/DeviceOpenOptions.md +0 -46
  208. package/docs/reference/type-aliases/DevicePort.md +0 -51
  209. package/docs/reference/type-aliases/DeviceScreen.md +0 -32
  210. package/docs/reference/type-aliases/DeviceTimeouts.md +0 -39
  211. package/docs/reference/type-aliases/DockerSpecConfig.md +0 -43
  212. package/docs/reference/type-aliases/DomMount.md +0 -21
  213. package/docs/reference/type-aliases/ElementKind.md +0 -27
  214. package/docs/reference/type-aliases/ElementMatch.md +0 -74
  215. package/docs/reference/type-aliases/ElementOptions.md +0 -29
  216. package/docs/reference/type-aliases/ElementRef.md +0 -128
  217. package/docs/reference/type-aliases/ExecOptions.md +0 -37
  218. package/docs/reference/type-aliases/FileAccessor.md +0 -54
  219. package/docs/reference/type-aliases/HonoApp.md +0 -31
  220. package/docs/reference/type-aliases/HttpContractFilter.md +0 -49
  221. package/docs/reference/type-aliases/HttpResponseInit.md +0 -46
  222. package/docs/reference/type-aliases/HttpStreamInit.md +0 -58
  223. package/docs/reference/type-aliases/IntegrationHandle.md +0 -46
  224. package/docs/reference/type-aliases/IntegrationSpecification.md +0 -111
  225. package/docs/reference/type-aliases/IntegrationSpecificationOptions.md +0 -42
  226. package/docs/reference/type-aliases/Intercept.md +0 -19
  227. package/docs/reference/type-aliases/InterceptOptions.md +0 -28
  228. package/docs/reference/type-aliases/InterceptScope.md +0 -11
  229. package/docs/reference/type-aliases/IsolationStrategy.md +0 -75
  230. package/docs/reference/type-aliases/JobHandle.md +0 -34
  231. package/docs/reference/type-aliases/JobsHandle.md +0 -57
  232. package/docs/reference/type-aliases/JobsResult.md +0 -16
  233. package/docs/reference/type-aliases/JobsSpecification.md +0 -94
  234. package/docs/reference/type-aliases/JobsSpecificationOptions.md +0 -56
  235. package/docs/reference/type-aliases/LandmarkKind.md +0 -20
  236. package/docs/reference/type-aliases/LiterateRunFlags.md +0 -27
  237. package/docs/reference/type-aliases/LiterateServeRegistration.md +0 -70
  238. package/docs/reference/type-aliases/MatchFixtureOptions.md +0 -27
  239. package/docs/reference/type-aliases/MatchableRequest.md +0 -61
  240. package/docs/reference/type-aliases/MatcherKind.md +0 -34
  241. package/docs/reference/type-aliases/MobileBackendOptions.md +0 -28
  242. package/docs/reference/type-aliases/MobileElementKind.md +0 -14
  243. package/docs/reference/type-aliases/MobileElementMatch.md +0 -60
  244. package/docs/reference/type-aliases/MobileElementRef.md +0 -20
  245. package/docs/reference/type-aliases/MobileHandle.md +0 -65
  246. package/docs/reference/type-aliases/MobileScenario.md +0 -20
  247. package/docs/reference/type-aliases/MobileSpecification.md +0 -51
  248. package/docs/reference/type-aliases/MobileSpecificationOptions.md +0 -126
  249. package/docs/reference/type-aliases/MobileVisitor.md +0 -81
  250. package/docs/reference/type-aliases/MockOfOptions.md +0 -26
  251. package/docs/reference/type-aliases/MockPort.md +0 -10
  252. package/docs/reference/type-aliases/PinnedClock.md +0 -10
  253. package/docs/reference/type-aliases/PostgresOptions.md +0 -32
  254. package/docs/reference/type-aliases/ProcessOptions.md +0 -112
  255. package/docs/reference/type-aliases/RedisOptions.md +0 -20
  256. package/docs/reference/type-aliases/RenderSubject.md +0 -12
  257. package/docs/reference/type-aliases/ScreenNode.md +0 -67
  258. package/docs/reference/type-aliases/ServerPort.md +0 -36
  259. package/docs/reference/type-aliases/ServerResponse.md +0 -46
  260. package/docs/reference/type-aliases/ServerSpec.md +0 -21
  261. package/docs/reference/type-aliases/ServiceHandle.md +0 -216
  262. package/docs/reference/type-aliases/ServiceRecord.md +0 -12
  263. package/docs/reference/type-aliases/SpecDocument.md +0 -84
  264. package/docs/reference/type-aliases/SpecEnvToken.md +0 -21
  265. package/docs/reference/type-aliases/SpecFileAssertion.md +0 -27
  266. package/docs/reference/type-aliases/SpecFixture.md +0 -30
  267. package/docs/reference/type-aliases/SpecKind.md +0 -8
  268. package/docs/reference/type-aliases/SpecRun.md +0 -108
  269. package/docs/reference/type-aliases/SpecServeEntry.md +0 -40
  270. package/docs/reference/type-aliases/SpecStream.md +0 -32
  271. package/docs/reference/type-aliases/SpecificationConfig.md +0 -290
  272. package/docs/reference/type-aliases/SqliteOptions.md +0 -36
  273. package/docs/reference/type-aliases/SseEvent.md +0 -58
  274. package/docs/reference/type-aliases/StreamBody.md +0 -61
  275. package/docs/reference/type-aliases/TextFilter.md +0 -22
  276. package/docs/reference/type-aliases/VisitScenario.md +0 -20
  277. package/docs/reference/type-aliases/Visitor.md +0 -215
  278. package/docs/reference/type-aliases/WaitUntilOptions.md +0 -46
  279. package/docs/reference/type-aliases/WebsiteBackendOptions.md +0 -37
  280. package/docs/reference/type-aliases/WebsiteHandle.md +0 -51
  281. package/docs/reference/type-aliases/WebsiteSpecification.md +0 -118
  282. package/docs/reference/type-aliases/WebsiteSpecificationOptions.md +0 -40
  283. package/docs/reference/variables/anthropic.md +0 -19
  284. package/docs/reference/variables/banner.md +0 -10
  285. package/docs/reference/variables/button.md +0 -10
  286. package/docs/reference/variables/clock.md +0 -16
  287. package/docs/reference/variables/complementary.md +0 -10
  288. package/docs/reference/variables/component.md +0 -10
  289. package/docs/reference/variables/content.md +0 -10
  290. package/docs/reference/variables/contentinfo.md +0 -10
  291. package/docs/reference/variables/dialog.md +0 -10
  292. package/docs/reference/variables/field.md +0 -10
  293. package/docs/reference/variables/form.md +0 -10
  294. package/docs/reference/variables/heading.md +0 -10
  295. package/docs/reference/variables/http.md +0 -40
  296. package/docs/reference/variables/intercept.md +0 -11
  297. package/docs/reference/variables/link.md +0 -10
  298. package/docs/reference/variables/listitem.md +0 -10
  299. package/docs/reference/variables/main.md +0 -10
  300. package/docs/reference/variables/match.md +0 -8
  301. package/docs/reference/variables/navigation.md +0 -10
  302. package/docs/reference/variables/openai.md +0 -21
  303. package/docs/reference/variables/option.md +0 -17
  304. package/docs/reference/variables/region.md +0 -10
  305. package/docs/reference/variables/row.md +0 -10
  306. package/docs/reference/variables/search.md +0 -10
  307. package/docs/reference/variables/specification.md +0 -8
  308. package/docs/reference/variables/status.md +0 -10
  309. package/docs/reference/variables/table.md +0 -10
@@ -1,332 +0,0 @@
1
- # 18 — Conventions: the constitution
2
-
3
- This chapter is the **constitution**: the principles, the non-mechanizable criteria, and the design rationales behind the conventions. It is hand-maintained and stable.
4
-
5
- The **mechanized per-rule catalogue** does not live here — it is **generated from the code** (`src/lint/manifest.ts`, where each rule carries its own normative text) into the [19 — Linting](19-linting.md) catalogue and the agent-facing [`skills/jterrazz-test/references/rules.md`](../skills/jterrazz-test/references/rules.md).
6
-
7
- This is the **docs-as-code inversion**: the code is the source of truth for the mechanized rules (a rule and its normative text live together, so they cannot drift), and this constitution is the source of truth for the principles. There is **no duplication** — a machine-checkable rule is written once, in the code. The broader repo-layout doctrine this follows — a written corpus, thin injection layers, a compiler that projects; committed projections against CI-built presentations — is `jterrazz-studio`'s repo-structure chapter, its canonical home for every language.
8
-
9
- Guiding aim: **most enforcement is programmatic, not manual review.**
10
-
11
- Which CHANNEL holds a given rule — static, checker, runtime, process, and the meta-test and type channels that double them — is not this chapter's: it is [01 — Architecture](01-architecture.md), where the shape of the enforcement machinery is drawn. What follows is what the rules SAY and what a reviewer must judge.
12
-
13
- ## The rule families
14
-
15
- The catalogue is organized by family. Each family's usage is illustrated in the chapters; the generated catalogue in [19 — Linting](19-linting.md) carries the normative sentence and channel of every rule.
16
-
17
- | Group | Scope | Explained in |
18
- | ----- | -------------------------------------------------------------------- | --------------------------------------------------------------------- |
19
- | A | Runner creation (constructors, services, root) | [02](02-developing.md), [10](10-api.md), [17](17-services.md) |
20
- | B | Spec chains (setups, terminal actions, Given/Then, `job` vocabulary) | [10](10-api.md), [11](11-jobs.md), [12](12-cli.md) |
21
- | C | Files & folders per feature | [02](02-developing.md), [14](14-assertions.md), [16](16-contracts.md) |
22
- | D | Assertions, tokens, snapshots, strict contracts | [14](14-assertions.md), [15](15-tokens.md), [16](16-contracts.md) |
23
- | E | Environment & runner configuration | [02](02-developing.md), [07](07-component.md) |
24
- | F | Imports (single package root) & production protection | [02](02-developing.md) |
25
- | W | Website & mobile specs (scenarios, user-facing elements) | [08](08-website.md), [09](09-mobile.md) |
26
- | G | Infrastructure | [12](12-cli.md), [17](17-services.md), [07](07-component.md) |
27
- | I | Source-code architecture (declared layers, sibling module tests) | below · [01](01-architecture.md) for this repo's own layer map |
28
- | J | Hygiene (no arbitrary sleeps, honest spec documents) | [19](19-linting.md) |
29
- | K | Retro-propagation — every defect class grows its own guard | below |
30
-
31
- ## The reach of the conventions
32
-
33
- The framework and the conventions do not have the same scope, and confusing the two is the commonest misreading of this constitution. `specification.*` — with its seeds, fixtures, contracts, goldens and containers — is for specifying a **surface** something is served through: an HTTP API, a background job, a CLI, a rendered page, a native screen. A plain unit test of a pure function has no such surface and needs none of it.
34
-
35
- A **rendered component** needs no RUNNER — nothing is started, so it has no constructor and no `*.specification.ts` — and it does need a real browser, a Vite pipeline and a network double, every one of which the framework owns. So the fork is the SUBJECT, not the amount of machinery: one unit — a module, a component — sits beside its code and is judged by its suffix (`<file>.test.ts`, `<file>.test.tsx`); the assembled product, reached through an entry, sits under `specs/`. [07 — Component specs](07-component.md) holds the rendered half.
36
-
37
- The conventions bind **every test file of the repository**, that plain unit test included, whether or not `@jterrazz/test` is imported in it. A test is a test: it sits beside the module it covers (I2), it narrates its Given then its Then (B4), it keeps its doubles out of `src/` (I4), and it stays honest under the J hygiene floor — no committed `.only`/`.skip`, an assertion in every test, no two literal titles alike in a file, a lowercase title. That floor is oxlint's own `vitest` plugin, which `@jterrazz/typescript` turns on over the test globs in every profile; this package's J family keeps only what the plugin has no counterpart for — the arbitrary-sleep ban (J2), which reaches every test file, since a sleep beside a module waits as blindly as one under `specs/`, and the spec-document mirrors. Every one of these is mechanized, so its normative sentence lives in the catalogue ([19 — Linting](19-linting.md)) and not here.
38
-
39
- The reason for the wide reach: a repository has **one** way to write a test, so a reader moving between a sibling module test and a spec of a surface reads the same shape and the tooling has a single target. A rule that applied only to framework tests would leave the majority of test files — the plain ones — unguarded.
40
-
41
- ## The third door: a repository suite
42
-
43
- Two doors carry every test the fork names: a UNIT sits beside the module it
44
- covers (`<file>.test.ts`, I2), and the ASSEMBLED product sits under
45
- `specs/<facet>/` and reaches the runner that folder promises (`<aspect>.spec.ts`,
46
- C12 and C18). A few specifications have neither address, and they are not
47
- mistakes.
48
-
49
- ### What has no address of its own
50
-
51
- What they specify is a REPOSITORY's own surface: the subpaths its
52
- `package.json` publishes, a `.d.ts` beside every `.js` it ships, the lint guards
53
- a tree owes, the files a build is expected to write. There is no one module to
54
- sit beside — they resolve every module there is — and no runner to reach, since
55
- nothing is started. Put under a facet folder, C18 refuses them ("reaches no
56
- runner"); put beside a module under `src/`, I2 refuses them ("no neighbour
57
- `exports.ts` found"). Both refusals are right, and a legitimate file shape with
58
- no green position is a hole in the conventions, not a defect in the file.
59
-
60
- ### The door, and the depth it is judged by
61
-
62
- **The third door is a FIRST-LEVEL folder under `specs/` that is not a facet
63
- name.** It is a repository suite: it covers a TREE rather than a product served
64
- through an entry. C12's rename clause does not reach it, C18 and C20 do not
65
- reach it, and C1's declared depth is what judges its shape.
66
-
67
- **C1 judges a suite exactly as it judges a facet.** The suite folder is the
68
- first level and the tests sit one level down, in a folder named for what they
69
- cover: at the default depth, `specs/<suite>/<domain>/<aspect>.test.ts`. A test
70
- written straight into the suite folder is refused for the same reason one at a
71
- facet root is — the level that says what a file covers would be missing. This
72
- package's own `specs/lint/` is two levels deep for that reason.
73
-
74
- ### The two names the estate shares
75
-
76
- Two suites carry names the whole estate shares, because two repositories
77
- inventing two names for one idea is the drift a vocabulary exists to stop: a
78
- package's own published SURFACE is `surface/`, and a repository's own lint
79
- guards are `lint/`. Every other suite is named for what it covers.
80
-
81
- ```
82
- specs/
83
- ├── surface/ # what the package publishes
84
- │ └── package/
85
- │ ├── exports.test.ts # every subpath the manifest resolves
86
- │ └── types.test.ts # a `.d.ts` beside each `.js` it ships
87
- └── lint/ # the guards the repository's own tree owes
88
- └── <family>/
89
- └── <rule>.test.ts
90
- ```
91
-
92
- ### The suffix, and the one thing the door does not license
93
-
94
- The files there carry `.test.ts` — the UNIT's word — because what they prove is
95
- read without assembling anything, and `unit()` is what collects them (`roots`,
96
- or `include` outright). A facet helper would put them in a project whose budget
97
- and services they never use.
98
-
99
- One thing the door does not license: a module test parked under `specs/` to
100
- escape I2. The question to ask of a file before it goes through is whether any
101
- single module could hold its test — if one could, that module is its address.
102
-
103
- ## Process rules (review-borne)
104
-
105
- Three rules cannot be mechanized — they turn on judgement no single channel can settle. They are listed in the catalogue for completeness, but their full rationale lives here.
106
-
107
- ### C1 — the folder follows the assets
108
-
109
- The grouping criterion: a test that owns **its own** asset directories (`_fixtures/`, `_expected/`, `_seeds/`, …) gets **its own** domain folder; specs **without local assets** (or sharing the `$FIXTURES/` pool) group as sibling `<aspect>.spec.ts` files inside a named **group** folder. Both shapes are legal — the assets decide, and a nascent single-test domain is legitimate. The static rule `c1-domain-structure` checks only placement; which of the two shapes is right is the review call.
110
-
111
- Placement itself is **declared**, because a spec tree may legitimately have a shape this package cannot know:
112
-
113
- ```jsonc
114
- "jterrazz/c1-domain-structure": ["error", { "depth": "facet-domain" }] // the default
115
- ```
116
-
117
- | `depth` | The tree |
118
- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
119
- | `'facet-domain'` | the default — `specs/<facet>/<domain>/<aspect>.spec.ts`, `*.specification.ts` at the facet root |
120
- | `'facet'` | the assets decide the folder — a spec at the facet root (`specs/<facet>/<aspect>.spec.ts`) OR one domain down, never deeper; `*.specification.ts` at the facet root |
121
- | `'mirror'` | the tree mirrors a structure outside itself (a command tree, a source tree): a spec at any depth ≥ 1, named `<dir>/<dir>.spec.ts` |
122
- | `'off'` | no placement check — for a tree whose shape is guarded by something stronger and project-specific |
123
-
124
- A project states the shape it has — `facet` when asset-less tests sit beside their siblings at the facet root, `mirror` when the tree mirrors something outside itself — and keeps a checked shape, instead of switching the rule off and keeping none.
125
-
126
- One clause of the rule is not the project's to declare, and holds in every mode, `off` included: **a folder whose name carries a leading underscore is ground, never a domain**, so no spec lives inside one. Depth is a shape a tree may choose; the ground/member split is the naming law recapped in [The naming recap](#the-naming-recap).
127
-
128
- Ground is not always inert. It may be **code** the specs stand on — the build of the subject under test, a harness the runner spawns — and code carries its unit test as a sibling under [I2](#i--source-code-architecture). So the clause lets exactly one pairing through: `<module>.test.ts` NEXT to the `<module>.ts` it is named after, inside the ground it belongs to. A test with no module beside it, or a `*.specification.ts`, is a spec that wandered in and is still reported — which is why the clause needs no `off`, and why no project has to switch off a rule it cannot switch off.
129
-
130
- ### D11 — golden-file, not a cluster of greps
131
-
132
- A tool's output (a linter, a compiler, a product CLI) is asserted as a **full snapshot per scoped use case**. Each case gets **its own fixture project** (its small valid/invalid files) — the fixture IS the Given, no shared `beforeAll` state — and the assertion is the whole snapshot (`expect(result.stdout).toMatch('<use-case>.txt')` + `exitCode`), volatile parts covered by tokens, generated with `TEST_UPDATE=1`.
133
-
134
- `.grep()` / `toContain` remain the **scalpel** — targeted probes, never the default mode. They are legitimate only for:
135
-
136
- - **(a)** assertions of **absence**;
137
- - **(b)** output **cut at an arbitrary instant** (`waitFor`, a long-running process);
138
- - **(c)** **container-log probes**;
139
- - **(d)** asserting **rule ids** in the E2E lint specs (avoiding coupling to a third-party binary's exact format);
140
- - **(e)** probes into **third-party-formatted** output.
141
-
142
- Every other use is converted to a full snapshot. A single "kitchen-sink" project + full snapshot serves as the whole-surface regression net (it churns — that is its job). The static channel cannot tell a legitimate grep from a lazy one, hence the process channel. The mechanized boundary for API responses (an amas of raw `.response.body` probes, or a lone HTTP-status probe) is caught by `d12w`/`d15w`; the negative-fixture guard is `d13w` (wrapped forms) plus a process rule for helper-routed residue.
143
-
144
- ### K1 — retro-propagation
145
-
146
- Every defect class discovered (review, bug, migration) grows, **in the same change**, the guard that stops it recurring — a static rule, a meta-test, or a runtime error — or an explicit note of why no channel is possible (e.g. "redundant test" is a human judgement). This is the rule that keeps the other six channels growing instead of decaying. When a defect class is mechanizable, its rule joins `src/lint/manifest.ts` and the catalogue regenerates.
147
-
148
- ## The naming recap
149
-
150
- One rule decides every folder of a spec tree: **what a spec stands on carries the underscore; a spec's own folder never does.** The four names are `_fixtures/`, `_expected/`, `_requests/` and `_seeds/` — inert material the framework resolves by path. A facet, a domain, and `contracts/` are members of the row, not ground: a contract is TypeScript a spec imports, so it stays bare. `docker/` sits at the project root, outside any row of specs, and is untouched by the rule.
151
-
152
- Two further rules decide WHERE a fixture lives, and they are the same question asked twice. **The pool is for what several leaves share**: a directory of `specs/_fixtures/` that exactly one spec directory reaches for belongs beside that leaf, as `<leaf>/_fixtures/<name>/` (C14, autofixed by `jterrazz-test-check --fix`). **A leaf's own ground is reached only from that leaf**: a `.fixture()` path that climbs out of the referring spec's `_fixtures/` is an error, and the sharing it wants is what the pool declares (C15).
153
-
154
- | Thing | Rule |
155
- | --------------- | --------------------------------------------------------------------------------------------------------------------------- |
156
- | Specs root | `specs/` (`api/`, `jobs/`, `cli/`, `integration/`, `website/`, `mobile/`, `_fixtures/`) — one folder per constructor |
157
- | Specification | `specs/<facet>/<name>.specification.ts` (at the facet root) |
158
- | Instances | `api`, `jobs`, `cli`, `website`, `mobile` — enforced by the destructuring (A3) |
159
- | Spec file | `specs/<facet>/<domain>/<aspect>.spec.ts` — the assembled product's word (C12) |
160
- | Spec document | `<case>.spec.yaml`, beside the spec it belongs to — never under `_expected/` ([12](12-cli.md#spec-documents--casespecyaml)) |
161
- | Module test | `<file>.test.ts`, sibling of `<file>.ts` (under `src/`) |
162
- | Component test | `<file>.test.tsx`, sibling of `<file>.tsx` — or of the `<file>.ts` of a hook or a DOM function ([07](07-component.md)) |
163
- | Module fixtures | `<file>.fixtures.ts`, sibling of the `.test.ts` (typed exports) |
164
- | Contracts | `contracts/<name>.contracts.ts` (facade) · `contracts/<provider>/<name>.ts` (unit, provider ∈ http\|openai\|anthropic) |
165
- | Contract data | `contracts/<provider>/<name>[.<qualifier>].response.json` (served) · `<name>.request.ts` (matched) |
166
- | Requests | `_requests/<name>.http` (inputs) |
167
- | Seeds | `_seeds/<name>.sql` (database state) |
168
- | Fixtures | `_fixtures/<name>` (file state) — the leaf's own; the shared pool is `specs/_fixtures/`, reached by `$FIXTURES/` |
169
- | Snapshots | `_expected/<name>` (all expected, flat, extension included — incl. response `.http`) |
170
- | Service keys | name the service: the report prints the key, and its kebab-case form is the folder its init script sits in |
171
- | Framework env | `TEST_UPDATE` |
172
-
173
- ## I — Source-code architecture
174
-
175
- Family I governs the source tree rather than the spec tree, and it splits in two.
176
-
177
- **The layer map is the project's to declare.** `i1-layer-boundaries` ships **inert**: this package cannot know another repository's architecture, so the rule enforces nothing until the project states its own layers in `oxlint.config.ts`. What THIS repository declares there, and why each sanctioned edge exists, is [01 — Architecture](01-architecture.md) — a consumer's own map is its own chapter's, written the same way.
178
-
179
- **The test-file rules need no declaration.** I2 (a module's test is its sibling) and I4 (no `vi.mock`, `__mocks__/`, `__fixtures__/` or data-asset imports under `src/`) hold in any repository that adopts this preset — they are part of the floor described above, not of the layer map. A module's typed fixtures are a sibling `<file>.fixtures.ts`, as the naming recap says.
180
-
181
- ## The fork — which kind of test this is
182
-
183
- The questions are asked IN ORDER, and the first yes is the kind. The answer fixes the folder, the constructor (or none), and the project. The table is generated from `src/lint/cards.ts`, where the signature cards read the same rows.
184
-
185
- <!-- GENERATED:fork — do not edit by hand; run `npm run docs`. Source: src/lint/cards.ts -->
186
-
187
- | Question | Kind | Lives at | Constructor | Project |
188
- | ---------------------------------------------------------------------- | ---------------- | --------------------------------------------- | ----------------------------- | ------------- |
189
- | Does it render in a browser as a whole served page? | website | `specs/website/<domain>/<aspect>.spec.ts` | `specification.website()` | `website` |
190
- | Does it render as a component (React, a DOM function, a hook)? | component | `<file>.test.tsx` beside `<file>.tsx` | none — the `component` chain | `component` |
191
- | Does it render on a simulator? | mobile | `specs/mobile/<domain>/<aspect>.spec.ts` | `specification.mobile()` | `mobile` |
192
- | Does it answer HTTP? | api | `specs/api/<domain>/<aspect>.spec.ts` | `specification.api()` | `api` |
193
- | Is it triggered by name, in-process? | jobs | `specs/jobs/<domain>/<aspect>.spec.ts` | `specification.jobs()` | `jobs` |
194
- | Is it a binary? | cli | `specs/cli/<domain>/` (+ `<case>.spec.yaml`) | `specification.cli(bin)` | `cli` |
195
- | Is it a module that needs a real service, or whose oracle is a golden? | integration | `specs/integration/<domain>/<aspect>.spec.ts` | `specification.integration()` | `integration` |
196
- | Is it a module alone? | module | `<file>.test.ts` beside `<file>.ts` | none | `unit` |
197
- | Is it the repository itself — a suite over several apps? | repository suite | `specs/<family>/<aspect>.test.ts` | none | any name |
198
-
199
- <!-- /GENERATED:fork -->
200
-
201
- The SUFFIX says the kind, and the checker holds it (rule C12): `.test.ts(x)`
202
- beside the code for a unit, `.spec.ts` under `specs/<facet>/` for the assembled
203
- product, `<case>.spec.yaml` for a literate document, `<facet>.specification.ts`
204
- for the file that builds a runner. A repository suite is the one tree under
205
- `specs/` that keeps `.test.ts`: its first level is not a facet, it covers a tree
206
- rather than a product, and C1's declared depth is what judges its shape.
207
-
208
- The fork is the SUBJECT, never the amount of machinery. A rendered component needs a real browser, a Vite pipeline, a network double and a golden engine — all of them the framework's — and it is still a unit, so it sits beside its code. A module that needs a real database needs nothing new at all, and it is still an assembled thing, so it sits under `specs/` ([06](06-integration.md)).
209
-
210
- The table is total: **a folder under `specs/` is a constructor's name, and there is no ninth row**. A probe of an adapter against a real service — a database driver, a cache handle — answers the seventh question, so it is an integration spec like any other; what it cannot reach through the package's public entry it should not be reaching from a spec at all, and that probe is a module test beside its module (rule F3).
211
-
212
- ## The doubles ladder
213
-
214
- Closed and ordered. An agent takes the FIRST rung that fits, and nothing below rung four is a rung.
215
-
216
- 1. **The real thing**, in-process — a pure module, a Hono app handed to `server`, a component mounted in a real Chromium.
217
- 2. **A declared service** — `sqlite()`, `postgres()`, `redis()`, `process()` ([17](17-services.md)): real, isolated per worker, reset at the start of every chain.
218
- 3. **A contract** — `.intercept()` on a chain (api, jobs, integration, component, website, mobile), `intercept()` in module scope ([16](16-contracts.md)). One queue, three transports, strict from the first contract (rule D7); a streamed body is `http.stream` / `http.sse`.
219
- 4. **A typed port double** — `mockOf<Port>()` for an injected interface, `vi.fn<Fn>()` for a function-shaped dependency. Asserting a call on one is a boundary observation; asserting ONLY on doubles the test built is not a test of anything. `T` is constrained to `object` and has no default, so a double asked for nothing answers for `object` — TypeScript falls back to the constraint where no inference site says otherwise, and the refusal lands where the double is USED rather than where it was made. State the port.
220
-
221
- **Off the ladder**, each with a destination: `vi.stubGlobal('fetch')` → `intercept()`; a raw `msw`/`nock`/`sinon` import → a contract; `vi.useFakeTimers` / `vi.setSystemTime` → `clock`; `toMatchSnapshot` → a golden under `_expected/`; `vi.mock` of an application module → inject the port instead. A native module a consumer genuinely cannot inject is the one allow-listed exception, per specifier, with a reason.
222
-
223
- Where a test lives is what makes the rungs mechanizable: a module test may not carry a golden, and a module that needs a real service is an integration spec ([06](06-integration.md)). The reasoning is [ADR-005](decisions/005-the-doubles-ladder-is-closed.md).
224
-
225
- ## Time — one primitive, two depths
226
-
227
- Determinism is structural (rule D16): a value a test SAMPLES — `Date.now()`, `new Date()`, `Math.random()`, `randomUUID()` — never reaches an oracle. Time is the one of those the framework pins for you, and `clock` is the whole vocabulary. `vi.useFakeTimers()` and `vi.setSystemTime()` are what it replaces: each of them owns a teardown a test will one day forget.
228
-
229
- ```typescript
230
- import { clock } from '@jterrazz/test';
231
- import { expect, test } from 'vitest';
232
-
233
- test('stamps the moment the receipt was issued', () => {
234
- // Given - the calendar pinned for this scope
235
- using _ = clock.at('2026-03-04T09:30:00Z');
236
-
237
- // Then - the stamp is the stated instant
238
- expect(new Date().toISOString()).toBe('2026-03-04T09:30:00.000Z');
239
- });
240
- ```
241
-
242
- | Form | Takes | For |
243
- | ------------------- | ------------------------------ | -------------------------------------------------------------------------- |
244
- | `clock.at(iso)` | the calendar (`Date`) | a subject that READS the time — a stamp, an expiry, a greeting |
245
- | `clock.run(iso?)` | the calendar and the scheduler | a subject whose behaviour IS elapsed time — a debounce, a backoff, a poll |
246
- | `clock.advance(ms)` | — | moving the pinned clock on; under `run()`, firing everything that came due |
247
-
248
- Both forms are `Disposable`: `using _ = clock.at(…)` gives the REAL clock back at the end of the scope, however the scope ends. `at()` leaves the scheduler real, so promises, renders and the framework's own loops keep running; `run()` queues them instead, which is what lets a test assert a five-second backoff without the arbitrary sleep J2 forbids.
249
-
250
- One clock per test: a second scope taken inside the first is refused, not nested. Disposing restores the real clock — the only state vitest's fake timers can give back — so a nested scope that ended would silently end the outer one too, and the refusal says which of the two the test meant.
251
-
252
- Migrating a `beforeEach`/`afterEach` pair holds the `Disposable` by hand:
253
-
254
- ```typescript
255
- let pinned: Disposable;
256
- beforeEach(() => {
257
- pinned = clock.at('2026-03-04T09:30:00Z');
258
- });
259
- afterEach(() => {
260
- pinned[Symbol.dispose]();
261
- });
262
- ```
263
-
264
- That form works, and it is the migration shape rather than the destination: `using _ = clock.at(…)` inside the test states the instant where the test that needs it can be read, and cannot be forgotten.
265
-
266
- A facet pins the same instant for the chain it is stated on — `.clock(iso)` on api, jobs, integration, component, and on a website visit (the PAGE's calendar, through the browser). A cli spec is another process: its instant travels through the product's own env (`TZ`) or is absorbed by a `{{iso8601}}` token in the golden.
267
-
268
- ## The backlog — the criterion written, coded on its first occurrence
269
-
270
- A rule is written when its defect class has been SEEN once and its criterion is
271
- decidable (rule K1). A class nobody has seen yet is not a rule: it is a line
272
- here, with the criterion it would be written against, so the day it appears the
273
- guard is a translation rather than a design. The R8 dry run of 16.0 — the whole
274
- catalogue run over every repository of the workbench — is what moves a line
275
- either way.
276
-
277
- | Line | The criterion, ready to code |
278
- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
279
- | B12 marker between statements | a Given/When/Then marker whose offset falls inside a `VariableDeclaration` with ≥ 2 declarators |
280
- | B13 no intercept in a loop | an `.intercept(` call inside a `for`/`while` body, or a `.map(`/`forEach(` callback, in a test file |
281
- | B15 pinned path tail | a `toMatch(` fixture name whose directory part repeats the leaf's own domain folder |
282
- | C17 fixtures sibling | a `_fixtures/` directory whose only referrer sits in a sibling domain rather than beside it |
283
- | C19 ground per facet | a `_seeds/` or `_requests/` under a facet whose specification constructs `website()` or `mobile()` — a screen owns no database |
284
- | D21w expected pinned value | a uuid, or an iso8601 whose time is NOT midnight UTC, in a file under `_expected/` that appears in no `_seeds/`, `_requests/`, `_fixtures/`, `contracts/` or referring test of the same leaf, nor in the `$FIXTURES` pool. The hour is part of the criterion: a clock read during a run is never exactly midnight, a publication date always is ([ADR-006](decisions/006-the-catalogue-reaches-a-file-by-its-role.md) records what the dry run found) |
285
- | C22 golden in a module test | a `toMatch('<name>.<ext>')` — a string literal carrying an extension — in a `module`-role file OUTSIDE every `specs/` tree: a unit test standing on a golden file is an integration spec, and `specs/integration/` is where it belongs (C6 holds the same call inside a specs tree) |
286
- | D27w unreachable as a negative | a test whose contract list is `http.unreachable()` on every feed and whose assertions name `result.error` alone: the case proves the call FAILED, not that it never happened. Waits on the surface — `http.never(request)` is a product line ([TODO.md](../TODO.md) § What the contract vocabulary still cannot say) |
287
- | D22w empty golden | a file under `_expected/` whose trimmed content is exactly `{{any}}` |
288
- | D25 duplicate contract payload | two response payloads with the same content hash under one `contracts/` tree, outside the shared pool |
289
- | D26w inline request body | a request body written inline in a test that also carries a `_requests/` directory |
290
- | E10 no retry | a `retry:` option on `test(`/`describe(`, or `test.retry(`, or `retry` in a vitest config — react-query's `retry: false` is not one |
291
- | I5 no namespace spy | a `vi.spyOn(<namespace import>, …)` — a module's own export spied through its namespace object |
292
-
293
- **Parked**, for a criterion that is not settled: **D23** no negated golden (15
294
- hits on the workbench, every one a `toMatch(/re/)` the criterion must exclude)
295
- and **D24** unreachable contract (the criterion has to compare filter arguments,
296
- not just methods and urls).
297
-
298
- ## What stays human, and why
299
-
300
- Every convention a machine can settle is a row with a channel. These are the
301
- ones no channel can decide — listed with the reason, and with what the machine
302
- takes of them anyway, so neither side is silently dropped.
303
-
304
- | Convention | Why no channel decides it | What the machine takes |
305
- | ------------------------------------------------- | --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
306
- | C1 grouping — own domain vs sibling aspects | what the assets will become is a reading of the domain | C21w flags the one-owner half |
307
- | D11 the legitimacy of a probe | absence, cut output and a third-party format are meanings, not shapes | d12w / d15w / D19w draw the cluster boundary |
308
- | D13 residue routed through a helper | inter-procedural analysis is out of an oxlint JS plugin's reach | d13w's bounded heuristic |
309
- | K1 — whether a defect is a CLASS | a judgement about what will happen again | K2–K5 hold the catalogue complete and true |
310
- | the truth of a Given/Then sentence, or of a title | a sentence can be well-formed and false | B4/B10/B11 and `vitest/valid-title` hold the shape |
311
- | whether an updated golden is RIGHT | correctness is the diff, read at review | d5 holds the volatile literals, in a document and under `_expected/` alike; the pinned-value half of ground waits for D21w |
312
- | whether an allow-listed `vi.mock` has no seam | the claim is disputable, and recorded in the config comment | I4 holds the list |
313
- | `exact` versus `within` when both disambiguate | taste between two user-facing forms | W2 forces a reason on the escape hatch; W3 offers both |
314
- | one fixture per answer of the system | a reading of what the chain proves | J4 holds unique descriptions |
315
- | contract realism | only a recorded exchange could judge it | — |
316
- | the depth a repository declares | reviewed once, when the tree is born | C1 thereafter |
317
-
318
- ## Maintaining the constitution
319
-
320
- - A new **mechanizable** rule is added to the **code** (`src/lint/manifest.ts` + its implementation), not here — then `npm run docs` regenerates the catalogue. The freshness meta-test fails if the committed catalogue is no longer byte-identical.
321
- - A new **principle** or a non-mechanizable criterion is added here, in its family section or as a process rule.
322
- - Never duplicate a mechanized rule in this constitution: the code is its single source of truth.
323
-
324
- ## Pitfalls
325
-
326
- - **Treating these docs as the spec.** They explain and illustrate; this constitution and the generated catalogue ([19 — Linting](19-linting.md)) decide. When you find a discrepancy in the docs, fix the docs in the same change — [02 — Developing](02-developing.md) § What a change owes.
327
- - **Editing the generated catalogue by hand.** The [19 — Linting](19-linting.md) catalogue and `skills/jterrazz-test/references/rules.md` are GENERATED from `src/lint/manifest.ts` — a hand edit is overwritten by the next `npm run docs` and fails the freshness meta-test. Change the rule's text in the code; regenerate.
328
- - **Adding a mechanized rule to the constitution.** A machine-checkable rule lives in the code (its `meta.docs` / the manifest), not in this chapter. The constitution holds principles and non-mechanizable criteria only.
329
-
330
- ## Related
331
-
332
- [01 — Architecture](01-architecture.md) · [02 — Developing](02-developing.md) · [03 — Testing](03-testing.md) · [14 — Assertions](14-assertions.md) · [19 — Linting](19-linting.md) · [06 — Integration specs](06-integration.md)