@geonosis/oxlint-plugin-biological-architecture 2.10.0 → 2.11.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.
- package/CHANGELOG.md +303 -0
- package/README.md +22 -20
- package/RULES.md +152 -121
- package/corpus/.oxlintrc.json +37 -7
- package/corpus/app/arrow-route/page.tsx +1 -0
- package/corpus/app/barrel-delegate/page.tsx +5 -0
- package/corpus/app/good-delegate/page.tsx +2 -0
- package/corpus/atoms/index.ts +1 -0
- package/corpus/cells/alias-of-cell.tsx +3 -0
- package/corpus/cells/index.ts +1 -0
- package/corpus/cells/laundered-compound.tsx +3 -0
- package/corpus/cells/laundered-organelle.tsx +3 -0
- package/corpus/compounds/bad-aliased-barrel-organelle-import.tsx +6 -0
- package/corpus/compounds/bad-barrel-organelle-import.tsx +7 -0
- package/corpus/compounds/bad-member-hook.tsx +8 -0
- package/corpus/compounds/good-atom-through-barrel.tsx +6 -0
- package/corpus/dates/bad-method-order.ts +7 -0
- package/corpus/dates/good-time-order.ts +2 -0
- package/corpus/disguise/bad-namespace-effect.tsx +7 -0
- package/corpus/disguise/good-rendering-effect.tsx +7 -0
- package/corpus/emails/welcome.ts +4 -0
- package/corpus/failure/logged-by-name.ts +2 -0
- package/corpus/hidden/bad-inert-hidden.tsx +3 -0
- package/corpus/hidden/good-hidden-input.tsx +3 -0
- package/corpus/inline-data/cells/bad-inline-options.tsx +15 -0
- package/corpus/inline-data/cells/good-imported-options.tsx +11 -0
- package/corpus/integrations/providers/email-gmail.ts +2 -2
- package/corpus/medusa/queries/bad-unordered-page.ts +1 -1
- package/corpus/medusa/queries/good-ordered-page.ts +1 -1
- package/corpus/medusa/subscribers/bad-satisfied-config.ts +5 -0
- package/corpus/molecules/alias-of-molecule.tsx +2 -0
- package/corpus/molecules/good-molecule.types.ts +1 -0
- package/corpus/molecules/laundered-atom.tsx +3 -0
- package/corpus/organelles/bad-logic.ts +5 -0
- package/corpus/organelles/custom-carousel.tsx +11 -0
- package/corpus/organelles/index.ts +2 -0
- package/corpus/organelles/reveal-on-scroll-list.tsx +9 -0
- package/corpus/organelles/use-cart-effect.ts +11 -0
- package/corpus/packages/db/src/cell/good-cell-transaction.ts +7 -0
- package/corpus/ports/good-engine-callback.ts +4 -0
- package/corpus/subpaths/bad-root-reexport.ts +3 -0
- package/corpus/time/bad-reexported-door.ts +5 -0
- package/corpus/tissues/alias-of-tissue.tsx +2 -0
- package/corpus/tissues/bad-laundered-organelle.tsx +6 -0
- package/corpus/tissues/bad-laundered-through-cells-barrel.tsx +6 -0
- package/corpus/tissues/bad-member-hook.tsx +11 -0
- package/corpus/tissues/bad-namespace-laundered.tsx +7 -0
- package/corpus/tissues/bad-star-laundered.tsx +2 -0
- package/corpus/tissues/good-cell-through-barrel.tsx +6 -0
- package/corpus/tissues/good-namespace-real-cell.tsx +7 -0
- package/corpus/tissues/laundered-compound.tsx +3 -0
- package/corpus/todos/bad-xxx-marker.ts +2 -0
- package/corpus/todos/good-marker-inside-a-number.ts +2 -0
- package/corpus/tokens/theme/palette.tsx +1 -1
- package/corpus/tsconfig.json +3 -0
- package/corpus/workers/bad-satisfied-worker.ts +7 -0
- package/dist/chunk-H7UQNDFE.js +109 -0
- package/dist/{chunk-24K5232K.js → chunk-ZY3XIRRE.js} +1 -1
- package/dist/discover-atoms.js +4 -3
- package/dist/index.d.ts +3 -4
- package/dist/index.js +1417 -823
- package/dist/presets.d.ts +5 -5
- package/dist/presets.js +1 -1
- package/package.json +5 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,308 @@
|
|
|
1
1
|
# @geonosis/oxlint-plugin-biological-architecture
|
|
2
2
|
|
|
3
|
+
## 2.11.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 0f99e26: What one consumer's adoption measured, fixed in the plugin and the parity tool (2026-09-19).
|
|
8
|
+
|
|
9
|
+
**`@geonosis/lint-parity` — a refusal is never reach (row 559).** Since D-084 a rule enabled
|
|
10
|
+
without an option it requires reports that as an ordinary finding, and `--corpus` counted it as the
|
|
11
|
+
rule firing: a bare, unconfigured rule read as covered. The finding is read out by its header
|
|
12
|
+
(`<rule> requires option \`<option>\`.`) and listed under **enabled but unconfigured**, apart from
|
|
13
|
+
the rules that fire nowhere; the run fails on either. `CorpusReport` gains `unconfigured`,
|
|
14
|
+
`RuleReach` gains `refusedInA` / `refusedInB`, and `corpusPassed` / `isRefusal` are exported.
|
|
15
|
+
|
|
16
|
+
**Two rules now REQUIRE their option, reporting once a run when enabled without it:**
|
|
17
|
+
|
|
18
|
+
- `no-swallowed-catch` requires `paths` (regex sources: the fetch and refresh modules). It was
|
|
19
|
+
silent bare while its `fixShape` promised a refusal (row 539). Every config extending
|
|
20
|
+
`failure-paths` names `paths` or gets the one finding.
|
|
21
|
+
- `no-runtime-dead-affordance` requires the positional `element→atom map` its twin
|
|
22
|
+
`no-raw-html-atoms` takes. It was silent bare while the docs said it refused (#8). Every config
|
|
23
|
+
extending `frontend` / `recommended` that enables it names the map or gets the one finding.
|
|
24
|
+
|
|
25
|
+
**`subpath-over-barrel` no longer requires `packages`.** The map is the package's own
|
|
26
|
+
`package.json` `exports`, read for the repo's own packages — `node_modules/<name>` is a symlink or
|
|
27
|
+
its manifest is `private` — with the names behind each door read off its entry file (`export *`
|
|
28
|
+
followed inside the package). `{ name, subpaths }` still replaces the map for one package;
|
|
29
|
+
`{ name }` alone opts a third-party package in. Doors written as patterns (`./icons/*`) are not
|
|
30
|
+
read. Bare, the rule judges every root import of an own package: expect findings a hand-written
|
|
31
|
+
map never covered.
|
|
32
|
+
|
|
33
|
+
**The seven #12 tree options take a string or a list alike:** `schema`, `tenant`, `queries`,
|
|
34
|
+
`seam`, `exempt`, `transacts`, `except`. A consumer wrote `seam` as a string and oxlint refused the
|
|
35
|
+
whole config at options validation — every rule read 0, exit 1. A bare string is a one-element
|
|
36
|
+
list now; existing configs of either shape keep working.
|
|
37
|
+
|
|
38
|
+
`max-comment-density` and `ssot-no-inline-facts` say in their descriptions which repos they suit
|
|
39
|
+
and which they drown (3954 and 3056 findings on one consumer's tree, by house style rather than
|
|
40
|
+
defect). `todo-names-its-plan` no longer reads `XXX` inside `$X,XXX.XX`: a marker is followed by
|
|
41
|
+
`(`, `:`, whitespace or the end of the comment. The generated skill says how to measure a rule:
|
|
42
|
+
with a copy of the config, because `oxlint -D` does not lift a config's `"off"`.
|
|
43
|
+
|
|
44
|
+
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
|
|
45
|
+
would normally want a major. It ships in a minor by the owner's ruling of 2026-09-19, on a measured
|
|
46
|
+
basis: every consumer pins this kit EXACTLY — three consumers, at 2.10.0, 2.6.1 and 2.10.0, no
|
|
47
|
+
caret anywhere — so nothing auto-upgrades and the version number protects nobody here. The warning
|
|
48
|
+
lives in this changelog instead, and the two consumers that could be reached were told directly
|
|
49
|
+
before the cut. The third is three releases behind; it is the one that must be told by hand.
|
|
50
|
+
- 0f99e26: Four bugs a consumer filed against 2.10.0 (#13–#16).
|
|
51
|
+
|
|
52
|
+
`no-void-port` takes `engineCallbacks` — regexes over an exported alias name, empty by default —
|
|
53
|
+
so a workflow-hook check the engine catches (`HookCheck<TInput> = (input, ctx) => Promise<void>`)
|
|
54
|
+
can be named as not a port; a `Result` there would never be read.
|
|
55
|
+
|
|
56
|
+
`geonosis-doctor`: the Claude-plugin train is held only against the install that LOADS at the
|
|
57
|
+
doctored root (user scope, or a project scope naming that root), never another repo's; the
|
|
58
|
+
spare-declaration question reads the script files scripts and hooks run, CI workflows and
|
|
59
|
+
`geonosis.json` tiers, sees a bin quoted as an argv string, and says nothing about a package with
|
|
60
|
+
no bin. Expect four fewer WARNs on a root whose gate lives in a `.mjs`.
|
|
61
|
+
|
|
62
|
+
`geonosis-mcp`: a server started before an in-session `geonosis update` refuses every call naming
|
|
63
|
+
the version it is running, the version the repo now has, and the restart (`/mcp` → reconnect) —
|
|
64
|
+
never the install. `geonosis update` ends by saying so where `.mcp.json` names a `geonosis` server.
|
|
65
|
+
- 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.
|
|
66
|
+
- 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.
|
|
67
|
+
- 0f99e26: Four rules see what they were blind to, and eleven readings refuse instead of banking a zero.
|
|
68
|
+
|
|
69
|
+
`tissue-no-hooks` and `compound-must-be-stateless` report a hook called as a member —
|
|
70
|
+
`React.useState(0)`, `hooks.useCart()` — where they read only a bare name. `time-through-the-door`
|
|
71
|
+
sees the library through every import door (`export * from`, `import()`, `require()`), and
|
|
72
|
+
`subpath-over-barrel` reads `export { X } from '<root>'` as the import it is. Expect new findings on
|
|
73
|
+
a tree that spelled a hook through its default React import.
|
|
74
|
+
|
|
75
|
+
`geonosis-ratchet`: a shell killed by a signal, a formatter that exited non-zero naming no file, a
|
|
76
|
+
`turbo boundaries` that printed no summary, a `geonosis.json` that will not parse, a stale report at
|
|
77
|
+
a named `reportPath`, and an oxlint run that answered in a shape `expectFormat` did not ask for are
|
|
78
|
+
each a `CounterError` now. Every one of them read as a number and lowered the baseline to it.
|
|
79
|
+
|
|
80
|
+
`geonosis-doctor`: `deployed`, `seams`, `rails` and `drift` say FAIL over a `geonosis.json` that is
|
|
81
|
+
there and will not parse, where each had read it as a repo that declared nothing.
|
|
82
|
+
- 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.
|
|
83
|
+
- 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/`.
|
|
84
|
+
- 0f99e26: **One declared list of tiers, read by both rules that name them (#5).** `no-ts-in-bio-folders`
|
|
85
|
+
carried its own idea of which folders belong to the architecture — `atoms, molecules, compounds,
|
|
86
|
+
cells, tissues`, a CMS-block folder, `emails` and `organs` — and `no-invalid-feature-folders`
|
|
87
|
+
carried a different one beside it. The first omitted `organelles`, a tier LAW.md declares, and
|
|
88
|
+
named three folders the law never mentions. Both rules now read `TIERS`, which a test holds against
|
|
89
|
+
the line in LAW.md that draws it, and neither restates it.
|
|
90
|
+
|
|
91
|
+
**Reach moves in both directions.** `no-ts-in-bio-folders` gains `organelles/`: a `.ts` file
|
|
92
|
+
sitting directly in an organelle folder is a finding where it was invisible. A `use-*.ts` hook
|
|
93
|
+
there is NOT — `organelles/` is where every other rule in the plugin sends a hook, and judging the
|
|
94
|
+
tier without exempting the shape it exists to hold would leave a hook with no legal folder. The
|
|
95
|
+
barrel and a co-located `<component>.types.ts` stay exempt as before. MEASURED across three
|
|
96
|
+
consumer trees — two Medusa + Next, one Cloudflare Workers + D1: 115 `.ts` files sit directly in
|
|
97
|
+
an `organelles/` folder and every one of them is a barrel, a `.types.ts` or a hook, so the widening
|
|
98
|
+
costs those three trees nothing today.
|
|
99
|
+
|
|
100
|
+
The rule loses the CMS-block folder, `emails/` and `organs/`, which were one repo's vocabulary
|
|
101
|
+
living in shared code (LAW 6) and are not tiers. A repo that genuinely holds components in a folder
|
|
102
|
+
the law does not name now says so: `extraFolders` (default: none, so the rule does nothing until
|
|
103
|
+
someone opts in), taking a bare string or a list like every other list option here.
|
|
104
|
+
|
|
105
|
+
**Two fixes that came with reading the parent folder rather than an unanchored regex.**
|
|
106
|
+
`subtissues/thing.ts` was read as `tissues/` and is not any more, and a file one folder BELOW a
|
|
107
|
+
tier (`cells/parts/helper.ts`) is left to the folder it is actually in.
|
|
108
|
+
|
|
109
|
+
`no-invalid-feature-folders` is unchanged in behaviour: the six tiers plus `lib` and `stores`, its
|
|
110
|
+
own two non-tier neighbours.
|
|
111
|
+
- 0f99e26: An enabled rule missing its option reports ONE finding a run instead of throwing (D-084), and the
|
|
112
|
+
five rules that chose their files from paths compiled in read them from an option (#12).
|
|
113
|
+
|
|
114
|
+
**The posture.** Up to 2.10.0 a rule enabled without an option it requires threw from `create()`.
|
|
115
|
+
Measured: the throw erased every other finding in the file it surfaced on, and rendered under
|
|
116
|
+
`--format=unix` as `:0:0: [Warning]` naming nothing. It is a finding now — attributable, counted by
|
|
117
|
+
the ratchet, neighbours intact — reported once per run on the first file oxlint constructs the rule
|
|
118
|
+
for, with the same `biological-architecture/<rule> requires option \`<option>\`.` header. Nothing the
|
|
119
|
+
plugin exports throws for a missing option. Expect exactly one new finding per bare rule per run
|
|
120
|
+
where a run used to be refused whole.
|
|
121
|
+
|
|
122
|
+
**#12 — four rules now REQUIRE a tree option; every config extending `backend-workflows` or
|
|
123
|
+
`tenant-scoping` must name it or gets the one finding above:**
|
|
124
|
+
|
|
125
|
+
- `tables-declare-their-plane` requires `schema` (a regex over the filename: the schema tree);
|
|
126
|
+
`control/` and `tenant/` are read under it.
|
|
127
|
+
- `tenant-tables-carry-org-id` requires `tenant` (a regex: the tenant-plane schema tree).
|
|
128
|
+
- `queries-require-org-scope` requires `queries` (a regex: the query modules).
|
|
129
|
+
- `dialect-through-the-seam` requires `seam` (regex sources: the files that may name a dialect);
|
|
130
|
+
`exempt` defaults to the test paths (`__tests__/`, `.test.`, `.spec.`) — a repo whose test helpers
|
|
131
|
+
live elsewhere (`testing/`) names them.
|
|
132
|
+
|
|
133
|
+
`no-d1-transaction` takes `transacts` and `except` (regex sources), both defaulting to nothing: the
|
|
134
|
+
plane the rule exempted was one repo's, and a D1-only repo has no plane to exempt. Expect new
|
|
135
|
+
findings on every `db.transaction()` that used to sit inside `/packages/db/(src|testing)/`.
|
|
136
|
+
|
|
137
|
+
One consumer's values, verbatim, are in `proofs/issue-12-and-option-posture-2026-09-18.md`.
|
|
138
|
+
|
|
139
|
+
`@geonosis/doctor`: `missingOptionOf` hears a refusal a rule reports as well as one it throws;
|
|
140
|
+
`exercised` no longer counts a bare rule's refusal finding as reach — it is a FAIL naming the option.
|
|
141
|
+
`@geonosis/cli`: `optionsNeededBy` reads through the same reader, so `init` still leaves those rules
|
|
142
|
+
out and names them.
|
|
143
|
+
|
|
144
|
+
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
|
|
145
|
+
would normally want a major. It ships in a minor by the owner's ruling of 2026-09-19, on a measured
|
|
146
|
+
basis: every consumer pins this kit EXACTLY — three consumers, at 2.10.0, 2.6.1 and 2.10.0, no
|
|
147
|
+
caret anywhere — so nothing auto-upgrades and the version number protects nobody here. The warning
|
|
148
|
+
lives in this changelog instead, and the two consumers that could be reached were told directly
|
|
149
|
+
before the cut. The third is three releases behind; it is the one that must be told by hand.
|
|
150
|
+
- 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.
|
|
151
|
+
- 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$"]`.
|
|
152
|
+
- 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`.
|
|
153
|
+
- 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.
|
|
154
|
+
- 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.
|
|
155
|
+
- 0f99e26: The five blind spots left after #4 and #11 are closed (rows 591–595). Expect new findings on a
|
|
156
|
+
tree that reaches a tier through a namespace import or `export *`, that hides a stateless compound
|
|
157
|
+
behind a one-line re-export in `cells/`, `tissues/` or `molecules/`, or that declares its aliases
|
|
158
|
+
somewhere the resolver did not read.
|
|
159
|
+
|
|
160
|
+
**Where an import lands, read from more places.** A `#ui/*` specifier declared in `package.json`
|
|
161
|
+
`imports` and nowhere else now lands (row 591) — the manifest is read first for a `#` name, as Node
|
|
162
|
+
does; a directory with only a `jsconfig.json` has its `paths` read. A `tsconfig.json` whose `extends`
|
|
163
|
+
names a package (`@repo/tsconfig/base.json`, or `@repo/tsconfig` alone) is followed through
|
|
164
|
+
`node_modules` (row 592), and `${configDir}` reads as the leaf's directory, so a shared base can
|
|
165
|
+
declare `paths` and they still land.
|
|
166
|
+
|
|
167
|
+
**A namespace import is no longer judged at the door** (row 593). `import * as cells from
|
|
168
|
+
'@/components/cells'` is judged by the members the file reaches — `cells.CustomCarousel`,
|
|
169
|
+
`<cells.CustomCarousel />` — each followed to the file that declares it; taken whole, and for
|
|
170
|
+
`export *`, `import()` and `require()`, every name the door carries is followed. The message names
|
|
171
|
+
the home as before: `"@/components/cells (→ src/kit/organelles/carousel.tsx)"`. A namespace whose
|
|
172
|
+
reached members all live where the door says stays silent.
|
|
173
|
+
|
|
174
|
+
**Three rules stop exempting a re-export shim outright** (row 594). `cell-must-be-stateful`,
|
|
175
|
+
`tissue-must-compose` and `molecule-must-compose` returned on any pure `export … from` file. Now
|
|
176
|
+
the alias of a file in the shim's own tier stays exempt, and a shim whose code lives elsewhere is
|
|
177
|
+
judged as that code: a stateless compound behind one line in `cells/` is a cell with no state, a
|
|
178
|
+
compound behind one line in `tissues/` is a tissue that composes nothing, an atom behind one line in
|
|
179
|
+
`molecules/` is an atom filed as a molecule. A Next.js route file that delegates by re-export is the
|
|
180
|
+
thin delegate another rule demands and stays exempt; a re-export nothing on disk answers stays
|
|
181
|
+
exempt, as before.
|
|
182
|
+
|
|
183
|
+
**The corpus tally separates named from unnamed findings** (row 595). `CASES` is its own module,
|
|
184
|
+
`tallyCorpus` reports per rule how many findings a case asked for and which files carry findings no
|
|
185
|
+
case named, and `pnpm corpus:tally` in the plugin package prints the table a proof reports
|
|
186
|
+
(`--unnamed` exits 1 on any noise). Today: 131 named, 111 unnamed over 244 cases.
|
|
187
|
+
|
|
188
|
+
Measured read-only on the three consumer trees against each one's own config narrowed to the
|
|
189
|
+
fourteen affected rules — with the narrowed config placed so that the consumer's `overrides` and
|
|
190
|
+
`ignorePatterns` are in scope, which the earlier measurements were not (see below) — before (this
|
|
191
|
+
repo's `main` build) and after: **a second Medusa reference 12 → 12** (1,723 files), **a Medusa +
|
|
192
|
+
Next consumer 0 → 0** (2,345 files), **a Cloudflare Workers + D1 consumer 0 → 0** (2,566 files).
|
|
193
|
+
None of the three has a namespace import of a tier barrel, a pure re-export shim in a tier folder
|
|
194
|
+
outside `dist/`, a `#` map absent from its tsconfig, or `paths` hoisted into a shared base. Each
|
|
195
|
+
zero stands beside a planted positive that fired in the same run under the same config: on the
|
|
196
|
+
kit's corpus, and on a scratch copy of one feature of each tree that read zero, where the same
|
|
197
|
+
barrel, shim and namespace plants read 0 on the installed 2.6.1, 2 on `main` (the namespace still
|
|
198
|
+
invisible) and 3 on this build. Wall time on the narrowed runs moved within noise (the reference
|
|
199
|
+
567 → 467 ms, Medusa + Next 1,023 → 891 ms, Workers + D1 842 → 723 ms).
|
|
200
|
+
|
|
201
|
+
**A correction to `where-an-import-lands`.** oxlint resolves an override's `files` and
|
|
202
|
+
`ignorePatterns` relative to the CONFIG FILE's directory. The consumer numbers in that changeset
|
|
203
|
+
were taken with a narrowed config written outside each tree, so the Workers + D1 consumer's
|
|
204
|
+
direction rules — which live only in `overrides` — were never in scope (its "0 → 0" measured
|
|
205
|
+
nothing), and the Medusa + Next consumer's 39 `cell-must-not-compose-cell` were findings in
|
|
206
|
+
`__tests__/` and `.stories.tsx` files its own `ignorePatterns` exclude. The reference's 12 were
|
|
207
|
+
top-level rules on un-ignored files and stand. Rules
|
|
208
|
+
affected: the eleven direction rules named in `where-an-import-lands`, plus `cell-must-be-stateful`,
|
|
209
|
+
`tissue-must-compose`, `molecule-must-compose`.
|
|
210
|
+
- 0f99e26: `tissue-no-data-props` reads a handler written as a method signature (`onSelect(id: string): void`) as the handler it is.
|
|
211
|
+
- 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.
|
|
212
|
+
- 0f99e26: Every direction rule judges where an import LANDS, not only how it is spelled (#4, #11). Expect
|
|
213
|
+
new findings on a tree that reaches a tier through its barrel or through a one-line re-export.
|
|
214
|
+
|
|
215
|
+
Two consumer issues were one blindness. `import { X } from '@/components/organelles'` names no file
|
|
216
|
+
in a tier folder, so `compound-no-stores` and its ten siblings read the string and stopped (#4); a
|
|
217
|
+
`cells/Carousel.tsx` that is exactly `export { CustomCarousel } from '@/kit/organelles/carousel'`
|
|
218
|
+
put an organelle in the cell tier for `tissue-no-organelles` and every rule that looked (#11).
|
|
219
|
+
Both counts were a lower bound of unknown depth on a kit whose number may only shrink.
|
|
220
|
+
|
|
221
|
+
Now the specifier is resolved on disk — relative to the importer, or through the repo's own
|
|
222
|
+
`tsconfig.json` `paths` (comments, trailing commas and a relative `extends` chain read; nothing
|
|
223
|
+
this plugin knows by name) — and the file it lands on is judged beside the spelling, so a bare
|
|
224
|
+
barrel is the tier folder it names. Each name the import takes is then followed through
|
|
225
|
+
`export … from` and `export *` to the file that declares it, eight hops deep and never twice through
|
|
226
|
+
one file, and THAT file is judged too; the message says where the name went:
|
|
227
|
+
`cannot import organelle "@/components/cells (→ src/kit/organelles/carousel.tsx)"`. A landed path
|
|
228
|
+
is judged from the importer's package root, never as an absolute path, so a checkout under `/app/`
|
|
229
|
+
does not become a breach. One import is one finding, whichever of its landings is the breach.
|
|
230
|
+
|
|
231
|
+
What is still judged at the door only: a namespace import, `export *`, `import()`, `require()`, a
|
|
232
|
+
name the door does not export, and a bare package. A specifier nothing on disk answers is judged as
|
|
233
|
+
before — except that one whose last segment is a tier folder is read as that folder.
|
|
234
|
+
|
|
235
|
+
Measured on the reporter's tree (a second Medusa reference, 1,554 files, baseline 0): **0 → 12**
|
|
236
|
+
across the eleven direction rules — `tissue-no-organelles` 8, `compound-no-stores` 3,
|
|
237
|
+
`cell-must-not-compose-cell` 1 — every one a barrel import that was silent. On a Medusa + Next
|
|
238
|
+
consumer (2,832 files): `molecule-atoms-only` 0 → 4, `atom-no-deps` 0 → 1, `compound-no-stores`
|
|
239
|
+
0 → 1, and the 39 `cell-must-not-compose-cell` findings already there unchanged. On a Cloudflare
|
|
240
|
+
Workers + D1 consumer: 0 → 0. The direction rules' share of a run grew by roughly a quarter (the
|
|
241
|
+
reference +80 ms, Medusa + Next +270 ms), paid once per imported file per process. Nothing that
|
|
242
|
+
fired before stops firing: the spelled source is still judged first. Rules affected:
|
|
243
|
+
`atom-no-deps`, `cell-must-not-compose-cell`,
|
|
244
|
+
`cell-no-cross-feature-organelles`, `cell-no-tissues`, `compound-no-stores`, `molecule-atoms-only`,
|
|
245
|
+
`no-cross-feature-stores`, `no-orm-outside-db`, `organelle-dependency`, `tissue-no-organelles`,
|
|
246
|
+
`tissue-no-stores`.
|
|
247
|
+
- 0f99e26: `worker-handles-are-scoped` reads a handler object written `export default { fetch … } satisfies ExportedHandler<Env>` — the shape Cloudflare documents — which it passed unread.
|
|
248
|
+
|
|
249
|
+
### Patch Changes
|
|
250
|
+
|
|
251
|
+
- 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.
|
|
252
|
+
- 0f99e26: `no-brand-names` says what it judges (#10): the brands a repo names — its own and its consumers' — so
|
|
253
|
+
a third-party mark a kit renders, a social network's or a review platform's, is not one of them.
|
|
254
|
+
- 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.
|
|
255
|
+
- 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.
|
|
256
|
+
- 0f99e26: Atom discovery strips comments with the string-aware scanner, so a glob string like `'icons/*'` no longer swallows the element an atom renders.
|
|
257
|
+
- 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.
|
|
258
|
+
- 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.
|
|
259
|
+
- 0f99e26: `no-invalid-feature-folders` names every folder it accepts — `atoms` and `molecules` included — read from the same set it judges by.
|
|
260
|
+
- 0f99e26: `font-roles-only`'s `fixShape` said an enabled rule with no `roles` refuses the run. It reports that
|
|
261
|
+
once a run and judges nothing, as its own description and every other option-gated rule already
|
|
262
|
+
said. A test now asks every exported rule that none claims to refuse a run and none throws or
|
|
263
|
+
reports twice when constructed with no options.
|
|
264
|
+
|
|
265
|
+
The README said the plugin carries 55 rules and that a bare rule refuses the run. It carries 80 a
|
|
266
|
+
consumer can enable, and a bare rule says so once a run and judges nothing. A test holds both.
|
|
267
|
+
- 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.
|
|
268
|
+
- 0f99e26: `layer-walls`' probe tries every layer a wall forbids, not only the first, before it refuses.
|
|
269
|
+
- 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.
|
|
270
|
+
- 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.
|
|
271
|
+
- 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.
|
|
272
|
+
- 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.
|
|
273
|
+
- 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.
|
|
274
|
+
- 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.
|
|
275
|
+
- 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.
|
|
276
|
+
- 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.
|
|
277
|
+
- 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.
|
|
278
|
+
- 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.
|
|
279
|
+
- 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.
|
|
280
|
+
- 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.
|
|
281
|
+
- 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.
|
|
282
|
+
- 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.
|
|
283
|
+
- 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.
|
|
284
|
+
- 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.
|
|
285
|
+
- 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.
|
|
286
|
+
- 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.
|
|
287
|
+
- 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.
|
|
288
|
+
- 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.
|
|
289
|
+
- 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.
|
|
290
|
+
- 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.
|
|
291
|
+
- 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.
|
|
292
|
+
- 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.
|
|
293
|
+
- 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.
|
|
294
|
+
- 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.
|
|
295
|
+
- 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.
|
|
296
|
+
- 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.
|
|
297
|
+
- 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.
|
|
298
|
+
- 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.
|
|
299
|
+
- 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.
|
|
300
|
+
- 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.
|
|
301
|
+
- 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.
|
|
302
|
+
- 0f99e26: `no-type-definitions-in-components` points outside a feature folder at a co-located `<component>.types.ts` rather than `features//lib/types.ts`.
|
|
303
|
+
- 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.
|
|
304
|
+
- 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.
|
|
305
|
+
|
|
3
306
|
## 2.10.0
|
|
4
307
|
|
|
5
308
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# @geonosis/oxlint-plugin-biological-architecture
|
|
2
2
|
|
|
3
|
-
|
|
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
|
|
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`) |
|
|
61
|
-
| `backend-workflows` |
|
|
62
|
-
| `
|
|
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
|
-
| `
|
|
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
|
-
| `
|
|
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
|
|
130
|
-
than quietly finding nothing. That is deliberate: a rule enabled at
|
|
131
|
-
compare against is indistinguishable from a clean tree, and one consumer
|
|
132
|
-
months. Carry these across, or turn the rule off:
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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.
|