@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 +236 -0
- package/README.md +80 -38
- package/bin/geonosis-doctor.mjs +164 -17
- package/dist/{chunk-2ZVCJG5R.js → chunk-2VHAFVWH.js} +759 -489
- package/dist/doctor-cli.js +15 -8
- package/dist/index.d.ts +117 -33
- package/dist/index.js +15 -1
- package/package.json +4 -4
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
out
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
|
123
|
-
|
|
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
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
`layers/core`. Both source repos hit exactly this:
|
|
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).
|
|
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
|
|
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.
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
347
|
-
| the floor is declared and no test file imports
|
|
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 |
|