@geonosis/oxlint-plugin-biological-architecture 2.10.0 → 2.11.1

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 (64) hide show
  1. package/CHANGELOG.md +309 -0
  2. package/README.md +22 -20
  3. package/RULES.md +152 -121
  4. package/corpus/.oxlintrc.json +37 -7
  5. package/corpus/app/arrow-route/page.tsx +1 -0
  6. package/corpus/app/barrel-delegate/page.tsx +5 -0
  7. package/corpus/app/good-delegate/page.tsx +2 -0
  8. package/corpus/atoms/index.ts +1 -0
  9. package/corpus/cells/alias-of-cell.tsx +3 -0
  10. package/corpus/cells/index.ts +1 -0
  11. package/corpus/cells/laundered-compound.tsx +3 -0
  12. package/corpus/cells/laundered-organelle.tsx +3 -0
  13. package/corpus/compounds/bad-aliased-barrel-organelle-import.tsx +6 -0
  14. package/corpus/compounds/bad-barrel-organelle-import.tsx +7 -0
  15. package/corpus/compounds/bad-member-hook.tsx +8 -0
  16. package/corpus/compounds/good-atom-through-barrel.tsx +6 -0
  17. package/corpus/dates/bad-method-order.ts +7 -0
  18. package/corpus/dates/good-time-order.ts +2 -0
  19. package/corpus/disguise/bad-namespace-effect.tsx +7 -0
  20. package/corpus/disguise/good-rendering-effect.tsx +7 -0
  21. package/corpus/emails/welcome.ts +4 -0
  22. package/corpus/failure/logged-by-name.ts +2 -0
  23. package/corpus/hidden/bad-inert-hidden.tsx +3 -0
  24. package/corpus/hidden/good-hidden-input.tsx +3 -0
  25. package/corpus/inline-data/cells/bad-inline-options.tsx +15 -0
  26. package/corpus/inline-data/cells/good-imported-options.tsx +11 -0
  27. package/corpus/integrations/providers/email-gmail.ts +2 -2
  28. package/corpus/medusa/queries/bad-unordered-page.ts +1 -1
  29. package/corpus/medusa/queries/good-ordered-page.ts +1 -1
  30. package/corpus/medusa/subscribers/bad-satisfied-config.ts +5 -0
  31. package/corpus/molecules/alias-of-molecule.tsx +2 -0
  32. package/corpus/molecules/good-molecule.types.ts +1 -0
  33. package/corpus/molecules/laundered-atom.tsx +3 -0
  34. package/corpus/organelles/bad-logic.ts +5 -0
  35. package/corpus/organelles/custom-carousel.tsx +11 -0
  36. package/corpus/organelles/index.ts +2 -0
  37. package/corpus/organelles/reveal-on-scroll-list.tsx +9 -0
  38. package/corpus/organelles/use-cart-effect.ts +11 -0
  39. package/corpus/packages/db/src/cell/good-cell-transaction.ts +7 -0
  40. package/corpus/ports/good-engine-callback.ts +4 -0
  41. package/corpus/subpaths/bad-root-reexport.ts +3 -0
  42. package/corpus/time/bad-reexported-door.ts +5 -0
  43. package/corpus/tissues/alias-of-tissue.tsx +2 -0
  44. package/corpus/tissues/bad-laundered-organelle.tsx +6 -0
  45. package/corpus/tissues/bad-laundered-through-cells-barrel.tsx +6 -0
  46. package/corpus/tissues/bad-member-hook.tsx +11 -0
  47. package/corpus/tissues/bad-namespace-laundered.tsx +7 -0
  48. package/corpus/tissues/bad-star-laundered.tsx +2 -0
  49. package/corpus/tissues/good-cell-through-barrel.tsx +6 -0
  50. package/corpus/tissues/good-namespace-real-cell.tsx +7 -0
  51. package/corpus/tissues/laundered-compound.tsx +3 -0
  52. package/corpus/todos/bad-xxx-marker.ts +2 -0
  53. package/corpus/todos/good-marker-inside-a-number.ts +2 -0
  54. package/corpus/tokens/theme/palette.tsx +1 -1
  55. package/corpus/tsconfig.json +3 -0
  56. package/corpus/workers/bad-satisfied-worker.ts +7 -0
  57. package/dist/chunk-H7UQNDFE.js +109 -0
  58. package/dist/{chunk-24K5232K.js → chunk-ZY3XIRRE.js} +1 -1
  59. package/dist/discover-atoms.js +4 -3
  60. package/dist/index.d.ts +3 -4
  61. package/dist/index.js +1417 -823
  62. package/dist/presets.d.ts +5 -5
  63. package/dist/presets.js +1 -1
  64. package/package.json +5 -4
package/CHANGELOG.md CHANGED
@@ -1,5 +1,314 @@
1
1
  # @geonosis/oxlint-plugin-biological-architecture
2
2
 
3
+ ## 2.11.1
4
+
5
+ ### Patch Changes
6
+
7
+ - d3fc711: The reach corpus is synced from the fixtures in place instead of deleted and recopied. A pack or build that runs while a gate reads the corpus leaves every unchanged file where the reader has it; only files the fixtures dropped are removed.
8
+
9
+ ## 2.11.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 0f99e26: What one consumer's adoption measured, fixed in the plugin and the parity tool (2026-09-19).
14
+
15
+ **`@geonosis/lint-parity` — a refusal is never reach (row 559).** Since D-084 a rule enabled
16
+ without an option it requires reports that as an ordinary finding, and `--corpus` counted it as the
17
+ rule firing: a bare, unconfigured rule read as covered. The finding is read out by its header
18
+ (`<rule> requires option \`<option>\`.`) and listed under **enabled but unconfigured**, apart from
19
+ the rules that fire nowhere; the run fails on either. `CorpusReport` gains `unconfigured`,
20
+ `RuleReach` gains `refusedInA` / `refusedInB`, and `corpusPassed` / `isRefusal` are exported.
21
+
22
+ **Two rules now REQUIRE their option, reporting once a run when enabled without it:**
23
+
24
+ - `no-swallowed-catch` requires `paths` (regex sources: the fetch and refresh modules). It was
25
+ silent bare while its `fixShape` promised a refusal (row 539). Every config extending
26
+ `failure-paths` names `paths` or gets the one finding.
27
+ - `no-runtime-dead-affordance` requires the positional `element→atom map` its twin
28
+ `no-raw-html-atoms` takes. It was silent bare while the docs said it refused (#8). Every config
29
+ extending `frontend` / `recommended` that enables it names the map or gets the one finding.
30
+
31
+ **`subpath-over-barrel` no longer requires `packages`.** The map is the package's own
32
+ `package.json` `exports`, read for the repo's own packages — `node_modules/<name>` is a symlink or
33
+ its manifest is `private` — with the names behind each door read off its entry file (`export *`
34
+ followed inside the package). `{ name, subpaths }` still replaces the map for one package;
35
+ `{ name }` alone opts a third-party package in. Doors written as patterns (`./icons/*`) are not
36
+ read. Bare, the rule judges every root import of an own package: expect findings a hand-written
37
+ map never covered.
38
+
39
+ **The seven #12 tree options take a string or a list alike:** `schema`, `tenant`, `queries`,
40
+ `seam`, `exempt`, `transacts`, `except`. A consumer wrote `seam` as a string and oxlint refused the
41
+ whole config at options validation — every rule read 0, exit 1. A bare string is a one-element
42
+ list now; existing configs of either shape keep working.
43
+
44
+ `max-comment-density` and `ssot-no-inline-facts` say in their descriptions which repos they suit
45
+ and which they drown (3954 and 3056 findings on one consumer's tree, by house style rather than
46
+ defect). `todo-names-its-plan` no longer reads `XXX` inside `$X,XXX.XX`: a marker is followed by
47
+ `(`, `:`, whitespace or the end of the comment. The generated skill says how to measure a rule:
48
+ with a copy of the config, because `oxlint -D` does not lift a config's `"off"`.
49
+
50
+ REFUSAL-IN-MINOR: **no-swallowed-catch and no-runtime-dead-affordance** refuse a bare run where they did not at 2.10.0, which is a break and
51
+ would normally want a major. It ships in a minor by the owner's ruling of 2026-09-19, on a measured
52
+ basis: every consumer pins this kit EXACTLY — three consumers, at 2.10.0, 2.6.1 and 2.10.0, no
53
+ caret anywhere — so nothing auto-upgrades and the version number protects nobody here. The warning
54
+ lives in this changelog instead, and the two consumers that could be reached were told directly
55
+ before the cut. The third is three releases behind; it is the one that must be told by hand.
56
+ - 0f99e26: Four bugs a consumer filed against 2.10.0 (#13–#16).
57
+
58
+ `no-void-port` takes `engineCallbacks` — regexes over an exported alias name, empty by default —
59
+ so a workflow-hook check the engine catches (`HookCheck<TInput> = (input, ctx) => Promise<void>`)
60
+ can be named as not a port; a `Result` there would never be read.
61
+
62
+ `geonosis-doctor`: the Claude-plugin train is held only against the install that LOADS at the
63
+ doctored root (user scope, or a project scope naming that root), never another repo's; the
64
+ spare-declaration question reads the script files scripts and hooks run, CI workflows and
65
+ `geonosis.json` tiers, sees a bin quoted as an argv string, and says nothing about a package with
66
+ no bin. Expect four fewer WARNs on a root whose gate lives in a `.mjs`.
67
+
68
+ `geonosis-mcp`: a server started before an in-session `geonosis update` refuses every call naming
69
+ the version it is running, the version the repo now has, and the restart (`/mcp` → reconnect) —
70
+ never the install. `geonosis update` ends by saying so where `.mcp.json` names a `geonosis` server.
71
+ - 0f99e26: `no-date-string-compare` also reports a timestamp turned into text by its own method — `toString()`, `toDateString()`, `toUTCString()`, the `toLocale*` forms — and ordered; `toISOString()` is left alone.
72
+ - 0f99e26: `no-hook-in-component-disguise` reads a hook called through a namespace (`React.useEffect`), so such a component is named a hook in disguise and a `page.tsx` running one loses the routing-marker exemption.
73
+ - 0f99e26: Four rules see what they were blind to, and eleven readings refuse instead of banking a zero.
74
+
75
+ `tissue-no-hooks` and `compound-must-be-stateless` report a hook called as a member —
76
+ `React.useState(0)`, `hooks.useCart()` — where they read only a bare name. `time-through-the-door`
77
+ sees the library through every import door (`export * from`, `import()`, `require()`), and
78
+ `subpath-over-barrel` reads `export { X } from '<root>'` as the import it is. Expect new findings on
79
+ a tree that spelled a hook through its default React import.
80
+
81
+ `geonosis-ratchet`: a shell killed by a signal, a formatter that exited non-zero naming no file, a
82
+ `turbo boundaries` that printed no summary, a `geonosis.json` that will not parse, a stale report at
83
+ a named `reportPath`, and an oxlint run that answered in a shape `expectFormat` did not ask for are
84
+ each a `CounterError` now. Every one of them read as a number and lowered the baseline to it.
85
+
86
+ `geonosis-doctor`: `deployed`, `seams`, `rails` and `drift` say FAIL over a `geonosis.json` that is
87
+ there and will not parse, where each had read it as a repo that declared nothing.
88
+ - 0f99e26: `ssot-no-process-env` takes `doors` — the repo's own env module, as regex sources — beside the conventional ones, and treats a test beside its source (`x.test.ts`, `x.spec.tsx`) as a door, as it already did a tests folder.
89
+ - 0f99e26: `no-inline-data-in-jsx` reads an array through `as const` and `satisfies` in a prop value as well as before `.map()`, and outside a feature folder points at a `lib/` module beside the component rather than `features//lib/`.
90
+ - 0f99e26: **One declared list of tiers, read by both rules that name them (#5).** `no-ts-in-bio-folders`
91
+ carried its own idea of which folders belong to the architecture — `atoms, molecules, compounds,
92
+ cells, tissues`, a CMS-block folder, `emails` and `organs` — and `no-invalid-feature-folders`
93
+ carried a different one beside it. The first omitted `organelles`, a tier LAW.md declares, and
94
+ named three folders the law never mentions. Both rules now read `TIERS`, which a test holds against
95
+ the line in LAW.md that draws it, and neither restates it.
96
+
97
+ **Reach moves in both directions.** `no-ts-in-bio-folders` gains `organelles/`: a `.ts` file
98
+ sitting directly in an organelle folder is a finding where it was invisible. A `use-*.ts` hook
99
+ there is NOT — `organelles/` is where every other rule in the plugin sends a hook, and judging the
100
+ tier without exempting the shape it exists to hold would leave a hook with no legal folder. The
101
+ barrel and a co-located `<component>.types.ts` stay exempt as before. MEASURED across three
102
+ consumer trees — two Medusa + Next, one Cloudflare Workers + D1: 115 `.ts` files sit directly in
103
+ an `organelles/` folder and every one of them is a barrel, a `.types.ts` or a hook, so the widening
104
+ costs those three trees nothing today.
105
+
106
+ The rule loses the CMS-block folder, `emails/` and `organs/`, which were one repo's vocabulary
107
+ living in shared code (LAW 6) and are not tiers. A repo that genuinely holds components in a folder
108
+ the law does not name now says so: `extraFolders` (default: none, so the rule does nothing until
109
+ someone opts in), taking a bare string or a list like every other list option here.
110
+
111
+ **Two fixes that came with reading the parent folder rather than an unanchored regex.**
112
+ `subtissues/thing.ts` was read as `tissues/` and is not any more, and a file one folder BELOW a
113
+ tier (`cells/parts/helper.ts`) is left to the folder it is actually in.
114
+
115
+ `no-invalid-feature-folders` is unchanged in behaviour: the six tiers plus `lib` and `stores`, its
116
+ own two non-tier neighbours.
117
+ - 0f99e26: An enabled rule missing its option reports ONE finding a run instead of throwing (D-084), and the
118
+ five rules that chose their files from paths compiled in read them from an option (#12).
119
+
120
+ **The posture.** Up to 2.10.0 a rule enabled without an option it requires threw from `create()`.
121
+ Measured: the throw erased every other finding in the file it surfaced on, and rendered under
122
+ `--format=unix` as `:0:0: [Warning]` naming nothing. It is a finding now — attributable, counted by
123
+ the ratchet, neighbours intact — reported once per run on the first file oxlint constructs the rule
124
+ for, with the same `biological-architecture/<rule> requires option \`<option>\`.` header. Nothing the
125
+ plugin exports throws for a missing option. Expect exactly one new finding per bare rule per run
126
+ where a run used to be refused whole.
127
+
128
+ **#12 — four rules now REQUIRE a tree option; every config extending `backend-workflows` or
129
+ `tenant-scoping` must name it or gets the one finding above:**
130
+
131
+ - `tables-declare-their-plane` requires `schema` (a regex over the filename: the schema tree);
132
+ `control/` and `tenant/` are read under it.
133
+ - `tenant-tables-carry-org-id` requires `tenant` (a regex: the tenant-plane schema tree).
134
+ - `queries-require-org-scope` requires `queries` (a regex: the query modules).
135
+ - `dialect-through-the-seam` requires `seam` (regex sources: the files that may name a dialect);
136
+ `exempt` defaults to the test paths (`__tests__/`, `.test.`, `.spec.`) — a repo whose test helpers
137
+ live elsewhere (`testing/`) names them.
138
+
139
+ `no-d1-transaction` takes `transacts` and `except` (regex sources), both defaulting to nothing: the
140
+ plane the rule exempted was one repo's, and a D1-only repo has no plane to exempt. Expect new
141
+ findings on every `db.transaction()` that used to sit inside `/packages/db/(src|testing)/`.
142
+
143
+ One consumer's values, verbatim, are in `proofs/issue-12-and-option-posture-2026-09-18.md`.
144
+
145
+ `@geonosis/doctor`: `missingOptionOf` hears a refusal a rule reports as well as one it throws;
146
+ `exercised` no longer counts a bare rule's refusal finding as reach — it is a FAIL naming the option.
147
+ `@geonosis/cli`: `optionsNeededBy` reads through the same reader, so `init` still leaves those rules
148
+ out and names them.
149
+
150
+ REFUSAL-IN-MINOR: **tables-declare-their-plane, tenant-tables-carry-org-id, queries-require-org-scope and dialect-through-the-seam** refuse a bare run where they did not at 2.10.0, which is a break and
151
+ would normally want a major. It ships in a minor by the owner's ruling of 2026-09-19, on a measured
152
+ basis: every consumer pins this kit EXACTLY — three consumers, at 2.10.0, 2.6.1 and 2.10.0, no
153
+ caret anywhere — so nothing auto-upgrades and the version number protects nobody here. The warning
154
+ lives in this changelog instead, and the two consumers that could be reached were told directly
155
+ before the cut. The third is three releases behind; it is the one that must be told by hand.
156
+ - 0f99e26: `no-orm-outside-db` takes the data layer as an option: `dataLayer` holds regex sources over the path, and the default is `/packages/db/`, so an unconfigured repo sees no change. Its message no longer tells every consumer to import from `@app/db`, one repo's package. `ssot-no-inline-facts`'s description drops the same repo's `@app/client` from its examples.
157
+ - 0f99e26: `no-raw-sql-outside-allowed` takes the modules where raw SQL is allowed as an option, `allow` (regex sources over the path). It used to compile in four modules of one repo's data layer, a backup, a dialect module, an app-role file and a search module, and grant them to every repo. Migrations, seeds, scripts and tests stay allowed everywhere. A repo that relied on the old list names those modules in `allow`, for example `["/ops/backup\\.ts$", "/queries/(reads|writes)/search\\.ts$"]`.
158
+ - 0f99e26: `ssot-no-inline-facts` takes the modules that own facts as an option, `owners` (regex sources over the path). It used to compile in one repo's layout as owners: a domains package, a client package, a selectors module, two `paths.ts` modules and a schema directory. The conventions the rule names stay: a constants file, `lib/env.ts`, `lib/routes.ts`, and tests, scripts, seeds, migrations and configs. A repo that relied on the old list names its own owner modules in `owners`.
159
+ - 0f99e26: `store-route-scopes-tenant-data`'s default `entities` no longer carries `planned_order` and `production_order`, which were one repo's own entities: a repo that owns such entities names them in `entities`. The default keeps Medusa's customer-owned entities and the B2B starter's company, employee and quote.
160
+ - 0f99e26: `queries-require-org-scope` and `tenant-tables-carry-org-id` take `tenantKey` — the field a tenant is scoped by, default `orgId` — and read a quoted key as a bare one. Their diagnostics no longer cite one consumer's plan numbers or column helper.
161
+ - 0f99e26: The five blind spots left after #4 and #11 are closed (rows 591–595). Expect new findings on a
162
+ tree that reaches a tier through a namespace import or `export *`, that hides a stateless compound
163
+ behind a one-line re-export in `cells/`, `tissues/` or `molecules/`, or that declares its aliases
164
+ somewhere the resolver did not read.
165
+
166
+ **Where an import lands, read from more places.** A `#ui/*` specifier declared in `package.json`
167
+ `imports` and nowhere else now lands (row 591) — the manifest is read first for a `#` name, as Node
168
+ does; a directory with only a `jsconfig.json` has its `paths` read. A `tsconfig.json` whose `extends`
169
+ names a package (`@repo/tsconfig/base.json`, or `@repo/tsconfig` alone) is followed through
170
+ `node_modules` (row 592), and `${configDir}` reads as the leaf's directory, so a shared base can
171
+ declare `paths` and they still land.
172
+
173
+ **A namespace import is no longer judged at the door** (row 593). `import * as cells from
174
+ '@/components/cells'` is judged by the members the file reaches — `cells.CustomCarousel`,
175
+ `<cells.CustomCarousel />` — each followed to the file that declares it; taken whole, and for
176
+ `export *`, `import()` and `require()`, every name the door carries is followed. The message names
177
+ the home as before: `"@/components/cells (→ src/kit/organelles/carousel.tsx)"`. A namespace whose
178
+ reached members all live where the door says stays silent.
179
+
180
+ **Three rules stop exempting a re-export shim outright** (row 594). `cell-must-be-stateful`,
181
+ `tissue-must-compose` and `molecule-must-compose` returned on any pure `export … from` file. Now
182
+ the alias of a file in the shim's own tier stays exempt, and a shim whose code lives elsewhere is
183
+ judged as that code: a stateless compound behind one line in `cells/` is a cell with no state, a
184
+ compound behind one line in `tissues/` is a tissue that composes nothing, an atom behind one line in
185
+ `molecules/` is an atom filed as a molecule. A Next.js route file that delegates by re-export is the
186
+ thin delegate another rule demands and stays exempt; a re-export nothing on disk answers stays
187
+ exempt, as before.
188
+
189
+ **The corpus tally separates named from unnamed findings** (row 595). `CASES` is its own module,
190
+ `tallyCorpus` reports per rule how many findings a case asked for and which files carry findings no
191
+ case named, and `pnpm corpus:tally` in the plugin package prints the table a proof reports
192
+ (`--unnamed` exits 1 on any noise). Today: 131 named, 111 unnamed over 244 cases.
193
+
194
+ Measured read-only on the three consumer trees against each one's own config narrowed to the
195
+ fourteen affected rules — with the narrowed config placed so that the consumer's `overrides` and
196
+ `ignorePatterns` are in scope, which the earlier measurements were not (see below) — before (this
197
+ repo's `main` build) and after: **a second Medusa reference 12 → 12** (1,723 files), **a Medusa +
198
+ Next consumer 0 → 0** (2,345 files), **a Cloudflare Workers + D1 consumer 0 → 0** (2,566 files).
199
+ None of the three has a namespace import of a tier barrel, a pure re-export shim in a tier folder
200
+ outside `dist/`, a `#` map absent from its tsconfig, or `paths` hoisted into a shared base. Each
201
+ zero stands beside a planted positive that fired in the same run under the same config: on the
202
+ kit's corpus, and on a scratch copy of one feature of each tree that read zero, where the same
203
+ barrel, shim and namespace plants read 0 on the installed 2.6.1, 2 on `main` (the namespace still
204
+ invisible) and 3 on this build. Wall time on the narrowed runs moved within noise (the reference
205
+ 567 → 467 ms, Medusa + Next 1,023 → 891 ms, Workers + D1 842 → 723 ms).
206
+
207
+ **A correction to `where-an-import-lands`.** oxlint resolves an override's `files` and
208
+ `ignorePatterns` relative to the CONFIG FILE's directory. The consumer numbers in that changeset
209
+ were taken with a narrowed config written outside each tree, so the Workers + D1 consumer's
210
+ direction rules — which live only in `overrides` — were never in scope (its "0 → 0" measured
211
+ nothing), and the Medusa + Next consumer's 39 `cell-must-not-compose-cell` were findings in
212
+ `__tests__/` and `.stories.tsx` files its own `ignorePatterns` exclude. The reference's 12 were
213
+ top-level rules on un-ignored files and stand. Rules
214
+ affected: the eleven direction rules named in `where-an-import-lands`, plus `cell-must-be-stateful`,
215
+ `tissue-must-compose`, `molecule-must-compose`.
216
+ - 0f99e26: `tissue-no-data-props` reads a handler written as a method signature (`onSelect(id: string): void`) as the handler it is.
217
+ - 0f99e26: Rules that read a config object — steps, emits, subscribers, durable hosts, query steps — read it through `as` and `satisfies`: `{ event: 'order.placed' } satisfies SubscriberConfig` is the object it annotates, not an unreadable one.
218
+ - 0f99e26: Every direction rule judges where an import LANDS, not only how it is spelled (#4, #11). Expect
219
+ new findings on a tree that reaches a tier through its barrel or through a one-line re-export.
220
+
221
+ Two consumer issues were one blindness. `import { X } from '@/components/organelles'` names no file
222
+ in a tier folder, so `compound-no-stores` and its ten siblings read the string and stopped (#4); a
223
+ `cells/Carousel.tsx` that is exactly `export { CustomCarousel } from '@/kit/organelles/carousel'`
224
+ put an organelle in the cell tier for `tissue-no-organelles` and every rule that looked (#11).
225
+ Both counts were a lower bound of unknown depth on a kit whose number may only shrink.
226
+
227
+ Now the specifier is resolved on disk — relative to the importer, or through the repo's own
228
+ `tsconfig.json` `paths` (comments, trailing commas and a relative `extends` chain read; nothing
229
+ this plugin knows by name) — and the file it lands on is judged beside the spelling, so a bare
230
+ barrel is the tier folder it names. Each name the import takes is then followed through
231
+ `export … from` and `export *` to the file that declares it, eight hops deep and never twice through
232
+ one file, and THAT file is judged too; the message says where the name went:
233
+ `cannot import organelle "@/components/cells (→ src/kit/organelles/carousel.tsx)"`. A landed path
234
+ is judged from the importer's package root, never as an absolute path, so a checkout under `/app/`
235
+ does not become a breach. One import is one finding, whichever of its landings is the breach.
236
+
237
+ What is still judged at the door only: a namespace import, `export *`, `import()`, `require()`, a
238
+ name the door does not export, and a bare package. A specifier nothing on disk answers is judged as
239
+ before — except that one whose last segment is a tier folder is read as that folder.
240
+
241
+ Measured on the reporter's tree (a second Medusa reference, 1,554 files, baseline 0): **0 → 12**
242
+ across the eleven direction rules — `tissue-no-organelles` 8, `compound-no-stores` 3,
243
+ `cell-must-not-compose-cell` 1 — every one a barrel import that was silent. On a Medusa + Next
244
+ consumer (2,832 files): `molecule-atoms-only` 0 → 4, `atom-no-deps` 0 → 1, `compound-no-stores`
245
+ 0 → 1, and the 39 `cell-must-not-compose-cell` findings already there unchanged. On a Cloudflare
246
+ Workers + D1 consumer: 0 → 0. The direction rules' share of a run grew by roughly a quarter (the
247
+ reference +80 ms, Medusa + Next +270 ms), paid once per imported file per process. Nothing that
248
+ fired before stops firing: the spelled source is still judged first. Rules affected:
249
+ `atom-no-deps`, `cell-must-not-compose-cell`,
250
+ `cell-no-cross-feature-organelles`, `cell-no-tissues`, `compound-no-stores`, `molecule-atoms-only`,
251
+ `no-cross-feature-stores`, `no-orm-outside-db`, `organelle-dependency`, `tissue-no-organelles`,
252
+ `tissue-no-stores`.
253
+ - 0f99e26: `worker-handles-are-scoped` reads a handler object written `export default { fetch … } satisfies ExportedHandler<Env>` — the shape Cloudflare documents — which it passed unread.
254
+
255
+ ### Patch Changes
256
+
257
+ - 0f99e26: Every rule reads a tier, `stores`, `domains` or `features` folder as a whole path segment, as `lib/tiers` already did: `subcells/`, `datastores/`, `subdomains/` and `mytissues/` are no longer taken for the tiers they end in.
258
+ - 0f99e26: `no-brand-names` says what it judges (#10): the brands a repo names — its own and its consumers' — so
259
+ a third-party mark a kit renders, a social network's or a review platform's, is not one of them.
260
+ - 57d794d: The packed corpus is rebuilt from the fixtures at pack time, so a turbo cache restore that left stray files in `corpus/` no longer ships them.
261
+ - 0f99e26: `no-runtime-dead-affordance`'s guidance says its map names only controls: every atom in it is judged as one, so copying `no-raw-html-atoms`' whole map would report a `<Header>` dead.
262
+ - 0f99e26: Atom discovery strips comments with the string-aware scanner, so a glob string like `'icons/*'` no longer swallows the element an atom renders.
263
+ - 0f99e26: `no-duplicate-jsx-patterns` reports a repeated className once when it contains `::` (`[&::-webkit-scrollbar]:hidden`); the split key had cut it and reported the same nodes twice.
264
+ - 0f99e26: `emit-declares-attempts` reads a quoted key (`'attempts': 3`) as the name it spells and a computed key as no name, through the shared object readers.
265
+ - 0f99e26: `no-invalid-feature-folders` names every folder it accepts — `atoms` and `molecules` included — read from the same set it judges by.
266
+ - 0f99e26: `font-roles-only`'s `fixShape` said an enabled rule with no `roles` refuses the run. It reports that
267
+ once a run and judges nothing, as its own description and every other option-gated rule already
268
+ said. A test now asks every exported rule that none claims to refuse a run and none throws or
269
+ reports twice when constructed with no options.
270
+
271
+ The README said the plugin carries 55 rules and that a bare rule refuses the run. It carries 80 a
272
+ consumer can enable, and a bare rule says so once a run and judges nothing. A test holds both.
273
+ - 0f99e26: `no-index-count-as-exact` judges a nested object as its own payload: an outer object is no longer reported for an estimate its inner payload labels, nor twice for one it does not.
274
+ - 0f99e26: `layer-walls`' probe tries every layer a wall forbids, not only the first, before it refuses.
275
+ - 0f99e26: Two diagnostics printed literal double braces: `step-opens-its-own-cell` suggested `scope: {{ perStep: … }}` and `no-render-prop-reader` named `View={{}}`. They print `scope: { perStep: … }` and `View={…}`, and a test holds every message to placeholders only.
276
+ - 0f99e26: The plugin's text readers share one comment stripper, which blanks a comment to spaces rather than cutting it — so `a/* note */b` stays two tokens — and `store-route-scopes-tenant-data` drops its private copy.
277
+ - 0f99e26: `organelle-single-source` reads a form context imported through a `providers` barrel (`'../providers'`) as a form source, as it already did through a file.
278
+ - 0f99e26: A `tsconfig` `paths` or `package.json` `imports` key that equals the specifier wins over a wildcard that also matches it, whatever order the map lists them in — as tsc and Node resolve it.
279
+ - 0f99e26: Alias resolution follows Node and `tsc` where it had not. A `#` specifier is read only from the nearest `package.json`, Node's package scope: a nested package with no `imports` map no longer borrows its parent's map and lands where Node would refuse. For a tsconfig that `extends` several bases, the later base's `paths` and `baseUrl` win, as `tsc` resolves them.
280
+ - 0f99e26: The `backend-sagaflow-cf` deprecation line says the old preset name resolves "until the next major", which is what the 2.3.0 changelog promised. It used to say "for one release", seven minor releases on. The retired-rule docs and RULES.md now use the same wording.
281
+ - 0f99e26: `cell-no-tissues` and `organelle-dependency` treat an `app/` route as above them wherever the repo keeps its app, the way the other tier rules already did. They used to know only one repo's `apps/web/app/`. This widens them: a cell or organelle importing from `src/app/…` used to pass. `organelle-dependency`'s description and message name the `sharedFeatures` option in place of one repo's shared features, and `compound-must-be-stateless` drops a dead pattern that named one repo's UI package.
282
+ - 0f99e26: A bare `.` or `..` import specifier, which loads that directory's index, is read as relative. The direction rules resolve it against the importer the way they resolve `./x` and `../x`, where they used to treat it as a package name and never see the breach. `layer-walls` under `externals: 'deny'` no longer refuses `import … from '..'` as an external import.
283
+ - 0f99e26: `boundary-time-is-primitive` judges a default-exported arrow or function expression (`export default (at: Date) => …`), which it used to pass unread. A comment claiming the rule's exemption is a list of modules "never a path pattern" is gone: since #204 a door may be a path, a specifier or a regex.
284
+ - 0f99e26: `tokens-only-colors` reads a vendor-prefixed colour property as React spells it (`WebkitTextFillColor`), and Tailwind's logical border utilities (`border-s-[…]`, `border-e-[…]`) and v4's `inset-shadow-[…]` / `inset-ring-[…]` as colour utilities. This widens the rule: a hardcoded colour behind any of those used to pass.
285
+ - 0f99e26: `compound-must-be-stateless` refuses a compound's import from a `contexts/` module, as its description always said, and not only from `providers/`. This widens the rule: a compound importing `../contexts/theme` used to pass.
286
+ - 0f99e26: The plugin names no consumer. Its package description, rule docs, fixShapes, RULES.md and tests describe the repos a rule was measured in by their stack: a Medusa storefront, a Workers + D1 app. Test scopes are neutral (`@shop/`, `@app/`). No rule's behaviour changes.
287
+ - 0f99e26: `no-unregistered-plugin-dir` strips a registry's comments with the string-aware scanner, so a `https://` inside a string no longer hides the registrations after it on that line.
288
+ - 0f99e26: `effect-hook-naming` holds both halves of the name, as its fixShape says. A file already named `-effect.ts` whose export is not `…Effect` now fires; the rule used to return early for every such file. An exported arrow hook (`export const useCart = () => …`) is judged like a function declaration, where it used to pass unread. This widens the rule, and the corpus gains `organelles/use-cart-effect.ts` as its evidence.
289
+ - 0f99e26: The module reader behind barrel and shim resolution strips comments without touching strings. A glob like `'src/*'` in a string used to open a "comment" that ran to the next `*/`, dropping every export between the two from the module's table. A name then landed at the barrel instead of at its home, and the direction rules judged the wrong file. An object literal's computed key (`{ [retries]: 3 }`) is no longer read as a property named after the variable, including where a step's name is read.
290
+ - 0f99e26: `molecule-must-compose` reads a path that arrives with backslashes, as every other rule does through the shared `normalise`. It leaves a co-located `<component>.types.ts` alone, the file `no-ts-in-bio-folders` sanctions in a tier folder. The two rules used to demand opposite things of it.
291
+ - 0f99e26: The README's preset table names every preset the plugin ships with the rules it carries — six were missing and four counts were wrong — and a test holds it to `PRESETS`. The hand-kept options table, six rules of twenty-two, points at `RULES.md`'s generated one instead.
292
+ - 0f99e26: `plugin-route-namespaced` derives a route from the path inside its package, so an `api/` directory in the checkout path above it no longer becomes the scope, and reads `under` as whole segments.
293
+ - 0f99e26: `no-card-shaped-div`, `no-renamed-html-props` and `queue-loop-is-the-library` read the filename through the shared `normalise`, as every other rule does. On a Windows path the first two used to judge no file, and the third reported the `__tests__` files it exempts.
294
+ - 0f99e26: The style reader behind `tokens-only-colors` and `font-roles-only` leaves a computed key (`style={{ [prop]: 'red' }}`) alone, as its doc always said. It used to read the key as a property named after the variable. `font-roles-only`'s fixShape quotes an invented family, not a real brand's font.
295
+ - 0f99e26: `no-swallowed-catch` reports `.catch(console.error)` and the rest of the logger family handed over by name. That is the log-only handler the rule already refused when it was written inline, and it used to pass unread as a named function.
296
+ - 0f99e26: `no-react-namespace` suggests `import { useState }` for a value and `import type { ReactNode }` for a type; it suggested `import type` for both, which erases a value.
297
+ - 0f99e26: A name read into a regular expression is escaped: verify-arch's Medusa pack reads a module named through an identifier holding `$` (it read `$` as an anchor and named no module), and `store-route-scopes-tenant-data` matches a configured entity or scoping name literally.
298
+ - 0f99e26: `service-name-not-reserved` says its default is the 32 keys of Medusa 2.19's `Modules` enum — which is what it ships, checked against the installed enum; the prose said 33.
299
+ - 0f99e26: `router-schema-parity` leaves a co-located test (`x.test.ts`, `x.spec.ts`) and a declaration file (`x.d.ts`) beside the routers or schemas alone; neither is half of the mirror.
300
+ - 0f99e26: `tissue-no-stores` no longer sends a reader to `organ-no-stores`, a rule that does not exist; a test now holds every rule's prose to rule names the plugin ships or has retired.
301
+ - 0f99e26: Every order a tool writes, compares or applies — generated files, migration order, listings, cursors — sorts with the `en-US` collation, named rather than inherited from the machine. On an `en-US` machine nothing moves; on one set to another language (Estonian sorts `z` before `t`) the output is now the same one.
302
+ - 0f99e26: `subpath-over-barrel` no longer reads a third-party package as the repo's own because pnpm linked it: a link counts only when it leaves `node_modules`, as a workspace link does.
303
+ - 0f99e26: `next-route-segment-is-thin-delegate` accepts a tissue imported through its barrel (`…/tissues`), and judges a default-exported arrow or function expression by what it returns.
304
+ - 0f99e26: `time-through-the-door`'s probe reads the door and the enumerated exemptions the way the rule does: a door that is a path but no valid regex no longer crashes it, and it never plants where `exemptModules` already reaches.
305
+ - 0f99e26: `todo-names-its-plan` holds a whole citation to the whole `id` shape — an alternation no longer lets `TODO(025abc)` pass — and names the line the marker is on inside a block comment, not the comment's first line.
306
+ - 0f99e26: `tokens-only-colors` reports a `var()` default as a foreign colour only when the prop it defaults is a colour property: `gap = 'var(--app-space)'` is spacing, not a bypassed token. A written-out colour default is still reported under any name.
307
+ - 0f99e26: `no-trivial-wrapper-component` lets a tissue bridge to a cell, compound, organelle, molecule or atom imported through its barrel (`'../cells'`), as it already did through a file.
308
+ - 0f99e26: `no-type-definitions-in-components` points outside a feature folder at a co-located `<component>.types.ts` rather than `features//lib/types.ts`.
309
+ - 0f99e26: The shared AST walk visits nodes in source order. `unique-query-step-name` now reports the SECOND of two same-named steps — the one that replaced the first — and every rule that reports "the first" of something reports the first in the file.
310
+ - 0f99e26: `zustand-v5-best-practices` never reads a hook the file imported from `react` as a store: `useSyncExternalStore` matched the `use…Store` shape and was reported as a store read twice.
311
+
3
312
  ## 2.10.0
4
313
 
5
314
  ### Minor Changes
package/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # @geonosis/oxlint-plugin-biological-architecture
2
2
 
3
- 55 architecture rules as an oxlint JS plugin: which tier a file belongs to, what it may import,
3
+ 80 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
- options it needs refuses the run rather than passing silently.
6
+ options it needs says so once a run and judges nothing, rather than passing silently.
7
7
 
8
8
  ## Install
9
9
 
@@ -57,13 +57,19 @@ everything below the organelle is inert. A cell never composes another cell.
57
57
 
58
58
  | preset | rules | what it is |
59
59
  | --- | --- | --- |
60
- | `frontend` (= `recommended`) | 35 | tier direction, state placement, element choice, file shape |
61
- | `backend-workflows` | 12 | workflows, the data layer, single source of truth |
62
- | `tenant-scoping` | 4 | every row placed, scoped and narrowed to one organisation |
60
+ | `frontend` (= `recommended`) | 37 | tier direction, state placement, element choice, file shape |
61
+ | `backend-workflows` | 15 | workflows, time, the data layer, single source of truth |
62
+ | `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
+ | `tooling` | 3 | a repo's own layers as data, no generic `utils` package, debt markers name a plan |
63
65
  | `backend-d1` | 2 | Cloudflare D1 facts — false on an engine with transactions |
64
- | `backend-medusa` | 1 | the storefront route surface |
66
+ | `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
+ | `backend-trpc` | 1 | every router has its schema, and every schema its router |
65
69
  | `backend-workflows-cf` | 1 | durable runs on Cloudflare Workflows — false of an inline run |
66
- | `tooling` | 1 | `layer-walls` — a repo's OWN layers, declared as data and walled |
70
+ | `failure-paths` | 1 | no catch that swallows the failure it caught |
71
+ | `integrations` | 1 | every plugin directory is named by the registry |
72
+ | `package-subpaths` | 1 | import the subpath that owns a name, not the package root |
67
73
  | `comment-ceiling` | 1 | the comment posture, opt-in (+ oxlint's `capitalized-comments`) |
68
74
 
69
75
  A rule that is false on another stack gets its own preset rather than riding along in a generic
@@ -126,19 +132,15 @@ not is a defect, not a difference.
126
132
  ### What the two configs must say for the findings to match
127
133
 
128
134
  A fork usually had its repo's vocabulary compiled in. Here it is configuration — and **a rule
129
- enabled without the options it needs refuses the run**, naming itself and the missing option, rather
130
- than quietly finding nothing. That is deliberate: a rule enabled at `"error"` with nothing to
131
- compare against is indistinguishable from a clean tree, and one consumer shipped exactly that for
132
- months. Carry these across, or turn the rule off:
133
-
134
- | Rule | Option | Enabled without it |
135
- | --- | --- | --- |
136
- | `no-brand-names` | `{ brands, allowedSubstrings }` | refuses the run |
137
- | `organelle-dependency` | `{ sharedFeatures }` | only `shared` crosses a feature boundary |
138
- | `cell-no-cross-feature-organelles` | `{ sharedFeatures }` | only `shared` crosses a feature boundary |
139
- | `step-opens-its-own-cell` | `{ engine, within, wrapper }` | refuses the run |
140
- | `no-raw-html-atoms` | the positional element→atom map | refuses the run |
141
- | `layer-walls` | `{ layers: [{ name, paths, mayImport }], externals? }` | refuses the run |
135
+ enabled without the options it needs says so once a run**, naming itself and the missing option,
136
+ and judges no file, rather than quietly finding nothing. That is deliberate: a rule enabled at
137
+ `"error"` with nothing to compare against is indistinguishable from a clean tree, and one consumer
138
+ shipped exactly that for months. Carry these across, or turn the rule off:
139
+
140
+ Which rules refuse a bare run, and the option each one names, is in `RULES.md` — generated from
141
+ the rules themselves, so it cannot fall behind them. Two defaults are worth knowing before a diff
142
+ surprises you: `organelle-dependency` and `cell-no-cross-feature-organelles` let only a feature
143
+ called `shared` cross a feature boundary until `sharedFeatures` names others.
142
144
 
143
145
  An empty list counts as unconfigured. A rule set to `"off"`, or never named, is untouched — "does
144
146
  nothing until configured" survives for a rule nobody enabled, which is oxlint's job, not ours.