@geonosis/oxlint-plugin-biological-architecture 3.1.0 → 3.3.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 (248) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/README.md +75 -6
  3. package/RULES.md +202 -101
  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/medusa/queries/bad-silent-cap.ts +11 -0
  13. package/corpus/medusa/queries/good-paged-through.ts +19 -0
  14. package/corpus/medusa/queries/integration-tests/any-row.ts +10 -0
  15. package/corpus/tissues/bad-awaits-data.tsx +8 -0
  16. package/corpus/tissues/bad-fetches-data.tsx +7 -0
  17. package/corpus/tissues/good-takes-products.tsx +5 -0
  18. package/dist/discover-atoms.d.ts +2 -37
  19. package/dist/discover-atoms.js +1 -104
  20. package/dist/index.d.ts +3 -12
  21. package/dist/index.js +21 -176
  22. package/dist/presets.d.ts +11 -19
  23. package/dist/presets.js +94 -4
  24. package/dist/registry.d.ts +2 -0
  25. package/dist/registry.js +172 -0
  26. package/dist/rules/atom-no-deps.d.ts +1 -0
  27. package/dist/rules/atom-no-deps.js +10 -11
  28. package/dist/rules/boundary-time-is-primitive.d.ts +0 -7
  29. package/dist/rules/cell-must-be-stateful.d.ts +2 -2
  30. package/dist/rules/cell-must-be-stateful.js +24 -23
  31. package/dist/rules/cell-must-not-compose-cell.d.ts +1 -0
  32. package/dist/rules/cell-must-not-compose-cell.js +10 -3
  33. package/dist/rules/cell-no-cross-feature-organelles.d.ts +1 -0
  34. package/dist/rules/cell-no-cross-feature-organelles.js +7 -0
  35. package/dist/rules/cell-no-tissues.d.ts +1 -0
  36. package/dist/rules/cell-no-tissues.js +11 -7
  37. package/dist/rules/cells-folder-index-is-barrel.d.ts +12 -10
  38. package/dist/rules/cells-folder-index-is-barrel.js +66 -34
  39. package/dist/rules/compound-must-be-stateless.d.ts +2 -0
  40. package/dist/rules/compound-must-be-stateless.js +24 -26
  41. package/dist/rules/compound-no-stores.d.ts +1 -0
  42. package/dist/rules/compound-no-stores.js +12 -10
  43. package/dist/rules/constants-in-constants-file.d.ts +2 -8
  44. package/dist/rules/constants-in-constants-file.js +5 -6
  45. package/dist/rules/dialect-through-the-seam.d.ts +0 -13
  46. package/dist/rules/durable-body-reads-through-steps.d.ts +0 -14
  47. package/dist/rules/durable-body-reads-through-steps.js +8 -3
  48. package/dist/rules/effect-hook-naming.d.ts +9 -11
  49. package/dist/rules/effect-hook-naming.js +33 -31
  50. package/dist/rules/emit-declares-attempts.d.ts +0 -17
  51. package/dist/rules/emit-declares-attempts.js +5 -10
  52. package/dist/rules/font-roles-only.d.ts +0 -18
  53. package/dist/rules/font-roles-only.js +19 -22
  54. package/dist/rules/hermetic-e2e-no-wall-clock-waits.d.ts +53 -0
  55. package/dist/rules/hermetic-e2e-no-wall-clock-waits.js +100 -0
  56. package/dist/rules/icon-control-has-name.js +7 -1
  57. package/dist/rules/layer-walls.d.ts +0 -25
  58. package/dist/rules/layer-walls.js +7 -13
  59. package/dist/rules/lib/atom-map.d.ts +24 -0
  60. package/dist/rules/lib/atom-map.js +16 -0
  61. package/dist/rules/lib/cells-barrel.d.ts +0 -1
  62. package/dist/rules/lib/colors.d.ts +0 -19
  63. package/dist/rules/lib/colors.js +10 -243
  64. package/dist/rules/lib/comments.d.ts +19 -0
  65. package/dist/rules/lib/comments.js +5 -0
  66. package/dist/rules/lib/context.d.ts +0 -10
  67. package/dist/rules/lib/direction-rule.d.ts +4 -21
  68. package/dist/rules/lib/direction-rule.js +25 -4
  69. package/dist/rules/lib/doors.d.ts +2 -18
  70. package/dist/rules/lib/doors.js +81 -41
  71. package/dist/rules/lib/fresh.d.ts +5 -0
  72. package/dist/rules/lib/fresh.js +15 -0
  73. package/dist/rules/lib/hooks.d.ts +2 -17
  74. package/dist/rules/lib/hooks.js +14 -9
  75. package/dist/rules/lib/imports.d.ts +2 -23
  76. package/dist/rules/lib/imports.js +7 -2
  77. package/dist/rules/lib/jsx.d.ts +31 -0
  78. package/dist/rules/lib/jsx.js +11 -0
  79. package/dist/rules/lib/landing.d.ts +0 -40
  80. package/dist/rules/lib/literal-call.d.ts +1 -0
  81. package/dist/rules/lib/literal-call.js +2 -0
  82. package/dist/rules/lib/module-text.d.ts +3 -27
  83. package/dist/rules/lib/module-text.js +19 -4
  84. package/dist/rules/lib/mutations.d.ts +0 -14
  85. package/dist/rules/lib/nodes.d.ts +1 -18
  86. package/dist/rules/lib/nodes.js +1 -0
  87. package/dist/rules/lib/objects.d.ts +2 -11
  88. package/dist/rules/lib/objects.js +21 -0
  89. package/dist/rules/lib/options.d.ts +3 -52
  90. package/dist/rules/lib/options.js +49 -18
  91. package/dist/rules/lib/package-imports.d.ts +0 -5
  92. package/dist/rules/lib/package-imports.js +4 -10
  93. package/dist/rules/lib/paths.d.ts +15 -41
  94. package/dist/rules/lib/paths.js +34 -1
  95. package/dist/rules/lib/pattern-match.d.ts +2 -12
  96. package/dist/rules/lib/pattern-match.js +5 -1
  97. package/dist/rules/lib/probe.d.ts +0 -43
  98. package/dist/rules/lib/reexports.d.ts +0 -18
  99. package/dist/rules/lib/render-tracking.d.ts +0 -8
  100. package/dist/rules/lib/run-files.d.ts +18 -0
  101. package/dist/rules/lib/run-files.js +144 -0
  102. package/dist/rules/lib/shared-features.d.ts +0 -8
  103. package/dist/rules/lib/shared-features.js +2 -9
  104. package/dist/rules/lib/state-sources.d.ts +8 -0
  105. package/dist/rules/lib/state-sources.js +55 -0
  106. package/dist/rules/lib/steps.d.ts +3 -5
  107. package/dist/rules/lib/steps.js +6 -1
  108. package/dist/rules/lib/styling.d.ts +9 -42
  109. package/dist/rules/lib/styling.js +36 -10
  110. package/dist/rules/lib/tiers.d.ts +11 -24
  111. package/dist/rules/lib/tiers.js +41 -1
  112. package/dist/rules/lib/tissue-entrypoint.d.ts +0 -1
  113. package/dist/rules/lib/tissue-entrypoint.js +4 -5
  114. package/dist/rules/lib/token-layer.d.ts +19 -0
  115. package/dist/rules/lib/token-layer.js +170 -0
  116. package/dist/rules/lib/tsconfig-paths.d.ts +0 -4
  117. package/dist/rules/lib/tsconfig-paths.js +47 -15
  118. package/dist/rules/lib/type-members.d.ts +6 -0
  119. package/dist/rules/lib/type-members.js +165 -0
  120. package/dist/rules/lib/visitors.d.ts +0 -22
  121. package/dist/rules/lib/workspace-members.d.ts +2 -0
  122. package/dist/rules/lib/workspace-members.js +113 -0
  123. package/dist/rules/list-take-without-order.d.ts +24 -0
  124. package/dist/rules/list-take-without-order.js +293 -16
  125. package/dist/rules/max-comment-density.d.ts +3 -18
  126. package/dist/rules/max-comment-density.js +3 -3
  127. package/dist/rules/molecule-atoms-only.d.ts +1 -0
  128. package/dist/rules/molecule-atoms-only.js +11 -7
  129. package/dist/rules/molecule-must-compose.d.ts +2 -0
  130. package/dist/rules/molecule-must-compose.js +17 -21
  131. package/dist/rules/mutating-step-declares-retry.d.ts +2 -7
  132. package/dist/rules/mutating-step-declares-retry.js +29 -11
  133. package/dist/rules/named-when-block.d.ts +0 -2
  134. package/dist/rules/named-when-block.js +2 -8
  135. package/dist/rules/next-route-segment-is-thin-delegate.d.ts +4 -19
  136. package/dist/rules/next-route-segment-is-thin-delegate.js +47 -71
  137. package/dist/rules/no-brand-names.d.ts +0 -9
  138. package/dist/rules/no-brand-names.js +1 -1
  139. package/dist/rules/no-card-shaped-div.d.ts +4 -42
  140. package/dist/rules/no-card-shaped-div.js +12 -4
  141. package/dist/rules/no-cross-feature-stores.d.ts +1 -0
  142. package/dist/rules/no-cross-feature-stores.js +9 -5
  143. package/dist/rules/no-d1-transaction.d.ts +2 -5
  144. package/dist/rules/no-d1-transaction.js +7 -0
  145. package/dist/rules/no-date-string-compare.d.ts +2 -0
  146. package/dist/rules/no-date-string-compare.js +14 -8
  147. package/dist/rules/no-duplicate-jsx-patterns.d.ts +3 -1
  148. package/dist/rules/no-duplicate-jsx-patterns.js +16 -1
  149. package/dist/rules/no-generic-utility-package.d.ts +48 -2
  150. package/dist/rules/no-generic-utility-package.js +48 -9
  151. package/dist/rules/no-hook-in-component-disguise.d.ts +27 -13
  152. package/dist/rules/no-hook-in-component-disguise.js +38 -30
  153. package/dist/rules/no-index-count-as-exact.d.ts +0 -3
  154. package/dist/rules/no-inert-hidden-jsx.d.ts +4 -21
  155. package/dist/rules/no-inert-hidden-jsx.js +13 -12
  156. package/dist/rules/no-inline-data-in-jsx.d.ts +2 -0
  157. package/dist/rules/no-inline-data-in-jsx.js +17 -2
  158. package/dist/rules/no-invalid-feature-folders.d.ts +2 -0
  159. package/dist/rules/no-invalid-feature-folders.js +12 -4
  160. package/dist/rules/no-loader-side-effect-in-index-file.d.ts +2 -9
  161. package/dist/rules/no-logic-in-component-files.d.ts +6 -5
  162. package/dist/rules/no-logic-in-component-files.js +52 -31
  163. package/dist/rules/no-mock-db-in-integration.d.ts +0 -15
  164. package/dist/rules/no-mock-db-in-integration.js +3 -4
  165. package/dist/rules/no-orm-outside-db.d.ts +1 -0
  166. package/dist/rules/no-orm-outside-db.js +10 -7
  167. package/dist/rules/no-raw-html-atoms.d.ts +3 -22
  168. package/dist/rules/no-raw-html-atoms.js +5 -17
  169. package/dist/rules/no-raw-sql-outside-allowed.d.ts +2 -0
  170. package/dist/rules/no-raw-sql-outside-allowed.js +10 -15
  171. package/dist/rules/no-react-namespace.d.ts +2 -0
  172. package/dist/rules/no-react-namespace.js +13 -0
  173. package/dist/rules/no-renamed-html-props.d.ts +2 -0
  174. package/dist/rules/no-renamed-html-props.js +11 -2
  175. package/dist/rules/no-render-prop-reader.d.ts +4 -20
  176. package/dist/rules/no-render-prop-reader.js +13 -5
  177. package/dist/rules/no-runtime-dead-affordance.d.ts +3 -26
  178. package/dist/rules/no-runtime-dead-affordance.js +7 -20
  179. package/dist/rules/no-swallowed-catch.js +2 -8
  180. package/dist/rules/no-trivial-wrapper-component.d.ts +4 -19
  181. package/dist/rules/no-trivial-wrapper-component.js +24 -33
  182. package/dist/rules/no-ts-in-bio-folders.d.ts +2 -13
  183. package/dist/rules/no-ts-in-bio-folders.js +9 -7
  184. package/dist/rules/no-type-definitions-in-components.d.ts +2 -0
  185. package/dist/rules/no-type-definitions-in-components.js +12 -1
  186. package/dist/rules/no-unregistered-plugin-dir.d.ts +0 -41
  187. package/dist/rules/no-unregistered-plugin-dir.js +1 -9
  188. package/dist/rules/no-void-port.d.ts +2 -4
  189. package/dist/rules/no-void-port.js +7 -0
  190. package/dist/rules/organelle-dependency.d.ts +1 -0
  191. package/dist/rules/organelle-dependency.js +10 -8
  192. package/dist/rules/organelle-single-source.d.ts +2 -0
  193. package/dist/rules/organelle-single-source.js +58 -9
  194. package/dist/rules/plugin-route-namespaced.d.ts +0 -11
  195. package/dist/rules/plugin-route-namespaced.js +2 -2
  196. package/dist/rules/queries-require-org-scope.d.ts +0 -3
  197. package/dist/rules/queries-require-org-scope.js +3 -10
  198. package/dist/rules/queue-loop-is-the-library.js +3 -2
  199. package/dist/rules/route-no-inline-mutation.d.ts +1 -25
  200. package/dist/rules/route-no-inline-mutation.js +2 -7
  201. package/dist/rules/router-schema-parity.d.ts +0 -9
  202. package/dist/rules/router-schema-parity.js +5 -9
  203. package/dist/rules/service-name-not-reserved.d.ts +0 -3
  204. package/dist/rules/service-name-not-reserved.js +1 -34
  205. package/dist/rules/ssot-no-inline-facts.js +2 -5
  206. package/dist/rules/ssot-no-process-env.js +4 -12
  207. package/dist/rules/step-declares-compensation-or-none.d.ts +2 -1
  208. package/dist/rules/step-declares-compensation-or-none.js +17 -1
  209. package/dist/rules/step-opens-its-own-cell.d.ts +3 -26
  210. package/dist/rules/step-opens-its-own-cell.js +8 -13
  211. package/dist/rules/store-route-scopes-tenant-data.js +8 -26
  212. package/dist/rules/subpath-over-barrel.d.ts +3 -10
  213. package/dist/rules/subpath-over-barrel.js +55 -10
  214. package/dist/rules/subscriber-declares-id.d.ts +0 -2
  215. package/dist/rules/subscriber-declares-id.js +2 -8
  216. package/dist/rules/tables-declare-their-plane.d.ts +0 -2
  217. package/dist/rules/tables-declare-their-plane.js +4 -10
  218. package/dist/rules/tenant-tables-carry-org-id.d.ts +1 -4
  219. package/dist/rules/tenant-tables-carry-org-id.js +5 -12
  220. package/dist/rules/test-no-clock-polling-for-promise-flips.d.ts +7 -1
  221. package/dist/rules/test-no-clock-polling-for-promise-flips.js +21 -1
  222. package/dist/rules/time-through-the-door.d.ts +0 -24
  223. package/dist/rules/time-through-the-door.js +5 -5
  224. package/dist/rules/tissue-must-compose.d.ts +4 -2
  225. package/dist/rules/tissue-must-compose.js +25 -20
  226. package/dist/rules/tissue-no-data-props.d.ts +57 -1
  227. package/dist/rules/tissue-no-data-props.js +114 -32
  228. package/dist/rules/tissue-no-fetch.d.ts +26 -0
  229. package/dist/rules/tissue-no-fetch.js +57 -0
  230. package/dist/rules/tissue-no-hooks.d.ts +21 -23
  231. package/dist/rules/tissue-no-hooks.js +22 -4
  232. package/dist/rules/tissue-no-organelles.d.ts +1 -0
  233. package/dist/rules/tissue-no-organelles.js +9 -1
  234. package/dist/rules/tissue-no-stores.d.ts +1 -0
  235. package/dist/rules/tissue-no-stores.js +9 -1
  236. package/dist/rules/todo-names-its-plan.d.ts +1 -28
  237. package/dist/rules/todo-names-its-plan.js +2 -2
  238. package/dist/rules/tokens-only-colors.d.ts +8 -46
  239. package/dist/rules/tokens-only-colors.js +64 -20
  240. package/dist/rules/unique-query-step-name.d.ts +0 -7
  241. package/dist/rules/unique-query-step-name.js +6 -10
  242. package/dist/rules/worker-handles-are-scoped.d.ts +11 -8
  243. package/dist/rules/worker-handles-are-scoped.js +12 -5
  244. package/dist/rules/zustand-v5-best-practices.d.ts +8 -18
  245. package/dist/rules/zustand-v5-best-practices.js +32 -56
  246. package/dist/timing.d.ts +18 -0
  247. package/dist/timing.js +75 -0
  248. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,62 @@
1
1
  # @geonosis/oxlint-plugin-biological-architecture
2
2
 
3
+ ## 3.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 17dc880: `list-take-without-order` reads the D1 stack's three spellings of a page read in no order, and joins the `backend-d1` preset (#72): a Drizzle select chain with `.limit()` and no `.orderBy()`; a relational `findMany({ limit })` with no `orderBy`, or a `findFirst` — `LIMIT 1` — with no `orderBy`; and on the raw binding a `prepare()` SELECT with LIMIT and no ORDER BY, or a `.first()` over a SELECT with no ORDER BY (D1's `.first()` does not alter the statement). A `where` that is `eq(t.<unique>, …)`, or a SQL WHERE `<unique> = ?` with no OR, pins the row; the columns are the new `uniqueColumns` option (default `id`). An aggregate SELECT with no GROUP BY is one row. The rule now presumes no single engine. A `backend-d1` repo sees new findings where these spellings occur.
8
+
9
+ ### Patch Changes
10
+
11
+ - fb08b28: A refusal that stands in for every file a rule did not read (a rule enabled without an option, a root import with no package resolving) is carried by the lexically first file the rule judges, not the first file oxlint happens to visit, so two runs of one tree print the same lines. The file is read off the command line, a JSON config and `git ls-files`; a run the plugin cannot read behaves as before. `unresolvedPackage` is now said once in a file, not once in a run. The varying counts reported for `ssot-no-inline-facts`, `constants-in-constants-file`, `max-comment-density` and `ssot-no-process-env` were not those rules: oxlint 1.87 on macOS drops lines of its stdout when it is a shell pipe, and a plugin with one rule that reports every string shows it. The README says so.
12
+ - fb08b28: `GEONOSIS_LINT_TIMING=1` makes the plugin time each rule's visitor set-up and handlers and print the slowest ten to stderr when the run ends; `GEONOSIS_LINT_TIMING_TOP` lists more and `GEONOSIS_LINT_TIMING_FILE` writes every rule as JSON (#53). Without the variable nothing is measured. The timing found where one lint of this repo spent its time: the workspace and `node_modules` lookups read manifests on every import, every `directionRule` recomputed an import's landings, `no-orm-outside-db` computed them for imports of anything, and `test-no-clock-polling-for-promise-flips` walked every function of a test that names no `waitFor`. Lookups are now kept for one second, an import's landings are computed once for all direction rules, and the two rules skip what they cannot report; no rule's findings change, and the whole lint of this repo takes 29% less CPU (7.2 s to 5.1 s on average over five runs each).
13
+ - fb08b28: `list-take-without-order` no longer judges test files by default (`__tests__/`, `.test.`, `.spec.`, `integration-tests/`): the row a fixture takes does not matter, and 72 of one tree's 198 findings were fixtures. `exempt` replaces that list and `exempt: []` judges test files again; a baseline keyed on the old count falls by the test findings. A literal `take` above 1 from the first row, whose result is iterated, counted or handed whole to another call, now gets the message `truncated` ("reads at most N rows and the code treats them as all of them") instead of "name the column", which left the dropped rows dropped. Since 3.2.1, a capped read used as if complete is reported even when it names an order, because ordering does not make the cap all the rows; the count may rise. Same rule id throughout, so no consumer's counter changes by key.
14
+ - fb08b28: `tokens-only-colors`: since 3.2.0 the rule also reads colour literals in plain object properties, which is why a count rose without a change in the code (one Medusa storefront went from 3 to 69 findings; the 3.2.0 entry named only Tailwind variants). A theme passed to `defineTheme` (or to `compile`, `toDtcg`, `emitVariables`, `valueAt`) from `@geonosis/themekit` is the token layer and is no longer judged, whether the object is written in the call or held in a `const` of the same file. So is an object literal typed as a themekit palette type (`Theme`, `ThemeColors`, `ThemeMode` and the other types a `Theme` is made of) imported from `@geonosis/themekit`, by a `const` annotation, `satisfies` or `as`, so a brand theme defined in its own module is covered too; a local type of the same name is not. Their `exemptModules` entries can go. The new option `ignoreKeys` names property keys whose values are data that only look like colours (a shader's mask channels); it is empty by default and changes nothing until set.
15
+
16
+ ## 3.2.0
17
+
18
+ ### Minor Changes
19
+
20
+ - 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.
21
+
22
+ 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.
23
+
24
+ `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.
25
+
26
+ `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.
27
+
28
+ 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.
29
+ - 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.
30
+
31
+ 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.
32
+
33
+ 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.
34
+ - 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.
35
+ - 58eaf3a: `no-inert-hidden-jsx` reads `className={'hidden'}` and a hole-free template literal as it reads `className="hidden"`.
36
+ - 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.
37
+ - 58eaf3a: `effect-hook-naming` reads `React.useEffect` and `React.useLayoutEffect`, and judges the name of a default-exported hook.
38
+ - 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.
39
+ - 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.
40
+ - 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.
41
+ - 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.
42
+
43
+ `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`.
44
+
45
+ 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.
46
+
47
+ 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.
48
+
49
+ `waitForBuild(root)` and `buildLockPath(root)` are exported for a tier that must not run beside a build.
50
+
51
+ `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.
52
+ - 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.
53
+
54
+ ### Patch Changes
55
+
56
+ - 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.
57
+ - 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.
58
+ - 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).
59
+
3
60
  ## 3.1.0
4
61
 
5
62
  ### 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
- | `backend-d1` | 2 | Cloudflare D1 facts — false on an engine with transactions |
66
+ | `backend-d1` | 3 | Cloudflare D1: no transactions, scoped handles, and a page read in no order (Drizzle and the raw binding) |
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
@@ -199,6 +242,32 @@ Then find the stray copy, and mind that the answer is package-manager-specific:
199
242
  `.pnpm`. Read the links instead: `ls -l node_modules/@geonosis` in each workspace, and check the
200
243
  version in the target path.
201
244
 
245
+ ## Where the plugin spends its time
246
+
247
+ `GEONOSIS_LINT_TIMING=1 oxlint …` prints, to stderr when the run ends, the rules that cost the most:
248
+ the time each spent building its visitor (`create`, once per file) and inside its handlers
249
+ (`visit`), and the files it was handed. `GEONOSIS_LINT_TIMING_TOP=25` lists more than the ten
250
+ slowest, and `GEONOSIS_LINT_TIMING_FILE=timing.json` writes every rule as JSON. Without the variable
251
+ nothing is measured and nothing is printed. A rule that pays for a shared index (the workspace and
252
+ `node_modules` lookups, the export tables) pays on its first use, so read the numbers as where a run
253
+ spent its time and not as what one rule costs on its own. The lookups are kept for one second, so a
254
+ host that edits a `package.json` sees the edit within it.
255
+
256
+ ## A run reads the same every time
257
+
258
+ Every rule's findings are a function of the tree alone. The one thing that was not is where a
259
+ refusal lands: a rule enabled without an option reports once a run, "standing in for every file it did
260
+ not read", and oxlint visits files in an order that changes between runs. When the plugin can read the
261
+ run (oxlint started from the command line, a JSON config, a git checkout) the refusal is carried by the
262
+ lexically first file the rule judges: files git ignores or `ignorePatterns` names are not candidates,
263
+ nor is a file that switches the lint off, nor one an override for that rule matches. In any other run it
264
+ lands on the first file visited, as before.
265
+
266
+ oxlint 1.87 on macOS loses lines of its own output when its stdout is a shell pipe and the output passes
267
+ the pipe's buffer (`oxlint … | sort`, 6,000 findings: 5,445 to 6,294 lines over six runs; a plugin with one
268
+ rule that reports every string reproduces it). Written to a file, or read by a program that spawns it,
269
+ the same run gives the same lines every time. Count findings from the file, not from a pipeline.
270
+
202
271
  ## Where to read on
203
272
 
204
273
  - `RULES.md` — in this tarball: every rule, its preset, which source tree it came from, how each
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-one 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`, `tissue-no-fetch` and `hermetic-e2e-no-wall-clock-waits` — **83**. The two retired ids went in 3.0.0.
19
+ Every one of the thirty-one 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-d1 + backend-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 · backend-d1 3 · tenant-scoping 3 · testbed 3 · tooling 3 · 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,9 +978,10 @@ 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
- | `list-take-without-order` | `callees`, `orderKeys` |
984
+ | `list-take-without-order` | `callees`, `exempt`, `orderKeys`, `uniqueColumns` |
885
985
  | `max-comment-density` | — |
886
986
  | `molecule-atoms-only` | — |
887
987
  | `molecule-must-compose` | — |
@@ -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,11 +1037,12 @@ 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` |
944
- | `tokens-only-colors` | `allow`, `exemptModules`, `tokenPrefix` |
1045
+ | `tokens-only-colors` | `allow`, `exemptModules`, `ignoreKeys`, `tokenPrefix` |
945
1046
  | `unique-query-step-name` | `queryStepCalls`, `workflowFactories` |
946
1047
  | `worker-handles-are-scoped` | `within`, `wrapper` |
947
1048
  | `zustand-v5-best-practices` | — |
@@ -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
+ }