@geonosis/oxlint-plugin-biological-architecture 2.9.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 +321 -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-handler-prop.tsx +7 -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-passthrough.tsx +4 -2
- package/corpus/tissues/bad-star-laundered.tsx +2 -0
- package/corpus/tissues/bad-untyped-prop.tsx +7 -0
- package/corpus/tissues/good-cell-through-barrel.tsx +6 -0
- package/corpus/tissues/good-data-props.tsx +13 -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 +1469 -914
- package/dist/presets.d.ts +5 -5
- package/dist/presets.js +1 -1
- package/package.json +5 -4
- package/corpus/tissues/bad-data-props.tsx +0 -5
package/RULES.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Read from the two source trees on 2026-08-29:
|
|
4
4
|
|
|
5
|
-
- `
|
|
6
|
-
- `
|
|
5
|
+
- a Medusa storefront's `packages/eslint-plugin-biological-architecture` — **36 rules**
|
|
6
|
+
- a Workers + D1 app's `packages/eslint-plugin-biological-architecture` — **52 rules** (51 when the union was
|
|
7
7
|
cut; `step-opens-its-own-cell` landed there hours later and was folded in afterwards)
|
|
8
8
|
|
|
9
9
|
One plugin name, two rule sets, no shared code. **33 rules had the same filename in both trees; only
|
|
@@ -15,8 +15,8 @@ plus twenty-eight the kit wrote for itself: `layer-walls`, `emit-declares-attemp
|
|
|
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 — **80**. The two retired ids still resolve
|
|
19
|
-
they are retired, so a config naming them parses
|
|
18
|
+
of the W67 batch — **80**. The two retired ids still resolve until the next major, as no-ops that
|
|
19
|
+
say they are retired, so a config naming them parses.
|
|
20
20
|
Every one of the twenty-eight is marked **written here** in the inventory, and the table below is
|
|
21
21
|
held against that column rather than kept by hand.
|
|
22
22
|
|
|
@@ -24,8 +24,8 @@ held against that column rather than kept by hand.
|
|
|
24
24
|
|---|---|
|
|
25
25
|
| in both trees, byte-identical | 3 |
|
|
26
26
|
| in both trees, **diverged** | 30 |
|
|
27
|
-
|
|
|
28
|
-
|
|
|
27
|
+
| A Medusa storefront only | 3 |
|
|
28
|
+
| A Workers + D1 app only | 16 |
|
|
29
29
|
| **union** | **52** |
|
|
30
30
|
| written here | 28 |
|
|
31
31
|
| **total** | **80** |
|
|
@@ -34,12 +34,12 @@ held against that column rather than kept by hand.
|
|
|
34
34
|
|
|
35
35
|
Two shapes of divergence, and one resolution rule each.
|
|
36
36
|
|
|
37
|
-
**
|
|
37
|
+
**A Medusa storefront broadened the path patterns.** Every Medusa storefront rule scoped its file test as
|
|
38
38
|
`(?:features/[^/]+/)?<tier>/`, so it fires on a FLAT kit (`packages/ui/src/tissues/…`) as well as a
|
|
39
|
-
feature-sliced app.
|
|
39
|
+
feature-sliced app. A Workers + D1 app's kept `features/<name>/<tier>/`. **The broader pattern wins** — a
|
|
40
40
|
rule that reaches more files is the stricter rule.
|
|
41
41
|
|
|
42
|
-
**
|
|
42
|
+
**A Workers + D1 app broadened the node coverage.** Its August 2026 sweep added
|
|
43
43
|
`ExportAllDeclaration` / `ExportNamedDeclaration` handlers to every import-direction rule (a
|
|
44
44
|
re-export names a module exactly as an import does), and turned `tissue-no-hooks` from an import
|
|
45
45
|
check into a hook-CALL check — the hole three review rounds missed was a tissue reaching a store
|
|
@@ -50,19 +50,19 @@ Where a divergence was a false-positive fix rather than a reach change (own-sibl
|
|
|
50
50
|
`Kind`-vocabulary props), it was kept: both repos' fixtures depend on those, and a rule that fires on
|
|
51
51
|
an accepted convention is a rule people learn to ignore.
|
|
52
52
|
|
|
53
|
-
**Both repos' test suites are ported and green** —
|
|
54
|
-
`real-source-tests` cases through the real binary, and
|
|
53
|
+
**Both repos' test suites are ported and green** — a Workers + D1 app's 300-odd hand-built-AST tests, its
|
|
54
|
+
`real-source-tests` cases through the real binary, and a Medusa storefront's 45-case oxlint fixture lock.
|
|
55
55
|
|
|
56
56
|
## Named decisions
|
|
57
57
|
|
|
58
|
-
### `cell-must-be-stateful` (
|
|
58
|
+
### `cell-must-be-stateful` (a Medusa storefront) vs `cell-handles-are-scoped` (a Workers + D1 app) — **both kept, one renamed**
|
|
59
59
|
|
|
60
60
|
These share only the word "cell", and they mean different things by it.
|
|
61
61
|
|
|
62
|
-
-
|
|
62
|
+
- A Medusa storefront's `cell-must-be-stateful` is the **biological tier**: a file in `cells/` whose whole family
|
|
63
63
|
is props-in / JSX-out is a compound in the wrong folder. It is the inverse of
|
|
64
64
|
`compound-must-be-stateless`. → kept as-is, `frontend`.
|
|
65
|
-
-
|
|
65
|
+
- A Workers + D1 app's `cell-handles-are-scoped` is a **Cloudflare D1 database cell**: a worker that opens a
|
|
66
66
|
cell handle must wrap every invocation in `withCells` or it leaks a pool per call. Nothing to do
|
|
67
67
|
with a tier. → kept, **renamed `worker-handles-are-scoped`**, `backend-d1`.
|
|
68
68
|
|
|
@@ -75,7 +75,7 @@ vocabulary at every diagnostic, so the backend one gave up the word.
|
|
|
75
75
|
compiled in — a rule exactly one repo could enable, and the thing pinning that repo's last
|
|
76
76
|
generic-utility package in place, because moving the door would have silenced the rule guarding it.
|
|
77
77
|
The door is now the `door` option and the directories are `exempt`, both regex sources over the
|
|
78
|
-
normalised filename, and the rule
|
|
78
|
+
normalised filename, and the rule reports the missing `door` once a run. The LIBRARY it polices was
|
|
79
79
|
compiled in for one release longer — `dayjs`, so a repo on date-fns kept the raw-`Date` half and
|
|
80
80
|
lost the half that catches a second configured formatter, which is the defect that half exists for.
|
|
81
81
|
It is the `libraries` list now, defaulting to `['dayjs']`, and every diagnostic names the library
|
|
@@ -98,14 +98,45 @@ work. What changed is that they are counted: each of those rules declares `meta.
|
|
|
98
98
|
pattern exempts in THIS tree beside the number the enumeration names — so a grant nobody decided
|
|
99
99
|
stops being a grant nobody can see.
|
|
100
100
|
|
|
101
|
+
### An enabled rule that cannot fire **reports that, once a run** (2.11.0 — amends the 0.2.0 refusal)
|
|
102
|
+
|
|
103
|
+
The requirement below stands unchanged: a rule enabled without the option it cannot run without is
|
|
104
|
+
a config error, and it has to be visible. What changed on 2026-09-18 is HOW. From 0.2.0 the rule
|
|
105
|
+
threw from `create()`, which read as "the run is refused". Measured, it was destructive and
|
|
106
|
+
unreadable at once: oxlint reports a throwing plugin rule against the file it was being constructed
|
|
107
|
+
for and drops every other finding in that file — one file carrying a real `todo-names-its-plan`
|
|
108
|
+
finding, linted beside a bare `no-brand-names`, reported nothing but the throw — and under
|
|
109
|
+
`--format=unix`, the format every counter and the parity tool read, the throw rendered as
|
|
110
|
+
`:0:0: [Warning]`: no path, no rule id, no sentence.
|
|
111
|
+
|
|
112
|
+
So the rule now reports the refusal as a **finding**, on the first file a run constructs it for,
|
|
113
|
+
and is inert for the files after it (#202: one finding, one problem in the summary, however many
|
|
114
|
+
files the run has — measured over 300 files and 8 threads). The finding has a place and a rule id,
|
|
115
|
+
the ratchet counts it, the parity tool attributes it, its neighbours stay where they were, and the
|
|
116
|
+
run still exits non-zero. The wording is the same header as before —
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
biological-architecture/<rule> requires option `<option>`. It is enabled but its `<option>` option
|
|
120
|
+
is nothing, so it judges no file — this finding stands in for every file it did not read, once a
|
|
121
|
+
run. Configure `<option>`, or turn the rule off: an enabled rule that judges nothing would
|
|
122
|
+
otherwise read exactly like a rule that found nothing. It accepts { … }.
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
— so the doctor and `optionsNeededBy` read the rule and the option out of the same fixed position
|
|
126
|
+
(#199), and both hear a refusal a plugin throws (older builds) as well as one it reports. Nothing
|
|
127
|
+
exported from the plugin throws for a missing option any more; `requireOption` inside a rule module
|
|
128
|
+
raises the requirement, and the registry (`src/index.ts`) turns it into the finding. Everywhere
|
|
129
|
+
the prose below says "refuses the run", read "reports the missing option once a run and judges
|
|
130
|
+
nothing".
|
|
131
|
+
|
|
101
132
|
### An enabled rule that cannot fire **refuses the run** (0.2.0)
|
|
102
133
|
|
|
103
134
|
This replaces the "does nothing until configured" posture below, and it is the one breaking change
|
|
104
|
-
in 0.2.0 that a consumer will feel.
|
|
135
|
+
in 0.2.0 that a consumer will feel. (The mechanism was amended in 2.11.0 — see above.)
|
|
105
136
|
|
|
106
137
|
Making every repo-specific list an option (law 6) has an obvious implementation and a wrong one:
|
|
107
138
|
return an empty visitor when the option is missing. The result is a rule that is enabled, graded
|
|
108
|
-
`"error"`, and **indistinguishable from compliance**.
|
|
139
|
+
`"error"`, and **indistinguishable from compliance**. A Medusa storefront ran `no-raw-html-atoms` at `"error"`
|
|
109
140
|
for months with no element→atom map. 136 raw elements in the tree. Zero findings. A green gate. It
|
|
110
141
|
was found by hand, not by a gate, because there was no gate — only a rule with nothing to compare
|
|
111
142
|
against, which is the silent-fallback class one level up from a counter reading zero under a format
|
|
@@ -117,7 +148,7 @@ So the posture splits in two:
|
|
|
117
148
|
- a rule that **is** enabled without the options it needs is a **config error**, and `create()`
|
|
118
149
|
throws, naming the rule and the missing option. oxlint refuses and exits non-zero.
|
|
119
150
|
|
|
120
|
-
|
|
151
|
+
The rules that take this are not listed by hand: `src/rules/option-gated.ts` names
|
|
121
152
|
each with the option its refusal must say and a bag that makes it run clean, and
|
|
122
153
|
`src/option-gated.test.ts` asks every rule the plugin exports whether it refuses a bare `create()`
|
|
123
154
|
and fails when one of them is not on the list — or is on it and no longer refuses. It found seven
|
|
@@ -143,7 +174,7 @@ non-zero, and the summary counts a problem no line accounts for — which is exa
|
|
|
143
174
|
never re-enter kit source (the ones the fork had compiled into its regex) — proved reaching by
|
|
144
175
|
adding one to a source file and watching it fire.
|
|
145
176
|
|
|
146
|
-
### `no-brand-names` (
|
|
177
|
+
### `no-brand-names` (a Medusa storefront only) — brand list is now an **option**
|
|
147
178
|
|
|
148
179
|
The rule shipped with a two-brand regex and a matching CDN-host allowance compiled into it. It could
|
|
149
180
|
only ever serve the repo that wrote it. Now:
|
|
@@ -155,7 +186,7 @@ only ever serve the repo that wrote it. Now:
|
|
|
155
186
|
]
|
|
156
187
|
```
|
|
157
188
|
|
|
158
|
-
Enabled with no `brands`, it
|
|
189
|
+
Enabled with no `brands`, it reports that once a run and judges nothing — see the decision above.
|
|
159
190
|
|
|
160
191
|
### `step-opens-its-own-cell` — one law, two spellings, every fact an **option**
|
|
161
192
|
|
|
@@ -197,7 +228,7 @@ finding there is a saga importing the engine's `step` around that wrapper — re
|
|
|
197
228
|
]
|
|
198
229
|
```
|
|
199
230
|
|
|
200
|
-
Enabled with no `within`, it
|
|
231
|
+
Enabled with no `within`, it reports that once a run. Type-only imports never fire: they bring no handle, so
|
|
201
232
|
they can share none. The diagnostic quotes the option values rather than any repo's paths, and the
|
|
202
233
|
preset is its own — the invariant is true of a DURABLE run that hibernates between steps and false
|
|
203
234
|
of an inline saga sharing the request's connection, so `backend-workflows` would have been a repo
|
|
@@ -270,7 +301,7 @@ run, like every other option a rule cannot fire without.
|
|
|
270
301
|
|
|
271
302
|
Preset `backend-medusa`, which is twelve rules now.
|
|
272
303
|
|
|
273
|
-
**One correction to the brief that asked for it**:
|
|
304
|
+
**One correction to the brief that asked for it**: a Medusa storefront does not guard 50 emit sites this way
|
|
274
305
|
today. It has **four** `emitEventStep` calls (`create-sample-order`, `ingest-shipment-milestone`,
|
|
275
306
|
`convert-sample-to-order` ×2), and **all four would fire** — three carry
|
|
276
307
|
`options: { priority: EventPriority.CRITICAL }` with no attempts, one carries no options at all.
|
|
@@ -290,12 +321,12 @@ registry is read as SOURCE — a rule that had to import it would need the repo'
|
|
|
290
321
|
up inside a linter, and every string in it is a name the registry knows whether it is spelled as an
|
|
291
322
|
id or as an import path.
|
|
292
323
|
|
|
293
|
-
|
|
324
|
+
A Medusa storefront is the fixture that matters just as much: measured, **zero** of its provider directories are
|
|
294
325
|
unregistered. A rule that fired on the clean tree would be off within a day. And its third seam has
|
|
295
326
|
no directories at all — `email-gmail.ts` and `whatsapp-360dialog.ts` sit flat beside their registry —
|
|
296
327
|
so it is out of scope because nothing matches `<root>/<dir>/<manifest>`, not by a special case.
|
|
297
328
|
|
|
298
|
-
*`registry` takes a list, because a registry can be split.*
|
|
329
|
+
*`registry` takes a list, because a registry can be split.* A Medusa storefront's `medusa-config.ts` imports
|
|
299
330
|
`medusa-config.fulfillment.ts`, and the carrier directories are registered in the half that is
|
|
300
331
|
imported. Reading only the file the option named made two REGISTERED directories a standing FAIL
|
|
301
332
|
there — a false positive on a working tree, which is how a rule gets turned off. The halves are
|
|
@@ -313,16 +344,16 @@ its fixture exists to catch. The hand-built nodes could not find this: a hand-bu
|
|
|
313
344
|
prose in it. Comments are stripped first now, and the direction is the point — a name that appears
|
|
314
345
|
only in a comment is usually the note explaining why the directory was abandoned.
|
|
315
346
|
|
|
316
|
-
**`router-schema-parity`.**
|
|
347
|
+
**`router-schema-parity`.** A Workers + D1 app holds the mirror by hand at 21 of 22 routers and 21 of 23
|
|
317
348
|
contracts; Midday holds its own at 33 of 37. A router with no schema is a wire contract written
|
|
318
349
|
wherever the handler happens to say it; a schema with no router is a contract nothing serves.
|
|
319
350
|
|
|
320
351
|
Two facts were measured before the rule was written, and both changed its shape:
|
|
321
352
|
|
|
322
|
-
1.
|
|
353
|
+
1. A Workers + D1 app's two halves are in **different packages** — `apps/api/src/trpc/routers` against
|
|
323
354
|
`packages/api-contract/src` — so the options are repo-root-relative. A package-relative option
|
|
324
355
|
could not name them both.
|
|
325
|
-
2.
|
|
356
|
+
2. A Workers + D1 app's three apparent gaps are ONE deliberate arrangement: `routers/documents-mounted.ts`
|
|
326
357
|
mounts the invoice and purchase-order entities and imports both contracts. A strict filename
|
|
327
358
|
mirror reports all three and is wrong all three times, on the very tree the rule was built from.
|
|
328
359
|
So `mounts` (`{ "documents-mounted": ["invoice", "purchase-order"] }`) is config, and the corpus
|
|
@@ -334,20 +365,20 @@ Neither of these came from review. Both came from running the rule read-only ove
|
|
|
334
365
|
then running a CONTROL that should have made the same files fire.
|
|
335
366
|
|
|
336
367
|
**`no-unregistered-plugin-dir.manifests` is required, with no default.** The rule first shipped a
|
|
337
|
-
default entry-file list — `index.ts`, `index.tsx`, `config.ts`. Over
|
|
368
|
+
default entry-file list — `index.ts`, `index.tsx`, `config.ts`. Over a Medusa storefront's two provider roots it
|
|
338
369
|
reported zero findings, and every directory there IS registered, so the run read as a pass. It was
|
|
339
|
-
not one:
|
|
370
|
+
not one: a Medusa storefront's plugin directories contain `service.ts` and no index at all, so the rule matched
|
|
340
371
|
no file and answered a question it had never asked. The control — the same files, with a registry
|
|
341
372
|
naming none of them — also reported zero, which is what exposed it. With `manifests: ["service.ts"]`
|
|
342
373
|
the control fires on all three directories and the real config on none.
|
|
343
374
|
|
|
344
375
|
**`emit-declares-attempts.budgetHelpers`, empty by default.** The claim this rule carried was that it
|
|
345
|
-
covers
|
|
376
|
+
covers a Medusa storefront's emit sites, so that repo's 132-line source walker could be deleted. Measured: all
|
|
346
377
|
**54** `emitEventStep` sites spell the bag `options: retryableEvent(…)` — zero object literals, zero
|
|
347
378
|
missing bags — and a call is precisely the shape the rule declines to judge. It reported zero, and
|
|
348
379
|
that zero proved only that no bag was absent. Named as a budget helper, a call to `retryableEvent`
|
|
349
380
|
passes and a call to anything else is reported as a budget nobody can check; the control naming a
|
|
350
|
-
helper
|
|
381
|
+
helper a Medusa storefront does not use reports all 54. Empty by default, so no existing consumer moves.
|
|
351
382
|
|
|
352
383
|
Both are the same failure as a counter that reads zero under a format it cannot parse, and both were
|
|
353
384
|
invisible to the rules' own fixtures — a fixture is written to match the rule, which is exactly what
|
|
@@ -359,8 +390,8 @@ The only rule in the plugin whose landmine is in **neither** source repo. Measur
|
|
|
359
390
|
|
|
360
391
|
| tree | `mockDb` | `vi.mock('…db…')` | `vi.mock(` in integration suites |
|
|
361
392
|
|---|---:|---:|---:|
|
|
362
|
-
|
|
|
363
|
-
|
|
|
393
|
+
| A Medusa storefront | 0 | 0 | **0** of 106 integration files |
|
|
394
|
+
| A Workers + D1 app | 0 | 0 | 0 (`vi.mock` appears nowhere in the tree) |
|
|
364
395
|
| Midday | 32 references in 4 files | — | the gate |
|
|
365
396
|
|
|
366
397
|
Midday's shape: `apps/api/src/__tests__/setup.ts:80` exports `mockDb = createMockDb()`;
|
|
@@ -391,22 +422,22 @@ Three matching decisions, each with a case in the node layer:
|
|
|
391
422
|
|
|
392
423
|
Every rule below exists because the framework **infers** something instead of making you say it.
|
|
393
424
|
None of them throws at runtime; all of them are found in production, far from the cause. Six were
|
|
394
|
-
scans in
|
|
425
|
+
scans in a Medusa storefront's `packages/verify-arch` first and are now also per-file rules; two come from
|
|
395
426
|
Medusa's own source; one from an estimate a route already labels by hand. Three more — the per-file
|
|
396
427
|
halves of R4.3, R3.8 and R5.1 — landed after adoption showed what a text scan misses.
|
|
397
428
|
|
|
398
429
|
| rule | the silence | source |
|
|
399
430
|
|---|---|---|
|
|
400
|
-
| `mutating-step-declares-retry` | `maxRetries` defaults to **0** (`orchestration/transaction/types.ts:48`), so a transient failure reverts real state a retry would have absorbed |
|
|
401
|
-
| `step-declares-compensation-or-none` | `stepConfig.noCompensation = !compensateFn` (`create-step.ts:184`) — a deliberate non-compensation and a forgotten one are the same source | analysis §1.1, "highest-value lint gap";
|
|
402
|
-
| `named-when-block` | an unnamed `when()` derives a `when-then-{ulid}` step name (`composer/when.ts:120-126`), so a resumed run cannot find its checkpoint |
|
|
403
|
-
| `subscriber-declares-id` | the loader infers a subscriber id from a function or file name; two that infer alike dedupe each other |
|
|
404
|
-
| `no-loader-side-effect-in-index-file` | `ResourceLoader.discoverResources` filters files named `index` (`resource-loader.js:51`) |
|
|
405
|
-
| `service-name-not-reserved` | a module key matching the `Modules` enum silently REPLACES the built-in (`medusa-app.ts:175-198`); `registerModule` guards only aliases and mains | analysis §1.3 —
|
|
406
|
-
| `no-index-count-as-exact` | the index engine's count is an EXPLAIN plan-row estimate and reads as an exact total | analysis §3a;
|
|
407
|
-
| `unique-query-step-name` | the step-handler map is a plain assignment and the orchestrator appends a duplicate action with no dedupe, so two un-named query steps in one body last-win: one read never happens |
|
|
408
|
-
| `plugin-route-namespaced` | the routes-loader assigns last-write-wins, so a plugin route with no namespace segment is claimable by any other package with no error |
|
|
409
|
-
| `route-no-inline-mutation` | a route that writes directly succeeds, returns 200, and skips the compensation and retry class that live behind the workflow door |
|
|
431
|
+
| `mutating-step-declares-retry` | `maxRetries` defaults to **0** (`orchestration/transaction/types.ts:48`), so a transient failure reverts real state a retry would have absorbed | a Medusa storefront's scan R5.2 |
|
|
432
|
+
| `step-declares-compensation-or-none` | `stepConfig.noCompensation = !compensateFn` (`create-step.ts:184`) — a deliberate non-compensation and a forgotten one are the same source | analysis §1.1, "highest-value lint gap"; a Medusa storefront's scan R5.6 |
|
|
433
|
+
| `named-when-block` | an unnamed `when()` derives a `when-then-{ulid}` step name (`composer/when.ts:120-126`), so a resumed run cannot find its checkpoint | a Medusa storefront's scan R5.5 |
|
|
434
|
+
| `subscriber-declares-id` | the loader infers a subscriber id from a function or file name; two that infer alike dedupe each other | a Medusa storefront's scan R5.4 |
|
|
435
|
+
| `no-loader-side-effect-in-index-file` | `ResourceLoader.discoverResources` filters files named `index` (`resource-loader.js:51`) | a Medusa storefront's scan #15442 |
|
|
436
|
+
| `service-name-not-reserved` | a module key matching the `Modules` enum silently REPLACES the built-in (`medusa-app.ts:175-198`); `registerModule` guards only aliases and mains | analysis §1.3 — a Medusa storefront's `catalog_search` scar |
|
|
437
|
+
| `no-index-count-as-exact` | the index engine's count is an EXPLAIN plan-row estimate and reads as an exact total | analysis §3a; a Medusa storefront's b2b route labels it by hand |
|
|
438
|
+
| `unique-query-step-name` | the step-handler map is a plain assignment and the orchestrator appends a duplicate action with no dedupe, so two un-named query steps in one body last-win: one read never happens | a Medusa storefront's scan R4.3 |
|
|
439
|
+
| `plugin-route-namespaced` | the routes-loader assigns last-write-wins, so a plugin route with no namespace segment is claimable by any other package with no error | a Medusa storefront's scan R3.8 |
|
|
440
|
+
| `route-no-inline-mutation` | a route that writes directly succeeds, returns 200, and skips the compensation and retry class that live behind the workflow door | a Medusa storefront's scan R5.1 |
|
|
410
441
|
|
|
411
442
|
Four postures they share, all of them the one `emit-declares-attempts` set:
|
|
412
443
|
|
|
@@ -435,7 +466,7 @@ parameter and its doc comment, and nothing else). So a `workflows/index.ts` **is
|
|
|
435
466
|
2.19 and a `subscribers/index.ts` is not. A rule firing on a file the framework reads is a false
|
|
436
467
|
positive, and false positives are how a rule gets ignored — so the default is what was measured, and
|
|
437
468
|
a repo below 2.19 adds `workflows/` back. The `medusa` scanner pack in `@geonosis/verify-arch`
|
|
438
|
-
defaults the same way, and `examples/
|
|
469
|
+
defaults the same way, and `examples/medusa-storefront.verify-arch.json` adds the third kind, which is what
|
|
439
470
|
keeps that repo's numbers identical to today's.
|
|
440
471
|
|
|
441
472
|
**The class an AST rule wins over a text scan.** These three are the per-file halves of scans a
|
|
@@ -463,48 +494,48 @@ claiming one route, a `serviceName` that must be unique across every module. The
|
|
|
463
494
|
decision rule, not a ranking: whole-graph facts are scans, per-file facts are rules, and the six
|
|
464
495
|
that are both are defence in depth on purpose.
|
|
465
496
|
|
|
466
|
-
**`step-declares-compensation-or-none` judges every step, not only mutating ones.**
|
|
497
|
+
**`step-declares-compensation-or-none` judges every step, not only mutating ones.** A Medusa storefront's R5.6
|
|
467
498
|
scan restricts itself to steps whose text contains a write; the rule does not, because
|
|
468
499
|
`noCompensation = !compensateFn` is true of every step and a read-only step declares
|
|
469
500
|
`noCompensation: true` once and is done. The two are therefore not redundant: the scan is the
|
|
470
501
|
repo-wide backstop, the rule is the finer instrument. `noCompensation: false` is the inferred state
|
|
471
502
|
written out and is still reported.
|
|
472
503
|
|
|
473
|
-
### `store-route-scopes-tenant-data` (
|
|
504
|
+
### `store-route-scopes-tenant-data` (a Medusa storefront only) — Medusa-specific
|
|
474
505
|
|
|
475
506
|
`/api/store/**/route.ts` is a Medusa convention. It is in `backend-medusa`, and also in
|
|
476
507
|
`tenant-scoping` because that is what it enforces.
|
|
477
508
|
|
|
478
|
-
### `no-logic-in-component-files` —
|
|
509
|
+
### `no-logic-in-component-files` — a Medusa storefront's narrowed version
|
|
479
510
|
|
|
480
|
-
|
|
511
|
+
A Workers + D1 app's version also reported type re-exports and custom hooks in component files. Both are
|
|
481
512
|
covered by dedicated rules in the union (`no-type-definitions-in-components`,
|
|
482
|
-
`no-hook-in-component-disguise`), and
|
|
513
|
+
`no-hook-in-component-disguise`), and a Workers + D1 app's own suite does not test this rule. A Medusa storefront's
|
|
483
514
|
narrower "one public component per file" version is the one under test, and the one kept.
|
|
484
515
|
|
|
485
|
-
### `tissue-must-compose` —
|
|
516
|
+
### `tissue-must-compose` — a Medusa storefront's, minus its backstop
|
|
486
517
|
|
|
487
|
-
|
|
518
|
+
A Medusa storefront added a "renders ANY PascalCase tag" backstop. A Workers + D1 app has a test proving that backstop
|
|
488
519
|
lets a tissue satisfy the gate by rendering a component it imported as a **type** — precisely the
|
|
489
|
-
lint-theater the rule exists for. The backstop is dropped;
|
|
520
|
+
lint-theater the rule exists for. The backstop is dropped; a Medusa storefront's broader `COMPOSITION_PATTERNS`
|
|
490
521
|
(feature barrels, `.parts`/`.sections` siblings, react-email primitives) already recognise real
|
|
491
|
-
arrangements, and
|
|
522
|
+
arrangements, and a Medusa storefront's fixtures stay green without it.
|
|
492
523
|
|
|
493
524
|
### `cell-must-not-compose-cell` — the union's two halves collided on the barrel
|
|
494
525
|
|
|
495
|
-
The kit's version is
|
|
496
|
-
no `features/<name>/` segment) plus
|
|
526
|
+
The kit's version is a Medusa storefront's scope (`(?:^|/)cells/`, so the rule reaches a `packages/ui` tree with
|
|
527
|
+
no `features/<name>/` segment) plus a Workers + D1 app's visitors (`ExportAllDeclaration` and
|
|
497
528
|
`ExportNamedDeclaration`, so a cell cannot launder an import through `export … from`). Each half is
|
|
498
529
|
right. Together, unguarded, they reported every `cells/index.ts` barrel — because a barrel is
|
|
499
|
-
nothing but `export … from` lines naming sibling cells.
|
|
530
|
+
nothing but `export … from` lines naming sibling cells. A Medusa storefront's first adoption produced 32 such
|
|
500
531
|
errors, and every one of them was a file obeying `cells-folder-index-is-barrel`, which is also an
|
|
501
532
|
error and demands exactly that shape.
|
|
502
533
|
|
|
503
534
|
Neither fork could see it:
|
|
504
535
|
|
|
505
|
-
- **
|
|
536
|
+
- **A Medusa storefront** had the broad scope but visited `ImportDeclaration` only, so the re-exports in its
|
|
506
537
|
barrels were invisible to the rule;
|
|
507
|
-
- **
|
|
538
|
+
- **A Workers + D1 app** visited all three, but scoped to `features/[^/]+/cells/`, and it has no
|
|
508
539
|
`cells/index.ts` or `cells/dynamic.ts*` anywhere in `apps/` or `packages/` — nothing to collide
|
|
509
540
|
with.
|
|
510
541
|
|
|
@@ -519,7 +550,7 @@ holds `cells/`.
|
|
|
519
550
|
### `no-raw-html-atoms` — the tier it was always documented for (0.3.0)
|
|
520
551
|
|
|
521
552
|
Its own description said levels 2-6 and it fired at every level, `atoms/` included, where a raw
|
|
522
|
-
`<button>` is not a violation but the whole point of the file.
|
|
553
|
+
`<button>` is not a violation but the whole point of the file. A Medusa storefront armed it by switching the rule
|
|
523
554
|
off over `**/atoms/**` in its own config: a repo carrying the kit's defect as configuration.
|
|
524
555
|
|
|
525
556
|
It is now silent inside the atom tier — `(?:^|/)(?:features/[^/]+/)?atoms/`, the merged pattern from
|
|
@@ -547,17 +578,17 @@ rule the option refusal exists to stop, one option along.
|
|
|
547
578
|
|
|
548
579
|
| Rule | Option | Unconfigured |
|
|
549
580
|
|---|---|---|
|
|
550
|
-
| `organelle-dependency` | `sharedFeatures` | `['shared']` —
|
|
581
|
+
| `organelle-dependency` | `sharedFeatures` | `['shared']` — a Workers + D1 app's `charts`/`document` are config, not law |
|
|
551
582
|
| `mutating-step-declares-retry` | `retryKey` | `maxRetries` — Medusa's key. `@geonosis/workflows` spells the same budget `retry: { limit, backoff }` (`create-step.ts`), and with the key compiled in a correctly budgeted step on that engine was a finding for ever |
|
|
552
583
|
| `mutating-step-declares-retry` | `retryLimitKey` | `limit` — the key that holds the NUMBER when the budget is an object. #191: reading only the bare form let `retry: { limit: 0 }` pass, which is the same silence as `maxRetries: 0` |
|
|
553
584
|
| `worker-handles-are-scoped` | `within` + `wrapper` | **refuses the run** — `withCells` and `/apps/api/src/` were compiled in, so the rule served one repo's layout and one repo's vocabulary and a scope opener called anything else read as unscoped for ever |
|
|
554
|
-
| `subpath-over-barrel` | `packages` (`{ name, subpaths
|
|
585
|
+
| `subpath-over-barrel` | `packages` (`{ name, subpaths?: { <subpath>: string[] } }[]`) | optional — the map is the package's own `package.json` `exports`, read for the repo's own packages (linked into `node_modules`, or private) with the names behind each door read off its entry file. `{ name, subpaths }` replaces the map for one package; `{ name }` alone reads a third-party package too |
|
|
555
586
|
| `durable-body-reads-through-steps` | `ports` | **refuses the run** — what a body reaches the outside world through is the repo's vocabulary; `durableKey` / `durableValue` (default `durable: true`) say how a workflow declares itself durable |
|
|
556
|
-
| `unique-query-step-name` | `queryStepCalls` | **refuses the run** — the two names it shipped with are Medusa framework calls with no counterpart, so on
|
|
587
|
+
| `unique-query-step-name` | `queryStepCalls` | **refuses the run** — the two names it shipped with are Medusa framework calls with no counterpart, so on `@geonosis/workflows` the rule had zero reach while reading as enforced (026-W2, measured through the binary). There is no second spelling to inherit: `@geonosis/workflows` has no query-step primitive, and a repeated step name there is DEFINED rather than lost — `reserve`, `reserve#2` in call order (the engine's `reserveName`) |
|
|
557
588
|
| `no-raw-html-atoms` | (positional) element→atom map | **refuses the run** |
|
|
558
589
|
| `no-raw-html-atoms` | `scope` (second positional, `{ scope: ["cells", …] }`) | every file except the atom tier |
|
|
559
590
|
|
|
560
|
-
Package specifiers in diagnostics (`@
|
|
591
|
+
Package specifiers in diagnostics (`@shop/ui`, `@app/utils/time`) were made generic, and
|
|
561
592
|
`CLAUDE.md §…` citations were removed: they pointed at a law file this package does not ship, and
|
|
562
593
|
the remedy now lives in each rule's `fixShape`.
|
|
563
594
|
|
|
@@ -577,7 +608,7 @@ fix to how a path is read had to be found rule by rule.
|
|
|
577
608
|
| `lib/imports.ts` | `sourceOf` (import · `export … from` · `export * from` · `import()` · `require()`), `isTypeOnly`, `resolveRelative` |
|
|
578
609
|
| `lib/visitors.ts` | `onImportSource` — one visitor set over all five shapes — and `mergeVisitors` |
|
|
579
610
|
| `lib/direction-rule.ts` | `directionRule()`: a direction rule as a declaration (scope, forbidden, messages) |
|
|
580
|
-
| `lib/options.ts` | `requireOption` — an enabled rule that cannot fire
|
|
611
|
+
| `lib/options.ts` | `requireOption` — an enabled rule that cannot fire says so; `reportingOncePerRun` — the registry reports it as one finding a run |
|
|
581
612
|
| `lib/cells-barrel.ts` | the barrel list two rules must agree on |
|
|
582
613
|
| `lib/literal-call.ts` | the string a `saga('x', …)`-shaped call names |
|
|
583
614
|
|
|
@@ -588,7 +619,7 @@ declaration and holds no mechanism at all.
|
|
|
588
619
|
`onImportSource` visits a type-only import only when the rule counts one. Four do:
|
|
589
620
|
`no-orm-outside-db` and `dialect-through-the-seam` say why in their docs (a type borrowed from the
|
|
590
621
|
ORM is a line a replacement would still have to rewrite), and `atom-no-deps` and
|
|
591
|
-
`molecule-atoms-only` inherited it from
|
|
622
|
+
`molecule-atoms-only` inherited it from a Medusa storefront — an atom that borrows a domain's type is an atom
|
|
592
623
|
that knows a domain. The other six direction rules skip them, as they always did.
|
|
593
624
|
|
|
594
625
|
### Direction rules resolve relative imports (0.4.0)
|
|
@@ -597,7 +628,7 @@ that knows a domain. The other six direction rules skip them, as they always did
|
|
|
597
628
|
importing file before judging it. The other eight direction rules read the string as written, so
|
|
598
629
|
`../../billing/organelles/totals` — which names no feature and no tier until it is resolved — was
|
|
599
630
|
invisible to them. Whether a relative climb across a feature boundary was a finding depended on
|
|
600
|
-
which rule happened to look at it.
|
|
631
|
+
which rule happened to look at it. A Workers + D1 app's A2b probe found this; it was one resolver in two
|
|
601
632
|
rules and no resolver in eight.
|
|
602
633
|
|
|
603
634
|
**Now:** the classification every direction rule judges by goes through `resolveRelative` once, in
|
|
@@ -624,9 +655,9 @@ argued — `proofs/018-T3-relative-imports/`:
|
|
|
624
655
|
| tree | A | B | lost | gained |
|
|
625
656
|
|---|---:|---:|---:|---:|
|
|
626
657
|
| the kit's corpus | 99 | 101 | 0 | 2 — `no-cross-feature-stores` and `organelle-dependency`, on the two fixtures written RED for this |
|
|
627
|
-
|
|
|
658
|
+
| A Medusa storefront, its own config, `apps packages` | 135 | 135 | 0 | 0 |
|
|
628
659
|
|
|
629
|
-
|
|
660
|
+
A Medusa storefront's zero is not luck: that tree has seven relative cross-feature imports of a store or an
|
|
630
661
|
organelle, five of them into `shared/organelles/*` (which `organelle-dependency`'s `sharedFeatures`
|
|
631
662
|
option permits) and two of them in **cells** reaching another feature's organelle — which no rule
|
|
632
663
|
here polices, because `organelle-dependency` is scoped to `organelles/` and `no-cross-feature-stores`
|
|
@@ -641,7 +672,7 @@ and says nothing about who imports it. `no-cross-feature-stores` is scoped to ce
|
|
|
641
672
|
but only judges `features/<x>/stores/`. So a **cell** importing another feature's **organelle** — a
|
|
642
673
|
hook holding that feature's state, pulled across the boundary — passed both.
|
|
643
674
|
|
|
644
|
-
It is not hypothetical.
|
|
675
|
+
It is not hypothetical. A Medusa storefront, on its own config, with the rule enabled through a temporary config
|
|
645
676
|
and nothing else changed:
|
|
646
677
|
|
|
647
678
|
| site | import |
|
|
@@ -672,42 +703,42 @@ theme whether or not it is organised biologically. `backend-d1` exists because `
|
|
|
672
703
|
them would be a repo fact wearing a preset's name. `backend-workflows-cf` exists for the same reason:
|
|
673
704
|
`step-opens-its-own-cell` is true of a durable run on Cloudflare Workflows and false of an inline
|
|
674
705
|
one. It was called `backend-sagaflow-cf` until 1.5.0, after the engine it was written for; the old
|
|
675
|
-
name resolves to the same rules for ONE release and says so once, then goes. `package-subpaths` exists because `subpath-over-barrel`
|
|
676
|
-
|
|
677
|
-
|
|
706
|
+
name resolves to the same rules for ONE release and says so once, then goes. `package-subpaths` exists because `subpath-over-barrel` reads the repo's own packages off disk —
|
|
707
|
+
their `exports` and the entry file behind each door — which is a fact about that repo's layout, not
|
|
708
|
+
every repo's.
|
|
678
709
|
|
|
679
|
-
| Rule | Preset(s) | Requires | Core | In
|
|
710
|
+
| Rule | Preset(s) | Requires | Core | In a Medusa storefront | In a Workers + D1 app | Same code |
|
|
680
711
|
|---|---|---|---|---|---|---|
|
|
681
712
|
| `atom-no-deps` | frontend | — | `directionRule` | yes | yes | diverged |
|
|
682
713
|
| `boundary-time-is-primitive` | backend-workflows | `types` | `nodes` + `paths` + `options` | — | — | **written here** (D-050, #145) |
|
|
683
|
-
| `cell-must-be-stateful` | frontend | — | `paths` | yes | — |
|
|
714
|
+
| `cell-must-be-stateful` | frontend | — | `paths` | yes | — | a Medusa storefront only |
|
|
684
715
|
| `cell-must-not-compose-cell` | frontend | — | `directionRule` + `cells-barrel` | yes | yes | diverged |
|
|
685
716
|
| `cell-no-cross-feature-organelles` | frontend | — | `directionRule` | — | — | **written here** |
|
|
686
717
|
| `cell-no-tissues` | frontend | — | `directionRule` | yes | yes | diverged |
|
|
687
718
|
| `cells-folder-index-is-barrel` | frontend | — | `paths` + `cells-barrel` | yes | yes | diverged |
|
|
688
719
|
| `compound-must-be-stateless` | frontend | — | `paths` | yes | yes | diverged |
|
|
689
720
|
| `compound-no-stores` | frontend | — | `directionRule` | yes | yes | diverged |
|
|
690
|
-
| `constants-in-constants-file` | backend-workflows | — | `paths` | — | yes |
|
|
691
|
-
| `dialect-through-the-seam` | backend-workflows |
|
|
721
|
+
| `constants-in-constants-file` | backend-workflows | — | `paths` | — | yes | a Workers + D1 app only |
|
|
722
|
+
| `dialect-through-the-seam` | backend-workflows | `seam` | `onImportSource` + `mergeVisitors` + `paths` + `options` | — | yes | a Workers + D1 app only, de-hardcoded |
|
|
692
723
|
| `durable-body-reads-through-steps` | backend-workflows | `ports` | `nodes` + `objects` + `steps` + `options` | — | — | **written here** (#167, design-026 §8) |
|
|
693
724
|
| `effect-hook-naming` | frontend | — | `paths` | yes | yes | diverged |
|
|
694
725
|
| `emit-declares-attempts` | backend-medusa | — | `options` | — | — | **written here** |
|
|
695
726
|
| `font-roles-only` | themekit | `roles` | `paths` + `options` + `styling` | — | — | **written here** (U2) |
|
|
696
727
|
| `layer-walls` | tooling | `layers` | `onImportSource` + `imports` + `paths` + `options` | — | — | **written here** |
|
|
697
728
|
| `list-take-without-order` | backend-medusa | — | `nodes` + `options` | — | — | **written here** (#21) |
|
|
698
|
-
| `max-comment-density` | comment-ceiling | — | `paths` | — | yes |
|
|
729
|
+
| `max-comment-density` | comment-ceiling | — | `paths` | — | yes | a Workers + D1 app only |
|
|
699
730
|
| `molecule-atoms-only` | frontend | — | `directionRule` | yes | yes | diverged |
|
|
700
731
|
| `molecule-must-compose` | frontend | — | — | yes | yes | diverged |
|
|
701
|
-
| `mutating-step-declares-retry` | backend-medusa + backend-workflows | — | `nodes` + `objects` + `steps` + `options` | — | — | **written here** (
|
|
702
|
-
| `named-when-block` | backend-medusa | — | `nodes` + `options` | — | — | **written here** (
|
|
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) |
|
|
703
734
|
| `next-route-segment-is-thin-delegate` | frontend | — | `paths` | yes | yes | diverged |
|
|
704
735
|
| `no-unregistered-plugin-dir` | integrations | `roots` | `options` + `paths` | — | — | **written here** (Midday's 8 dead dirs; flat seam #36) |
|
|
705
736
|
| `no-mock-db-in-integration` | testbed | `integrationDirs` | `options` + `paths` | — | — | **written here** (Midday's 35 mocked router suites) |
|
|
706
|
-
| `router-schema-parity` | backend-trpc | `routers` | `options` + `paths` | — | — | **written here** (
|
|
707
|
-
| `no-brand-names` | frontend | `brands` | `paths` + `options` | yes | — |
|
|
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 |
|
|
708
739
|
| `no-card-shaped-div` | frontend | — | — | yes | yes | diverged |
|
|
709
740
|
| `no-cross-feature-stores` | frontend | — | `directionRule` | yes | yes | diverged |
|
|
710
|
-
| `no-d1-transaction` | backend-d1 | — | `paths` | — | yes |
|
|
741
|
+
| `no-d1-transaction` | backend-d1 | — | `paths` + `options` | — | yes | a Workers + D1 app only, de-hardcoded |
|
|
711
742
|
| `no-date-string-compare` | backend-workflows | — | `nodes` + `options` | — | — | **written here** (#20) |
|
|
712
743
|
| `no-duplicate-jsx-patterns` | frontend | — | `paths` | yes | yes | diverged |
|
|
713
744
|
| `no-generic-utility-package` | tooling | — | `paths` + `options` | — | — | **written here** (#171) |
|
|
@@ -716,47 +747,47 @@ has, and one a kit that guessed at it would keep stale on somebody else's behalf
|
|
|
716
747
|
| `no-inert-hidden-jsx` | frontend | — | `paths` | yes | yes | diverged |
|
|
717
748
|
| `no-inline-data-in-jsx` | frontend | — | `paths` | yes | yes | diverged |
|
|
718
749
|
| `no-invalid-feature-folders` | frontend | — | `paths` | yes | yes | diverged |
|
|
719
|
-
| `no-loader-side-effect-in-index-file` | backend-medusa | — | `nodes` + `paths` + `options` | — | — | **written here** (
|
|
750
|
+
| `no-loader-side-effect-in-index-file` | backend-medusa | — | `nodes` + `paths` + `options` | — | — | **written here** (a Medusa storefront's scan #15442) |
|
|
720
751
|
| `no-logic-in-component-files` | frontend | — | `paths` | yes | yes | diverged |
|
|
721
|
-
| `plugin-route-namespaced` | backend-medusa | `packages` | `paths` + `options` | — | — | **written here** (
|
|
722
|
-
| `no-orm-outside-db` | backend-workflows | — | `directionRule` | — | yes |
|
|
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 |
|
|
723
754
|
| `no-raw-html-atoms` | frontend | `element→atom map` | `tiers` + `options` | yes | yes | diverged |
|
|
724
|
-
| `no-raw-sql-outside-allowed` | backend-workflows | — | `paths` | — | yes |
|
|
755
|
+
| `no-raw-sql-outside-allowed` | backend-workflows | — | `paths` + `options` | — | yes | a Workers + D1 app only, de-hardcoded |
|
|
725
756
|
| `no-react-namespace` | frontend | — | — | yes | yes | **identical** |
|
|
726
757
|
| `no-renamed-html-props` | frontend | — | — | yes | yes | **identical** |
|
|
727
758
|
| `no-render-prop-reader` | frontend | — | `paths` | yes | yes | diverged |
|
|
728
|
-
| `no-swallowed-catch` | failure-paths |
|
|
729
|
-
| `no-runtime-dead-affordance` | frontend |
|
|
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) |
|
|
730
761
|
| `no-trivial-wrapper-component` | frontend | — | `paths` | yes | yes | diverged |
|
|
731
762
|
| `no-ts-in-bio-folders` | frontend | — | `paths` | yes | yes | diverged |
|
|
732
763
|
| `no-type-definitions-in-components` | frontend | — | `paths` | yes | yes | diverged |
|
|
733
|
-
| `no-void-port` | backend-workflows | — | — | — | yes |
|
|
764
|
+
| `no-void-port` | backend-workflows | — | — | — | yes | a Workers + D1 app only |
|
|
734
765
|
| `organelle-dependency` | frontend | — | `directionRule` | yes | yes | diverged |
|
|
735
766
|
| `organelle-single-source` | frontend | — | `onImportSource` + `mergeVisitors` + `paths` | yes | yes | diverged |
|
|
736
|
-
| `queries-require-org-scope` | tenant-scoping |
|
|
737
|
-
| `queue-loop-is-the-library` | backend-workflows | — | — | — | yes |
|
|
738
|
-
| `route-no-inline-mutation` | backend-medusa | — | `nodes` + `paths` + `comments` + `options` | — | — | **written here** (
|
|
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) |
|
|
739
770
|
| `service-name-not-reserved` | backend-medusa | — | `nodes` + `objects` + `options` | — | — | **written here** (Medusa source) |
|
|
740
|
-
| `ssot-no-inline-facts` | backend-workflows | — | `paths` | — | yes |
|
|
741
|
-
| `ssot-no-process-env` | backend-workflows | — | `paths` | — | yes |
|
|
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 |
|
|
742
773
|
| `step-declares-compensation-or-none` | backend-medusa + backend-workflows | — | `nodes` + `objects` + `steps` + `options` | — | — | **written here** (Medusa source) |
|
|
743
|
-
| `step-opens-its-own-cell` | backend-workflows-cf | `within` | `nodes` + `objects` + `paths` + `options` | — | yes |
|
|
744
|
-
| `store-route-scopes-tenant-data` | backend-medusa + tenant-scoping | — | `nodes` + `imports` + `paths` + `options` | yes | — |
|
|
745
|
-
| `subpath-over-barrel` | package-subpaths |
|
|
746
|
-
| `subscriber-declares-id` | backend-medusa | — | `objects` + `paths` + `options` | — | — | **written here** (
|
|
747
|
-
| `tables-declare-their-plane` | backend-workflows + tenant-scoping |
|
|
748
|
-
| `tenant-tables-carry-org-id` | tenant-scoping |
|
|
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 |
|
|
749
780
|
| `test-no-clock-polling-for-promise-flips` | testbed | — | `nodes` + `paths` + `options` | — | — | **written here** (#4) |
|
|
750
781
|
| `tokens-only-colors` | themekit | `tokenPrefix` | `paths` + `options` + `styling` + `colors` | — | — | **written here** (U2) |
|
|
751
|
-
| `time-through-the-door` | backend-workflows | `door` | `paths` + `options` | — | yes |
|
|
782
|
+
| `time-through-the-door` | backend-workflows | `door` | `paths` + `options` | — | yes | a Workers + D1 app only, de-hardcoded |
|
|
752
783
|
| `tissue-must-compose` | frontend | — | `paths` | yes | yes | diverged |
|
|
753
784
|
| `tissue-no-data-props` | frontend | — | `paths` | yes | yes | diverged |
|
|
754
785
|
| `tissue-no-hooks` | frontend | — | `paths` | yes | yes | diverged |
|
|
755
786
|
| `tissue-no-organelles` | frontend | — | `directionRule` | yes | yes | diverged |
|
|
756
787
|
| `tissue-no-stores` | frontend | — | `directionRule` | yes | yes | diverged |
|
|
757
788
|
| `todo-names-its-plan` | tooling | `markers` | `comments` + `options` | — | — | **written here** (W28) |
|
|
758
|
-
| `unique-query-step-name` | backend-medusa | `queryStepCalls` | `nodes` + `objects` + `options` | — | — | **written here** (
|
|
759
|
-
| `worker-handles-are-scoped` | backend-d1 | `within` | `paths` + `options` | — | yes (as `cell-handles-are-scoped`) |
|
|
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 |
|
|
760
791
|
| `zustand-v5-best-practices` | frontend | — | — | yes | yes | **identical** |
|
|
761
792
|
|
|
762
793
|
Preset sizes: frontend 36 · backend-workflows 16 · backend-medusa 12 · tenant-scoping 4 ·
|
|
@@ -770,13 +801,13 @@ the retry rule only since its key became an option — so both presets carry the
|
|
|
770
801
|
of `backend-medusa` would take a live rule away from the repo it was written for, which is a
|
|
771
802
|
downgrade wearing a preset change. `unique-query-step-name` stays Medusa's alone: the collision it
|
|
772
803
|
reports is that framework's step-handler map last-winning, and a repeated step name on the org
|
|
773
|
-
engine is DEFINED rather than lost (`reserve`, `reserve#2`,
|
|
804
|
+
engine is DEFINED rather than lost (`reserve`, `reserve#2`, the engine's `reserveName`).
|
|
774
805
|
|
|
775
806
|
## The build
|
|
776
807
|
|
|
777
|
-
Two builds existed.
|
|
808
|
+
Two builds existed. A Workers + D1 app's `package.json` resolves `.` through a `bun` condition to
|
|
778
809
|
`src/index.ts` and only falls back to `dist/index.js`; without bun, oxlint gets the fallback anyway.
|
|
779
|
-
|
|
810
|
+
A Medusa storefront's is the plain one — `tsup … --format esm --no-dts --platform node --out-dir dist` with
|
|
780
811
|
`"exports": { ".": "./dist/index.js" }` — and that is what this package uses.
|
|
781
812
|
|
|
782
813
|
It is **source-proven**, not assumed: `src/rules/fixtures.test.ts` runs the real `oxlint` binary with
|
|
@@ -787,7 +818,7 @@ fire" case would pass silently without that assertion.
|
|
|
787
818
|
## `fixShape`
|
|
788
819
|
|
|
789
820
|
Every rule exports `{ meta, create, fixShape }`. `fixShape` is a short Markdown string in the voice
|
|
790
|
-
of
|
|
821
|
+
of a Workers + D1 app's own code skill: what the gate rejects and what to write instead.
|
|
791
822
|
`pnpm generate:skill` assembles them into `skills/geonosis-code/SKILL.md`, grouped by preset. A
|
|
792
823
|
staleness test fails when the committed skill is not what the rules generate, so the skill and the
|
|
793
824
|
rules cannot drift.
|
|
@@ -807,7 +838,7 @@ enforce them on every file — which is what both source repos reported against
|
|
|
807
838
|
2026-08-30. `@geonosis/doctor`'s `exercised` check reads `probe` off the plugin a config loads,
|
|
808
839
|
writes it into a copy of the corpus, and runs again.
|
|
809
840
|
|
|
810
|
-
A probe is a CLAIM, so `src/probes.test.ts` tries to break every one of them through the real
|
|
841
|
+
A probe is a CLAIM, so `src/probes.bin.test.ts` tries to break every one of them through the real
|
|
811
842
|
binary, under consumer-shaped options, and requires exactly one finding. Two rules deserve the
|
|
812
843
|
detail:
|
|
813
844
|
|
|
@@ -816,7 +847,7 @@ detail:
|
|
|
816
847
|
registry, so its probe declares BOTH — the directory under `roots`, and a registry carrying no
|
|
817
848
|
string literal at all, so nothing in it can name the directory beside it. It was a single file
|
|
818
849
|
until 2026-08-30 and fired nothing, which was the truth about the probe and a lie about every
|
|
819
|
-
consumer:
|
|
850
|
+
consumer: a Medusa storefront enforces this rule at error over four registries and read as a permanent FAIL
|
|
820
851
|
for it.
|
|
821
852
|
- `layer-walls` and the path-regex options are inverted by `lib/probe.ts`'s `pathMatching`, which
|
|
822
853
|
builds a path back out of the pattern and then TESTS it against that pattern, refusing with a
|
|
@@ -843,7 +874,7 @@ option not named here is one no rule reads.
|
|
|
843
874
|
| `compound-must-be-stateless` | — |
|
|
844
875
|
| `compound-no-stores` | — |
|
|
845
876
|
| `constants-in-constants-file` | — |
|
|
846
|
-
| `dialect-through-the-seam` |
|
|
877
|
+
| `dialect-through-the-seam` | `exempt`, `seam` |
|
|
847
878
|
| `durable-body-reads-through-steps` | `durableKey`, `durableValue`, `ports`, `stepFactories`, `stepProxies`, `workflowFactories` |
|
|
848
879
|
| `effect-hook-naming` | — |
|
|
849
880
|
| `emit-declares-attempts` | `budgetHelpers`, `emitters` |
|
|
@@ -859,7 +890,7 @@ option not named here is one no rule reads.
|
|
|
859
890
|
| `no-brand-names` | `allowedSubstrings`, `brands` |
|
|
860
891
|
| `no-card-shaped-div` | — |
|
|
861
892
|
| `no-cross-feature-stores` | — |
|
|
862
|
-
| `no-d1-transaction` |
|
|
893
|
+
| `no-d1-transaction` | `except`, `transacts` |
|
|
863
894
|
| `no-date-string-compare` | `fieldPattern` |
|
|
864
895
|
| `no-duplicate-jsx-patterns` | — |
|
|
865
896
|
| `no-generic-utility-package` | `entries`, `names` |
|
|
@@ -871,36 +902,36 @@ option not named here is one no rule reads.
|
|
|
871
902
|
| `no-loader-side-effect-in-index-file` | `directories`, `resourceFactories` |
|
|
872
903
|
| `no-logic-in-component-files` | — |
|
|
873
904
|
| `no-mock-db-in-integration` | `dbModules`, `integrationDirs`, `mockNames` |
|
|
874
|
-
| `no-orm-outside-db` |
|
|
905
|
+
| `no-orm-outside-db` | `dataLayer` |
|
|
875
906
|
| `no-raw-html-atoms` | (a positional list), `scope` |
|
|
876
|
-
| `no-raw-sql-outside-allowed` |
|
|
907
|
+
| `no-raw-sql-outside-allowed` | `allow` |
|
|
877
908
|
| `no-react-namespace` | — |
|
|
878
909
|
| `no-renamed-html-props` | — |
|
|
879
910
|
| `no-render-prop-reader` | — |
|
|
880
911
|
| `no-runtime-dead-affordance` | (a positional list) |
|
|
881
912
|
| `no-swallowed-catch` | `paths` |
|
|
882
913
|
| `no-trivial-wrapper-component` | — |
|
|
883
|
-
| `no-ts-in-bio-folders` |
|
|
914
|
+
| `no-ts-in-bio-folders` | `extraFolders` |
|
|
884
915
|
| `no-type-definitions-in-components` | — |
|
|
885
916
|
| `no-unregistered-plugin-dir` | `manifests`, `registry`, `roots` |
|
|
886
|
-
| `no-void-port` |
|
|
917
|
+
| `no-void-port` | `engineCallbacks` |
|
|
887
918
|
| `organelle-dependency` | `sharedFeatures` |
|
|
888
919
|
| `organelle-single-source` | — |
|
|
889
920
|
| `plugin-route-namespaced` | `packages`, `scopes` |
|
|
890
|
-
| `queries-require-org-scope` |
|
|
921
|
+
| `queries-require-org-scope` | `queries`, `tenantKey` |
|
|
891
922
|
| `queue-loop-is-the-library` | — |
|
|
892
923
|
| `route-no-inline-mutation` | `inlineMutationOk`, `mutationPrefixes`, `paths` |
|
|
893
924
|
| `router-schema-parity` | `ignore`, `mounts`, `routers`, `schemas`, `suffix` |
|
|
894
925
|
| `service-name-not-reserved` | `moduleFactories`, `reserved` |
|
|
895
|
-
| `ssot-no-inline-facts` | `declarationCallees` |
|
|
896
|
-
| `ssot-no-process-env` |
|
|
926
|
+
| `ssot-no-inline-facts` | `declarationCallees`, `owners` |
|
|
927
|
+
| `ssot-no-process-env` | `doors` |
|
|
897
928
|
| `step-declares-compensation-or-none` | `stepFactories` |
|
|
898
929
|
| `step-opens-its-own-cell` | `engine`, `hosts`, `scopeKey`, `within`, `wrapper` |
|
|
899
930
|
| `store-route-scopes-tenant-data` | `entities`, `evidenceCalls`, `routes`, `scopingCalls` |
|
|
900
931
|
| `subpath-over-barrel` | `packages` |
|
|
901
932
|
| `subscriber-declares-id` | `paths` |
|
|
902
|
-
| `tables-declare-their-plane` |
|
|
903
|
-
| `tenant-tables-carry-org-id` |
|
|
933
|
+
| `tables-declare-their-plane` | `schema` |
|
|
934
|
+
| `tenant-tables-carry-org-id` | `tenant`, `tenantKey` |
|
|
904
935
|
| `test-no-clock-polling-for-promise-flips` | `pollers`, `testGlobs` |
|
|
905
936
|
| `time-through-the-door` | `door`, `exempt`, `exemptModules`, `libraries` |
|
|
906
937
|
| `tissue-must-compose` | — |
|