@geonosis/doctor 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 CHANGED
@@ -1,5 +1,234 @@
1
1
  # @geonosis/doctor
2
2
 
3
+ ## 2.11.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 0f99e26: A `formatter` check (#7): the repo's own oxfmt is asked whether it would rewrite the kit's rule files
8
+ under `.claude/rules/geonosis`. `oxfmt --migrate=prettier` writes `ignorePatterns: []`, and the
9
+ first `format` of a migrated repo rewrote the generated `tiers.md`. WARN names the ignore entry to
10
+ add; UNJUDGED when oxfmt could not be asked.
11
+ - 0f99e26: Four rules see what they were blind to, and eleven readings refuse instead of banking a zero.
12
+
13
+ `tissue-no-hooks` and `compound-must-be-stateless` report a hook called as a member —
14
+ `React.useState(0)`, `hooks.useCart()` — where they read only a bare name. `time-through-the-door`
15
+ sees the library through every import door (`export * from`, `import()`, `require()`), and
16
+ `subpath-over-barrel` reads `export { X } from '<root>'` as the import it is. Expect new findings on
17
+ a tree that spelled a hook through its default React import.
18
+
19
+ `geonosis-ratchet`: a shell killed by a signal, a formatter that exited non-zero naming no file, a
20
+ `turbo boundaries` that printed no summary, a `geonosis.json` that will not parse, a stale report at
21
+ a named `reportPath`, and an oxlint run that answered in a shape `expectFormat` did not ask for are
22
+ each a `CounterError` now. Every one of them read as a number and lowered the baseline to it.
23
+
24
+ `geonosis-doctor`: `deployed`, `seams`, `rails` and `drift` say FAIL over a `geonosis.json` that is
25
+ there and will not parse, where each had read it as a repo that declared nothing.
26
+ - 0f99e26: An enabled rule missing its option reports ONE finding a run instead of throwing (D-084), and the
27
+ five rules that chose their files from paths compiled in read them from an option (#12).
28
+
29
+ **The posture.** Up to 2.10.0 a rule enabled without an option it requires threw from `create()`.
30
+ Measured: the throw erased every other finding in the file it surfaced on, and rendered under
31
+ `--format=unix` as `:0:0: [Warning]` naming nothing. It is a finding now — attributable, counted by
32
+ the ratchet, neighbours intact — reported once per run on the first file oxlint constructs the rule
33
+ for, with the same `biological-architecture/<rule> requires option \`<option>\`.` header. Nothing the
34
+ plugin exports throws for a missing option. Expect exactly one new finding per bare rule per run
35
+ where a run used to be refused whole.
36
+
37
+ **#12 — four rules now REQUIRE a tree option; every config extending `backend-workflows` or
38
+ `tenant-scoping` must name it or gets the one finding above:**
39
+
40
+ - `tables-declare-their-plane` requires `schema` (a regex over the filename: the schema tree);
41
+ `control/` and `tenant/` are read under it.
42
+ - `tenant-tables-carry-org-id` requires `tenant` (a regex: the tenant-plane schema tree).
43
+ - `queries-require-org-scope` requires `queries` (a regex: the query modules).
44
+ - `dialect-through-the-seam` requires `seam` (regex sources: the files that may name a dialect);
45
+ `exempt` defaults to the test paths (`__tests__/`, `.test.`, `.spec.`) — a repo whose test helpers
46
+ live elsewhere (`testing/`) names them.
47
+
48
+ `no-d1-transaction` takes `transacts` and `except` (regex sources), both defaulting to nothing: the
49
+ plane the rule exempted was one repo's, and a D1-only repo has no plane to exempt. Expect new
50
+ findings on every `db.transaction()` that used to sit inside `/packages/db/(src|testing)/`.
51
+
52
+ One consumer's values, verbatim, are in `proofs/issue-12-and-option-posture-2026-09-18.md`.
53
+
54
+ `@geonosis/doctor`: `missingOptionOf` hears a refusal a rule reports as well as one it throws;
55
+ `exercised` no longer counts a bare rule's refusal finding as reach — it is a FAIL naming the option.
56
+ `@geonosis/cli`: `optionsNeededBy` reads through the same reader, so `init` still leaves those rules
57
+ out and names them.
58
+
59
+ 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
60
+ would normally want a major. It ships in a minor by the owner's ruling of 2026-09-19, on a measured
61
+ basis: every consumer pins this kit EXACTLY — three consumers, at 2.10.0, 2.6.1 and 2.10.0, no
62
+ caret anywhere — so nothing auto-upgrades and the version number protects nobody here. The warning
63
+ lives in this changelog instead, and the two consumers that could be reached were told directly
64
+ before the cut. The third is three releases behind; it is the one that must be told by hand.
65
+ - 0f99e26: Two defects the gates found (rows 576, 577) and five frictions one consumer measured (#1, #2, #5,
66
+ #9, #10), plus the stale-build guard every adopter-from-a-checkout hits.
67
+
68
+ `geonosis-ratchet`: a red test suite is a reading again (row 576). The #18 crash detector ran
69
+ before a counter's `okExits` and refused every red suite — an `AssertionError` over frames, and
70
+ Node's trailer whenever a failing assertion quoted the child it ran — so `testFailures` refused
71
+ at the one moment it had something to say. A crash is now a STREAM whose last lines are a trace
72
+ (the runtime's trailer, or an error line over at-frames with nothing after), judged at any exit
73
+ code; a runner that quoted a trace and printed its summary after it is read. `node crash.mjs`
74
+ still refuses, and so does the same crash hidden behind a pipe's exit 0. And a file-set counter
75
+ names its offenders (#2): `unformattedFiles` cites the files it named, most recently modified
76
+ first, under a line that says so. The `evidence` hook receives `cwd`, and the report keeps a
77
+ counter's FIRST ten citations rather than the tail.
78
+
79
+ `geonosis-doctor`: an `exercised` line per config lists the rules the plugin ships that the
80
+ config never mentions — absent, told apart from off (#5) — so a release's new rules are a list,
81
+ not an audit. Expect one new WARN on any config that leaves shipped rules unnamed. A `drift`
82
+ line reports a `pnpm` block in `package.json` that pnpm 11 does not read (#1): FAIL under a pin
83
+ of 11 or later, with the keys, the new home and the install repair. The observability age and
84
+ probe questions SKIP for a sink that reaches nothing (#10) instead of measuring how recently a
85
+ developer ran the app.
86
+
87
+ `geonosis`: the update's restart line and the MCP skew refusal say the bins in
88
+ `node_modules/.bin` are already the new release (#9).
89
+
90
+ Every kit bin's stale-build guard compares CONTENT, not only mtime: a checkout rewrites `src`
91
+ with the same content and the next `pnpm build` is a cache hit that leaves the current `dist`
92
+ untouched, so every bin refused with "run pnpm build" — the action that had just changed
93
+ nothing. Each shim keeps a digest of `src` from the last run that found `dist` current; a real
94
+ change still refuses, and the refusal names `pnpm build --force --filter=<package>`.
95
+
96
+ ### Patch Changes
97
+
98
+ - 0f99e26: Row 549: `group`'s `declaredGroupsOf` and `exercised`'s `repoCorpusOf` each read `geonosis.json`
99
+ with their own `catch { return undefined }` — a parse error read as "this repo declares nothing",
100
+ so an unreadable `geonosis.json` declaring `doctor.groups` had the kit's own default group judged
101
+ in its place, and `exercised` silently ran without a repo's own reach corpus. Both now read through
102
+ `geonosis-file.ts`, the same reader `deployed`, `seams`, `rails` and `apparatus` already use, and
103
+ throw rather than answer `absent` on a file that cannot be parsed; `checkGroup` and the `exercised`
104
+ check turn that into their own FAIL line, so a corrupt `geonosis.json` no longer reads as a clean
105
+ tree in either check. `observability` carried a third, already-correct-but-separate reader of its
106
+ own; routed it through the same one.
107
+ - 0f99e26: Every bin's stale-build guard now sees the workspace dependencies its build read. A `dist`
108
+ current against its own `src` could still have been built against an older dependency's `dist` —
109
+ literally, where tsup inlined a workspace devDependency into it — and a dependency whose own `src`
110
+ had moved past its `dist` was read stale by every bin above it; the guard answered "current" for
111
+ both. Now each shim holds every `workspace:` dependency, transitively, to the same reading as its
112
+ own package (never built, stale against its src, current), and refuses when this `dist` is older
113
+ than a dependency's — by content, so a dependency rebuilt to the same bytes with
114
+ `--force --filter` is the same build and does not poison the tree. Unknown is still stale: the
115
+ first run over a tree where a dependency was rebuilt alone, before any record, refuses once and
116
+ names `--force --filter=<this package>`. Only a checkout with `src/` beside `dist/` sees any of
117
+ this; the published package ships no `src/` and the guard is skipped. Measured: within noise for a
118
+ bin with one dependency, +8 ms for `geonosis`, which walks fourteen.
119
+ - 0f99e26: Every bin's stale-build guard decides, and never throws. The digest guard's refusal read
120
+ `package.json` to name `pnpm build --force --filter=<name>`; over a checkout with no manifest
121
+ beside the bin it ended with exit 1 and a `node:fs` frame instead of exit 2 and a sentence. The
122
+ name is now a courtesy the refusal does without. The contract, stated: a source newer by mtime whose
123
+ content the bin saw beside a current dist is not stale; one whose content differs is, and the
124
+ refusal names the fix; one the bin has no record for is stale — unknown is stale; a missing dist is
125
+ "never built". Each is exit 2 with a sentence. Only a checkout with `src/` beside `dist/` sees any
126
+ of this; the published package ships no `src/` and the guard is skipped.
127
+ - 0f99e26: `geonosis-doctor` no longer judges a rule-exercising config as though it governed a product. The
128
+ `exercised` engine line (#159) read the plugin's shipped corpus config and a fixture tree's config
129
+ and WARNed, eleven times each, that no manifest they govern declares Medusa — about a tree nobody
130
+ ships, burying the lines about the tree that is. A config inside a directory the doctor already
131
+ reads as evidence — the corpus the loaded plugin ships, the corpus `geonosis.json` names under
132
+ `doctor.corpus` — is apparatus, and gets one SKIP saying so instead. A fixture tree that is the
133
+ SOURCE of a corpus ships no manifest, so a repo names it under the new `doctor.apparatus` list;
134
+ the default is empty and every config in a repo that declares nothing is judged as before. A
135
+ product config beside a corpus is still judged in the same run.
136
+ - 0f99e26: Four bugs a consumer filed against 2.10.0 (#13–#16).
137
+
138
+ `no-void-port` takes `engineCallbacks` — regexes over an exported alias name, empty by default —
139
+ so a workflow-hook check the engine catches (`HookCheck<TInput> = (input, ctx) => Promise<void>`)
140
+ can be named as not a port; a `Result` there would never be read.
141
+
142
+ `geonosis-doctor`: the Claude-plugin train is held only against the install that LOADS at the
143
+ doctored root (user scope, or a project scope naming that root), never another repo's; the
144
+ spare-declaration question reads the script files scripts and hooks run, CI workflows and
145
+ `geonosis.json` tiers, sees a bin quoted as an argv string, and says nothing about a package with
146
+ no bin. Expect four fewer WARNs on a root whose gate lives in a `.mjs`.
147
+
148
+ `geonosis-mcp`: a server started before an in-session `geonosis update` refuses every call naming
149
+ the version it is running, the version the repo now has, and the restart (`/mcp` → reconnect) —
150
+ never the install. `geonosis update` ends by saying so where `.mcp.json` names a `geonosis` server.
151
+ - 0f99e26: The kit names no consumer. Docs, doc comments and messages describe the repos a pattern was measured in by their stack (a Medusa storefront, a Workers + D1 app, a Mercur v1 marketplace). Example configs are named the same way: `examples/medusa-storefront.*.json`, `examples/workers-app.*.json`. No behaviour changes.
152
+ - 0f99e26: Three defects a consumer measured against 2.10.0 (#17, #18, #19) and one this tree found.
153
+
154
+ `geonosis-ratchet`: a counter's command that crashed is never a reading (#18). Every counter that
155
+ runs a tool declares the exit codes it reads — `[0, 1]` for a linter, `[0, 1, 2]` for tsc, `[0]`
156
+ where nothing is declared — and an entry may override them with `okExits`. Any other exit refuses
157
+ the counter, and so does a non-zero exit whose output is a Node stack trace, whatever the set says.
158
+ Before, a counter script that threw had its trace's line numbers summed as findings: reported as a
159
+ regression when they summed high, and BANKED as a shrink when they summed low — the baseline
160
+ rewritten to a number no measurement produced. Expect `CANNOT MEASURE` where a command exits with
161
+ a code its tool never uses for findings; declare `okExits` on that entry if the tool is right.
162
+
163
+ `geonosis-doctor`, `geonosis`, `geonosis-release`, `geonosis-observability`: the bins set
164
+ `process.exitCode` and let the runtime drain instead of calling `process.exit()` after writing the
165
+ report (#17). Through a pipe on macOS, anything past 64 KiB was dropped with a clean exit code —
166
+ the doctor delivered 65,536 of 323,665 bytes, `release migrations` 50 locations for 51 warnings.
167
+
168
+ The Claude plugin's Stop hook (#19): a cached fast-tier report is fresh only while the tree it was
169
+ judged over is still here, gitignored artifacts included — deleting a `playwright-report/` the
170
+ lint step had read now relaunches the tier instead of replaying the stale FAIL for ever. And a
171
+ replayed report says how many of its steps never ran, red or green.
172
+ - 0f99e26: `geonosis-doctor --baseline-against <ref>` parses what `git show` printed on stdout alone. A warning
173
+ git writes to stderr on its way to a success — a ref name that is both a branch and a tag — was
174
+ read into the file, and a counter that had grown read as "no readable baseline", a SKIP.
175
+ - 0f99e26: The `rails` check reads `.geonosis/rails-run.json` for denied egress attempts in a repo that allows
176
+ no egress at all. It returned before the record was read whenever no allowlist was declared, so the
177
+ deny-all repo — the one where a denied attempt matters most — never had its gate at zero asked.
178
+ - 0f99e26: `deniedEgress` fails a run record that lists no `deniedEgress` array, as it already failed one that was not JSON: `{}` read as zero denied attempts, so a run able to write under `.geonosis/` could clear its own.
179
+ - 0f99e26: An `overrides[].files` glob with a comma outside braces is read as the file name it is. The doctor
180
+ turned every comma into an alternation, so `src/a,b.ts` claimed `src/abc.ts`, and a stray `}` made
181
+ the pattern throw.
182
+ - 0f99e26: The `group` check prints no OK line for a group whose members disagree inside a workspace. Beside
183
+ the FAIL naming the split it also said the group held, "at 1.1.0, 1.4.0, one copy each".
184
+ - 0f99e26: `geonosis-doctor --help` says the envelope lists the registry's checks, twelve today, where it said
185
+ eleven; names observability and deployed where it said "the last two", which the exams and seams
186
+ checks had pushed down the list; and stops counting the rules only a repo's own corpus can answer.
187
+ - 0f99e26: `drift` and `runner` read what a repo runs through one reader: scripts, `geonosis.json` verify
188
+ tiers, ratchet counters' commands and workflows. `drift` ignored the tiers, so a generated file
189
+ whose `geonosis:gated-by:` gate ran only from a verify tier was reported as a gate nothing runs.
190
+ - 0f99e26: The path-grant line counts the files of this tree only. It walked into `.claude/worktrees/`,
191
+ `.turbo/` and every other dot-directory, counting a worktree's copy of the tree as more exempted
192
+ files and advising the repo to enumerate them.
193
+ - 0f99e26: The `loaded` check says an installed Claude plugin NEWER than the packages is ahead of them — its
194
+ hooks and skills can name commands the packages do not have yet — instead of saying every session
195
+ runs the older hooks, which was the reverse of true.
196
+ - 0f99e26: The `exercised` check says the declared probes could not be run, with what oxlint printed, when the
197
+ probe run dies or prints nothing it can read. It read such a run as "nothing fired", and FAILed
198
+ every probed rule as firing nowhere — blaming the rules for the instrument.
199
+ - 0f99e26: The README documents every check the doctor runs — `group`, `envelope` and `rails` had no section —
200
+ and stops counting them ("seven questions", "the eleven checks"); a test holds a heading per check
201
+ to the registry. It names `--only` in the usage line, says what a bare option-gated rule does since
202
+ D-084, and describes `exams` per exam.
203
+ - 0f99e26: The required-options line counts rules, not the layers that configure them: a rule enabled in the
204
+ base block and again in an override is one rule, and one FAIL when it lacks its option in both. It
205
+ also says `1 rule requires options, 1 carries them` rather than `1 rules require options`.
206
+ - 0f99e26: `drift` reads an unscoped platform-split family — `turbo-darwin-arm64`, `turbo-linux-x64` — as one
207
+ family, `turbo`. It took everything before the last `/` as the family, so each unscoped platform
208
+ package was a family of one and an allow-list naming every platform was warned for each. A
209
+ `generated` file's FAIL also names a verify tier among the places its gate can run from.
210
+ - 0f99e26: The `loaded` check's nested-copy FAIL tells a repo that declares `^2.10.0` to run
211
+ `geonosis update --to 2.10.0`; it quoted the range, and `update --to` takes a version.
212
+ - 0f99e26: `drift` finds a workspace bin a script calls in that workspace's own `node_modules/.bin`, where pnpm
213
+ links it for a package that depends on the bin's workspace. It looked under the root alone, and a
214
+ workspace that declared the dependency correctly was told the bin was missing.
215
+ - 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.
216
+ - Updated dependencies [0f99e26]
217
+ - Updated dependencies [0f99e26]
218
+ - Updated dependencies [0f99e26]
219
+ - Updated dependencies [0f99e26]
220
+ - Updated dependencies [0f99e26]
221
+ - Updated dependencies [0f99e26]
222
+ - Updated dependencies [0f99e26]
223
+ - @geonosis/lint-parity@2.11.0
224
+
225
+ ## 2.10.0
226
+
227
+ ### Patch Changes
228
+
229
+ - Updated dependencies [3863c79]
230
+ - @geonosis/lint-parity@2.10.0
231
+
3
232
  ## 2.9.0
4
233
 
5
234
  ### Patch Changes
@@ -224,6 +453,13 @@
224
453
  scratch root instead. The kit's own vitest config sets it to the repo root, so a test that writes an
225
454
  envelope there names itself — which is how the offender that filed this row was found again.
226
455
  - c4c9330: `runDoctor` takes `home` — where `~/.claude` lives for the plugin registry and the user settings — so a doctor run over a scratch tree is judged against the registry a caller names, never against the machine's own plugin. The kit's scaffolded-tree exam read this machine's plugin (2.1.0) against a tree at 2.2.0 and went red for a drift that was the exam's, not the scaffold's.
456
+ - df49097: **Installed-not-composed reads an IMPORT, not a mention (#251).** The line answered OK for any
457
+ source file whose text contained the package's name, so a comment saying a floor would be used one
458
+ day counted as composing it. It now looks for the specifier behind `from` / `import` / `require`,
459
+ subpaths included; an entity declared with `defineEntity` over `@geonosis/db` satisfies it. A repo
460
+ whose only mention of a kit package is a comment, a string or a doc block flips from `OK` to `WARN`
461
+ on that package's line. _Added 2026-09-23: the changeset was held for this cut and never released,
462
+ so 2.2.0 shipped the change without this entry._
227
463
  - Updated dependencies [d15a654]
228
464
  - @geonosis/lint-parity@2.2.0
229
465
 
package/README.md CHANGED
@@ -3,10 +3,11 @@
3
3
  Through the front door: `geonosis doctor` — the metapackage pins this and every other kit tool at
4
4
  ONE version, and passes the exit code through unchanged.
5
5
 
6
- Seven questions a version bump is not finished until something has asked. The first four are ways
7
- enforcement has reported **green while measuring nothing** — in real repos, for weeks at a time.
8
- `drift` asks whether the gate is still there at all. The last two ask the same question one layer
9
- out: whether what a pipeline reported is what actually happened.
6
+ The questions a version bump is not finished until something has asked. Most are ways enforcement
7
+ has reported **green while measuring nothing** — in real repos, for weeks at a time. `drift` asks
8
+ whether the gate is still there at all; `observability` and `deployed` ask the same question one
9
+ layer out, whether what a pipeline reported is what actually happened; `exams` and `seams` ask
10
+ whether an adoption can see the floors it took. `geonosis-doctor --help` lists every one.
10
11
 
11
12
  ```bash
12
13
  pnpm add -D @geonosis/doctor
@@ -22,14 +23,14 @@ print is about a gate that is not there. It used to answer `doctor PASS` at exit
22
23
  three vacuous OKs — from the tool whose thesis is that a SKIP is not a pass.
23
24
 
24
25
  ```
25
- geonosis-doctor [--root <dir>] [--json] [--strict] [--baseline-against [<ref>]] [--oxlint <path>]
26
+ geonosis-doctor [--root <dir>] [--only <check,…>] [--json] [--strict] [--baseline-against [<ref>]] [--oxlint <path>]
26
27
  ```
27
28
 
28
29
  ## One check, asked alone
29
30
 
30
- `geonosis-doctor --only loaded` runs that check and no other, in well under a second: a manifest read and a resolution per config. It exists because the doctor subsumed a consumer's own declared-vs-loaded script that lived in a 2.5 s local gate, and eleven seconds for every check cannot live there — so the question that catches a stale nested copy earliest had fallen out of the gate that runs most often (measured in a consumer, 2026-08-30: a per-workspace `bun add` left one workspace loading 1.0.0 against configs written for 1.1.0, and the script about to be deleted caught it). `--only loaded,runner` takes a list; a name the doctor does not have is refused with the ones it does. A `loaded` FAIL now says the remedy: remove the nested copies and reinstall.
31
+ `geonosis-doctor --only loaded` runs that check and no other, in well under a second: a manifest read and a resolution per config. It exists because the doctor subsumed a consumer's own declared-vs-loaded script that lived in a 2.5 s local gate, and eleven seconds for every check cannot live there — so the question that catches a stale nested copy earliest had fallen out of the gate that runs most often (measured in a consumer, 2026-08-30: a per-workspace `bun add` left one workspace loading 1.0.0 against configs written for 1.1.0, and the script about to be deleted caught it). `--only loaded,runner` takes a list; a name the doctor does not have is refused with the ones it does. A `loaded` FAIL names the remedy: `geonosis update --to <version>`, which removes the nested copies and reinstalls.
31
32
 
32
- ## The eleven checks
33
+ ## The checks
33
34
 
34
35
  ### `loaded` — declared ≠ loaded
35
36
 
@@ -65,7 +66,7 @@ geonosis bin through `pnpm`, `npx`, `bunx` or `yarn` is a **FAIL** naming the di
65
66
  replaces it.
66
67
 
67
68
  A git hook's PATH is not the shell's: no nvm shim, no corepack shim, nothing a version manager put
68
- there. dielime's first hooked commit was "refused" on 2026-08-30 and the ledger never ran — `pnpm`
69
+ there. A Medusa storefront's first hooked commit was "refused" on 2026-08-30 and the ledger never ran — `pnpm`
69
70
  was not found, and the sentence its author read was a version manager's failure wearing a gate's
70
71
  refusal costume. That is the worst shape a gate has, because it is indistinguishable from the gate
71
72
  working. `node_modules/.bin/<bin>` is linked by every package manager and needs nothing on PATH.
@@ -79,7 +80,20 @@ in the same commit — a consumer told to do one thing by one gate and undo it b
79
80
  neither (#176).
80
81
 
81
82
  It is a `loaded` line, not a `drift` one: it reads a committed file and nothing else, and
82
- during.day's fast tier runs `--only loaded`, so as a drift line it fired in CI and never locally.
83
+ a Workers + D1 app's fast tier runs `--only loaded`, so as a drift line it fired in CI and never locally.
84
+
85
+ ### `group` — one version, one copy
86
+
87
+ The packages published on ONE version, asked of every workspace: one copy repo-wide, and one number
88
+ per workspace. A consumer carried `@geonosis/visual-diff` at 1.1.0 and 1.4.0 in one tree while
89
+ every other line read green, because they are all scoped to the plugin a config names (#158).
90
+ Membership ships with the kit; a repo declares its OWN held-together sets under `doctor.groups` in
91
+ `geonosis.json`. Asked of the resolver, never of `find`, and a SKIP when the tree installs none of
92
+ the group.
93
+
94
+ It also reads what the ROOT declares: the door (`@geonosis/cli`, or the `geonosis` metapackage)
95
+ plus the bins its own scripts, hooks, script files, workflows and tiers call by name — and WARNs a
96
+ declaration the door already brings that nothing calls (#177).
83
97
 
84
98
  ## The options a rule actually runs under
85
99
 
@@ -91,7 +105,7 @@ resolves them the way oxlint does — **measured**, not assumed, against oxlint
91
105
  - when several overrides claim one file, the **last** of them wins outright;
92
106
  - a rule an override names and the base block never mentions is **enabled**, on those files.
93
107
 
94
- Reading `rules` alone is one config with two answers. dielime registers its Medusa providers from
108
+ Reading `rules` alone is one config with two answers. A Medusa storefront registers its Medusa providers from
95
109
  three files — a base `medusa-config.ts` and two carrier registries — and declares that exactly
96
110
  where oxlint reads it, in overrides naming the carrier directories. The rule resolved it; `drift`
97
111
  read the base object and reported two working carriers as directories the registry "never names";
@@ -119,8 +133,9 @@ The reach engine is [`@geonosis/lint-parity`](https://www.npmjs.com/package/@geo
119
133
  `corpusOf`, and the corpus is found on the **loaded** plugin, at `corpus/manifest.json`. A plugin
120
134
  that ships no corpus is a SKIP: it never claimed to be evidence about anything.
121
135
 
122
- A rule enabled without the options it needs makes oxlint refuse. That refusal is reported as the
123
- finding — with the sentence naming the rule, which takes a second run to obtain (see below).
136
+ A rule enabled without the options it needs judges nothing and reports only that it is missing
137
+ (D-084). Here that is a **FAIL** — its one finding is the refusal, and a refusal is not reach — and
138
+ `loaded` names the option.
124
139
 
125
140
  #### The probe a rule declares for itself
126
141
 
@@ -164,7 +179,7 @@ regex, refusing with a sentence rather than placing a file the rule will never l
164
179
 
165
180
  A probe declares FILES, not a file. `no-unregistered-plugin-dir` reports a directory its registry
166
181
  does not name and reads that registry off disk, returning silently when it is not there — so one
167
- file could never exercise it, and dielime, enforcing it at error over four registries, read as a
182
+ file could never exercise it, and a Medusa storefront, enforcing it at error over four registries, read as a
168
183
  permanent FAIL for a gate that was working. Its probe now declares the directory AND the registry,
169
184
  and every file a probe declares is mounted into the copy. A probe declaring only `{ path, source }`
170
185
  is the one-file case of the same thing and is read unchanged, which matters: this reads probes off
@@ -173,10 +188,10 @@ answers in the bare form.
173
188
 
174
189
  #### The corpus a shipped one cannot be
175
190
 
176
- Three rules cannot be answered by the plugin's corpus at all, because their reach is a **repo's own
177
- vocabulary**: `layer-walls` fires on the layers this config declares, `no-brand-names` on the brands
178
- it names, `plugin-route-namespaced` on its package root — and the shipped corpus says `acme` and
179
- `layers/core`. Both source repos hit exactly this: dielime's `no-brand-names` and during.day's
191
+ A rule whose reach is a **repo's own vocabulary** cannot be answered by the plugin's corpus at all —
192
+ `layer-walls` fires on the layers this config declares, `no-brand-names` on the brands it names,
193
+ `plugin-route-namespaced` on its package root, `step-opens-its-own-cell` on its `within` — and the
194
+ shipped corpus says `acme` and `layers/core`. Both source repos hit exactly this: a Medusa storefront's `no-brand-names` and a Workers + D1 app's
180
195
  `step-opens-its-own-cell` read as firing nowhere in trees that enforce them correctly.
181
196
 
182
197
  So a repo may ship its own, and point at it:
@@ -201,7 +216,7 @@ A rule that reaches ONE engine carries it in its own metadata — `presumes: { e
201
216
  and this asks every manifest the config governs — its own, the ones above it up to the root, and the
202
217
  workspaces underneath — whether any of those packages is declared, naming the ones it read. A
203
218
  backend one directory BELOW a root config declares the engine for the rules that config enables over
204
- it (#227). during.day ran three Medusa rules at `error` over sagaflow trees: the default step
219
+ it (#227). A Workers + D1 app ran three Medusa rules at `error` over sagaflow trees: the default step
205
220
  factory is `createStep`, sagaflow has none, and the possible findings were zero. Enabled-with-no-
206
221
  engine and clean are the identical green.
207
222
 
@@ -269,18 +284,27 @@ spend a release reading zero off a mis-escaped match (#140, measured in a consum
269
284
  A repo with counters and no `--prove` in any verify tier, script or workflow is a **WARN** naming a
270
285
  tier it already declares. A repo with no ratchet configured gets no line at all.
271
286
 
287
+ ### `envelope` — every gate read as many things as it was handed
288
+
289
+ For each `.geonosis/envelopes/<tool>.json` a gate wrote, `considered === read + refused + excused`.
290
+ Four denominator bugs landed in one day and every one was green — a migrations run that reported on
291
+ three of four files, a plan check that printed `PASS — 0 plan(s)` over a directory of twenty-one, a
292
+ parity run over a tree the second config ignored. Each published a numerator and no denominator.
293
+ Reads the files and imports nothing of the tools that wrote them; no envelopes at all is a SKIP with
294
+ the sentence.
295
+
272
296
  ### `drift` — the gate that is no longer there
273
297
 
274
- The other four ask whether a gate measures what it names. This one asks whether it still runs.
298
+ The checks above ask whether a gate measures what it names. This one asks whether it still runs.
275
299
 
276
300
  | Line | Verdict | What it is |
277
301
  |---|---|---|
278
302
  | `.github/workflows/*.yml` | FAIL | a job or step switched off by a condition that can never be true. One consumer carried 90 tests, a pgvector container and an E2E suite and ran none of it, the disablement documented as "TEMPORARY" in a comment. It is "never downgrade a rule", one level up |
279
303
  | test files | FAIL | a `*.test.*` file with no `test` script in any workspace from it up to the root. A monorepo that runs one vitest over the whole tree has no orphan in it, and this says so |
280
- | script paths | FAIL | a `scripts` entry that hands `bun`, `node` or `tsx` a file that is not on disk. during.day's `bun run fast` began `bun scripts/check-plugin-version.ts && …` and the script had been deleted: the 2.5 s gate had failed at its first command for as long as nobody looked. Only a word with a file extension is asked about, so `bun test`, `bun run lint` and `node --version` are not paths |
281
- | generated files | FAIL | a file carrying `geonosis:gated-by: <gate>` that nothing here runs the gate for — checked against every manifest script, every ratchet counter command and every workflow, because what matters is that SOMETHING runs it. A file that names its **writer** and no gate at all fails on that alone: during.day's AGENTS.md is a 1,117-line byte-copy of its own law, written by `geonosis sync`, and nothing ever ran `sync --check` — the doctor stood beside it warning about the shorter original |
282
- | `NODE_OPTIONS` | WARN | a manifest script preloading through `NODE_OPTIONS` beside a ratchet counter whose command shells to `pnpm`. The nested pnpm inherits the option: dielime's ts5 shim sent it looking for a `.pnpmfile.mjs` that is not there, the counter died, the ratchet refused correctly, and nothing in the message was near the cause. Both halves must be present, and the line names the script and the counter |
283
- | workspace bins | FAIL | a script calling a bin THIS repo's own workspaces declare, with no `node_modules/.bin/<name>` at the root. pnpm links a workspace bin only for the packages that depend on it: dielime's `@dielime/gate-tools` had to become an explicit root devDependency before the gate scripts could find it, and naming that took a debugging session. External commands are not judged — that would be a guess |
304
+ | script paths | FAIL | a `scripts` entry that hands `bun`, `node` or `tsx` a file that is not on disk. A Workers + D1 app's `bun run fast` began `bun scripts/check-plugin-version.ts && …` and the script had been deleted: the 2.5 s gate had failed at its first command for as long as nobody looked. Only a word with a file extension is asked about, so `bun test`, `bun run lint` and `node --version` are not paths |
305
+ | generated files | FAIL | a file carrying `geonosis:gated-by: <gate>` that nothing here runs the gate for — checked against every manifest script, every ratchet counter command and every workflow, because what matters is that SOMETHING runs it. A file that names its **writer** and no gate at all fails on that alone: a Workers + D1 app's AGENTS.md is a 1,117-line byte-copy of its own law, written by `geonosis sync`, and nothing ever ran `sync --check` — the doctor stood beside it warning about the shorter original |
306
+ | `NODE_OPTIONS` | WARN | a manifest script preloading through `NODE_OPTIONS` beside a ratchet counter whose command shells to `pnpm`. The nested pnpm inherits the option: a Medusa storefront's ts5 shim sent it looking for a `.pnpmfile.mjs` that is not there, the counter died, the ratchet refused correctly, and nothing in the message was near the cause. Both halves must be present, and the line names the script and the counter |
307
+ | workspace bins | FAIL | a script calling a bin THIS repo's own workspaces declare, with no `node_modules/.bin/<name>` at the root. pnpm links a workspace bin only for the packages that depend on it: a Medusa storefront's `@shop/gate-tools` had to become an explicit root devDependency before the gate scripts could find it, and naming that took a debugging session. External commands are not judged — that would be a guess |
284
308
  | plugin directories | FAIL | a manifest directory under the roots `no-unregistered-plugin-dir` names that the registry never mentions. SKIP when the rule is not configured — the roots are the repo's, read from the rule it already wrote, never a list invented here |
285
309
  | `publicHoistPattern` | FAIL / SKIP | a pattern in `pnpm-workspace.yaml` with no root `node_modules` link for a workspace package it matches. pnpm dropped one during an **unrelated** `add`, then answered "Already up to date" to every install afterwards over a clean `git status` — the tree was fixed only by deleting `.modules.yaml` and `.pnpm-workspace-state-v1.json`, which the line carries verbatim. Only workspace packages are judged, because one exists on disk by construction and a missing link for it cannot be "the install never ran"; the root package is never asked about, since no package manager puts a repo's root inside its own `node_modules`. A pattern matching no workspace package is SKIP, never a pass |
286
310
  | `@geonosis/observability` | WARN | it is declared in a manifest here and **no source file imports it** — installed is not composed. The kit ships the sink adapter and the reference consumer sent production errors nowhere, because nothing at its composition root ever built one; from outside the process that reads exactly like a project with nothing to report (#165). No line at all in a repo that never installed it |
@@ -329,6 +353,15 @@ environments inherit nothing per binding) and `release.secrets`.
329
353
  It reads the file, and imports nothing of `@geonosis/release` — the same wall `observability` keeps,
330
354
  held by the kit's own `layer-walls` config, so a repo can install either package alone.
331
355
 
356
+ ### `rails` — the egress a run is bounded by, in the settings that load
357
+
358
+ The hosts `geonosis.json` → `rails.egress.allow` declares, read back out of the settings that
359
+ actually LOAD rather than the file this repo rendered. Claude Code merges
360
+ `sandbox.network.allowedDomains` across the managed, user, project and local scopes; a settings file
361
+ that fails validation is silently ignored under `-p`, and a managed `allowManagedDomainsOnly` makes
362
+ every other scope's list ignored. `deniedEgress` in `.geonosis/rails-run.json` is a **gate at zero**
363
+ (D-025), asked whether or not an allowlist is declared: one denied attempt fails.
364
+
332
365
  ### `exams` — a floor adopted and never examined
333
366
 
334
367
  A floor bump that breaks search is invisible to a tier that never runs the search exam. A verify
@@ -343,12 +376,33 @@ for a fact the floors already state, and wrong the day a floor adds one.
343
376
  | | |
344
377
  |---|---|
345
378
  | no declared package ships an exam | **SKIP** — nothing here to ask about |
346
- | a test file imports one of the floor's exams | **OK**, naming the file and the exams it runs |
347
- | the floor is declared and no test file imports any of its exams | **WARN**, naming the floor and every exam it ships |
379
+ | a test file imports an exam | **OK** for that exam, naming the file |
380
+ | the floor is declared and no test file imports an exam it ships | **WARN** for that exam — one line per exam, never one per package (#353) |
348
381
 
349
382
  It WARNs rather than FAILs: the check cannot tell a floor adopted last week from one whose exam
350
383
  somebody deleted, and a FAIL on every fresh install is the shape that teaches a reader to skim.
351
384
 
385
+ ### `seams` — a declared seam that names nothing
386
+
387
+ `adoption.seams` in `geonosis.json` is what `geonosis update` measures a floor's adoption by:
388
+ lines deleted against lines added in the files a consumer says hold a floor's shape. A glob whose
389
+ file was deleted and never undeclared measures nothing, and every bump report reads `0 deleted,
390
+ 0 added` over it — a Workers + D1 app carried three of eight that way for a month.
391
+
392
+ | Seen | Line |
393
+ | ----------------------------------------------- | --------------------------------------------------- |
394
+ | no `adoption.seams` | **SKIP**, naming the key |
395
+ | every glob names a file (never in node_modules) | **OK**, with the count |
396
+ | a glob names no file | **WARN** per glob, saying to drop or replace it |
397
+
398
+ ### `formatter` — the kit's rules, out of the formatter's reach
399
+
400
+ A consumer links or copies the plugin's rule files into `.claude/rules/geonosis`, and `tiers.md`
401
+ there is generated. `oxfmt --migrate=prettier` writes `ignorePatterns: []`, so the first `format` of
402
+ a repo that migrated rather than scaffolded rewrote them. The repo's own oxfmt is asked with
403
+ `--list-different`, so its ignore rules are read by the tool that applies them: WARN, naming the
404
+ entry to add, when the files are within its reach; UNJUDGED when oxfmt could not be asked.
405
+
352
406
  ## What `--format=unix` does not tell you
353
407
 
354
408
  Measured on oxlint 1.80: a JS plugin rule that throws from `create()` is reported **once per file**,
@@ -374,6 +428,7 @@ The whole report, for a CI step to read:
374
428
  "counts": { "FAIL": 1, "OK": 12, "SKIP": 2, "UNJUDGED": 0, "WARN": 0 },
375
429
  "findings": [{ "check": "loaded", "message": "…", "subject": "apps/web/.oxlintrc.json", "verdict": "FAIL" }],
376
430
  "ok": false,
431
+ "ran": ["loaded", "group", "exercised"],
377
432
  "root": "/repo"
378
433
  }
379
434
  ```
@@ -387,16 +442,3 @@ consumer wrote. It depends on `@geonosis/lint-parity` for the reach engine and o
387
442
  the kit's own `layer-walls` config holds every one of those walls.
388
443
 
389
444
  Apache-2.0.
390
-
391
- ### `seams` — a declared seam that names nothing
392
-
393
- `adoption.seams` in `geonosis.json` is what `geonosis update` measures a floor's adoption by:
394
- lines deleted against lines added in the files a consumer says hold a floor's shape. A glob whose
395
- file was deleted and never undeclared measures nothing, and every bump report reads `0 deleted,
396
- 0 added` over it — during.day carried three of eight that way for a month.
397
-
398
- | Seen | Line |
399
- | ----------------------------------------------- | --------------------------------------------------- |
400
- | no `adoption.seams` | **SKIP**, naming the key |
401
- | every glob names a file (never in node_modules) | **OK**, with the count |
402
- | a glob names no file | **WARN** per glob, saying to drop or replace it |