@geonosis/doctor 2.10.0 → 2.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,239 @@
1
1
  # @geonosis/doctor
2
2
 
3
+ ## 2.11.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 08611c4: Every child whose output a tool captures is read whole, where Node's default silently cut it at 1 MiB and killed the child.
8
+
9
+ - **Real bug:** a gate step printing more than 1 MiB read as failed to `geonosis-verify`.
10
+ - **At risk:** the smoke's `pnpm pack --json`, `discover`'s oxlint run, and the git, squawk and oxfmt reads could hit the same cap.
11
+
12
+ `--require-work` also counts a file that a test runs through a helper naming its bin, not only through the test itself.
13
+ - @geonosis/lint-parity@2.11.1
14
+
15
+ ## 2.11.0
16
+
17
+ ### Minor Changes
18
+
19
+ - 0f99e26: A `formatter` check (#7): the repo's own oxfmt is asked whether it would rewrite the kit's rule files
20
+ under `.claude/rules/geonosis`. `oxfmt --migrate=prettier` writes `ignorePatterns: []`, and the
21
+ first `format` of a migrated repo rewrote the generated `tiers.md`. WARN names the ignore entry to
22
+ add; UNJUDGED when oxfmt could not be asked.
23
+ - 0f99e26: Four rules see what they were blind to, and eleven readings refuse instead of banking a zero.
24
+
25
+ `tissue-no-hooks` and `compound-must-be-stateless` report a hook called as a member —
26
+ `React.useState(0)`, `hooks.useCart()` — where they read only a bare name. `time-through-the-door`
27
+ sees the library through every import door (`export * from`, `import()`, `require()`), and
28
+ `subpath-over-barrel` reads `export { X } from '<root>'` as the import it is. Expect new findings on
29
+ a tree that spelled a hook through its default React import.
30
+
31
+ `geonosis-ratchet`: a shell killed by a signal, a formatter that exited non-zero naming no file, a
32
+ `turbo boundaries` that printed no summary, a `geonosis.json` that will not parse, a stale report at
33
+ a named `reportPath`, and an oxlint run that answered in a shape `expectFormat` did not ask for are
34
+ each a `CounterError` now. Every one of them read as a number and lowered the baseline to it.
35
+
36
+ `geonosis-doctor`: `deployed`, `seams`, `rails` and `drift` say FAIL over a `geonosis.json` that is
37
+ there and will not parse, where each had read it as a repo that declared nothing.
38
+ - 0f99e26: An enabled rule missing its option reports ONE finding a run instead of throwing (D-084), and the
39
+ five rules that chose their files from paths compiled in read them from an option (#12).
40
+
41
+ **The posture.** Up to 2.10.0 a rule enabled without an option it requires threw from `create()`.
42
+ Measured: the throw erased every other finding in the file it surfaced on, and rendered under
43
+ `--format=unix` as `:0:0: [Warning]` naming nothing. It is a finding now — attributable, counted by
44
+ the ratchet, neighbours intact — reported once per run on the first file oxlint constructs the rule
45
+ for, with the same `biological-architecture/<rule> requires option \`<option>\`.` header. Nothing the
46
+ plugin exports throws for a missing option. Expect exactly one new finding per bare rule per run
47
+ where a run used to be refused whole.
48
+
49
+ **#12 — four rules now REQUIRE a tree option; every config extending `backend-workflows` or
50
+ `tenant-scoping` must name it or gets the one finding above:**
51
+
52
+ - `tables-declare-their-plane` requires `schema` (a regex over the filename: the schema tree);
53
+ `control/` and `tenant/` are read under it.
54
+ - `tenant-tables-carry-org-id` requires `tenant` (a regex: the tenant-plane schema tree).
55
+ - `queries-require-org-scope` requires `queries` (a regex: the query modules).
56
+ - `dialect-through-the-seam` requires `seam` (regex sources: the files that may name a dialect);
57
+ `exempt` defaults to the test paths (`__tests__/`, `.test.`, `.spec.`) — a repo whose test helpers
58
+ live elsewhere (`testing/`) names them.
59
+
60
+ `no-d1-transaction` takes `transacts` and `except` (regex sources), both defaulting to nothing: the
61
+ plane the rule exempted was one repo's, and a D1-only repo has no plane to exempt. Expect new
62
+ findings on every `db.transaction()` that used to sit inside `/packages/db/(src|testing)/`.
63
+
64
+ One consumer's values, verbatim, are in `proofs/issue-12-and-option-posture-2026-09-18.md`.
65
+
66
+ `@geonosis/doctor`: `missingOptionOf` hears a refusal a rule reports as well as one it throws;
67
+ `exercised` no longer counts a bare rule's refusal finding as reach — it is a FAIL naming the option.
68
+ `@geonosis/cli`: `optionsNeededBy` reads through the same reader, so `init` still leaves those rules
69
+ out and names them.
70
+
71
+ 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
72
+ would normally want a major. It ships in a minor by the owner's ruling of 2026-09-19, on a measured
73
+ basis: every consumer pins this kit EXACTLY — three consumers, at 2.10.0, 2.6.1 and 2.10.0, no
74
+ caret anywhere — so nothing auto-upgrades and the version number protects nobody here. The warning
75
+ lives in this changelog instead, and the two consumers that could be reached were told directly
76
+ before the cut. The third is three releases behind; it is the one that must be told by hand.
77
+ - 0f99e26: Two defects the gates found (rows 576, 577) and five frictions one consumer measured (#1, #2, #5,
78
+ #9, #10), plus the stale-build guard every adopter-from-a-checkout hits.
79
+
80
+ `geonosis-ratchet`: a red test suite is a reading again (row 576). The #18 crash detector ran
81
+ before a counter's `okExits` and refused every red suite — an `AssertionError` over frames, and
82
+ Node's trailer whenever a failing assertion quoted the child it ran — so `testFailures` refused
83
+ at the one moment it had something to say. A crash is now a STREAM whose last lines are a trace
84
+ (the runtime's trailer, or an error line over at-frames with nothing after), judged at any exit
85
+ code; a runner that quoted a trace and printed its summary after it is read. `node crash.mjs`
86
+ still refuses, and so does the same crash hidden behind a pipe's exit 0. And a file-set counter
87
+ names its offenders (#2): `unformattedFiles` cites the files it named, most recently modified
88
+ first, under a line that says so. The `evidence` hook receives `cwd`, and the report keeps a
89
+ counter's FIRST ten citations rather than the tail.
90
+
91
+ `geonosis-doctor`: an `exercised` line per config lists the rules the plugin ships that the
92
+ config never mentions — absent, told apart from off (#5) — so a release's new rules are a list,
93
+ not an audit. Expect one new WARN on any config that leaves shipped rules unnamed. A `drift`
94
+ line reports a `pnpm` block in `package.json` that pnpm 11 does not read (#1): FAIL under a pin
95
+ of 11 or later, with the keys, the new home and the install repair. The observability age and
96
+ probe questions SKIP for a sink that reaches nothing (#10) instead of measuring how recently a
97
+ developer ran the app.
98
+
99
+ `geonosis`: the update's restart line and the MCP skew refusal say the bins in
100
+ `node_modules/.bin` are already the new release (#9).
101
+
102
+ Every kit bin's stale-build guard compares CONTENT, not only mtime: a checkout rewrites `src`
103
+ with the same content and the next `pnpm build` is a cache hit that leaves the current `dist`
104
+ untouched, so every bin refused with "run pnpm build" — the action that had just changed
105
+ nothing. Each shim keeps a digest of `src` from the last run that found `dist` current; a real
106
+ change still refuses, and the refusal names `pnpm build --force --filter=<package>`.
107
+
108
+ ### Patch Changes
109
+
110
+ - 0f99e26: Row 549: `group`'s `declaredGroupsOf` and `exercised`'s `repoCorpusOf` each read `geonosis.json`
111
+ with their own `catch { return undefined }` — a parse error read as "this repo declares nothing",
112
+ so an unreadable `geonosis.json` declaring `doctor.groups` had the kit's own default group judged
113
+ in its place, and `exercised` silently ran without a repo's own reach corpus. Both now read through
114
+ `geonosis-file.ts`, the same reader `deployed`, `seams`, `rails` and `apparatus` already use, and
115
+ throw rather than answer `absent` on a file that cannot be parsed; `checkGroup` and the `exercised`
116
+ check turn that into their own FAIL line, so a corrupt `geonosis.json` no longer reads as a clean
117
+ tree in either check. `observability` carried a third, already-correct-but-separate reader of its
118
+ own; routed it through the same one.
119
+ - 0f99e26: Every bin's stale-build guard now sees the workspace dependencies its build read. A `dist`
120
+ current against its own `src` could still have been built against an older dependency's `dist` —
121
+ literally, where tsup inlined a workspace devDependency into it — and a dependency whose own `src`
122
+ had moved past its `dist` was read stale by every bin above it; the guard answered "current" for
123
+ both. Now each shim holds every `workspace:` dependency, transitively, to the same reading as its
124
+ own package (never built, stale against its src, current), and refuses when this `dist` is older
125
+ than a dependency's — by content, so a dependency rebuilt to the same bytes with
126
+ `--force --filter` is the same build and does not poison the tree. Unknown is still stale: the
127
+ first run over a tree where a dependency was rebuilt alone, before any record, refuses once and
128
+ names `--force --filter=<this package>`. Only a checkout with `src/` beside `dist/` sees any of
129
+ this; the published package ships no `src/` and the guard is skipped. Measured: within noise for a
130
+ bin with one dependency, +8 ms for `geonosis`, which walks fourteen.
131
+ - 0f99e26: Every bin's stale-build guard decides, and never throws. The digest guard's refusal read
132
+ `package.json` to name `pnpm build --force --filter=<name>`; over a checkout with no manifest
133
+ beside the bin it ended with exit 1 and a `node:fs` frame instead of exit 2 and a sentence. The
134
+ name is now a courtesy the refusal does without. The contract, stated: a source newer by mtime whose
135
+ content the bin saw beside a current dist is not stale; one whose content differs is, and the
136
+ refusal names the fix; one the bin has no record for is stale — unknown is stale; a missing dist is
137
+ "never built". Each is exit 2 with a sentence. Only a checkout with `src/` beside `dist/` sees any
138
+ of this; the published package ships no `src/` and the guard is skipped.
139
+ - 0f99e26: `geonosis-doctor` no longer judges a rule-exercising config as though it governed a product. The
140
+ `exercised` engine line (#159) read the plugin's shipped corpus config and a fixture tree's config
141
+ and WARNed, eleven times each, that no manifest they govern declares Medusa — about a tree nobody
142
+ ships, burying the lines about the tree that is. A config inside a directory the doctor already
143
+ reads as evidence — the corpus the loaded plugin ships, the corpus `geonosis.json` names under
144
+ `doctor.corpus` — is apparatus, and gets one SKIP saying so instead. A fixture tree that is the
145
+ SOURCE of a corpus ships no manifest, so a repo names it under the new `doctor.apparatus` list;
146
+ the default is empty and every config in a repo that declares nothing is judged as before. A
147
+ product config beside a corpus is still judged in the same run.
148
+ - 0f99e26: Four bugs a consumer filed against 2.10.0 (#13–#16).
149
+
150
+ `no-void-port` takes `engineCallbacks` — regexes over an exported alias name, empty by default —
151
+ so a workflow-hook check the engine catches (`HookCheck<TInput> = (input, ctx) => Promise<void>`)
152
+ can be named as not a port; a `Result` there would never be read.
153
+
154
+ `geonosis-doctor`: the Claude-plugin train is held only against the install that LOADS at the
155
+ doctored root (user scope, or a project scope naming that root), never another repo's; the
156
+ spare-declaration question reads the script files scripts and hooks run, CI workflows and
157
+ `geonosis.json` tiers, sees a bin quoted as an argv string, and says nothing about a package with
158
+ no bin. Expect four fewer WARNs on a root whose gate lives in a `.mjs`.
159
+
160
+ `geonosis-mcp`: a server started before an in-session `geonosis update` refuses every call naming
161
+ the version it is running, the version the repo now has, and the restart (`/mcp` → reconnect) —
162
+ never the install. `geonosis update` ends by saying so where `.mcp.json` names a `geonosis` server.
163
+ - 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.
164
+ - 0f99e26: Three defects a consumer measured against 2.10.0 (#17, #18, #19) and one this tree found.
165
+
166
+ `geonosis-ratchet`: a counter's command that crashed is never a reading (#18). Every counter that
167
+ runs a tool declares the exit codes it reads — `[0, 1]` for a linter, `[0, 1, 2]` for tsc, `[0]`
168
+ where nothing is declared — and an entry may override them with `okExits`. Any other exit refuses
169
+ the counter, and so does a non-zero exit whose output is a Node stack trace, whatever the set says.
170
+ Before, a counter script that threw had its trace's line numbers summed as findings: reported as a
171
+ regression when they summed high, and BANKED as a shrink when they summed low — the baseline
172
+ rewritten to a number no measurement produced. Expect `CANNOT MEASURE` where a command exits with
173
+ a code its tool never uses for findings; declare `okExits` on that entry if the tool is right.
174
+
175
+ `geonosis-doctor`, `geonosis`, `geonosis-release`, `geonosis-observability`: the bins set
176
+ `process.exitCode` and let the runtime drain instead of calling `process.exit()` after writing the
177
+ report (#17). Through a pipe on macOS, anything past 64 KiB was dropped with a clean exit code —
178
+ the doctor delivered 65,536 of 323,665 bytes, `release migrations` 50 locations for 51 warnings.
179
+
180
+ The Claude plugin's Stop hook (#19): a cached fast-tier report is fresh only while the tree it was
181
+ judged over is still here, gitignored artifacts included — deleting a `playwright-report/` the
182
+ lint step had read now relaunches the tier instead of replaying the stale FAIL for ever. And a
183
+ replayed report says how many of its steps never ran, red or green.
184
+ - 0f99e26: `geonosis-doctor --baseline-against <ref>` parses what `git show` printed on stdout alone. A warning
185
+ git writes to stderr on its way to a success — a ref name that is both a branch and a tag — was
186
+ read into the file, and a counter that had grown read as "no readable baseline", a SKIP.
187
+ - 0f99e26: The `rails` check reads `.geonosis/rails-run.json` for denied egress attempts in a repo that allows
188
+ no egress at all. It returned before the record was read whenever no allowlist was declared, so the
189
+ deny-all repo — the one where a denied attempt matters most — never had its gate at zero asked.
190
+ - 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.
191
+ - 0f99e26: An `overrides[].files` glob with a comma outside braces is read as the file name it is. The doctor
192
+ turned every comma into an alternation, so `src/a,b.ts` claimed `src/abc.ts`, and a stray `}` made
193
+ the pattern throw.
194
+ - 0f99e26: The `group` check prints no OK line for a group whose members disagree inside a workspace. Beside
195
+ the FAIL naming the split it also said the group held, "at 1.1.0, 1.4.0, one copy each".
196
+ - 0f99e26: `geonosis-doctor --help` says the envelope lists the registry's checks, twelve today, where it said
197
+ eleven; names observability and deployed where it said "the last two", which the exams and seams
198
+ checks had pushed down the list; and stops counting the rules only a repo's own corpus can answer.
199
+ - 0f99e26: `drift` and `runner` read what a repo runs through one reader: scripts, `geonosis.json` verify
200
+ tiers, ratchet counters' commands and workflows. `drift` ignored the tiers, so a generated file
201
+ whose `geonosis:gated-by:` gate ran only from a verify tier was reported as a gate nothing runs.
202
+ - 0f99e26: The path-grant line counts the files of this tree only. It walked into `.claude/worktrees/`,
203
+ `.turbo/` and every other dot-directory, counting a worktree's copy of the tree as more exempted
204
+ files and advising the repo to enumerate them.
205
+ - 0f99e26: The `loaded` check says an installed Claude plugin NEWER than the packages is ahead of them — its
206
+ hooks and skills can name commands the packages do not have yet — instead of saying every session
207
+ runs the older hooks, which was the reverse of true.
208
+ - 0f99e26: The `exercised` check says the declared probes could not be run, with what oxlint printed, when the
209
+ probe run dies or prints nothing it can read. It read such a run as "nothing fired", and FAILed
210
+ every probed rule as firing nowhere — blaming the rules for the instrument.
211
+ - 0f99e26: The README documents every check the doctor runs — `group`, `envelope` and `rails` had no section —
212
+ and stops counting them ("seven questions", "the eleven checks"); a test holds a heading per check
213
+ to the registry. It names `--only` in the usage line, says what a bare option-gated rule does since
214
+ D-084, and describes `exams` per exam.
215
+ - 0f99e26: The required-options line counts rules, not the layers that configure them: a rule enabled in the
216
+ base block and again in an override is one rule, and one FAIL when it lacks its option in both. It
217
+ also says `1 rule requires options, 1 carries them` rather than `1 rules require options`.
218
+ - 0f99e26: `drift` reads an unscoped platform-split family — `turbo-darwin-arm64`, `turbo-linux-x64` — as one
219
+ family, `turbo`. It took everything before the last `/` as the family, so each unscoped platform
220
+ package was a family of one and an allow-list naming every platform was warned for each. A
221
+ `generated` file's FAIL also names a verify tier among the places its gate can run from.
222
+ - 0f99e26: The `loaded` check's nested-copy FAIL tells a repo that declares `^2.10.0` to run
223
+ `geonosis update --to 2.10.0`; it quoted the range, and `update --to` takes a version.
224
+ - 0f99e26: `drift` finds a workspace bin a script calls in that workspace's own `node_modules/.bin`, where pnpm
225
+ links it for a package that depends on the bin's workspace. It looked under the root alone, and a
226
+ workspace that declared the dependency correctly was told the bin was missing.
227
+ - 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.
228
+ - Updated dependencies [0f99e26]
229
+ - Updated dependencies [0f99e26]
230
+ - Updated dependencies [0f99e26]
231
+ - Updated dependencies [0f99e26]
232
+ - Updated dependencies [0f99e26]
233
+ - Updated dependencies [0f99e26]
234
+ - Updated dependencies [0f99e26]
235
+ - @geonosis/lint-parity@2.11.0
236
+
3
237
  ## 2.10.0
4
238
 
5
239
  ### Patch Changes
@@ -231,6 +465,13 @@
231
465
  scratch root instead. The kit's own vitest config sets it to the repo root, so a test that writes an
232
466
  envelope there names itself — which is how the offender that filed this row was found again.
233
467
  - 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.
468
+ - df49097: **Installed-not-composed reads an IMPORT, not a mention (#251).** The line answered OK for any
469
+ source file whose text contained the package's name, so a comment saying a floor would be used one
470
+ day counted as composing it. It now looks for the specifier behind `from` / `import` / `require`,
471
+ subpaths included; an entity declared with `defineEntity` over `@geonosis/db` satisfies it. A repo
472
+ whose only mention of a kit package is a comment, a string or a doc block flips from `OK` to `WARN`
473
+ on that package's line. _Added 2026-09-23: the changeset was held for this cut and never released,
474
+ so 2.2.0 shipped the change without this entry._
234
475
  - Updated dependencies [d15a654]
235
476
  - @geonosis/lint-parity@2.2.0
236
477
 
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 |