@geonosis/oxlint-plugin-biological-architecture 3.1.0 → 3.2.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 (236) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +48 -5
  3. package/RULES.md +200 -99
  4. package/corpus/.oxlintrc.json +5 -0
  5. package/corpus/data/bad-dynamic-orm.ts +5 -0
  6. package/corpus/data/bad-type-only-orm.ts +3 -0
  7. package/corpus/e2e/bad-list-response.spec.ts +5 -0
  8. package/corpus/e2e/bad-timeout.spec.ts +5 -0
  9. package/corpus/e2e/good-detail-response.spec.ts +5 -0
  10. package/corpus/e2e/good-state.spec.ts +5 -0
  11. package/corpus/manifest.json +2 -0
  12. package/corpus/tissues/bad-awaits-data.tsx +8 -0
  13. package/corpus/tissues/bad-fetches-data.tsx +7 -0
  14. package/corpus/tissues/good-takes-products.tsx +5 -0
  15. package/dist/discover-atoms.d.ts +2 -37
  16. package/dist/discover-atoms.js +1 -104
  17. package/dist/index.d.ts +3 -12
  18. package/dist/index.js +19 -176
  19. package/dist/presets.d.ts +11 -19
  20. package/dist/presets.js +88 -3
  21. package/dist/registry.d.ts +2 -0
  22. package/dist/registry.js +172 -0
  23. package/dist/rules/atom-no-deps.d.ts +1 -0
  24. package/dist/rules/atom-no-deps.js +10 -11
  25. package/dist/rules/boundary-time-is-primitive.d.ts +0 -7
  26. package/dist/rules/cell-must-be-stateful.d.ts +2 -2
  27. package/dist/rules/cell-must-be-stateful.js +24 -23
  28. package/dist/rules/cell-must-not-compose-cell.d.ts +1 -0
  29. package/dist/rules/cell-must-not-compose-cell.js +10 -3
  30. package/dist/rules/cell-no-cross-feature-organelles.d.ts +1 -0
  31. package/dist/rules/cell-no-cross-feature-organelles.js +7 -0
  32. package/dist/rules/cell-no-tissues.d.ts +1 -0
  33. package/dist/rules/cell-no-tissues.js +11 -7
  34. package/dist/rules/cells-folder-index-is-barrel.d.ts +12 -10
  35. package/dist/rules/cells-folder-index-is-barrel.js +66 -34
  36. package/dist/rules/compound-must-be-stateless.d.ts +2 -0
  37. package/dist/rules/compound-must-be-stateless.js +24 -26
  38. package/dist/rules/compound-no-stores.d.ts +1 -0
  39. package/dist/rules/compound-no-stores.js +12 -10
  40. package/dist/rules/constants-in-constants-file.d.ts +2 -8
  41. package/dist/rules/constants-in-constants-file.js +5 -6
  42. package/dist/rules/dialect-through-the-seam.d.ts +0 -13
  43. package/dist/rules/durable-body-reads-through-steps.d.ts +0 -14
  44. package/dist/rules/durable-body-reads-through-steps.js +8 -3
  45. package/dist/rules/effect-hook-naming.d.ts +9 -11
  46. package/dist/rules/effect-hook-naming.js +33 -31
  47. package/dist/rules/emit-declares-attempts.d.ts +0 -17
  48. package/dist/rules/emit-declares-attempts.js +5 -10
  49. package/dist/rules/font-roles-only.d.ts +0 -18
  50. package/dist/rules/font-roles-only.js +19 -22
  51. package/dist/rules/hermetic-e2e-no-wall-clock-waits.d.ts +53 -0
  52. package/dist/rules/hermetic-e2e-no-wall-clock-waits.js +100 -0
  53. package/dist/rules/icon-control-has-name.js +7 -1
  54. package/dist/rules/layer-walls.d.ts +0 -25
  55. package/dist/rules/layer-walls.js +7 -13
  56. package/dist/rules/lib/atom-map.d.ts +24 -0
  57. package/dist/rules/lib/atom-map.js +16 -0
  58. package/dist/rules/lib/cells-barrel.d.ts +0 -1
  59. package/dist/rules/lib/colors.d.ts +0 -19
  60. package/dist/rules/lib/colors.js +10 -243
  61. package/dist/rules/lib/comments.d.ts +19 -0
  62. package/dist/rules/lib/comments.js +5 -0
  63. package/dist/rules/lib/context.d.ts +0 -10
  64. package/dist/rules/lib/direction-rule.d.ts +3 -21
  65. package/dist/rules/lib/direction-rule.js +12 -3
  66. package/dist/rules/lib/doors.d.ts +2 -18
  67. package/dist/rules/lib/doors.js +79 -41
  68. package/dist/rules/lib/hooks.d.ts +2 -17
  69. package/dist/rules/lib/hooks.js +14 -9
  70. package/dist/rules/lib/imports.d.ts +2 -23
  71. package/dist/rules/lib/imports.js +7 -2
  72. package/dist/rules/lib/jsx.d.ts +31 -0
  73. package/dist/rules/lib/jsx.js +11 -0
  74. package/dist/rules/lib/landing.d.ts +0 -40
  75. package/dist/rules/lib/literal-call.d.ts +1 -0
  76. package/dist/rules/lib/literal-call.js +2 -0
  77. package/dist/rules/lib/module-text.d.ts +3 -27
  78. package/dist/rules/lib/module-text.js +19 -4
  79. package/dist/rules/lib/mutations.d.ts +0 -14
  80. package/dist/rules/lib/nodes.d.ts +1 -18
  81. package/dist/rules/lib/nodes.js +1 -0
  82. package/dist/rules/lib/objects.d.ts +2 -11
  83. package/dist/rules/lib/objects.js +21 -0
  84. package/dist/rules/lib/options.d.ts +2 -52
  85. package/dist/rules/lib/options.js +44 -18
  86. package/dist/rules/lib/package-imports.d.ts +0 -5
  87. package/dist/rules/lib/package-imports.js +4 -10
  88. package/dist/rules/lib/paths.d.ts +15 -41
  89. package/dist/rules/lib/paths.js +34 -1
  90. package/dist/rules/lib/pattern-match.d.ts +2 -12
  91. package/dist/rules/lib/pattern-match.js +5 -1
  92. package/dist/rules/lib/probe.d.ts +0 -43
  93. package/dist/rules/lib/reexports.d.ts +0 -18
  94. package/dist/rules/lib/render-tracking.d.ts +0 -8
  95. package/dist/rules/lib/shared-features.d.ts +0 -8
  96. package/dist/rules/lib/shared-features.js +2 -9
  97. package/dist/rules/lib/state-sources.d.ts +8 -0
  98. package/dist/rules/lib/state-sources.js +55 -0
  99. package/dist/rules/lib/steps.d.ts +3 -5
  100. package/dist/rules/lib/steps.js +6 -1
  101. package/dist/rules/lib/styling.d.ts +9 -42
  102. package/dist/rules/lib/styling.js +36 -10
  103. package/dist/rules/lib/tiers.d.ts +11 -24
  104. package/dist/rules/lib/tiers.js +41 -1
  105. package/dist/rules/lib/tissue-entrypoint.d.ts +0 -1
  106. package/dist/rules/lib/tissue-entrypoint.js +4 -5
  107. package/dist/rules/lib/tsconfig-paths.d.ts +0 -4
  108. package/dist/rules/lib/tsconfig-paths.js +47 -15
  109. package/dist/rules/lib/type-members.d.ts +6 -0
  110. package/dist/rules/lib/type-members.js +165 -0
  111. package/dist/rules/lib/visitors.d.ts +0 -22
  112. package/dist/rules/lib/workspace-members.d.ts +2 -0
  113. package/dist/rules/lib/workspace-members.js +115 -0
  114. package/dist/rules/list-take-without-order.js +2 -8
  115. package/dist/rules/max-comment-density.d.ts +3 -18
  116. package/dist/rules/max-comment-density.js +3 -3
  117. package/dist/rules/molecule-atoms-only.d.ts +1 -0
  118. package/dist/rules/molecule-atoms-only.js +11 -7
  119. package/dist/rules/molecule-must-compose.d.ts +2 -0
  120. package/dist/rules/molecule-must-compose.js +17 -21
  121. package/dist/rules/mutating-step-declares-retry.d.ts +2 -7
  122. package/dist/rules/mutating-step-declares-retry.js +29 -11
  123. package/dist/rules/named-when-block.d.ts +0 -2
  124. package/dist/rules/named-when-block.js +2 -8
  125. package/dist/rules/next-route-segment-is-thin-delegate.d.ts +4 -19
  126. package/dist/rules/next-route-segment-is-thin-delegate.js +47 -71
  127. package/dist/rules/no-brand-names.d.ts +0 -9
  128. package/dist/rules/no-brand-names.js +1 -1
  129. package/dist/rules/no-card-shaped-div.d.ts +4 -42
  130. package/dist/rules/no-card-shaped-div.js +12 -4
  131. package/dist/rules/no-cross-feature-stores.d.ts +1 -0
  132. package/dist/rules/no-cross-feature-stores.js +9 -5
  133. package/dist/rules/no-d1-transaction.d.ts +2 -5
  134. package/dist/rules/no-d1-transaction.js +7 -0
  135. package/dist/rules/no-date-string-compare.d.ts +2 -0
  136. package/dist/rules/no-date-string-compare.js +14 -8
  137. package/dist/rules/no-duplicate-jsx-patterns.d.ts +3 -1
  138. package/dist/rules/no-duplicate-jsx-patterns.js +16 -1
  139. package/dist/rules/no-generic-utility-package.d.ts +48 -2
  140. package/dist/rules/no-generic-utility-package.js +48 -9
  141. package/dist/rules/no-hook-in-component-disguise.d.ts +27 -13
  142. package/dist/rules/no-hook-in-component-disguise.js +38 -30
  143. package/dist/rules/no-index-count-as-exact.d.ts +0 -3
  144. package/dist/rules/no-inert-hidden-jsx.d.ts +4 -21
  145. package/dist/rules/no-inert-hidden-jsx.js +13 -12
  146. package/dist/rules/no-inline-data-in-jsx.d.ts +2 -0
  147. package/dist/rules/no-inline-data-in-jsx.js +17 -2
  148. package/dist/rules/no-invalid-feature-folders.d.ts +2 -0
  149. package/dist/rules/no-invalid-feature-folders.js +12 -4
  150. package/dist/rules/no-loader-side-effect-in-index-file.d.ts +2 -9
  151. package/dist/rules/no-logic-in-component-files.d.ts +6 -5
  152. package/dist/rules/no-logic-in-component-files.js +52 -31
  153. package/dist/rules/no-mock-db-in-integration.d.ts +0 -15
  154. package/dist/rules/no-mock-db-in-integration.js +3 -4
  155. package/dist/rules/no-orm-outside-db.d.ts +1 -0
  156. package/dist/rules/no-orm-outside-db.js +9 -7
  157. package/dist/rules/no-raw-html-atoms.d.ts +3 -22
  158. package/dist/rules/no-raw-html-atoms.js +5 -17
  159. package/dist/rules/no-raw-sql-outside-allowed.d.ts +2 -0
  160. package/dist/rules/no-raw-sql-outside-allowed.js +10 -15
  161. package/dist/rules/no-react-namespace.d.ts +2 -0
  162. package/dist/rules/no-react-namespace.js +13 -0
  163. package/dist/rules/no-renamed-html-props.d.ts +2 -0
  164. package/dist/rules/no-renamed-html-props.js +11 -2
  165. package/dist/rules/no-render-prop-reader.d.ts +4 -20
  166. package/dist/rules/no-render-prop-reader.js +13 -5
  167. package/dist/rules/no-runtime-dead-affordance.d.ts +3 -26
  168. package/dist/rules/no-runtime-dead-affordance.js +7 -20
  169. package/dist/rules/no-swallowed-catch.js +2 -8
  170. package/dist/rules/no-trivial-wrapper-component.d.ts +4 -19
  171. package/dist/rules/no-trivial-wrapper-component.js +24 -33
  172. package/dist/rules/no-ts-in-bio-folders.d.ts +2 -13
  173. package/dist/rules/no-ts-in-bio-folders.js +9 -7
  174. package/dist/rules/no-type-definitions-in-components.d.ts +2 -0
  175. package/dist/rules/no-type-definitions-in-components.js +12 -1
  176. package/dist/rules/no-unregistered-plugin-dir.d.ts +0 -41
  177. package/dist/rules/no-unregistered-plugin-dir.js +1 -9
  178. package/dist/rules/no-void-port.d.ts +2 -4
  179. package/dist/rules/no-void-port.js +7 -0
  180. package/dist/rules/organelle-dependency.d.ts +1 -0
  181. package/dist/rules/organelle-dependency.js +10 -8
  182. package/dist/rules/organelle-single-source.d.ts +2 -0
  183. package/dist/rules/organelle-single-source.js +58 -9
  184. package/dist/rules/plugin-route-namespaced.d.ts +0 -11
  185. package/dist/rules/plugin-route-namespaced.js +2 -2
  186. package/dist/rules/queries-require-org-scope.d.ts +0 -3
  187. package/dist/rules/queries-require-org-scope.js +3 -10
  188. package/dist/rules/queue-loop-is-the-library.js +3 -2
  189. package/dist/rules/route-no-inline-mutation.d.ts +1 -25
  190. package/dist/rules/route-no-inline-mutation.js +2 -7
  191. package/dist/rules/router-schema-parity.d.ts +0 -9
  192. package/dist/rules/router-schema-parity.js +5 -9
  193. package/dist/rules/service-name-not-reserved.d.ts +0 -3
  194. package/dist/rules/service-name-not-reserved.js +1 -34
  195. package/dist/rules/ssot-no-inline-facts.js +2 -5
  196. package/dist/rules/ssot-no-process-env.js +4 -12
  197. package/dist/rules/step-declares-compensation-or-none.d.ts +2 -1
  198. package/dist/rules/step-declares-compensation-or-none.js +17 -1
  199. package/dist/rules/step-opens-its-own-cell.d.ts +3 -26
  200. package/dist/rules/step-opens-its-own-cell.js +8 -13
  201. package/dist/rules/store-route-scopes-tenant-data.js +8 -26
  202. package/dist/rules/subpath-over-barrel.d.ts +2 -10
  203. package/dist/rules/subpath-over-barrel.js +48 -9
  204. package/dist/rules/subscriber-declares-id.d.ts +0 -2
  205. package/dist/rules/subscriber-declares-id.js +2 -8
  206. package/dist/rules/tables-declare-their-plane.d.ts +0 -2
  207. package/dist/rules/tables-declare-their-plane.js +4 -10
  208. package/dist/rules/tenant-tables-carry-org-id.d.ts +1 -4
  209. package/dist/rules/tenant-tables-carry-org-id.js +5 -12
  210. package/dist/rules/test-no-clock-polling-for-promise-flips.d.ts +2 -0
  211. package/dist/rules/test-no-clock-polling-for-promise-flips.js +18 -1
  212. package/dist/rules/time-through-the-door.d.ts +0 -24
  213. package/dist/rules/time-through-the-door.js +5 -5
  214. package/dist/rules/tissue-must-compose.d.ts +5 -3
  215. package/dist/rules/tissue-must-compose.js +25 -20
  216. package/dist/rules/tissue-no-data-props.d.ts +57 -1
  217. package/dist/rules/tissue-no-data-props.js +114 -32
  218. package/dist/rules/tissue-no-fetch.d.ts +26 -0
  219. package/dist/rules/tissue-no-fetch.js +57 -0
  220. package/dist/rules/tissue-no-hooks.d.ts +21 -23
  221. package/dist/rules/tissue-no-hooks.js +22 -4
  222. package/dist/rules/tissue-no-organelles.d.ts +1 -0
  223. package/dist/rules/tissue-no-organelles.js +9 -1
  224. package/dist/rules/tissue-no-stores.d.ts +1 -0
  225. package/dist/rules/tissue-no-stores.js +9 -1
  226. package/dist/rules/todo-names-its-plan.d.ts +1 -28
  227. package/dist/rules/todo-names-its-plan.js +2 -2
  228. package/dist/rules/tokens-only-colors.d.ts +2 -37
  229. package/dist/rules/tokens-only-colors.js +33 -18
  230. package/dist/rules/unique-query-step-name.d.ts +0 -7
  231. package/dist/rules/unique-query-step-name.js +6 -10
  232. package/dist/rules/worker-handles-are-scoped.d.ts +12 -9
  233. package/dist/rules/worker-handles-are-scoped.js +12 -5
  234. package/dist/rules/zustand-v5-best-practices.d.ts +8 -18
  235. package/dist/rules/zustand-v5-best-practices.js +32 -56
  236. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,49 @@
1
1
  # @geonosis/oxlint-plugin-biological-architecture
2
2
 
3
+ ## 3.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - a4a7bba: Thirty-four rules the kit enables now declare a probe, so the doctor's `exercised` can ask them whether they reach anything and `probelessRules` falls with them. A direction rule declares its probe beside its scope.
8
+
9
+ New rule `hermetic-e2e-no-wall-clock-waits` (preset `testbed`): in an end-to-end spec a fixed-time wait (`waitForTimeout`, a `setTimeout` sleep) is reported, and a `waitForResponse` on a list endpoint is reported for the endpoints `listEndpoints` names. `specs`, `clockWaits` and `networkWaits` are options.
10
+
11
+ `worker-handles-are-scoped` accepts the wrapper as a member call (`cellOf(env).withConnection(...)`). `no-generic-utility-package` takes `allow: [{ package, exports }]` for the one door a generic name survives to hold, and reports a second export. `no-hook-in-component-disguise` takes `clientBoundary` (`false`, or the paths the directive is honoured under); its message no longer teaches the directive where it is withdrawn. `tissue-no-hooks` takes `contextHooks`, a list that is empty until the repo writes it and never excuses React's state hooks. `tokens-only-colors` reads a colour in a plain object, not only in a JSX style, an attribute or a default. `organelle-single-source` counts the sources of the sibling organelle hooks a file calls. `tissue-no-data-props` reads a props type named instead of written inline, from the declaration beside it or the file it is imported from, and its message says an inferred `any` belongs to the type-checker's rules. `cells-folder-index-is-barrel` reports a `cells/` file that only re-exports an organelle.
12
+
13
+ `subpath-over-barrel` expands a door written as a pattern (`./icons/*`) by listing the files its target names, reads the workspace membership (`pnpm-workspace.yaml`, `package.json` `workspaces`) as the repo's own packages where the linker writes no `node_modules`, re-reads a manifest when its mtime changes, and says once a run when an opted-in package resolves to nothing or the tree resolves no package at all.
14
+
15
+ A rule that throws a `SyntaxError` — a pattern option that is not a regular expression — is reported as one finding naming the rule, once a run, and no longer erases the file's other findings.
16
+ - a4a7bba: A dynamic `import()` or `require()` that is destructured — `const { Real } = await import('../cells')` — takes the names the pattern spells, as `import { Real }` does, so the other names the barrel carries are not its reach; a rest element, a computed key or a result bound to one name still takes the whole module. A solution-style `tsconfig.json` (no `paths`, only `references`) leaves the aliases to the referenced project that declares `paths` and includes the importing directory.
17
+
18
+ The tier folder is read in one place: a molecule, and the molecules/compounds/organelles/cells/layouts the card and prop-name rules police, are recognised when the path starts at the folder as well as when it is absolute, and a folder that only ends in a tier name is not that tier. `queue-loop-is-the-library` exempts a test through the shared reader, so a path relative to the working directory is exempt too. The cell and tissue private-sibling suffixes share one list.
19
+
20
+ A refusal for a missing option reports `data: { option, rule }` beside its sentence. `src/registry.ts` is generated from the rule modules on disk (`pnpm generate:registry`), and `RULES.md` carries a generated inventory (presets, required option, presumed engine) and summary beside a hand-kept provenance table.
21
+ - 58eaf3a: `organelle-dependency`, `tissue-no-organelles`, `atom-no-deps`, `molecule-atoms-only` and `compound-no-stores` judge a specifier that begins with a tier folder or `app/` (`cells/editor`, written against a baseUrl); `tissue-must-compose`, `molecule-must-compose`, `next-route-segment-is-thin-delegate` and `no-trivial-wrapper-component` no longer report a file whose composed import is spelled that way.
22
+ - 58eaf3a: `no-inert-hidden-jsx` reads `className={'hidden'}` and a hole-free template literal as it reads `className="hidden"`.
23
+ - 58eaf3a: `no-logic-in-component-files` counts a default-exported component and a component named in capitals (`FAQ`, `CTA`), which it skipped, and judges each declarator's own thinness. A capitalised name counts only when it is a function that returns JSX, so a helper such as `export const API = () => fetch(...)` stays out of the count.
24
+ - 58eaf3a: `effect-hook-naming` reads `React.useEffect` and `React.useLayoutEffect`, and judges the name of a default-exported hook.
25
+ - 58eaf3a: `no-runtime-dead-affordance` reads a handler as `on` followed by a capital, so a prop such as `only` or `once` no longer keeps a dead control alive. A lowercase `onclick` is no longer a handler either, and a control carrying only that is now reported: React does not attach it and warns, so it is a dead control there. A Preact-style tree that spells handlers in lowercase will see it.
26
+ - 58eaf3a: `tokens-only-colors` and `font-roles-only` read an arbitrary value through Tailwind variants and the important modifier — `data-[state=open]:bg-[#fff]`, `[&>span]:bg-[#fff]`, `!bg-[#fff]` — which they skipped.
27
+ - a4a7bba: `store-route-scopes-tenant-data` is in `backend-medusa` only. It presumes Medusa's `/api/store/**` route layout, and `tenant-scoping` is for a repo of any stack, so a Workers + D1 app that enabled `tenant-scoping` carried a rule that judged nothing there. A repo on Medusa that enables `tenant-scoping` without `backend-medusa` no longer gets this rule: add `backend-medusa`. A scaffold's Medusa answer already names both.
28
+ - dd5f57a: A tier rule now declares the tier folder it reaches (`rule.tier`), and `unreachableTierRules` counts the tier rules enabled over a workspace that ships tier folders but none of that rule's tier: they have no possible finding and print what a clean tree prints. A workspace with no tier folder is not judged; a plugin that declares no tier is a refusal.
29
+
30
+ `geonosis-ratchet --prove` now plants the tool failing as well as a finding. A counter must answer a failing tool with a `CounterError`, and a counter that ships no `refuses` probe reads UNPROVEN. The report names a counter's halves (`PROVEN archViolations (command)`, `UNPROVEN archViolations (match)`) and the summary counts halves. A counter you wrote needs `refuses: { input?, command?, params? }` beside its `probe`.
31
+
32
+ A named `roots` entry of `commentLines`, `suppressionCount` and `orphanTodos` that is not a directory is now a refusal rather than zero findings, and a `pnpm-workspace.yaml` that does not parse refuses the counters that read it.
33
+
34
+ A win one counter measured is now written to the baseline even when another counter could not be measured (a floor whose `dist/` is not built no longer holds every other floor's win back); the run still fails at the refusal and says which number was lowered.
35
+
36
+ `waitForBuild(root)` and `buildLockPath(root)` are exported for a tier that must not run beside a build.
37
+
38
+ `typecheckErrors` takes a `report` in place of a `command`: it reads the `{ errors, tree }` file a typecheck step wrote and refuses one whose tree is not the working tree in front of it, naming both digests, so `verify:release` no longer runs the same tsc program twice. `treeHashOf(cwd)` is exported for the step that writes it.
39
+ - a4a7bba: `tissue-no-fetch`, in the `frontend` preset: a file in a `tissues/` folder that contains an `await`, a `for await` loop or a call to `fetch` (bare, or through `globalThis`, `window` or `self`) waits for data, so the tissue owns it. A tissue arranges data it was handed; the read belongs in a loader the route segment calls. Tests, specs and stories beside a tissue are left alone, and so is every other tier. It needs no option to run.
40
+
41
+ ### Patch Changes
42
+
43
+ - a4a7bba: The plugin README lists the five oxlint built-ins that fire on idioms the presets ask for (`no-await-in-loop`, `unicorn/no-useless-undefined`, `unicorn/consistent-function-scoping`, `unicorn/throw-new-error`, `no-underscore-dangle`), measured on oxlint 1.87.0, with the config that quiets the four an override or option can reach and the rewrite for the loop. The kit's `tiers.md` rule file says what "inert below the organelle" means: no app or shared state (no store, no data); local UI state such as an open flag is allowed in an atom or a molecule, and a compound holds none. `gates.md` separates adopting a counter, which the ratchet writes and does not judge, from raising a baseline to make a gate go green, which stays forbidden. `geonosis-doctor`'s `rules` check compares against these copies.
44
+ - 58eaf3a: `icon-control-has-name` no longer reports a control whose icon names itself: an icon with its own `aria-label` or `aria-labelledby` (an icon component must forward it to its svg), or an `<svg>` holding a `<title>`, is the control's accessible name. A `title` attribute on an icon or an `<svg>` is not an accessible name and is still reported.
45
+ - f5632b7: `step-declares-compensation-or-none` declares a `probe`, so a repo that enables it for one directory (an override with a `files` glob) is judged by the doctor's `exercised` check instead of being skipped for want of a probe (#131).
46
+
3
47
  ## 3.1.0
4
48
 
5
49
  ### Minor Changes
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @geonosis/oxlint-plugin-biological-architecture
2
2
 
3
- 81 architecture rules as an oxlint JS plugin: which tier a file belongs to, what it may import,
3
+ 83 architecture rules as an oxlint JS plugin: which tier a file belongs to, what it may import,
4
4
  where state is allowed to live, and the backend invariants that go with them. Nothing is hardcoded
5
5
  to a repo — every list of brands, features, tables or paths is an option. A rule enabled without the
6
6
  options it needs says so once a run and judges nothing, rather than passing silently.
@@ -51,20 +51,21 @@ atoms → molecules → compounds → organelles → cells → tissues
51
51
  ```
52
52
 
53
53
  State lives **only** at the organelle and cell tiers. Everything above the cell arranges;
54
- everything below the organelle is inert. A cell never composes another cell.
54
+ everything below the organelle holds no app or shared state (no store, no data) — local UI state
55
+ such as an open flag is allowed there. A cell never composes another cell.
55
56
 
56
57
  ## Presets
57
58
 
58
59
  | preset | rules | what it is |
59
60
  | --- | --- | --- |
60
- | `frontend` (= `recommended`) | 38 | tier direction, state placement, element choice, file shape |
61
+ | `frontend` (= `recommended`) | 39 | tier direction, state placement, element choice, file shape |
61
62
  | `backend-workflows` | 15 | workflows, time, the data layer, single source of truth |
62
63
  | `backend-medusa` | 13 | Medusa steps, emits, subscribers, loaders and the store route surface |
63
- | `tenant-scoping` | 4 | every row placed, scoped and narrowed to one tenant |
64
+ | `tenant-scoping` | 3 | every row placed, scoped and narrowed to one tenant |
64
65
  | `tooling` | 3 | a repo's own layers as data, no generic `utils` package, debt markers name a plan |
65
66
  | `backend-d1` | 2 | Cloudflare D1 facts — false on an engine with transactions |
66
67
  | `themekit` | 2 | colours and fonts only through the theme's tokens and roles |
67
- | `testbed` | 2 | integration suites run on a real database; tests await rather than poll |
68
+ | `testbed` | 3 | integration suites run on a real database; tests await rather than poll |
68
69
  | `backend-trpc` | 1 | every router has its schema, and every schema its router |
69
70
  | `backend-workflows-cf` | 1 | durable runs on Cloudflare Workflows — false of an inline run |
70
71
  | `failure-paths` | 1 | no catch that swallows the failure it caught |
@@ -75,6 +76,48 @@ everything below the organelle is inert. A cell never composes another cell.
75
76
  A rule that is false on another stack gets its own preset rather than riding along in a generic
76
77
  one: a preset carrying a repo fact is a repo fact wearing a preset's name.
77
78
 
79
+ ## Oxlint built-ins that disagree with an idiom
80
+
81
+ Five built-in oxlint rules (measured on oxlint 1.87.0) fire on code the kit's own presets ask for.
82
+ They are oxlint's rules, not this plugin's, and the fix for four of them is in the repo's
83
+ `.oxlintrc.json`; an upstream change is oxc's to make. Turning a built-in off for a glob is law 2's
84
+ "ask first" when the repo has it at `error`, and the reason goes beside the override.
85
+
86
+ ```json
87
+ {
88
+ "rules": {
89
+ "no-underscore-dangle": ["error", { "allow": ["_tag"] }]
90
+ },
91
+ "overrides": [
92
+ {
93
+ "files": ["**/*.test.ts"],
94
+ "rules": {
95
+ "unicorn/consistent-function-scoping": "off",
96
+ "unicorn/no-useless-undefined": "off"
97
+ }
98
+ }
99
+ ]
100
+ }
101
+ ```
102
+
103
+ - **`no-await-in-loop`** has no small-bound allowance, and a bounded, deliberately sequential loop
104
+ (policy reads that journal, two iterations) is flagged. A `for await` loop is not flagged, so write
105
+ the sequence as one: an async generator that `yield`s each step's promise, consumed with
106
+ `for await`. The generator runs one step per pull. The other answer is an override naming the file
107
+ that holds the loop.
108
+ - **`unicorn/no-useless-undefined`** fires on `mockResolvedValue(undefined)`, the idiom for "resolves
109
+ to nothing"; `null` changes the assertion. The override above is for test files.
110
+ - **`unicorn/consistent-function-scoping`** hoists an arrange-act-assert helper out of the `describe`
111
+ that explains it. Same override.
112
+ - **`unicorn/throw-new-error`** matches any call whose callee name ends in `Error`, including the
113
+ `extends` clause of `class SinkFailure extends TaggedError('SinkFailure')<…>() {}`, where nothing
114
+ is thrown. `better-result`'s factory is named `TaggedError`, so the first error class of a
115
+ `Result` posture trips it. Import the factory under another name (`import { TaggedError as tagged }
116
+ from 'better-result'`); that is a rename, not a suppression.
117
+ - **`no-underscore-dangle`** flags `_tag`, `better-result`'s public discriminant. `allow: ["_tag"]`
118
+ above is the option the rule has for it; where a test can use `matchError` instead, that is the
119
+ behaviour anyway.
120
+
78
121
  ## Adopting
79
122
 
80
123
  A repo that already has its own copy of these rules does not delete it because the version numbers
package/RULES.md CHANGED
@@ -11,13 +11,15 @@ One plugin name, two rule sets, no shared code. **33 rules had the same filename
11
11
 
12
12
  The union here is **52 rules** — 54 when it was cut, less `document-sagas-are-generic` and
13
13
  `documents-share-one-table`, retired in 2.7.0 because both enforced one consumer's document kinds —
14
- plus twenty-nine the kit wrote for itself: `layer-walls`, `emit-declares-attempts`,
14
+ plus thirty the kit wrote for itself: `layer-walls`, `emit-declares-attempts`,
15
15
  `cell-no-cross-feature-organelles`, the `backend-medusa` ten, `no-unregistered-plugin-dir` +
16
16
  `router-schema-parity`, the `themekit` two, `no-mock-db-in-integration`, `todo-names-its-plan`,
17
17
  `boundary-time-is-primitive`, `durable-body-reads-through-steps`, `subpath-over-barrel` and the six
18
- of the W67 batch, and `icon-control-has-name` — **81**. The two retired ids went in 3.0.0.
19
- Every one of the twenty-nine is marked **written here** in the inventory, and the table below is
20
- held against that column rather than kept by hand.
18
+ of the W67 batch, `icon-control-has-name` and `tissue-no-fetch` — **82**. The two retired ids went in 3.0.0.
19
+ Every one of the thirty is marked **written here** in the provenance table, and the counts below
20
+ are generated from that column and from the rules the plugin exports.
21
+
22
+ <!-- GENERATED: the rules by where they came from -->
21
23
 
22
24
  | | count |
23
25
  |---|---|
@@ -26,8 +28,10 @@ held against that column rather than kept by hand.
26
28
  | A Medusa storefront only | 3 |
27
29
  | A Workers + D1 app only | 16 |
28
30
  | **union** | **52** |
29
- | written here | 29 |
30
- | **total** | **81** |
31
+ | written here | 31 |
32
+ | **total** | **83** |
33
+
34
+ <!-- END GENERATED SUMMARY -->
31
35
 
32
36
  ## How divergence was resolved
33
37
 
@@ -502,8 +506,8 @@ written out and is still reported.
502
506
 
503
507
  ### `store-route-scopes-tenant-data` (a Medusa storefront only) — Medusa-specific
504
508
 
505
- `/api/store/**/route.ts` is a Medusa convention. It is in `backend-medusa`, and also in
506
- `tenant-scoping` because that is what it enforces.
509
+ `/api/store/**/route.ts` is a Medusa convention. It is in `backend-medusa` only: `tenant-scoping`
510
+ is for a repo of any stack, and a repo on Medusa gets this rule from `backend-medusa`.
507
511
 
508
512
  ### `no-logic-in-component-files` — a Medusa storefront's narrowed version
509
513
 
@@ -706,94 +710,99 @@ name resolves to the same rules for ONE release and says so once, then goes. `pa
706
710
  their `exports` and the entry file behind each door — which is a fact about that repo's layout, not
707
711
  every repo's.
708
712
 
709
- | Rule | Preset(s) | Requires | Core | In a Medusa storefront | In a Workers + D1 app | Same code |
710
- |---|---|---|---|---|---|---|
711
- | `atom-no-deps` | frontend | — | `directionRule` | yes | yes | diverged |
712
- | `boundary-time-is-primitive` | backend-workflows | `types` | `nodes` + `paths` + `options` | — | — | **written here** (D-050, #145) |
713
- | `cell-must-be-stateful` | frontend | — | `paths` | yes | — | a Medusa storefront only |
714
- | `cell-must-not-compose-cell` | frontend | — | `directionRule` + `cells-barrel` | yes | yes | diverged |
715
- | `cell-no-cross-feature-organelles` | frontend | — | `directionRule` | — | — | **written here** |
716
- | `cell-no-tissues` | frontend | — | `directionRule` | yes | yes | diverged |
717
- | `cells-folder-index-is-barrel` | frontend | — | `paths` + `cells-barrel` | yes | yes | diverged |
718
- | `compound-must-be-stateless` | frontend | — | `paths` | yes | yes | diverged |
719
- | `compound-no-stores` | frontend | — | `directionRule` | yes | yes | diverged |
720
- | `constants-in-constants-file` | backend-workflows | — | `paths` | — | yes | a Workers + D1 app only |
721
- | `dialect-through-the-seam` | backend-workflows | `seam` | `onImportSource` + `mergeVisitors` + `paths` + `options` | — | yes | a Workers + D1 app only, de-hardcoded |
722
- | `durable-body-reads-through-steps` | backend-workflows | `ports` | `nodes` + `objects` + `steps` + `options` | — | — | **written here** (#167, design-026 §8) |
723
- | `effect-hook-naming` | frontend | — | `paths` | yes | yes | diverged |
724
- | `icon-control-has-name` | frontend | — | `nodes` + `options` | — | — | **written here** |
725
- | `emit-declares-attempts` | backend-medusa | — | `options` | — | — | **written here** |
726
- | `font-roles-only` | themekit | `roles` | `paths` + `options` + `styling` | — | — | **written here** (U2) |
727
- | `layer-walls` | tooling | `layers` | `onImportSource` + `imports` + `paths` + `options` | — | — | **written here** |
728
- | `list-take-without-order` | backend-medusa | — | `nodes` + `options` | — | — | **written here** (#21) |
729
- | `max-comment-density` | comment-ceiling | — | `paths` | — | yes | a Workers + D1 app only |
730
- | `molecule-atoms-only` | frontend | — | `directionRule` | yes | yes | diverged |
731
- | `molecule-must-compose` | frontend | — | — | yes | yes | diverged |
732
- | `mutating-step-declares-retry` | backend-medusa + backend-workflows | — | `nodes` + `objects` + `steps` + `options` | — | — | **written here** (a Medusa storefront's scan R5.2) |
733
- | `named-when-block` | backend-medusa | — | `nodes` + `options` | — | — | **written here** (a Medusa storefront's scan R5.5) |
734
- | `next-route-segment-is-thin-delegate` | frontend | — | `paths` | yes | yes | diverged |
735
- | `no-unregistered-plugin-dir` | integrations | `roots` | `options` + `paths` | — | — | **written here** (Midday's 8 dead dirs; flat seam #36) |
736
- | `no-mock-db-in-integration` | testbed | `integrationDirs` | `options` + `paths` | — | — | **written here** (Midday's 35 mocked router suites) |
737
- | `router-schema-parity` | backend-trpc | `routers` | `options` + `paths` | — | — | **written here** (a Workers + D1 app's mirror) |
738
- | `no-brand-names` | frontend | `brands` | `paths` + `options` | yes | — | a Medusa storefront only |
739
- | `no-card-shaped-div` | frontend | — | — | yes | yes | diverged |
740
- | `no-cross-feature-stores` | frontend | — | `directionRule` | yes | yes | diverged |
741
- | `no-d1-transaction` | backend-d1 | — | `paths` + `options` | — | yes | a Workers + D1 app only, de-hardcoded |
742
- | `no-date-string-compare` | backend-workflows | — | `nodes` + `options` | — | — | **written here** (#20) |
743
- | `no-duplicate-jsx-patterns` | frontend | — | `paths` | yes | yes | diverged |
744
- | `no-generic-utility-package` | tooling | — | `paths` + `options` | — | — | **written here** (#171) |
745
- | `no-hook-in-component-disguise` | frontend | — | `paths` | yes | yes | diverged |
746
- | `no-index-count-as-exact` | backend-medusa | — | `nodes` + `objects` + `options` | — | — | **written here** (Medusa source) |
747
- | `no-inert-hidden-jsx` | frontend | — | `paths` | yes | yes | diverged |
748
- | `no-inline-data-in-jsx` | frontend | — | `paths` | yes | yes | diverged |
749
- | `no-invalid-feature-folders` | frontend | — | `paths` | yes | yes | diverged |
750
- | `no-loader-side-effect-in-index-file` | backend-medusa | — | `nodes` + `paths` + `options` | — | — | **written here** (a Medusa storefront's scan #15442) |
751
- | `no-logic-in-component-files` | frontend | — | `paths` | yes | yes | diverged |
752
- | `plugin-route-namespaced` | backend-medusa | `packages` | `paths` + `options` | — | — | **written here** (a Medusa storefront's scan R3.8) |
753
- | `no-orm-outside-db` | backend-workflows | — | `directionRule` + `options` | — | yes | a Workers + D1 app only, de-hardcoded |
754
- | `no-raw-html-atoms` | frontend | `element→atom map` | `tiers` + `options` | yes | yes | diverged |
755
- | `no-raw-sql-outside-allowed` | backend-workflows | — | `paths` + `options` | — | yes | a Workers + D1 app only, de-hardcoded |
756
- | `no-react-namespace` | frontend | — | — | yes | yes | **identical** |
757
- | `no-renamed-html-props` | frontend | — | — | yes | yes | **identical** |
758
- | `no-render-prop-reader` | frontend | — | `paths` | yes | yes | diverged |
759
- | `no-swallowed-catch` | failure-paths | `paths` | `nodes` + `paths` + `options` | — | — | **written here** (#3) |
760
- | `no-runtime-dead-affordance` | frontend | `element→atom map` | `options` | — | — | **written here** (#5) |
761
- | `no-trivial-wrapper-component` | frontend | — | `paths` | yes | yes | diverged |
762
- | `no-ts-in-bio-folders` | frontend | — | `paths` | yes | yes | diverged |
763
- | `no-type-definitions-in-components` | frontend | — | `paths` | yes | yes | diverged |
764
- | `no-void-port` | backend-workflows | — | — | — | yes | a Workers + D1 app only |
765
- | `organelle-dependency` | frontend | — | `directionRule` | yes | yes | diverged |
766
- | `organelle-single-source` | frontend | — | `onImportSource` + `mergeVisitors` + `paths` | yes | yes | diverged |
767
- | `queries-require-org-scope` | tenant-scoping | `queries` | `paths` + `options` | — | yes | a Workers + D1 app only, de-hardcoded |
768
- | `queue-loop-is-the-library` | backend-workflows | — | — | — | yes | a Workers + D1 app only |
769
- | `route-no-inline-mutation` | backend-medusa | — | `nodes` + `paths` + `comments` + `options` | — | — | **written here** (a Medusa storefront's scan R5.1) |
770
- | `service-name-not-reserved` | backend-medusa | — | `nodes` + `objects` + `options` | — | — | **written here** (Medusa source) |
771
- | `ssot-no-inline-facts` | backend-workflows | — | `paths` + `options` | — | yes | a Workers + D1 app only, de-hardcoded |
772
- | `ssot-no-process-env` | backend-workflows | — | `paths` | — | yes | a Workers + D1 app only |
773
- | `step-declares-compensation-or-none` | backend-medusa + backend-workflows | — | `nodes` + `objects` + `steps` + `options` | — | — | **written here** (Medusa source) |
774
- | `step-opens-its-own-cell` | backend-workflows-cf | `within` | `nodes` + `objects` + `paths` + `options` | — | yes | a Workers + D1 app only, re-targeted |
775
- | `store-route-scopes-tenant-data` | backend-medusa + tenant-scoping | — | `nodes` + `imports` + `paths` + `options` | yes | — | a Medusa storefront only, de-hardcoded |
776
- | `subpath-over-barrel` | package-subpaths | — | `nodes` + `options` | — | — | **written here** (#180, a Workers + D1 app asked) |
777
- | `subscriber-declares-id` | backend-medusa | — | `objects` + `paths` + `options` | — | — | **written here** (a Medusa storefront's scan R5.4) |
778
- | `tables-declare-their-plane` | backend-workflows + tenant-scoping | `schema` | `paths` + `literal-call` + `options` | — | yes | a Workers + D1 app only, de-hardcoded |
779
- | `tenant-tables-carry-org-id` | tenant-scoping | `tenant` | `paths` + `literal-call` + `options` | — | yes | a Workers + D1 app only, de-hardcoded |
780
- | `test-no-clock-polling-for-promise-flips` | testbed | — | `nodes` + `paths` + `options` | — | — | **written here** (#4) |
781
- | `tokens-only-colors` | themekit | `tokenPrefix` | `paths` + `options` + `styling` + `colors` | — | — | **written here** (U2) |
782
- | `time-through-the-door` | backend-workflows | `door` | `paths` + `options` | — | yes | a Workers + D1 app only, de-hardcoded |
783
- | `tissue-must-compose` | frontend | — | `paths` | yes | yes | diverged |
784
- | `tissue-no-data-props` | frontend | — | `paths` | yes | yes | diverged |
785
- | `tissue-no-hooks` | frontend | — | `paths` | yes | yes | diverged |
786
- | `tissue-no-organelles` | frontend | — | `directionRule` | yes | yes | diverged |
787
- | `tissue-no-stores` | frontend | — | `directionRule` | yes | yes | diverged |
788
- | `todo-names-its-plan` | tooling | `markers` | `comments` + `options` | — | — | **written here** (W28) |
789
- | `unique-query-step-name` | backend-medusa | `queryStepCalls` | `nodes` + `objects` + `options` | — | — | **written here** (a Medusa storefront's scan R4.3) |
790
- | `worker-handles-are-scoped` | backend-d1 | `within` | `paths` + `options` | — | yes (as `cell-handles-are-scoped`) | a Workers + D1 app only, de-hardcoded |
791
- | `zustand-v5-best-practices` | frontend | — | — | yes | yes | **identical** |
792
-
793
- Preset sizes: frontend 36 · backend-workflows 16 · backend-medusa 12 · tenant-scoping 4 ·
794
- backend-d1 2 · tooling 2 · themekit 2 · backend-workflows-cf 1 · backend-trpc 1 · integrations 1 ·
795
- package-subpaths 1 · testbed 1 · comment-ceiling 1 (+ `capitalized-comments`, which is oxlint's own
796
- and only makes sense alongside the density rule).
713
+ <!-- GENERATED: the preset, option and engine of each rule -->
714
+
715
+ | Rule | Preset(s) | Requires | Presumes |
716
+ |---|---|---|---|
717
+ | `atom-no-deps` | frontend | — | — |
718
+ | `boundary-time-is-primitive` | backend-workflows | `types` | — |
719
+ | `cell-must-be-stateful` | frontend | — | — |
720
+ | `cell-must-not-compose-cell` | frontend | — | — |
721
+ | `cell-no-cross-feature-organelles` | frontend | — | — |
722
+ | `cell-no-tissues` | frontend | — | — |
723
+ | `cells-folder-index-is-barrel` | frontend | — | — |
724
+ | `compound-must-be-stateless` | frontend | — | — |
725
+ | `compound-no-stores` | frontend | — | — |
726
+ | `constants-in-constants-file` | backend-workflows | — | — |
727
+ | `dialect-through-the-seam` | backend-workflows | `seam` | — |
728
+ | `durable-body-reads-through-steps` | backend-workflows | `ports` | — |
729
+ | `effect-hook-naming` | frontend | — | — |
730
+ | `emit-declares-attempts` | backend-medusa | — | Medusa |
731
+ | `font-roles-only` | themekit | `roles` | — |
732
+ | `hermetic-e2e-no-wall-clock-waits` | testbed | — | — |
733
+ | `icon-control-has-name` | frontend | — | — |
734
+ | `layer-walls` | tooling | `layers` | — |
735
+ | `list-take-without-order` | backend-medusa | — | Medusa |
736
+ | `max-comment-density` | comment-ceiling | — | — |
737
+ | `molecule-atoms-only` | frontend | — | — |
738
+ | `molecule-must-compose` | frontend | — | — |
739
+ | `mutating-step-declares-retry` | backend-medusa + backend-workflows | — | — |
740
+ | `named-when-block` | backend-medusa | — | Medusa |
741
+ | `next-route-segment-is-thin-delegate` | frontend | — | — |
742
+ | `no-brand-names` | frontend | `brands` | — |
743
+ | `no-card-shaped-div` | frontend | — | — |
744
+ | `no-cross-feature-stores` | frontend | — | — |
745
+ | `no-d1-transaction` | backend-d1 | — | — |
746
+ | `no-date-string-compare` | backend-workflows | — | — |
747
+ | `no-duplicate-jsx-patterns` | frontend | — | — |
748
+ | `no-generic-utility-package` | tooling | — | — |
749
+ | `no-hook-in-component-disguise` | frontend | — | — |
750
+ | `no-index-count-as-exact` | backend-medusa | — | Medusa |
751
+ | `no-inert-hidden-jsx` | frontend | — | — |
752
+ | `no-inline-data-in-jsx` | frontend | — | — |
753
+ | `no-invalid-feature-folders` | frontend | — | — |
754
+ | `no-loader-side-effect-in-index-file` | backend-medusa | — | Medusa |
755
+ | `no-logic-in-component-files` | frontend | — | — |
756
+ | `no-mock-db-in-integration` | testbed | `integrationDirs` | — |
757
+ | `no-orm-outside-db` | backend-workflows | — | — |
758
+ | `no-raw-html-atoms` | frontend | `element→atom map` | — |
759
+ | `no-raw-sql-outside-allowed` | backend-workflows | — | — |
760
+ | `no-react-namespace` | frontend | — | — |
761
+ | `no-renamed-html-props` | frontend | — | — |
762
+ | `no-render-prop-reader` | frontend | — | — |
763
+ | `no-runtime-dead-affordance` | frontend | `element→atom map` | — |
764
+ | `no-swallowed-catch` | failure-paths | `paths` | — |
765
+ | `no-trivial-wrapper-component` | frontend | — | — |
766
+ | `no-ts-in-bio-folders` | frontend | — | — |
767
+ | `no-type-definitions-in-components` | frontend | — | — |
768
+ | `no-unregistered-plugin-dir` | integrations | `roots` | — |
769
+ | `no-void-port` | backend-workflows | — | — |
770
+ | `organelle-dependency` | frontend | — | — |
771
+ | `organelle-single-source` | frontend | — | — |
772
+ | `plugin-route-namespaced` | backend-medusa | `packages` | Medusa |
773
+ | `queries-require-org-scope` | tenant-scoping | `queries` | — |
774
+ | `queue-loop-is-the-library` | backend-workflows | — | — |
775
+ | `route-no-inline-mutation` | backend-medusa | — | Medusa |
776
+ | `router-schema-parity` | backend-trpc | `routers` | — |
777
+ | `service-name-not-reserved` | backend-medusa | — | Medusa |
778
+ | `ssot-no-inline-facts` | backend-workflows | — | — |
779
+ | `ssot-no-process-env` | backend-workflows | — | — |
780
+ | `step-declares-compensation-or-none` | backend-medusa + backend-workflows | — | — |
781
+ | `step-opens-its-own-cell` | backend-workflows-cf | `within` | — |
782
+ | `store-route-scopes-tenant-data` | backend-medusa | — | Medusa |
783
+ | `subpath-over-barrel` | package-subpaths | — | — |
784
+ | `subscriber-declares-id` | backend-medusa | — | Medusa |
785
+ | `tables-declare-their-plane` | backend-workflows + tenant-scoping | `schema` | — |
786
+ | `tenant-tables-carry-org-id` | tenant-scoping | `tenant` | — |
787
+ | `test-no-clock-polling-for-promise-flips` | testbed | — | — |
788
+ | `time-through-the-door` | backend-workflows | `door` | — |
789
+ | `tissue-must-compose` | frontend | — | — |
790
+ | `tissue-no-data-props` | frontend | — | — |
791
+ | `tissue-no-fetch` | frontend | — | — |
792
+ | `tissue-no-hooks` | frontend | — | — |
793
+ | `tissue-no-organelles` | frontend | — | — |
794
+ | `tissue-no-stores` | frontend | — | — |
795
+ | `todo-names-its-plan` | tooling | `markers` | — |
796
+ | `tokens-only-colors` | themekit | `tokenPrefix` | — |
797
+ | `unique-query-step-name` | backend-medusa | `queryStepCalls` | Medusa |
798
+ | `worker-handles-are-scoped` | backend-d1 | `within` | — |
799
+ | `zustand-v5-best-practices` | frontend | — | — |
800
+
801
+ Preset sizes: frontend 39 · backend-workflows 15 · backend-medusa 13 · tenant-scoping 3 · testbed 3 · tooling 3 · backend-d1 2 · themekit 2 · backend-workflows-cf 1 · backend-trpc 1 · integrations 1 · package-subpaths 1 · comment-ceiling 1 · failure-paths 1.
802
+
803
+ <!-- END GENERATED INVENTORY -->
804
+
805
+ `comment-ceiling` also carries `capitalized-comments`, which is oxlint's own and only makes sense alongside the density rule.
797
806
 
798
807
  `backend-workflows` and `backend-medusa` OVERLAP by two: `mutating-step-declares-retry` and
799
808
  `step-declares-compensation-or-none` reach both engines' spellings — measured in 026-W2, and true of
@@ -803,6 +812,96 @@ downgrade wearing a preset change. `unique-query-step-name` stays Medusa's alone
803
812
  reports is that framework's step-handler map last-winning, and a repeated step name on the org
804
813
  engine is DEFINED rather than lost (`reserve`, `reserve#2`, the engine's `reserveName`).
805
814
 
815
+ ## Where each rule came from
816
+
817
+ Hand-kept: the two source trees are history, and no source object carries which of them held a rule. A rule the kit wrote is a row here marked **written here**; the counts at the top and the inventory under Presets are generated (`pnpm generate:rules-doc`).
818
+
819
+ | Rule | Core | In a Medusa storefront | In a Workers + D1 app | Same code |
820
+ |---|---|---|---|---|
821
+ | `atom-no-deps` | `directionRule` | yes | yes | diverged |
822
+ | `boundary-time-is-primitive` | `nodes` + `paths` + `options` | — | — | **written here** (D-050, #145) |
823
+ | `cell-must-be-stateful` | `paths` | yes | — | a Medusa storefront only |
824
+ | `cell-must-not-compose-cell` | `directionRule` + `cells-barrel` | yes | yes | diverged |
825
+ | `cell-no-cross-feature-organelles` | `directionRule` | — | — | **written here** |
826
+ | `cell-no-tissues` | `directionRule` | yes | yes | diverged |
827
+ | `cells-folder-index-is-barrel` | `paths` + `cells-barrel` | yes | yes | diverged |
828
+ | `compound-must-be-stateless` | `paths` | yes | yes | diverged |
829
+ | `compound-no-stores` | `directionRule` | yes | yes | diverged |
830
+ | `constants-in-constants-file` | `paths` | — | yes | a Workers + D1 app only |
831
+ | `dialect-through-the-seam` | `onImportSource` + `mergeVisitors` + `paths` + `options` | — | yes | a Workers + D1 app only, de-hardcoded |
832
+ | `durable-body-reads-through-steps` | `nodes` + `objects` + `steps` + `options` | — | — | **written here** (#167, design-026 §8) |
833
+ | `effect-hook-naming` | `paths` | yes | yes | diverged |
834
+ | `hermetic-e2e-no-wall-clock-waits` | `nodes` + `paths` + `options` | — | — | **written here** (#6) |
835
+ | `icon-control-has-name` | `nodes` + `options` | — | — | **written here** |
836
+ | `emit-declares-attempts` | `options` | — | — | **written here** |
837
+ | `font-roles-only` | `paths` + `options` + `styling` | — | — | **written here** (U2) |
838
+ | `layer-walls` | `onImportSource` + `imports` + `paths` + `options` | — | — | **written here** |
839
+ | `list-take-without-order` | `nodes` + `options` | — | — | **written here** (#21) |
840
+ | `max-comment-density` | `paths` | — | yes | a Workers + D1 app only |
841
+ | `molecule-atoms-only` | `directionRule` | yes | yes | diverged |
842
+ | `molecule-must-compose` | — | yes | yes | diverged |
843
+ | `mutating-step-declares-retry` | `nodes` + `objects` + `steps` + `options` | — | — | **written here** (a Medusa storefront's scan R5.2) |
844
+ | `named-when-block` | `nodes` + `options` | — | — | **written here** (a Medusa storefront's scan R5.5) |
845
+ | `next-route-segment-is-thin-delegate` | `paths` | yes | yes | diverged |
846
+ | `no-unregistered-plugin-dir` | `options` + `paths` | — | — | **written here** (Midday's 8 dead dirs; flat seam #36) |
847
+ | `no-mock-db-in-integration` | `options` + `paths` | — | — | **written here** (Midday's 35 mocked router suites) |
848
+ | `router-schema-parity` | `options` + `paths` | — | — | **written here** (a Workers + D1 app's mirror) |
849
+ | `no-brand-names` | `paths` + `options` | yes | — | a Medusa storefront only |
850
+ | `no-card-shaped-div` | — | yes | yes | diverged |
851
+ | `no-cross-feature-stores` | `directionRule` | yes | yes | diverged |
852
+ | `no-d1-transaction` | `paths` + `options` | — | yes | a Workers + D1 app only, de-hardcoded |
853
+ | `no-date-string-compare` | `nodes` + `options` | — | — | **written here** (#20) |
854
+ | `no-duplicate-jsx-patterns` | `paths` | yes | yes | diverged |
855
+ | `no-generic-utility-package` | `paths` + `options` | — | — | **written here** (#171) |
856
+ | `no-hook-in-component-disguise` | `paths` | yes | yes | diverged |
857
+ | `no-index-count-as-exact` | `nodes` + `objects` + `options` | — | — | **written here** (Medusa source) |
858
+ | `no-inert-hidden-jsx` | `paths` | yes | yes | diverged |
859
+ | `no-inline-data-in-jsx` | `paths` | yes | yes | diverged |
860
+ | `no-invalid-feature-folders` | `paths` | yes | yes | diverged |
861
+ | `no-loader-side-effect-in-index-file` | `nodes` + `paths` + `options` | — | — | **written here** (a Medusa storefront's scan #15442) |
862
+ | `no-logic-in-component-files` | `paths` | yes | yes | diverged |
863
+ | `plugin-route-namespaced` | `paths` + `options` | — | — | **written here** (a Medusa storefront's scan R3.8) |
864
+ | `no-orm-outside-db` | `directionRule` + `options` | — | yes | a Workers + D1 app only, de-hardcoded |
865
+ | `no-raw-html-atoms` | `tiers` + `options` | yes | yes | diverged |
866
+ | `no-raw-sql-outside-allowed` | `paths` + `options` | — | yes | a Workers + D1 app only, de-hardcoded |
867
+ | `no-react-namespace` | — | yes | yes | **identical** |
868
+ | `no-renamed-html-props` | — | yes | yes | **identical** |
869
+ | `no-render-prop-reader` | `paths` | yes | yes | diverged |
870
+ | `no-swallowed-catch` | `nodes` + `paths` + `options` | — | — | **written here** (#3) |
871
+ | `no-runtime-dead-affordance` | `options` | — | — | **written here** (#5) |
872
+ | `no-trivial-wrapper-component` | `paths` | yes | yes | diverged |
873
+ | `no-ts-in-bio-folders` | `paths` | yes | yes | diverged |
874
+ | `no-type-definitions-in-components` | `paths` | yes | yes | diverged |
875
+ | `no-void-port` | — | — | yes | a Workers + D1 app only |
876
+ | `organelle-dependency` | `directionRule` | yes | yes | diverged |
877
+ | `organelle-single-source` | `onImportSource` + `mergeVisitors` + `paths` | yes | yes | diverged |
878
+ | `queries-require-org-scope` | `paths` + `options` | — | yes | a Workers + D1 app only, de-hardcoded |
879
+ | `queue-loop-is-the-library` | — | — | yes | a Workers + D1 app only |
880
+ | `route-no-inline-mutation` | `nodes` + `paths` + `comments` + `options` | — | — | **written here** (a Medusa storefront's scan R5.1) |
881
+ | `service-name-not-reserved` | `nodes` + `objects` + `options` | — | — | **written here** (Medusa source) |
882
+ | `ssot-no-inline-facts` | `paths` + `options` | — | yes | a Workers + D1 app only, de-hardcoded |
883
+ | `ssot-no-process-env` | `paths` | — | yes | a Workers + D1 app only |
884
+ | `step-declares-compensation-or-none` | `nodes` + `objects` + `steps` + `options` | — | — | **written here** (Medusa source) |
885
+ | `step-opens-its-own-cell` | `nodes` + `objects` + `paths` + `options` | — | yes | a Workers + D1 app only, re-targeted |
886
+ | `store-route-scopes-tenant-data` | `nodes` + `imports` + `paths` + `options` | yes | — | a Medusa storefront only, de-hardcoded |
887
+ | `subpath-over-barrel` | `nodes` + `options` | — | — | **written here** (#180, a Workers + D1 app asked) |
888
+ | `subscriber-declares-id` | `objects` + `paths` + `options` | — | — | **written here** (a Medusa storefront's scan R5.4) |
889
+ | `tables-declare-their-plane` | `paths` + `literal-call` + `options` | — | yes | a Workers + D1 app only, de-hardcoded |
890
+ | `tenant-tables-carry-org-id` | `paths` + `literal-call` + `options` | — | yes | a Workers + D1 app only, de-hardcoded |
891
+ | `test-no-clock-polling-for-promise-flips` | `nodes` + `paths` + `options` | — | — | **written here** (#4) |
892
+ | `tokens-only-colors` | `paths` + `options` + `styling` + `colors` | — | — | **written here** (U2) |
893
+ | `time-through-the-door` | `paths` + `options` | — | yes | a Workers + D1 app only, de-hardcoded |
894
+ | `tissue-must-compose` | `paths` | yes | yes | diverged |
895
+ | `tissue-no-data-props` | `paths` | yes | yes | diverged |
896
+ | `tissue-no-fetch` | `paths` | — | — | **written here** (D-095 9) |
897
+ | `tissue-no-hooks` | `paths` | yes | yes | diverged |
898
+ | `tissue-no-organelles` | `directionRule` | yes | yes | diverged |
899
+ | `tissue-no-stores` | `directionRule` | yes | yes | diverged |
900
+ | `todo-names-its-plan` | `comments` + `options` | — | — | **written here** (W28) |
901
+ | `unique-query-step-name` | `nodes` + `objects` + `options` | — | — | **written here** (a Medusa storefront's scan R4.3) |
902
+ | `worker-handles-are-scoped` | `paths` + `options` | — | yes (as `cell-handles-are-scoped`) | a Workers + D1 app only, de-hardcoded |
903
+ | `zustand-v5-best-practices` | — | yes | yes | **identical** |
904
+
806
905
  ## The build
807
906
 
808
907
  Two builds existed. A Workers + D1 app's `package.json` resolves `.` through a `bun` condition to
@@ -879,6 +978,7 @@ option not named here is one no rule reads.
879
978
  | `effect-hook-naming` | — |
880
979
  | `emit-declares-attempts` | `budgetHelpers`, `emitters` |
881
980
  | `font-roles-only` | `allow`, `exemptModules`, `prefix`, `roles` |
981
+ | `hermetic-e2e-no-wall-clock-waits` | `clockWaits`, `listEndpoints`, `networkWaits`, `specs` |
882
982
  | `icon-control-has-name` | `controls`, `icons` |
883
983
  | `layer-walls` | `externals`, `layers`, `typeOnly` |
884
984
  | `list-take-without-order` | `callees`, `orderKeys` |
@@ -894,8 +994,8 @@ option not named here is one no rule reads.
894
994
  | `no-d1-transaction` | `except`, `transacts` |
895
995
  | `no-date-string-compare` | `fieldPattern` |
896
996
  | `no-duplicate-jsx-patterns` | — |
897
- | `no-generic-utility-package` | `entries`, `names` |
898
- | `no-hook-in-component-disguise` | — |
997
+ | `no-generic-utility-package` | `allow`, `entries`, `names` |
998
+ | `no-hook-in-component-disguise` | `clientBoundary` |
899
999
  | `no-index-count-as-exact` | `estimateFields`, `labelKey` |
900
1000
  | `no-inert-hidden-jsx` | — |
901
1001
  | `no-inline-data-in-jsx` | — |
@@ -937,7 +1037,8 @@ option not named here is one no rule reads.
937
1037
  | `time-through-the-door` | `door`, `exempt`, `exemptModules`, `libraries` |
938
1038
  | `tissue-must-compose` | — |
939
1039
  | `tissue-no-data-props` | — |
940
- | `tissue-no-hooks` | — |
1040
+ | `tissue-no-fetch` | — |
1041
+ | `tissue-no-hooks` | `contextHooks` |
941
1042
  | `tissue-no-organelles` | — |
942
1043
  | `tissue-no-stores` | — |
943
1044
  | `todo-names-its-plan` | `id`, `markers` |
@@ -171,6 +171,10 @@
171
171
  ]
172
172
  ],
173
173
  "biological-architecture/no-raw-sql-outside-allowed": "error",
174
+ "biological-architecture/hermetic-e2e-no-wall-clock-waits": [
175
+ "error",
176
+ { "listEndpoints": ["corpus-orders$"] }
177
+ ],
174
178
  "biological-architecture/icon-control-has-name": "error",
175
179
  "biological-architecture/no-react-namespace": "error",
176
180
  "biological-architecture/no-runtime-dead-affordance": [
@@ -331,6 +335,7 @@
331
335
  }
332
336
  ],
333
337
  "biological-architecture/tissue-no-data-props": "error",
338
+ "biological-architecture/tissue-no-fetch": "error",
334
339
  "biological-architecture/tissue-no-hooks": "error",
335
340
  "biological-architecture/tissue-no-organelles": "error",
336
341
  "biological-architecture/tissue-no-stores": "error",
@@ -0,0 +1,5 @@
1
+ export const load = async () => {
2
+ const { eq } = await import('drizzle-orm')
3
+
4
+ return eq
5
+ }
@@ -0,0 +1,3 @@
1
+ import type { SQL } from 'drizzle-orm'
2
+
3
+ export type Predicate = SQL
@@ -0,0 +1,5 @@
1
+ declare const page: { waitForResponse: (match: RegExp) => Promise<void> }
2
+
3
+ export const buys = async (): Promise<void> => {
4
+ await page.waitForResponse(/corpus-orders/)
5
+ }
@@ -0,0 +1,5 @@
1
+ declare const page: { waitForTimeout: (ms: number) => Promise<void> }
2
+
3
+ export const buys = async (): Promise<void> => {
4
+ await page.waitForTimeout(2000)
5
+ }
@@ -0,0 +1,5 @@
1
+ declare const page: { waitForResponse: (match: RegExp) => Promise<void> }
2
+
3
+ export const buys = async (): Promise<void> => {
4
+ await page.waitForResponse(/corpus-orders-one/)
5
+ }
@@ -0,0 +1,5 @@
1
+ declare const expectVisible: (name: string) => Promise<void>
2
+
3
+ export const buys = async (): Promise<void> => {
4
+ await expectVisible('order placed')
5
+ }
@@ -16,6 +16,7 @@
16
16
  "biological-architecture/effect-hook-naming",
17
17
  "biological-architecture/emit-declares-attempts",
18
18
  "biological-architecture/font-roles-only",
19
+ "biological-architecture/hermetic-e2e-no-wall-clock-waits",
19
20
  "biological-architecture/icon-control-has-name",
20
21
  "biological-architecture/layer-walls",
21
22
  "biological-architecture/list-take-without-order",
@@ -74,6 +75,7 @@
74
75
  "biological-architecture/time-through-the-door",
75
76
  "biological-architecture/tissue-must-compose",
76
77
  "biological-architecture/tissue-no-data-props",
78
+ "biological-architecture/tissue-no-fetch",
77
79
  "biological-architecture/tissue-no-hooks",
78
80
  "biological-architecture/tissue-no-organelles",
79
81
  "biological-architecture/tissue-no-stores",
@@ -0,0 +1,8 @@
1
+ import { ProductGrid } from '../cells/product-grid'
2
+ import { loadProducts } from '../lib/load-products'
3
+
4
+ export async function BadAwaitsData() {
5
+ const products = await loadProducts()
6
+
7
+ return <ProductGrid products={products} />
8
+ }
@@ -0,0 +1,7 @@
1
+ import { ProductGrid } from '../cells/product-grid'
2
+
3
+ export function BadFetchesData() {
4
+ const pending = fetch('/api/products')
5
+
6
+ return <ProductGrid pending={pending} />
7
+ }
@@ -0,0 +1,5 @@
1
+ import { ProductGrid } from '../cells/product-grid'
2
+
3
+ export function GoodTakesProducts({ products }: { products: string[] }) {
4
+ return <ProductGrid products={products} />
5
+ }