@geonosis/doctor 1.1.0 → 1.3.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/README.md +73 -7
- package/dist/{chunk-CG2LN4VW.js → chunk-7BRGHYQR.js} +637 -86
- package/dist/doctor-cli.js +82 -35
- package/dist/index.d.ts +17 -11
- package/dist/index.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -13,10 +13,19 @@ npx geonosis-doctor
|
|
|
13
13
|
Run it **after every bump** and **in CI**. It exits 1 when any line is a FAIL or an UNJUDGED — a
|
|
14
14
|
question it could not ask is not a pass — and 2 when the run could not be made at all.
|
|
15
15
|
|
|
16
|
+
**A directory that is not a project is a 2, not a pass.** With no `package.json` and no
|
|
17
|
+
`.oxlintrc.json` anywhere under the root there is nothing here to examine, and every line it would
|
|
18
|
+
print is about a gate that is not there. It used to answer `doctor PASS` at exit 0 off six SKIPs and
|
|
19
|
+
three vacuous OKs — from the tool whose thesis is that a SKIP is not a pass.
|
|
20
|
+
|
|
16
21
|
```
|
|
17
22
|
geonosis-doctor [--root <dir>] [--json] [--strict] [--baseline-against [<ref>]] [--oxlint <path>]
|
|
18
23
|
```
|
|
19
24
|
|
|
25
|
+
## One check, asked alone
|
|
26
|
+
|
|
27
|
+
`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 seven checks cannot live there — so the question that catches a stale nested copy earliest had fallen out of the gate that runs most often (during.day, 2026-08-30: `bun add` across eleven workspaces left `packages/workflows` 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 seven it does. A `loaded` FAIL now says the remedy: remove the nested copies and reinstall.
|
|
28
|
+
|
|
20
29
|
## The seven checks
|
|
21
30
|
|
|
22
31
|
### `loaded` — declared ≠ loaded
|
|
@@ -46,6 +55,26 @@ npm and pnpm alike.
|
|
|
46
55
|
A declared range this check cannot read is a **FAIL**, not a pass. A version it cannot judge is a
|
|
47
56
|
refusal.
|
|
48
57
|
|
|
58
|
+
## The options a rule actually runs under
|
|
59
|
+
|
|
60
|
+
Every reader here that consumes a rule's configuration reads `overrides[]` as well as `rules`, and
|
|
61
|
+
resolves them the way oxlint does — **measured**, not assumed, against oxlint 1.80:
|
|
62
|
+
|
|
63
|
+
- an override entry naming a rule again **replaces** its options for the files its `files` globs
|
|
64
|
+
claim; nothing is deep-merged into the base;
|
|
65
|
+
- when several overrides claim one file, the **last** of them wins outright;
|
|
66
|
+
- a rule an override names and the base block never mentions is **enabled**, on those files.
|
|
67
|
+
|
|
68
|
+
Reading `rules` alone is one config with two answers. dielime registers its Medusa providers from
|
|
69
|
+
three files — a base `medusa-config.ts` and two carrier registries — and declares that exactly
|
|
70
|
+
where oxlint reads it, in overrides naming the carrier directories. The rule resolved it; `drift`
|
|
71
|
+
read the base object and reported two working carriers as directories the registry "never names";
|
|
72
|
+
`exercised` would have called a rule enabled only in an override "not enabled here" and skipped it.
|
|
73
|
+
|
|
74
|
+
The glob subset understood is `**` across segments, `*` and `?` within one, and `{a,b}`; a pattern
|
|
75
|
+
with no separator matches a basename anywhere. Like `pathMatching` in the rule plugin, it is a
|
|
76
|
+
subset that says so rather than a dependency that would have to be right about everything.
|
|
77
|
+
|
|
49
78
|
### `exercised` — enabled ≠ exercised
|
|
50
79
|
|
|
51
80
|
A rule at `"error"` over a tree with no violation of it, and a rule at `"error"` that can never
|
|
@@ -107,10 +136,14 @@ probe derives its path from the option, never from a fixed corpus location, and
|
|
|
107
136
|
`lib/probe.ts`'s `pathMatching` builds one back out of the regex and then tests it against that
|
|
108
137
|
regex, refusing with a sentence rather than placing a file the rule will never look at.
|
|
109
138
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
139
|
+
A probe declares FILES, not a file. `no-unregistered-plugin-dir` reports a directory its registry
|
|
140
|
+
does not name and reads that registry off disk, returning silently when it is not there — so one
|
|
141
|
+
file could never exercise it, and dielime, enforcing it at error over four registries, read as a
|
|
142
|
+
permanent FAIL for a gate that was working. Its probe now declares the directory AND the registry,
|
|
143
|
+
and every file a probe declares is mounted into the copy. A probe declaring only `{ path, source }`
|
|
144
|
+
is the one-file case of the same thing and is read unchanged, which matters: this reads probes off
|
|
145
|
+
the plugin build the consumer has INSTALLED, and every build published before this shape existed
|
|
146
|
+
answers in the bare form.
|
|
114
147
|
|
|
115
148
|
#### The corpus a shipped one cannot be
|
|
116
149
|
|
|
@@ -168,6 +201,10 @@ naming bun. It used to be warned with vitest's sentence — eight lines of it in
|
|
|
168
201
|
report, each blaming a runner the check had not read, which is how a reader learns to skim the
|
|
169
202
|
section and miss the vitest lines beside them that are true.
|
|
170
203
|
|
|
204
|
+
A workspace whose test script runs a runner over **zero test files** is a SKIP, not a warning:
|
|
205
|
+
dielime's `packages/eslint-config` runs `vitest run` and has no test file under it, so there is no
|
|
206
|
+
result for an exit code to be wrong about. The message says what was counted.
|
|
207
|
+
|
|
171
208
|
The message names the runner it read. What it does not do is see through a wrapper: a script that
|
|
172
209
|
runs `bun ./scripts/vitest-strict.ts` reads as neither, and that consumer's wrapper is in fact the
|
|
173
210
|
exemplary fix — it spawns vitest with `--reporter=json`, reads the report, and fails on a missing
|
|
@@ -181,10 +218,39 @@ The other four ask whether a gate measures what it names. This one asks whether
|
|
|
181
218
|
|---|---|---|
|
|
182
219
|
| `.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 |
|
|
183
220
|
| 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 |
|
|
221
|
+
| 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 |
|
|
222
|
+
| 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 |
|
|
223
|
+
| `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 |
|
|
224
|
+
| 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 |
|
|
184
225
|
| 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 |
|
|
185
|
-
|
|
|
186
|
-
|
|
|
187
|
-
|
|
|
226
|
+
| `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 |
|
|
227
|
+
| the law | WARN / SKIP | its measured length against `geonosis.json` → `law.maxLines`. **SKIP when the repo declares no ceiling** — during.day read "1,104 lines against a ceiling of 200" for a number it never chose, which is unactionable in both directions. The length is still printed, because that is the number a repo declares against |
|
|
228
|
+
| `.claude/settings.json` | WARN | nothing under `enabledPlugins` enables the kit's plugin, so the gates run and the method does not (D-041). The **user-scope** `~/.claude/settings.json` is read too and acknowledged as "installed elsewhere — not this repo's to declare": both source repos got this WARN while the plugin was installed machine-wide. The key is read, never the word `geonosis` appearing somewhere in the file |
|
|
229
|
+
| `geonosis.json` blocks | WARN | a block whose package **no manifest declares** — nothing reads it, and a stale `node_modules` copy is named as what a previous install left behind; a block whose package is declared and does not resolve — run the install; or a **declared** package with no block, running on defaults nobody chose |
|
|
230
|
+
|
|
231
|
+
The two directions do not read the same source. Whether a repo still *chose* a package is a question
|
|
232
|
+
for the **manifests**: a `release` block whose package had been removed from every manifest, with a
|
|
233
|
+
stale `node_modules` directory still present, resolved perfectly and the check said nothing (#78).
|
|
234
|
+
Whether a tool will actually load is the forward question, and that one is resolution's.
|
|
235
|
+
|
|
236
|
+
The "installed with no block" direction is scoped to **direct** dependencies. `@geonosis/review` and
|
|
237
|
+
`@geonosis/testbed` arrive transitively through `@geonosis/cli`, and asking a consumer to configure
|
|
238
|
+
a package they never installed is noise — which is how a reader learns to skip the section (#61). A
|
|
239
|
+
transitive presence gets an OK line naming where it came from, and no demand.
|
|
240
|
+
|
|
241
|
+
#### A geonosis bin a git hook cannot start
|
|
242
|
+
|
|
243
|
+
Every committed hook — `lefthook.yml`, `.husky/*`, `.githooks/*` — is read, and a line running a
|
|
244
|
+
geonosis bin through `pnpm`, `npx`, `bunx` or `yarn` is a **FAIL** naming the direct call that
|
|
245
|
+
replaces it.
|
|
246
|
+
|
|
247
|
+
A git hook's PATH is not the shell's: no nvm shim, no corepack shim, nothing a version manager put
|
|
248
|
+
there. dielime's first hooked commit was "refused" on 2026-08-30 and the ledger never ran — `pnpm`
|
|
249
|
+
was not found, and the sentence its author read was a version manager's failure wearing a gate's
|
|
250
|
+
refusal costume. That is the worst shape a gate has, because it is indistinguishable from the gate
|
|
251
|
+
working. `node_modules/.bin/<bin>` is linked by every package manager and needs nothing on PATH.
|
|
252
|
+
|
|
253
|
+
A hook file naming no geonosis bin is a **SKIP** with the sentence, never a vacuous OK.
|
|
188
254
|
|
|
189
255
|
### `observability` — a sink nobody can reach
|
|
190
256
|
|