@geonosis/doctor 2.3.1 → 2.4.3
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 +691 -0
- package/bin/geonosis-doctor.mjs +19 -5
- package/dist/{chunk-GWVSC7GZ.js → chunk-GXW7SYO2.js} +67 -19
- package/dist/doctor-cli.js +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +1 -1
- package/package.json +4 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
# @geonosis/doctor
|
|
2
|
+
|
|
3
|
+
## 2.4.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- e4da607: The document floor's core: the abstract `Document` (head + record + `extend`), the derived entity
|
|
8
|
+
registry (`documentEntities`, its name union DERIVED off the map's own keys), per-entity body
|
|
9
|
+
versioning (`documentBodyVersioning`, a step declared on one entity never running on another's
|
|
10
|
+
body), the status machine (`canTransition`, `isTerminalStatus`) and `documentIdentifiers`. Engine-
|
|
11
|
+
free, SQL-free — `src/store/`, `src/workflows/`, `src/contract/`, `src/surface/`, `src/ui/` and
|
|
12
|
+
`src/conformance/` are later tickets, and the in-package wall is drawn for each of them already.
|
|
13
|
+
|
|
14
|
+
`@geonosis/doctor`'s `FLOOR_PACKAGES` names `@geonosis/document`, so the uncomposed-battery drift
|
|
15
|
+
line and `tooling/group-membership.test.ts` both ask about it the moment it is installed anywhere.
|
|
16
|
+
- @geonosis/lint-parity@2.4.3
|
|
17
|
+
|
|
18
|
+
## 2.4.2
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- f3c3487: The plugin-drift line knows the repo that BUILDS the kit, not only a consumer installing a candidate. Between `changeset version` and `changeset publish` the kit's own packages are at a version npm has never seen, and unlike a consumer there is no `file:` override to recognise it by — so the same unavoidable, unactionable WARN fired on the kit's own tree the first cut after the doctor joined `pnpm verify`. A tree that provides the kit's own packages is in that window by construction.
|
|
23
|
+
- @geonosis/lint-parity@2.4.2
|
|
24
|
+
|
|
25
|
+
## 2.4.1
|
|
26
|
+
|
|
27
|
+
### Patch Changes
|
|
28
|
+
|
|
29
|
+
- cb783e8: A `geonosis:gated-by:` marker being QUOTED is a mention, not a use. The kit's own CHANGELOG describes the marker in prose — explaining the very fix that taught this check not to read its own generator as a product — and the check read that sentence as a directive naming a gate half a paragraph long. A directive is followed by a command; a literal in markdown or in code is followed by the quote that closes it.
|
|
30
|
+
|
|
31
|
+
Found by `pnpm verify` the first time the doctor ran inside the kit's own gate, which is the reason that step was added one release earlier.
|
|
32
|
+
- @geonosis/lint-parity@2.4.1
|
|
33
|
+
|
|
34
|
+
## 2.4.0
|
|
35
|
+
|
|
36
|
+
### Minor Changes
|
|
37
|
+
|
|
38
|
+
- 4962ed6: Every publishable package names `CHANGELOG.md` in `files`, not only the five floors. Measured on the 2.4.0 tarballs before they shipped: **0 of the 22 group packages carried one**, so `geonosis update`'s `BY HAND` block printed nothing for a group bump — the third consecutive release, in the cut where a consumer would have read the sweep, the holder and the Stop-hook fixes. `tooling/floor-changelog-shipped.test.ts` now holds the whole workspace rather than the floors, so a package cannot fall out of the convention silently.
|
|
39
|
+
|
|
40
|
+
The doctor's `exams` check asks one question per EXAM instead of one per package. `@geonosis/db` ships three (`sessionConformance`, `entityConformance`, `singletonConformance`), and importing any one of them anywhere used to answer OK for the whole package — so a consumer running the session exam alone read green while two floors' behaviour was never exercised. That is this check's own docstring failing inside the check written to state it.
|
|
41
|
+
|
|
42
|
+
The plugin-drift line knows a release candidate. A tree whose `overrides`/`resolutions` point a kit package at a `file:`, `link:`, `portal:` or `.tgz` spec is installed from a candidate the registry has never seen, so `claude plugin update` cannot reach that version at all — the WARN was unavoidable and unactionable for the whole life of every RC, in the window a consumer reads the doctor hardest. It now says the packages are a candidate and that the question becomes real when that version publishes.
|
|
43
|
+
|
|
44
|
+
`smoke run` refuses a STALE recording instead of rendering a verdict over it. The 2.4.0 sweep headlined `FAIL … the release broke 1 of their gates` and printed `STALE` as its last line, fifteen lines below: both true, and the FAIL was a consumer frozen two releases back crossing a break declared in the one before, while the tree they actually have had migrated past it and was green. A stale recording cannot answer whether THIS release breaks them — only what it does to a tree nobody has — so it leads the block, names the command that re-takes it, and exits 2, the code for a run that could not be made.
|
|
45
|
+
|
|
46
|
+
### Patch Changes
|
|
47
|
+
|
|
48
|
+
- bef73fb: Every shim tells a tree that was never built from a build that fell behind. `newest(dist)` answers 0 for a directory that is not there and for one that is merely old, so both conditions were one comparison and one sentence — and the staleness sentence was printed over trees that had never been built at all. It blocked a session's turn in a fresh git worktree, which has no `dist` because `dist` is not committed, with a message about a build going stale. Only one of the two is evidence of a mistake, and the two send a reader to different places.
|
|
49
|
+
|
|
50
|
+
The test that holds it plants BOTH cases and requires different sentences, which the previous one never did: it planted only a stale `dist`, so the check had never been shown a case whose answer should differ. Named for what it is, since the same gap produced two wrong published numbers the same day — a probe never shown a differing case has not been shown to discriminate, which is this repo's own "a gate never seen red has not been shown to measure", one layer down, applied to the thing doing the observing.
|
|
51
|
+
|
|
52
|
+
(This changeset names four packages of the fixed group deliberately: the group versions as one number and a changeset's description lands only in the packages it names, so naming several puts it in more than one place a reader might look. See #356.)
|
|
53
|
+
- a5b2319: The drift check no longer accuses the source that WRITES its marker of carrying one. A file that writes `geonosis:gated-by:` necessarily contains it, so a check that greps a tree for the marker reads its own generator as one of its own products — the kit's own `packages/cli/src/sync.ts` was accused for as long as the marker has existed, and the gate the accusation named was `${SYNC_GATE}`, a command no script anywhere can run. An unexpanded template is now the tell, and such a file gets a SKIP that says it writes the marker rather than a FAIL nobody can act on.
|
|
54
|
+
|
|
55
|
+
The marker also ends at a backtick now, not only at `-->`, `*/` or end of line. Read to end of line, a marker embedded in a template literal swallowed the closing backtick into the gate's NAME, so a repo that genuinely runs its gate matched nothing and was told it does not — a false FAIL, which is the verdict that gets a check switched off.
|
|
56
|
+
- @geonosis/lint-parity@2.4.0
|
|
57
|
+
|
|
58
|
+
## 2.3.1
|
|
59
|
+
|
|
60
|
+
### Patch Changes
|
|
61
|
+
|
|
62
|
+
- @geonosis/lint-parity@2.3.1
|
|
63
|
+
|
|
64
|
+
## 2.3.0
|
|
65
|
+
|
|
66
|
+
### Minor Changes
|
|
67
|
+
|
|
68
|
+
- 3dac9a7: **A path grant is enumerated, and what it exempts is counted (#139).** Measured from the option
|
|
69
|
+
table, the plugin has exactly three path GRANTS — `time-through-the-door`'s `exempt` and the
|
|
70
|
+
`allow` of `font-roles-only` and `tokens-only-colors`; `within`, `paths`, `roots` and `routers` are
|
|
71
|
+
scopes, not exemptions. Each now takes `exemptModules` beside its pattern: modules named in full,
|
|
72
|
+
matched by path or by the tail a repo writes them as, never as a pattern — the `boundary-time-is-primitive`
|
|
73
|
+
model. The patterns still work; what changed is that they are counted. Each such rule declares
|
|
74
|
+
`meta.grants` (`{ paths, modules }`), so the doctor keeps no list of another package's options, and
|
|
75
|
+
`geonosis-doctor`'s `exercised` line WARNs with the number of files the pattern exempts in THIS tree
|
|
76
|
+
beside the number the enumeration names.
|
|
77
|
+
- 3dac9a7: **`exams`, the doctor's eleventh check (#293).** A floor bump that breaks search is invisible to a
|
|
78
|
+
tier that never runs the search exam. A verify tier declares COMMANDS, not globs, so "the exam runs
|
|
79
|
+
in the fast tier" is not checkable — but whether a test file importing the exam exists at all is.
|
|
80
|
+
For every `@geonosis/*` package a manifest declares, the check reads the exams that package ships
|
|
81
|
+
(every export ending in `Conformance`, off the installed floor rather than a list held here) and
|
|
82
|
+
WARNs, naming them, when no test file imports one. The adoption sweep's consumer row now carries an
|
|
83
|
+
`EXAMS` line listing the exams the recording's own test files run.
|
|
84
|
+
- 3dac9a7: **The `runner` check opens the wrapper a test script names (#67).** A workspace whose `test` script
|
|
85
|
+
is `bun ../../scripts/strict.ts` names no runner in its manifest, and the check called that "runs
|
|
86
|
+
neither vitest nor bun test" — the right verdict for the wrong reason, over the very fix it exists
|
|
87
|
+
to ask for. It now reads ONE level into the local file the command names, judges the runner it finds
|
|
88
|
+
there, and says which file it read. A wrapper that asks the runner for a JSON report and parses that
|
|
89
|
+
report is OK: the exit code is not the verdict there.
|
|
90
|
+
|
|
91
|
+
### Patch Changes
|
|
92
|
+
|
|
93
|
+
- 3dac9a7: **A counter removed from the baseline is a rule downgrade, not a win (#14).** `checkBaseline`
|
|
94
|
+
compared `here[key] ?? was` against `was`, so a key present at the ref and deleted at HEAD read as
|
|
95
|
+
"no counter grew" — the one shape no gate inside the checkout can see. A key the reference has and
|
|
96
|
+
HEAD does not now FAILs naming the counter and its last number.
|
|
97
|
+
- 3dac9a7: **A JSON report nobody reads is not evidence (#15).** The `runner` check OK'd any test script
|
|
98
|
+
carrying `--outputFile`, so `vitest run --reporter=json --outputFile=/dev/null` passed it. The
|
|
99
|
+
report is now OK only where a `testFailures` counter in report mode covers the workspace — and the
|
|
100
|
+
line says so by path when the counter's `reportPath` or command names the file the script writes.
|
|
101
|
+
Otherwise it is a WARN naming the path: written, read by nothing.
|
|
102
|
+
- 3dac9a7: **One documented spelling (#90).** Every README of a package the metapackage fronts now names
|
|
103
|
+
`geonosis <tool>` — the door that pins the whole toolchain at one version — beside the bare bin, and
|
|
104
|
+
a `tooling/` test holds it. `#230`'s stamp test lands with it: every flag `docs/orchestration.md`'s
|
|
105
|
+
adoption loop names has to carry the version it first ships in, because the loop once told a
|
|
106
|
+
consumer to run a flag the published version did not have.
|
|
107
|
+
- 3dac9a7: **`geonosis explain <rule|counter|tier|package>` (#91).** The effective value of everything a name
|
|
108
|
+
decides, beside the file that decided it: a rule's severity and options out of `.oxlintrc.json` with
|
|
109
|
+
the presets that carry it, a counter's entry and its baseline, a tier's steps, and a package's own
|
|
110
|
+
`--print-config-shape` beside the config files this repo actually has. The doctor's "it runs on its
|
|
111
|
+
defaults, whatever they are" now names that command instead.
|
|
112
|
+
- Updated dependencies [3dac9a7]
|
|
113
|
+
- Updated dependencies [3dac9a7]
|
|
114
|
+
- Updated dependencies [3dac9a7]
|
|
115
|
+
- Updated dependencies [3dac9a7]
|
|
116
|
+
- @geonosis/lint-parity@2.3.0
|
|
117
|
+
|
|
118
|
+
## 2.2.0
|
|
119
|
+
|
|
120
|
+
### Minor Changes
|
|
121
|
+
|
|
122
|
+
- d15a654: **A rule names the floor its pattern belongs to, and the doctor says when it is not shipped (#290).**
|
|
123
|
+
`document-sagas-are-generic` and `documents-share-one-table` lint a pattern this kit ships no floor
|
|
124
|
+
for: the rule is the requirement and there is nothing to install behind it, so a second consumer
|
|
125
|
+
hand-writes the floor to satisfy the lint. A rule's `meta` carries `floor` — `{ name, pending }` —
|
|
126
|
+
and the doctor's `exercised` line says "names a pattern the kit does not ship yet (<floor>)" for a
|
|
127
|
+
pending one a config enables. RULES.md carries the generated table, so the inventory says so where
|
|
128
|
+
somebody reads a rule before enabling it, and the entry clears when the floor ships.
|
|
129
|
+
- d15a654: **The doctor asks whether the installed Claude plugin is this release's (#133).** A consumer's npm
|
|
130
|
+
bump moves the packages and never the plugin — the hooks, the agents and the skills live there and
|
|
131
|
+
move on `claude plugin update` — so a repo can be on this release while every session in it runs the
|
|
132
|
+
previous release's hooks, and a refusal added this release cannot fire. The `loaded` check now reads
|
|
133
|
+
`~/.claude/plugins/installed_plugins.json` (measured 2026-09-02: `version: 2`, keyed
|
|
134
|
+
`<plugin>@<marketplace>`, each installation carrying its `version` — the record `claude plugin list`
|
|
135
|
+
answers from) and WARNs when the installed version differs from the train this tree resolves, or
|
|
136
|
+
when the plugin is not installed here at all.
|
|
137
|
+
|
|
138
|
+
### Patch Changes
|
|
139
|
+
|
|
140
|
+
- d15a654: **An envelope writer refuses a root it has been told is not its (#241).** A suite ran one over the
|
|
141
|
+
SHARED repo root mid-run: parallel suites made the comparison racy, and the file it left behind is
|
|
142
|
+
one the next run reads as real. Every one of the seven writer copies now refuses to write when
|
|
143
|
+
`GEONOSIS_ENVELOPES_FORBIDDEN_ROOT` names the root it was pointed at, saying so and pointing at a
|
|
144
|
+
scratch root instead. The kit's own vitest config sets it to the repo root, so a test that writes an
|
|
145
|
+
envelope there names itself — which is how the offender that filed this row was found again.
|
|
146
|
+
- 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.
|
|
147
|
+
- Updated dependencies [d15a654]
|
|
148
|
+
- @geonosis/lint-parity@2.2.0
|
|
149
|
+
|
|
150
|
+
## 2.1.0
|
|
151
|
+
|
|
152
|
+
### Minor Changes
|
|
153
|
+
|
|
154
|
+
- c54d6a9: **The bump moves the floors, and names what no command can do (#221, #225).** `geonosis update --to` repinned the fixed group and left every floor package where it was, with one line saying so. It now takes `--floors latest` and `--floor <package>@<version>` (repeatable): each floor is resolved against the registry with everything else BEFORE a byte is written (#148), a `--floor` naming a package no manifest here declares is refused rather than skipped, and the ones that move become `moved <at> <name> <from> → <to>` in the report and in the printed commit body.
|
|
155
|
+
|
|
156
|
+
**And the doctor answers to the name it carries.** `required-options.ts` emits `check: 'loaded'` and was run by the `exercised` pass, so `geonosis-doctor --only loaded` — what the bump runs — could never print it: measured 2026-09-02 on a planted tree, `--only loaded` said nothing about a rule enabled without its option and `--only exercised` printed it under a heading called "loaded". That is how 2.0.0's new refusals reached a consumer's config with nothing between them and it. The check now runs under `loaded`, and the bump report carries a `BY HAND` block naming every rule and the option it needs. `update --prove` grows both plants and passes on bun and pnpm alike.
|
|
157
|
+
- a016fa4: **The doctor writes its own envelope (#234).** It was the one tool of the six that wrote none — `--json` went to stdout — so a consumer who redirected that stdout into `.geonosis/envelopes/doctor.json` had the same run's `envelope` check read the file the shell had just truncated and FAIL "is not readable JSON: Unexpected end of JSON input", exit 1 over a tree that had passed. Every run now writes the envelope after the checks, in the shape the W32 tools use: the ten checks CONSIDERED against the ones this run read, the rest excused by name, so a `--only` run can never read as a verdict about the whole tree. The envelope check SKIPs that one file — this run is the one writing it — and a run whose stdout IS that file writes no envelope and says so, rather than overwriting the report someone asked for.
|
|
158
|
+
|
|
159
|
+
### Patch Changes
|
|
160
|
+
|
|
161
|
+
- e0da9a3: **D-060 made mechanical, and the sweep it found (#220).** A published README or dist comment may name a consumer as the SOURCE of a pattern — that is attribution of the user's own products — and may not describe what is inside one. `tooling/provenance.ts` reads every publishable package's README and `dist/`, and fails on a consumer's name followed within the sentence by an internals noun (table, plane, column, schema, migration, incident, outage, postmortem, `apps/`, `packages/`) with no provenance phrase between them; the consumer names come from `geonosis.json`'s declared smoke snapshots, never from a list of its own — the kit's `no-brand-names` rule refuses a brand written into this repo, so a name enters that vocabulary only by being declared a consumer. It found nineteen sentences across nine packages — their file paths, their table and selector names, their line counts, their incident — and every one is reworded rather than stripped of its attribution.
|
|
162
|
+
- ef19211: **A kit omission is not a warning against the consumer (#237).** `exercised` WARNed "the loaded plugin ships no probe … the kit's omission, not this repo's (geonosis #131)" — 18 rules across three workspaces, on every run, in a tree where nothing was the consumer's to fix, and `--strict` promoted each one to a FAIL. A rule the loaded plugin ships no probe for is now a SKIP naming the kit's row: the question could not be asked here at all. A probe the CONFIG hides is unchanged — still UNJUDGED, because there the consumer can widen or confirm.
|
|
163
|
+
- e04c395: **A root declaration a workspace script calls is not unused (#236).** The `group` check read the ROOT's scripts and the committed hooks, and WARNed "the root declares it and no root script calls its bin" over declarations three workspace scripts depend on — `apps/api`'s `test: geonosis-testbed strict`, `apps/web`'s `verify:arch`. Under a hoisted linker the root declaration is exactly what makes those bins resolvable, and "declare it in the workspace whose code imports it" is advice about imports, not bins. The check now reads every workspace's `scripts` before calling a root declaration unused, and a bin a workspace calls is an OK line naming the workspace and the script.
|
|
164
|
+
- 849f72e: **A scaffolded tree earns no WARN of its own (#215).** `create-geonosis` and `geonosis init` left six out of the box, and a scaffold whose own doctor is dirty teaches every repo it makes that a WARN is scenery. The root now declares a `prove` script and the `full` tier names it when the root has one, so the counters are proved rather than only compared; `sync:graph` names the ledger bin the scaffold already runs, from the same one list `finish` runs it from; and `--kit` declares at the root exactly what a registry install would, instead of every package it linked so the bins resolve. Two of the six were the doctor contradicting itself: a `jsPlugins` entry is a caller with no bin to call — pnpm 11.8.0 resolves only DIRECT dependencies from a directory, so dropping that declaration is how a root config stops linting — and a `geonosis.json` block whose reader the declared door brings is read by something, which is the shape #177 asks a root to have.
|
|
165
|
+
- 4c91653: **The engine-presumption walk reads downwards too, and names what it read (#227).** The check asked the manifests from the config's directory UP to the root, so a Medusa consumer whose backend is `apps/backend/` below a root config was told two of the rules that backend exercises presume an engine "no manifest declares" — measured on dielime under 2.0.0. It now asks every manifest the config governs, the workspaces underneath included, and the WARN lists the ones it read: `none of the manifests this config governs (root, apps/backend) declares @medusajs/framework`.
|
|
166
|
+
- c3223cc: **The required-option check says so when everything is in order (#239).** On a tree where every gated rule already carried its option the check printed nothing, so a consumer could not tell "all options present" from "this check does not exist" — and the check is the one thing between a config and a run that lints nothing. Each config that enables a rule of the plugin now gets one OK line, `N rules require options, N carry them`, asked of the rules themselves; the FAIL lines are still the whole answer when one is missing.
|
|
167
|
+
- b7dab40: **The nested-copy FAIL stops prescribing the repair that bricks a pnpm tree (#156).** It said `rm -rf <workspace>/node_modules/@geonosis`, and on pnpm 11.8.0 no install puts the package back after that — plain, `--force` and state-deleted all print "Already up to date" over a tree the package is now missing from. The line now names `geonosis update --to <the version declared>`, says it takes the WHOLE node_modules of every non-root workspace carrying a copy and never the root's, and says out loud what deleting the scope directory alone costs.
|
|
168
|
+
- @geonosis/lint-parity@2.1.0
|
|
169
|
+
|
|
170
|
+
## 2.0.0
|
|
171
|
+
|
|
172
|
+
### Minor Changes
|
|
173
|
+
|
|
174
|
+
- 07cc9f2: **Egress: rendered by the rails, read back by the doctor (W24a R3).** `geonosis.json → rails.egress` renders through `geonosis-rails egress --write` into `.claude/settings.json` as `sandbox.network.allowedDomains` / `deniedDomains` — the shape Claude Code 2.1.258 reads, measured off the binary. The doctor gains a tenth check, `rails`, which reads each declared host back out of the settings that ACTUALLY load (managed, user, project, local — Claude Code merges the allow lists) and refuses when it is absent, naming the two measured causes: a settings file that fails validation is silently ignored under `-p`, and a managed `allowManagedDomainsOnly` makes every other scope's allow list ignored. `deniedEgress` in `.geonosis/rails-run.json` is a GATE AT ZERO (D-025) — one entry fails the run, and `tooling/gates-at-zero.test.ts` holds the name out of the ratchet config.
|
|
175
|
+
- 2ed0d01: **The drift line "installed but never composed" now asks it of the floors.** #165's observability
|
|
176
|
+
sentence becomes one case of a general question: a package a repo declares in order to COMPOSE, and
|
|
177
|
+
no source file naming it, is a WARN — the floor packages (`FLOOR_PACKAGES`, held against the
|
|
178
|
+
workspace by `tooling/group-membership.test.ts` exactly as `FIXED_GROUP` is) name the app's
|
|
179
|
+
`src/platform.ts` in the fix, which is the file `create-geonosis --domains` writes. The tree is
|
|
180
|
+
walked once for every package asked about rather than once per package.
|
|
181
|
+
- c43551a: **A missing-option refusal is raised once per run, and the doctor names it before lint runs (#202).** oxlint constructs a rule per FILE, so a bare option-gated rule printed "Error running JS plugin" once per file — during.day's first sight of a version bump was hundreds of unparseable problems and a counter answering CANNOT MEASURE. The refusal now speaks for the first file and the rule is inert for the rest; a tool that asks a rule what it needs (no filename, or the same file twice) still hears it every time. Beside it, `geonosis-doctor` constructs every rule a config enables with the options that config gives it and FAILs by name — `<config> → <rule>`, the option missing — which is the same defect said before any lint starts.
|
|
182
|
+
|
|
183
|
+
### Patch Changes
|
|
184
|
+
|
|
185
|
+
- c3c40e1: **The root-declares line counts a committed hook as a caller (#209).** during.day's `lefthook.yml` runs `node_modules/.bin/geonosis-ledger` — the exact path the hook-path check REQUIRES, since a bin reached through a version manager has to be found first — and this line called the same declaration unused, so two checks of one tool disagreed about "used". It now reads the hook files the hook-path check already reads.
|
|
186
|
+
- a5e2341: **A bare-run refusal names the rule and its REQUIRED option in a fixed first position (#199).** `biological-architecture/<rule> requires option \`<option>\`.` opens the message, and the thrown error is an `OptionMissing` carrying `rule` and `option` as fields — the sentence after it still quotes every option the rule accepts, which is what made a substring assertion unable to tell a required option from a mentioned one. Two wrong entries fell out of the stronger test the same hour: `worker-handles-are-scoped` refuses on `within` first, not `wrapper`, and `no-raw-html-atoms` names its positional `element→atom map`. The doctor reads the reason by any thrown class rather than by `Error:`.
|
|
187
|
+
- @geonosis/lint-parity@2.0.0
|
|
188
|
+
|
|
189
|
+
## 1.4.0
|
|
190
|
+
|
|
191
|
+
### Minor Changes
|
|
192
|
+
|
|
193
|
+
- 7c10970: **`drift` checks every `publicHoistPattern` has its root link (#126).** pnpm dropped the root `node_modules` link for a publicly hoisted workspace package during an unrelated `add`; every install afterwards answered "Already up to date" with a clean `git status`, and the package the pattern exists to make resolvable was not resolvable. The line names the pattern, the missing link, and the repair verbatim — `rm -rf node_modules/.modules.yaml node_modules/.pnpm-workspace-state-v1.json && pnpm install`. Only workspace packages are judged: one exists on disk by construction, so a missing root link for it cannot be "the install never ran". The root package is never asked about. A pattern matching no workspace package, a workspace file without the key, and no workspace file at all are each a SKIP with a sentence. `--print-config-shape` now declares `pnpm-workspace.yaml`, because a file the doctor reads and does not declare is the drift it exists to catch.
|
|
194
|
+
- 0b55a82: **A new `envelope` check: every gate read as many things as it was handed.** For each `.geonosis/envelopes/<tool>.json`, `considered === read + refused + excused`; a mismatch is a FAIL naming the tool and the three numbers, an unreadable or shapeless file is a FAIL rather than a skip, and no envelopes at all is a SKIP that says so out loud. It reads the files and imports nothing of the tools that wrote them — the discipline `deployed` and `observability` already follow — so it runs over a consumer's `.geonosis/` with none of the kit installed. Four denominator bugs landed in one day and every one of them was green; this is the one line that would have caught all four.
|
|
195
|
+
- 143281d: **Exercised answers with the right owner, and asks where the config looks (#137, #131's placement half).** An UNJUDGED whose cause is "the loaded plugin ships no probe" is now a WARN naming the KIT as the owner — a consumer's build no longer goes red for the kit's omission (during.day, 1.3.0: three rules that demonstrably bite). Probes whose declared path no enabling entry claims are REHOMED under a path concretized from the entry's own glob (extension included) and exercised there. And the probe run's config is written INTO the corpus copy — measured on oxlint 1.80: overrides globs anchor at the config file's directory, so a config left at its original path silently deconfigured every scoped override during the probe pass.
|
|
196
|
+
|
|
197
|
+
### Patch Changes
|
|
198
|
+
|
|
199
|
+
- 38524fb: **`drift` reads `no-unregistered-plugin-dir`'s `registry` in both shapes.** The rule now takes `string | string[]` so it can read a registry split across files (#123). The doctor demanded a string, so a layer configured with the list would have answered `undefined` and printed "the rule is not configured here" over a repo enforcing it — a silent zero, in the tool that exists to catch silent zeros, about the shape the kit itself recommends. The halves are read as one, a half that is not on disk is named, and a directory neither half reaches is still a FAIL.
|
|
200
|
+
- Updated dependencies [c712f3d]
|
|
201
|
+
- @geonosis/lint-parity@1.4.0
|
|
202
|
+
|
|
203
|
+
## 1.3.0
|
|
204
|
+
|
|
205
|
+
### Minor Changes
|
|
206
|
+
|
|
207
|
+
- c340de0: **`drift`: a geonosis bin a git hook cannot start (#106).** dielime's first hooked commit was
|
|
208
|
+
"refused" on 2026-08-30 and the ledger never ran: a git hook's PATH is not the shell's — no nvm
|
|
209
|
+
shim, no corepack shim — so `pnpm` was not FOUND, and the sentence its author read was a version
|
|
210
|
+
manager's failure wearing a gate's refusal costume. That is the worst shape a gate has, because it
|
|
211
|
+
is indistinguishable from the gate working. The fix was one word: call
|
|
212
|
+
`./node_modules/.bin/geonosis-ledger` directly.
|
|
213
|
+
|
|
214
|
+
Every committed hook — `lefthook.yml`, `.husky/*`, `.githooks/*` — is now read, and a line running a
|
|
215
|
+
geonosis bin through `pnpm`, `npx`, `bunx` or `yarn` is a FAIL naming the direct call that replaces
|
|
216
|
+
it. A hook file naming no geonosis bin is a SKIP with the sentence, never a vacuous OK.
|
|
217
|
+
|
|
218
|
+
**And the hook `create-geonosis` writes follows its own rule.** It wrote `npx oxfmt` / `bunx oxlint`
|
|
219
|
+
into the `lefthook.yml` it scaffolds — the same shape, printed by the kit, into the file where it
|
|
220
|
+
breaks. Both lines are now `node_modules/.bin/…`, with the reason in a comment above them.
|
|
221
|
+
- c9b63c0: **A probe declares FILES, not a file (#101).** A rule whose reach is an option declares
|
|
222
|
+
`probe(options)` so the doctor can exercise it under the repo's OWN values. One file was not enough
|
|
223
|
+
for a rule that reads a SIBLING off disk: `no-unregistered-plugin-dir` returns silently when the
|
|
224
|
+
registry its options name is not there, so its probe fired nothing — and dielime, enforcing that
|
|
225
|
+
rule at error over four registries, read as a permanent `exercised` FAIL for a gate that was
|
|
226
|
+
working. Their call, that the rule stays and the doctor is expected-red, was right, and it was the
|
|
227
|
+
indictment.
|
|
228
|
+
|
|
229
|
+
`probe` may now answer `{ files: [{ path, source }, …] }`, and `exercised` mounts every file it
|
|
230
|
+
declares into the copy of the corpus. `no-unregistered-plugin-dir` declares the directory under
|
|
231
|
+
`roots` AND the registry at the configured path — a registry carrying no string literal at all, so
|
|
232
|
+
nothing in it can name the directory beside it. Its own test moved from "fires nothing, and says so"
|
|
233
|
+
to the one finding every other probe has to produce, through the real binary.
|
|
234
|
+
|
|
235
|
+
The bare `{ path, source }` is the one-file case of the same contract and is read unchanged. That is
|
|
236
|
+
not tidiness: the doctor reads probes off the plugin build a consumer has INSTALLED, and every build
|
|
237
|
+
published before this shape existed answers in the bare form — refusing it would turn every probe in
|
|
238
|
+
an older install into no probe at all, which reads as an accusation against the repo.
|
|
239
|
+
|
|
240
|
+
The FAIL branch — a probe PLACED under the repo's options that still fires nothing — is now held by
|
|
241
|
+
a planted plugin whose one rule declares a probe and reports nothing, ever. It was held by
|
|
242
|
+
`no-unregistered-plugin-dir`, so fixing that rule would have quietly deleted the coverage.
|
|
243
|
+
- 228566f: **The doctor reads the options oxlint RESOLVES, not the base object beside them (#102).** A
|
|
244
|
+
config's `rules` block is only its first layer. An `overrides[]` entry naming a rule again replaces
|
|
245
|
+
its options for the files its globs claim — and dielime declares two of its three Medusa provider
|
|
246
|
+
registries exactly there, because that is where oxlint reads them. The rule resolved it correctly.
|
|
247
|
+
`drift` read `rules` alone and reported two working fulfillment carriers as directories the base
|
|
248
|
+
registry "never names": one config, two answers, from the tool whose whole subject is gates that
|
|
249
|
+
disagree with what they claim to measure.
|
|
250
|
+
|
|
251
|
+
Every reader that consumes a rule's configuration now takes base + matching overrides, resolved the
|
|
252
|
+
way oxlint resolves them — **measured** against oxlint 1.80, not assumed: an override REPLACES the
|
|
253
|
+
base's options for its files rather than merging into them, and the last of several matching
|
|
254
|
+
overrides wins outright.
|
|
255
|
+
|
|
256
|
+
- `drift`'s plugin-directory check asks each directory which layer governs it and checks it against
|
|
257
|
+
THAT layer's registry and manifests, so a repo with several registries gets a line per registry.
|
|
258
|
+
- `exercised` counts a rule an overrides entry enables — it read "no rule of this plugin is enabled
|
|
259
|
+
here" before, a SKIP from the check whose thesis is that an invisible check reads as a passing one
|
|
260
|
+
— and builds a probe from every configuration the repo gives a rule, not only the first.
|
|
261
|
+
- `@geonosis/lint-parity`'s `rulesNamedBy` reads overrides too: a rule configured only there fired
|
|
262
|
+
in the reach run and had no row in the report about which rules reach anything.
|
|
263
|
+
|
|
264
|
+
A layer's options are only half of what it resolves; the other half is which FILES it governs. A
|
|
265
|
+
probe built from an override's options and written where the RULE looks is a probe for a
|
|
266
|
+
configuration that will not run on it — during.day scopes `no-raw-html-atoms` to its compound and
|
|
267
|
+
cell folders, and the probe lands nowhere near them. When no layer enabling a rule claims where its
|
|
268
|
+
probe lands, the question was not ASKED: UNJUDGED, naming the globs, never a FAIL against a repo
|
|
269
|
+
enforcing the rule on every file it means to.
|
|
270
|
+
|
|
271
|
+
Measured read-only. dielime: `2 directory(ies) it never names` becomes five OK lines, one per
|
|
272
|
+
registry, and nothing else in the run moves. during.day: the rules it enables only in `overrides[]`
|
|
273
|
+
were invisible to this check and are now counted — two configs move from silence to UNJUDGED, which
|
|
274
|
+
is what "we could not tell" has always meant here, and neither passes.
|
|
275
|
+
|
|
276
|
+
### Patch Changes
|
|
277
|
+
|
|
278
|
+
- 915ac7c: **`drift`: a script that names a file that is not there (#74).** For every `scripts` entry in every
|
|
279
|
+
manifest, each file the script hands to `bun`, `node` or `tsx` must be on disk — during.day's
|
|
280
|
+
`bun run fast` began `bun scripts/check-plugin-version.ts && …`, the script was deleted, and the
|
|
281
|
+
2.5 s gate had been failing at its first command ever since, found by a lucky grep. Only a word
|
|
282
|
+
carrying a file extension is asked about, so `bun test`, `bun run lint` and `node --version` name
|
|
283
|
+
subcommands, not paths.
|
|
284
|
+
|
|
285
|
+
**It also answers `--print-config-shape` (#75)**, naming every file it reads and the blocks it reads out of them.
|
|
286
|
+
|
|
287
|
+
**The law ceiling is only asked when the repo declares one (#79).** during.day read `1104 lines against a ceiling of 200` for a default it never chose — unactionable both ways. With no `law.maxLines` in `geonosis.json` the line is a SKIP that still prints the measured length, because that is the number a repo declares against.
|
|
288
|
+
|
|
289
|
+
**`runner`: a test script over zero test files is a SKIP (#71).** dielime's `packages/eslint-config` runs `vitest run` with no test file under it and got the exit-code warning — a line about a runner lying over a result that does not exist. The test-file walk is now one source shared with `drift`, so the two checks cannot disagree about the same tree.
|
|
290
|
+
|
|
291
|
+
**`drift`: the plugin line reads the user scope, and says what satisfies it (#72).** Both source repos were told "nothing here installs the kit's plugin" while it was enabled in `~/.claude/settings.json` for every repo on the machine. The user scope is now read and acknowledged, the WARN names the file and the `enabledPlugins` key that would satisfy it, and the check reads that key rather than the word `geonosis` appearing anywhere in the file.
|
|
292
|
+
|
|
293
|
+
**`drift`: the reverse direction asks the MANIFESTS (#78).** A `release` block whose package had been removed from every manifest, with a stale `node_modules` directory still on disk, resolved perfectly and the check said nothing. Whether a repo declared a dependency is a manifest question; whether a tool will load is resolution's, and the forward direction keeps it.
|
|
294
|
+
|
|
295
|
+
**`drift`: the no-block WARN is scoped to direct dependencies (#61).** `@geonosis/review` and `@geonosis/testbed` arrive transitively through `@geonosis/cli`, and both were WARNed about in a repo whose manifests name neither — a section asking a reader to configure something they did not choose is noise. A transitive presence is now an OK line naming where it came from, with no demand.
|
|
296
|
+
|
|
297
|
+
**`drift`: a workspace bin a script names must resolve from the root (#87).** pnpm links a workspace's bin only for the packages that depend on it, so dielime's `@dielime/gate-tools` had to become an explicit root devDependency before its gate scripts could find it — a debugging session to name a "command not found". Only bins this repo declares are asked about; an external command cannot be judged without knowing what the install put on disk.
|
|
298
|
+
|
|
299
|
+
**`drift`: NODE_OPTIONS around a counter that shells to pnpm (#63).** dielime's ts5 shim is preloaded in its `lint` script; the nested pnpm a counter spawns inherits the option, looks for a `.pnpmfile.mjs` that is not there, and dies. Both halves must be present for the line to fire, and it names the script and the counter.
|
|
300
|
+
|
|
301
|
+
**A tree with nothing in it to examine is refused, not passed.** Run in a scratch directory the doctor printed six SKIPs, three vacuous OKs and `doctor PASS` at exit 0 — from the tool whose thesis is that a SKIP is not a pass. With no `package.json` and no `.oxlintrc.json` anywhere under the root it now exits 2 with the sentence, the posture `geonosis-verify-arch` already takes over a config that names no packs.
|
|
302
|
+
|
|
303
|
+
**The usage is assembled from the check registry.** It said "Five questions" while seven checks shipped, and listed them in an order the registry does not use — a count and a list written by hand beside a registry, which is the failure this package is about, committed by its own help text. The descriptions are now a `Record<CheckName, …>`, so a check added without one does not compile, and it says what a no-argument run does.
|
|
304
|
+
|
|
305
|
+
**`drift`: a generated file names the gate that reads it back, and this asks whether anything runs it (#82).** `geonosis sync --write` left a 1,117-line byte-copy of during.day's law in AGENTS.md and nothing ever ran `sync --check`; the doctor stood next to it warning about the shorter original and said nothing about the copy. A file carrying `geonosis:gated-by: <gate>` is checked against every script, counter command and workflow. A file naming its WRITER and no gate fails on that alone — which is what during.day's own AGENTS.md does, so the recorded incident is caught as it stands.
|
|
306
|
+
- b9ad142: `resolveFrom` no longer answers from the doctor's own environment: pnpm's NODE_PATH (the hidden `.pnpm/node_modules` hoist) made Node resolve bare names from ANY directory, so drift said "installed" about repos that installed nothing (#118). Now a node_modules on the way up from the asked directory must carry the package before the resolver may finish.
|
|
307
|
+
- fe413db: **`--print-config-shape` names `overrides` (#102, second half).** The doctor started reading
|
|
308
|
+
`overrides[]` when it stopped taking a rule's base options for its whole configuration, and the
|
|
309
|
+
shape it prints — the contract that says which file it reads and which keys out of each — still
|
|
310
|
+
named `rules` alone. A tool reading a key it does not declare is a tool whose users cannot know what
|
|
311
|
+
it read: the same drift, one file over, in the tool that exists to catch it. A test holds every key
|
|
312
|
+
the config reader takes against the shape the bin prints.
|
|
313
|
+
- Updated dependencies [0a7673c]
|
|
314
|
+
- Updated dependencies [228566f]
|
|
315
|
+
- @geonosis/lint-parity@1.3.0
|
|
316
|
+
|
|
317
|
+
## 1.2.0
|
|
318
|
+
|
|
319
|
+
### Minor Changes
|
|
320
|
+
|
|
321
|
+
- 90eddee: `geonosis-doctor --only <check,…>` runs the named checks and no other — `--only loaded` in well under
|
|
322
|
+
a second, so declared-vs-loaded can live in a sub-second local gate. The doctor had subsumed a
|
|
323
|
+
consumer's own version check that lived in its 2.5 s gate; at eleven seconds the doctor could not
|
|
324
|
+
take its place there, and the check that catches a stale nested copy earliest ran only in CI — during
|
|
325
|
+
that very upgrade a stale copy appeared, caught by the script about to be deleted. A `loaded` FAIL
|
|
326
|
+
now says the remedy (remove the nested copies, reinstall); a check the doctor does not have is refused
|
|
327
|
+
naming the seven it does; the summary counts the checks that ran.
|
|
328
|
+
|
|
329
|
+
### Patch Changes
|
|
330
|
+
|
|
331
|
+
- @geonosis/lint-parity@1.2.0
|
|
332
|
+
|
|
333
|
+
## 1.1.0
|
|
334
|
+
|
|
335
|
+
### Minor Changes
|
|
336
|
+
|
|
337
|
+
- 749239a: `geonosis-release deployed --check` compares `.geonosis/deployed.json` — what a pipeline reported
|
|
338
|
+
after promote — against the wrangler configs and secret names the tree declares. Drift either way
|
|
339
|
+
is exit 1; the file being absent is exit 2, never a pass. `@geonosis/doctor` gains a seventh line,
|
|
340
|
+
`deployed`, over the same file, reading it rather than importing the release package.
|
|
341
|
+
- 8464846: An option-gated rule can be exercised, and the `runner` line names the runner it read.
|
|
342
|
+
|
|
343
|
+
Both source repos adopted 1.0.0 and both filed the same two findings on 2026-08-30. Both were right
|
|
344
|
+
about the fact and wrong about whose defect it was.
|
|
345
|
+
|
|
346
|
+
**Probes.** A rule whose REACH is an option cannot fire on a fixed corpus: the shipped fixtures say
|
|
347
|
+
`acme` and `corpus/workflows/`, a repo says its own brand and `packages/workflows/src/`, and the
|
|
348
|
+
rule reads as firing nowhere in a tree that enforces it on every file. Each of the twenty
|
|
349
|
+
`requireOption` rules now declares a `probe(options)` beside its `fixShape` — the smallest source
|
|
350
|
+
that must fire it under the options it was handed, at a path that lands where it looks. The
|
|
351
|
+
doctor's `exercised` check writes that probe into a copy of the corpus and runs it again, and says
|
|
352
|
+
where the evidence came from. The PATH is half of it: `step-opens-its-own-cell` was silent because
|
|
353
|
+
its fixture sits at `corpus/workflows/` while the option says `packages/workflows/src/` — the same
|
|
354
|
+
bytes at the second path fire — so a probe derives its path from the option, never from a fixed
|
|
355
|
+
corpus location.
|
|
356
|
+
|
|
357
|
+
It does not soften the check. A probe that was PLACED under this repo's options and fired nothing is
|
|
358
|
+
a FAIL naming the option values. A rule with no probe, or one whose options admit no constructible
|
|
359
|
+
path, is a new fifth verdict — **UNJUDGED**: the question could not be ASKED. It does not pass (the
|
|
360
|
+
run still exits 1) and it does not accuse a repo enforcing the rule on every file, which a FAIL
|
|
361
|
+
there does — one consumer names such a rule as live enforcement in its own law.
|
|
362
|
+
`no-unregistered-plugin-dir` reads its registry off disk and cannot be probed by one file, which it
|
|
363
|
+
says rather than widening. The probes are read off the plugin the config loads, so a plugin too old
|
|
364
|
+
to declare any behaves exactly as before.
|
|
365
|
+
|
|
366
|
+
**`bun test`.** Measured on bun 1.4.0: a planted failing assertion exits 1, so does an awaited one,
|
|
367
|
+
so does a throw in `afterAll`. Its exit code is a verdict, and a bun workspace is now OK naming bun
|
|
368
|
+
and the measurement. The WARN is for vitest, whose `@cloudflare/vitest-pool-workers` really did
|
|
369
|
+
exit 0 over failing suites for weeks — eight lines of one consumer's report blamed it over scripts
|
|
370
|
+
running bun, which is how a reader learns to skim the section and miss the true lines beside them.
|
|
371
|
+
|
|
372
|
+
### Patch Changes
|
|
373
|
+
|
|
374
|
+
- @geonosis/lint-parity@1.1.0
|
|
375
|
+
|
|
376
|
+
## 1.0.1
|
|
377
|
+
|
|
378
|
+
### Patch Changes
|
|
379
|
+
|
|
380
|
+
- 1c2ee0b: **Every package ships its declarations and says where they are.** `@geonosis/visual-diff` and
|
|
381
|
+
`@geonosis/verify` emitted no `.d.ts` at all (TS7016 in a consumer: the comparator every visual test
|
|
382
|
+
calls became `any` at the one seam that decides whether a screenshot regressed), and no package
|
|
383
|
+
declared `types` — the nineteen that worked did so through the sibling-file fallback, which ends the
|
|
384
|
+
day `exports` gains a condition. Every export now carries a `types` condition beside `default`, every
|
|
385
|
+
manifest a `types` field, and `tooling/types.test.ts` holds all twenty-one to it after the build.
|
|
386
|
+
|
|
387
|
+
`geonosis-verify-arch` prints a check once when its label is its id ("direction, orphan", not
|
|
388
|
+
"direction direction").
|
|
389
|
+
- Updated dependencies [1c2ee0b]
|
|
390
|
+
- @geonosis/lint-parity@1.0.1
|
|
391
|
+
|
|
392
|
+
## 1.0.0
|
|
393
|
+
|
|
394
|
+
### Major Changes
|
|
395
|
+
|
|
396
|
+
- c575f44: Five counters, `tolerance`, and a baseline rewrite that only ever goes down.
|
|
397
|
+
|
|
398
|
+
- `disabledCiJobs` — lines of `if: false` across the workflow files (`dir`). A job switched off to
|
|
399
|
+
get a release through is debt exactly like a lint error: invisible, because the pipeline goes
|
|
400
|
+
green. A condition that merely mentions `false` is not one, and a repo with no workflows
|
|
401
|
+
directory reads 0 rather than refusing; a workflow that is there and cannot be read refuses.
|
|
402
|
+
- `bundleBytes` — one integer out of whatever the repo's sizing command printed (`command`,
|
|
403
|
+
`match`), separators and all. With `match` it takes the group that pattern names and refuses when
|
|
404
|
+
it matches nothing, rather than falling back to any integer and reporting the wrong number as
|
|
405
|
+
confidently as the right one.
|
|
406
|
+
- `fastTierMs` — `finishedAt − startedAt` from the gate report `geonosis-verify` wrote (`report`,
|
|
407
|
+
`tier`). It refuses a report of another tier: the full tier takes minutes, and timing it against
|
|
408
|
+
the fast tier's baseline would report a regression of the wrong thing.
|
|
409
|
+
- `testsWithoutRunner` — workspaces holding tests with no `test` script. Unmeasured and passing are
|
|
410
|
+
indistinguishable from outside, so the count of unmeasured things has to be a number of its own.
|
|
411
|
+
- `packagesWithoutTypecheck` — workspaces with no `typecheck` script (`script`).
|
|
412
|
+
|
|
413
|
+
`tolerance` is new on the verdict: the fraction of the baseline a number may drift UP before the
|
|
414
|
+
ratchet calls it growth. Bytes and milliseconds move on a dependency patch nobody chose, and a gate
|
|
415
|
+
that fails on +40 bytes is a gate that gets switched off. It forgives noise upward only — a shrink
|
|
416
|
+
of any size still lowers the baseline — and a `tolerance` that is not a non-negative number stops
|
|
417
|
+
the run naming the entry, because one that silently became `NaN` reads exactly like a counter that
|
|
418
|
+
can never grow.
|
|
419
|
+
|
|
420
|
+
**Fix:** a baseline rewrite now lowers only the numbers that SHRANK. Writing every measured key back
|
|
421
|
+
was a harmless no-op while `held` implied `now === baseline`. Tolerance breaks that implication, and
|
|
422
|
+
the blanket rewrite would have laundered growth-within-tolerance into the new floor on the first
|
|
423
|
+
unrelated win — a baseline going up on a pass.
|
|
424
|
+
|
|
425
|
+
`examples/dielime.ratchet.json` is dielime's Phase 3 config: 36 keys, one `testFailures` entry per
|
|
426
|
+
workspace. It keeps `--format=unix` and `expectFormat` on the two oxlint counters, which dielime's
|
|
427
|
+
own config drops — the summary cross-check can tell a misparse from a clean run only when the run
|
|
428
|
+
printed a summary, and a warnings-only run in an unreadable shape prints one that says nothing
|
|
429
|
+
about errors. Pinning the format is what closes that window.
|
|
430
|
+
- c418f91: The `geonosis` Claude Code plugin — the method as enforcement rather than advice.
|
|
431
|
+
|
|
432
|
+
`plugin/` ships five hooks, Node ≥ 22, zero dependencies, every tool resolved from the CONSUMER's
|
|
433
|
+
`node_modules/.bin` so a repo that has not adopted a tool is never blocked on a tool it lacks:
|
|
434
|
+
|
|
435
|
+
- **SessionStart** runs `geonosis-doctor` if it is installed and prints a one-line digest of
|
|
436
|
+
`.oxlintrc.json` — which plugins are loaded, how many architecture rules are at `error`. It never
|
|
437
|
+
fails a session.
|
|
438
|
+
- **PreToolUse / Bash** refuses `prettier` (law 4) and `--no-verify` (law 7), and hands a
|
|
439
|
+
`git commit` to `geonosis-ledger` — the message its `-m` flags compose, and whether the staged
|
|
440
|
+
diff delivers anything but comments. The detectors are the ledger's, never reimplemented here; a
|
|
441
|
+
repo without it gets a note and no opinion.
|
|
442
|
+
- **PreToolUse / Edit|Write|MultiEdit** refuses a write that ADDS `eslint-disable`,
|
|
443
|
+
`oxlint-disable`, `@ts-ignore` or `@ts-expect-error` (law 3), any write to `gate-baseline.json` or
|
|
444
|
+
under `.geonosis/` (D-007 — the scored agent never writes the scoreboard), and a lint config that
|
|
445
|
+
sets a rule to `"warn"`, `"off"`, `0` or `1` (law 2). It reads what the call would ADD, never what
|
|
446
|
+
it removes, so the edit that DELETES a suppression is not refused by the guard against them.
|
|
447
|
+
- **PostToolUse / Edit|Write** runs oxfmt on the file just written. It never blocks.
|
|
448
|
+
- **Stop** runs `geonosis-verify fast` and will not let the turn end on a red one, capped at five
|
|
449
|
+
blocks per session — an agent retrying the same broken thing five times has a problem it will not
|
|
450
|
+
solve on the sixth, so the sixth prints the report and lets go.
|
|
451
|
+
|
|
452
|
+
`.claude-plugin/marketplace.json` offers it from `./plugin`. `pnpm generate:skill` now writes the
|
|
453
|
+
plugin's copy of the mechanical skill, the engineer agent with the machine-specific path taken out,
|
|
454
|
+
and `plugin.json` carrying the kit's own version; staleness tests fail when any of the four drift.
|
|
455
|
+
- 5f09ddc: Every hook is driven end to end, as Claude Code drives it.
|
|
456
|
+
|
|
457
|
+
Each script is SPAWNED with real stdin JSON over a throwaway repo carrying a `geonosis.json`, a
|
|
458
|
+
`gate-baseline.json`, an `.oxlintrc.json` and a `node_modules/.bin` linked to the BUILT
|
|
459
|
+
`geonosis-verify` and `geonosis-ratchet`. A hook tested by calling its policy function proves the
|
|
460
|
+
policy; only a spawn proves the exit code, the JSON on stdout and the tool resolution — which is
|
|
461
|
+
the whole of what Claude Code actually reads.
|
|
462
|
+
|
|
463
|
+
The Stop hook is exercised against a real fast tier that really goes red: a law file one line over
|
|
464
|
+
its baseline, so the ratchet grows and the hook blocks with exit 2, the failing step and its tail on
|
|
465
|
+
stderr. Then the sixth block prints the report and exits 0; a green run clears the session's count;
|
|
466
|
+
`stop_hook_active` short-circuits; and a repo with a BACKLOG that did not regress is not blocked at
|
|
467
|
+
all — which is the whole adoption argument, tested rather than asserted.
|
|
468
|
+
|
|
469
|
+
The suite was falsified before it was trusted: making Stop exit 0 instead of 2, dropping the
|
|
470
|
+
delivery check, and raising the cap to 50 each turned exactly the tests that should have gone red,
|
|
471
|
+
red.
|
|
472
|
+
- 950d188: **`geonosis-doctor` gains a fifth check: `observability`.**
|
|
473
|
+
|
|
474
|
+
Two of the four lying instruments of `docs/rules-backlog.md` #42 are the same shape — a deploy job
|
|
475
|
+
that reported success having deployed nothing, and a `/health` that answered ok over an unreachable
|
|
476
|
+
database. Both leave a green build and a silent project, and neither is visible from inside the
|
|
477
|
+
process that is not reporting. So the doctor asks three things about the exporter named in
|
|
478
|
+
`geonosis.json`'s `observability` block:
|
|
479
|
+
|
|
480
|
+
- **Is one configured.** No `sink` is a FAIL. A `sink` of `memory`, `noop`, `none`, `null`,
|
|
481
|
+
`console` or `swallowing` is a WARN — correct in a dev tree, and in a deployed one it is the
|
|
482
|
+
instrument that cannot fail.
|
|
483
|
+
- **Can anything reach it.** A HEAD to `endpoint` with a three-second timeout. ANY HTTP answer is
|
|
484
|
+
reachable, because an ingest endpoint legitimately answers 404 or 405 to a HEAD; a refused
|
|
485
|
+
connection, an unresolvable name or a timeout is a FAIL. No endpoint named is a SKIP.
|
|
486
|
+
- **Has anything arrived lately.** Read from the `lastEventFile` the sink writes, against
|
|
487
|
+
`maxAgeSeconds` (3600 by default). Missing file, unreadable file, a record with no numeric `at`,
|
|
488
|
+
or an age past the window are each a FAIL; no `lastEventFile` configured is a SKIP that says so.
|
|
489
|
+
|
|
490
|
+
And a fourth line for the case both of those miss: a block with neither a `lastEventFile` to read
|
|
491
|
+
nor a `probe` command to name is a WARN, because nothing in that repo has ever established that a
|
|
492
|
+
report reaches the sink. The doctor **reports** the probe command; it never runs it.
|
|
493
|
+
|
|
494
|
+
**The doctor imports nothing of `@geonosis/observability`.** It parses `{ at, id, sink, release }`
|
|
495
|
+
off disk. A check that needed the library it checks cannot run in the tree where the library is
|
|
496
|
+
missing, which is the first case it exists to find — and the wall is planted in the plugin's
|
|
497
|
+
`dogfood.test.ts` beside the others.
|
|
498
|
+
|
|
499
|
+
The producer half is `withLastEvent(sink, record)` in `@geonosis/observability`: a decorator that
|
|
500
|
+
writes the record after a capture the sink accepted. The WRITER is injected, because a file is a
|
|
501
|
+
Node fact and the same record goes to a KV namespace on workerd. It writes nothing for a refusal
|
|
502
|
+
and nothing for a deduplicated repeat — a record for a capture that did not send is precisely the
|
|
503
|
+
lie the freshness line exists to catch. It writes one for a swallowing sink, which is why freshness
|
|
504
|
+
is a line and `--prove` is the proof.
|
|
505
|
+
- 89c6769: The ratchet's own layers are walled, and the walls have a door that is proved open.
|
|
506
|
+
|
|
507
|
+
The three ratchet layers named FILE paths only, so `packages/ratchet/src/index.ts` — the published
|
|
508
|
+
entry — sat in no layer and could import anything, the CLI included, with lint green. It is
|
|
509
|
+
`ratchet-api` now, and the formatter it shares with the CLI has moved into a `ratchet-report` layer
|
|
510
|
+
of its own rather than being counted as part of the CLI it is not.
|
|
511
|
+
|
|
512
|
+
The dogfood suite gains a second kind of plant. A wall that refused everything would be as useless
|
|
513
|
+
as one that refused nothing, so each layer's permitted import — `verify` reaching the lock through
|
|
514
|
+
`@geonosis/ratchet` — is planted and asserted to stay CLEAN. Without that, the next engineer widens
|
|
515
|
+
`mayImport` to get their work through and the wall goes with it.
|
|
516
|
+
- b2d915f: **Fix:** a counter's command no longer loses what it wrote to stderr.
|
|
517
|
+
|
|
518
|
+
`<command> 2>&1` binds the redirect to the LAST command of the string. `echo boom >&2; echo fine`
|
|
519
|
+
therefore merged nothing, and on a ZERO exit `execSync` hands back stdout alone — so a tool that
|
|
520
|
+
prints its findings to stderr and exits 0 read as a tool that found nothing. That is the exact shape
|
|
521
|
+
of a gate that has quietly stopped gating, and it is the one direction in which this kind of bug is
|
|
522
|
+
dangerous: the number comes back lower than the truth and the baseline banks it.
|
|
523
|
+
|
|
524
|
+
On a NON-ZERO exit the line survived, because the failure path concatenates both captured buffers —
|
|
525
|
+
but concatenation put every stderr line after every stdout line, moving each diagnostic away from
|
|
526
|
+
the line it was about, which is what the evidence tail on a regression is for.
|
|
527
|
+
|
|
528
|
+
The command now runs in a subshell, so the shell itself merges the streams in the order they were
|
|
529
|
+
written. Measured before and after: `echo boom >&2` on a zero exit read `""` and now reads `boom`;
|
|
530
|
+
`echo one; echo two >&2; echo three; exit 4` read `one three two` and now reads `one two three`.
|
|
531
|
+
- 48bce0c: **Fix:** only a counter that measures a quantity accepts a `tolerance`.
|
|
532
|
+
|
|
533
|
+
Any counter took one. `{"counter":"lawLineCount","tolerance":0.5}` over a two-line law with a
|
|
534
|
+
baseline of 2 printed `ratchet PASS` at three lines — a 50 % growth, waved through. The same edit is
|
|
535
|
+
`"tolerance": 100` on `oxlintErrors`: the gate switched off from the config file, with the run still
|
|
536
|
+
reporting green. That is law 2 — never downgrade a rule — wearing a friendlier name, and it was the
|
|
537
|
+
easiest edit in the repo to get past a reviewer.
|
|
538
|
+
|
|
539
|
+
`Counter` gains `tolerates?: true`, declared only by `bundleBytes` and `fastTierMs`. Whether a
|
|
540
|
+
number is a measured quantity or a count of findings is the counter's to know, not the config's.
|
|
541
|
+
Anything else now stops the run, naming the entry:
|
|
542
|
+
|
|
543
|
+
```
|
|
544
|
+
geonosis-ratchet: "oxlintErrors" does not accept a tolerance — only a counter that measures a
|
|
545
|
+
quantity declares one
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
The plugin's write guard gains its first **ask**. A write to `geonosis.ratchet.json` that introduces
|
|
549
|
+
or raises a `"tolerance"` is neither refused nor allowed: it goes in front of the human as
|
|
550
|
+
`permissionDecision: "ask"`. Widening a band is legitimate and is not the agent's call — a band
|
|
551
|
+
somebody chose and a band somebody's agent chose are not the same thing. A tolerance carried through
|
|
552
|
+
an edit unchanged is not a change and says nothing.
|
|
553
|
+
- 461233c: Docs for the two new things: `docs/plugin.md` and `packages/verify/README.md`.
|
|
554
|
+
|
|
555
|
+
`docs/plugin.md` covers installing the plugin from a local path and from the remote once it is
|
|
556
|
+
pushed, what each of the five hooks does and the reason it does it that way, the five-block cap,
|
|
557
|
+
which files the runner owns and why the agent may not write them, what to put in `geonosis.json`,
|
|
558
|
+
and — the section every README skips — what the plugin CANNOT do. A `fast` tier of `["true"]` passes
|
|
559
|
+
for ever; only `geonosis-ratchet --prove` shows a counter can go red.
|
|
560
|
+
|
|
561
|
+
`packages/verify/README.md` is the runner's own: `--prove` first, the config with no default tier,
|
|
562
|
+
the three exit codes and why a report that could not be written is exit 2, the report shape with the
|
|
563
|
+
`<tier>/<n>` id rule, and `--exclusive`.
|
|
564
|
+
|
|
565
|
+
`docs/README.md` indexes both. The ratchet README gains rows for the five new counters, a section on
|
|
566
|
+
`tolerance` and the shrank-only rewrite it forces, and the landmine that cost an afternoon: a
|
|
567
|
+
loader or shim named RELATIVELY in a counter's command resolves to nothing under `--prove`, whose
|
|
568
|
+
probes run in a scratch directory. Write it `$PWD/…` and it works in both.
|
|
569
|
+
- e48501b: `@geonosis/verify` — one gate runner, with a report a hook can read.
|
|
570
|
+
|
|
571
|
+
`geonosis-verify <tier>` runs the shell steps a repo declared under `verify` in `geonosis.json`,
|
|
572
|
+
serially, stopping at the first red, and writes `.geonosis/gate-report.json`:
|
|
573
|
+
`{ tier, startedAt, finishedAt, ok, steps: [{ id, command, ok, exitCode, ms, tail, skipped? }] }`.
|
|
574
|
+
A step whose text is another tier's name is that tier, inlined in place; the inlined steps keep the
|
|
575
|
+
tier that declared them, so `fast/2` is the same command whether the operator ran `fast` or ran the
|
|
576
|
+
`full` tier that inlines it.
|
|
577
|
+
|
|
578
|
+
There is no default tier and there never will be one: no config, no `verify` object, or an unknown
|
|
579
|
+
tier is refused by name with exit 2, and so is a green run whose report could not be written — a
|
|
580
|
+
gate that cannot record has not passed. A failed step is exit 1.
|
|
581
|
+
|
|
582
|
+
`--exclusive` takes the ratchet's machine-wide lock through `@geonosis/ratchet`'s published entry
|
|
583
|
+
(`--exclusive-timeout <seconds>` bounds the wait), so a verify and a ratchet on one laptop serialise
|
|
584
|
+
against each other rather than each against itself. `--prove` plants a tier with one passing step
|
|
585
|
+
and one that exits 3, runs the real binary over it in a temp directory, and checks the failure
|
|
586
|
+
reached the report — a runner that reports green is worth nothing until it has been watched going
|
|
587
|
+
red on purpose.
|
|
588
|
+
|
|
589
|
+
The kit walls the new package with its own `layer-walls` rule: `verify` may import `ratchet-api`
|
|
590
|
+
and nothing behind it, planted against in the dogfood suite like the other walls.
|
|
591
|
+
- 7a6b019: **A fifth check, and a corpus a shipped one cannot be.**
|
|
592
|
+
|
|
593
|
+
`drift` asks whether a gate that was set up is still running: a CI job switched off by a condition
|
|
594
|
+
that can never be true (FAIL), a test file with no `test` script in any workspace from it up to the
|
|
595
|
+
root (FAIL), a manifest directory the registry named by `no-unregistered-plugin-dir` never mentions
|
|
596
|
+
(FAIL), the law's measured length against the ceiling `geonosis.json` sets (WARN), a
|
|
597
|
+
`.claude/settings.json` that installs no plugin (WARN), and a `geonosis.json` block whose package is
|
|
598
|
+
not installed — or an installed package with no block (WARN). Measured read-only over both source
|
|
599
|
+
repos: dielime's law is 382 lines and during.day's is 1,104, and neither installs the plugin.
|
|
600
|
+
|
|
601
|
+
`exercised` now also reads the corpus a repo ships for **its own options**, named by `geonosis.json`
|
|
602
|
+
→ `doctor.corpus`. Three rules cannot be answered any other way — `layer-walls` fires on this repo's
|
|
603
|
+
layer names, `no-brand-names` on its brands, `plugin-route-namespaced` on its package root, and the
|
|
604
|
+
shipped corpus says `acme` and `layers/core`. Both source repos were failing on exactly that, in
|
|
605
|
+
trees that enforce those rules correctly. A rule is exercised when it fires in either corpus; a
|
|
606
|
+
declared corpus that is not there is a FAIL, and a rule the repo's own manifest names that fires
|
|
607
|
+
nowhere in it is a FAIL. It is read for the config beside `geonosis.json` and no other.
|
|
608
|
+
|
|
609
|
+
The kit now ships its own `lint-corpus/` and passes `geonosis-doctor` for the first time.
|
|
610
|
+
- 40cc71c: **Two fixtures that turned on the tree's install state, and the door's flag split.**
|
|
611
|
+
|
|
612
|
+
`the command registry > answers 2 when the bin is not installed here` and `doctor drift > warns
|
|
613
|
+
about a block whose package is not installed here` both named `@geonosis/testbed` while that package
|
|
614
|
+
did not exist — green for a reason that was not the code, and red the day the testbed shipped. Both
|
|
615
|
+
now name `@geonosis/definitely-absent`, which can never be installed: the registry case goes through
|
|
616
|
+
`runBin` directly, and `checkDrift` takes the block→reader map as a parameter (`READERS` is
|
|
617
|
+
exported) so a test can name a package no base will ever have. A new case asserts the inverse the
|
|
618
|
+
old one could not: every package the registry fronts is a dependency of `@geonosis/cli`, so
|
|
619
|
+
`findBin` finds all twelve.
|
|
620
|
+
|
|
621
|
+
`geonosis observability` joins the registry as a **write** — a real sink is somebody else's project,
|
|
622
|
+
and `kind` is now "something outside this process a reviewer would want to have approved", not only
|
|
623
|
+
a tracked file. `@geonosis/testbed` and `@geonosis/observability` are dependencies of the CLI, so
|
|
624
|
+
`geonosis testbed` resolves.
|
|
625
|
+
|
|
626
|
+
`geonosis <cmd> --help` used to print the DOOR's usage and never run the bin: the whole line was
|
|
627
|
+
scanned for `--help`. A flag before the command is the door's, everything after it is the command's.
|
|
628
|
+
|
|
629
|
+
### Patch Changes
|
|
630
|
+
|
|
631
|
+
- Updated dependencies [c575f44]
|
|
632
|
+
- Updated dependencies [c418f91]
|
|
633
|
+
- Updated dependencies [5f09ddc]
|
|
634
|
+
- Updated dependencies [89c6769]
|
|
635
|
+
- Updated dependencies [b2d915f]
|
|
636
|
+
- Updated dependencies [48bce0c]
|
|
637
|
+
- Updated dependencies [461233c]
|
|
638
|
+
- Updated dependencies [e48501b]
|
|
639
|
+
- @geonosis/lint-parity@1.0.0
|
|
640
|
+
|
|
641
|
+
## 0.6.0
|
|
642
|
+
|
|
643
|
+
### Minor Changes
|
|
644
|
+
|
|
645
|
+
- e446760: **New package `@geonosis/doctor` (bin `geonosis-doctor`).** Four questions a version bump is not
|
|
646
|
+
finished until something has asked. Each one is a way enforcement has reported green while measuring
|
|
647
|
+
nothing, in a real repo, for weeks.
|
|
648
|
+
|
|
649
|
+
- **`loaded` — declared ≠ loaded.** oxlint resolves a `jsPlugins` specifier from the CONFIG FILE's
|
|
650
|
+
directory, so a nested `apps/<ws>/node_modules/@scope/plugin` left behind by a per-workspace
|
|
651
|
+
install runs while every manifest and the lockfile say a different version. One consumer measured
|
|
652
|
+
a whole bug report against 0.3.0 on a repo pinned to 0.4.0. For every config in the tree naming a
|
|
653
|
+
`@geonosis/*` plugin, this resolves it from that config's own directory, imports it, reads
|
|
654
|
+
`meta.version`, and compares it to the version the nearest manifest declares — then lists every
|
|
655
|
+
copy **resolution** reaches, one question per workspace. Never a `find`: on pnpm the store keeps
|
|
656
|
+
every version ever installed, so counting directories answers 7 on a correct tree. A declared
|
|
657
|
+
range it cannot read is a FAIL, not a pass.
|
|
658
|
+
- **`exercised` — enabled ≠ exercised.** Every rule a config enables, run against the corpus the
|
|
659
|
+
loaded plugin ships, under that config's own options — because the options are usually where the
|
|
660
|
+
reach went. Reuses `@geonosis/lint-parity`'s `corpusOf`; a plugin shipping no corpus is a SKIP.
|
|
661
|
+
- **`baseline` — a number that may only shrink, against another ref.** A ratchet lowers its own
|
|
662
|
+
baseline when a number shrinks, so nothing inside one checkout can see a branch raise one back up.
|
|
663
|
+
`--baseline-against [ref]` compares `gate-baseline.json` at HEAD with the same file at a ref. It
|
|
664
|
+
is OFF unless asked for by name: it is the only check that runs git, and this tool gets pointed at
|
|
665
|
+
repos other people are working in.
|
|
666
|
+
- **`runner` — an exit code is not a verdict.** Workspaces whose `test` script runs vitest or
|
|
667
|
+
`bun test` without asking for a JSON report, and which no `testFailures` counter in report mode
|
|
668
|
+
covers. `@cloudflare/vitest-pool-workers` exited 0 over suites it had just reported as failing.
|
|
669
|
+
|
|
670
|
+
`--strict` promotes every WARN to a FAIL; `--json` prints the whole report for CI. Exits 1 on any
|
|
671
|
+
FAIL, 2 when the run could not be made at all.
|
|
672
|
+
|
|
673
|
+
The package depends on `@geonosis/lint-parity` and on **nothing from the rule plugin** — it asks
|
|
674
|
+
which plugin a config loads, and a copy in its own dependency tree would be one more copy for the
|
|
675
|
+
question to be wrong about. A `layer-walls` layer named `doctor` in the kit's own config holds that
|
|
676
|
+
wall, planted against in `dogfood.test.ts`.
|
|
677
|
+
|
|
678
|
+
**`@geonosis/lint-parity` exports `resolveOxlint(from)`** — the consumer's own oxlint, walking up
|
|
679
|
+
from a directory. It was already the answer the CLI needed; it is now the answer every tool that
|
|
680
|
+
spawns oxlint shares, rather than each keeping its own.
|
|
681
|
+
|
|
682
|
+
**Measured, and worth knowing before writing anything that reads oxlint's output:** on oxlint 1.80 a
|
|
683
|
+
JS plugin rule that throws from `create()` is reported ONCE PER FILE, and `--format=unix` renders
|
|
684
|
+
each of those as `:0:0: [Warning]` — no path, no rule id, no sentence. The reason survives only in
|
|
685
|
+
the default formatter. `geonosis-doctor` therefore asks a second time, over one trivial file, in the
|
|
686
|
+
format that answers. No rule changed in this release.
|
|
687
|
+
|
|
688
|
+
### Patch Changes
|
|
689
|
+
|
|
690
|
+
- Updated dependencies [e446760]
|
|
691
|
+
- @geonosis/lint-parity@0.6.0
|
package/bin/geonosis-doctor.mjs
CHANGED
|
@@ -17,11 +17,25 @@ const newest = (dir) =>
|
|
|
17
17
|
}, 0)
|
|
18
18
|
: 0
|
|
19
19
|
const src = join(here, '..', 'src')
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
const dist = join(here, '..', 'dist')
|
|
21
|
+
// `newest` answers 0 for a directory that is NOT THERE and for one that is merely old, so the
|
|
22
|
+
// two conditions were one comparison and one sentence — and the staleness sentence was printed
|
|
23
|
+
// over trees that had never been built at all, blocking a turn in a fresh git worktree, which
|
|
24
|
+
// has no dist because dist is not committed (#357). Only the second is evidence of a mistake.
|
|
25
|
+
if (existsSync(src)) {
|
|
26
|
+
const built = newest(dist)
|
|
27
|
+
if (built === 0) {
|
|
28
|
+
process.stderr.write(
|
|
29
|
+
'geonosis-doctor: this tree has never been built — run pnpm build. No dist here at all, which is what a fresh clone or a linked git worktree starts with; nothing is stale.\n',
|
|
30
|
+
)
|
|
31
|
+
process.exit(2)
|
|
32
|
+
}
|
|
33
|
+
if (newest(src) > built) {
|
|
34
|
+
process.stderr.write(
|
|
35
|
+
'geonosis-doctor: dist is older than src — run pnpm build before trusting this bin.\n',
|
|
36
|
+
)
|
|
37
|
+
process.exit(2)
|
|
38
|
+
}
|
|
25
39
|
}
|
|
26
40
|
|
|
27
41
|
await import('../dist/doctor-cli.js')
|
|
@@ -976,10 +976,12 @@ var preloadAroundPnpm = (root, workspaces) => {
|
|
|
976
976
|
)
|
|
977
977
|
];
|
|
978
978
|
};
|
|
979
|
-
var
|
|
979
|
+
var QUOTED_MARKER = /geonosis:gated-by:\s*[`'"]/;
|
|
980
|
+
var GATED_BY = /geonosis:gated-by:\s*(.+?)\s*(?:-->|\*\/|`|$)/m;
|
|
980
981
|
var GENERATED_BY = /Generated by\s+`([^`]+)`/m;
|
|
981
982
|
var CAN_CARRY_A_MARKER = /\.(?:mdc?|markdown|ya?ml|toml|[cm]?[jt]sx?|json[c5]?|txt|sh|mjs|cjs)$/i;
|
|
982
983
|
var HEAD_BYTES = 4096;
|
|
984
|
+
var NOT_EXPANDED_YET = /\$\{[^}]*\}|\{\{[^}]*\}\}/;
|
|
983
985
|
var markerIn = (path) => {
|
|
984
986
|
let handle;
|
|
985
987
|
try {
|
|
@@ -991,10 +993,11 @@ var markerIn = (path) => {
|
|
|
991
993
|
const buffer = Buffer.alloc(HEAD_BYTES);
|
|
992
994
|
const read = readSync(handle, buffer, 0, HEAD_BYTES, 0);
|
|
993
995
|
const head = buffer.toString("utf8", 0, read);
|
|
994
|
-
const gate = GATED_BY.exec(head)?.[1];
|
|
995
|
-
if (gate !== void 0) return { gate };
|
|
996
|
+
const gate = QUOTED_MARKER.test(head) ? void 0 : GATED_BY.exec(head)?.[1];
|
|
997
|
+
if (gate !== void 0) return NOT_EXPANDED_YET.test(gate) ? { writes: gate } : { gate };
|
|
996
998
|
const writer = GENERATED_BY.exec(head)?.[1];
|
|
997
|
-
|
|
999
|
+
if (writer === void 0) return void 0;
|
|
1000
|
+
return NOT_EXPANDED_YET.test(writer) ? { writes: writer } : { writer };
|
|
998
1001
|
} catch {
|
|
999
1002
|
return void 0;
|
|
1000
1003
|
} finally {
|
|
@@ -1030,6 +1033,13 @@ var generatedFiles = (root, workspaces) => {
|
|
|
1030
1033
|
const run = everythingRun(root, workspaces);
|
|
1031
1034
|
return marked.map(({ marker, path }) => {
|
|
1032
1035
|
const at = relativePath(root, path);
|
|
1036
|
+
if ("writes" in marker) {
|
|
1037
|
+
return finding4(
|
|
1038
|
+
at,
|
|
1039
|
+
"SKIP",
|
|
1040
|
+
`this file WRITES the marker rather than carrying one \u2014 the name it holds, \`${marker.writes}\`, is still a template, so it names no gate this repo could run and there is nothing here to hold against one. Whatever this file generates is judged where it lands`
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1033
1043
|
if (!("gate" in marker)) {
|
|
1034
1044
|
return finding4(
|
|
1035
1045
|
at,
|
|
@@ -1225,8 +1235,10 @@ var COMPOSITION_ROOT = "src/platform.ts";
|
|
|
1225
1235
|
var FLOOR_PACKAGES = [
|
|
1226
1236
|
"@geonosis/conformance",
|
|
1227
1237
|
"@geonosis/db",
|
|
1238
|
+
"@geonosis/document",
|
|
1228
1239
|
"@geonosis/events",
|
|
1229
1240
|
"@geonosis/search",
|
|
1241
|
+
"@geonosis/ui",
|
|
1230
1242
|
"@geonosis/workflows"
|
|
1231
1243
|
];
|
|
1232
1244
|
var COMPOSABLE = [
|
|
@@ -1996,20 +2008,20 @@ var checkExams = ({
|
|
|
1996
2008
|
for (const { from, name } of declaredBy(workspaces)) {
|
|
1997
2009
|
const exams = examsShippedBy(from, name);
|
|
1998
2010
|
if (exams.length === 0) continue;
|
|
1999
|
-
const
|
|
2000
|
-
(one) => one.source.includes(name) &&
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
)
|
|
2012
|
-
|
|
2011
|
+
for (const exam of exams) {
|
|
2012
|
+
const importing = tests.find((one) => one.source.includes(name) && one.source.includes(exam));
|
|
2013
|
+
found.push(
|
|
2014
|
+
importing === void 0 ? finding7(
|
|
2015
|
+
`${name} \u2192 ${exam}`,
|
|
2016
|
+
"WARN",
|
|
2017
|
+
`this tree declares ${name} and no test file imports ${exam}. A floor bump that broke it would read exactly like a green tier: the exam is the only thing that asks.`
|
|
2018
|
+
) : finding7(
|
|
2019
|
+
`${name} \u2192 ${exam}`,
|
|
2020
|
+
"OK",
|
|
2021
|
+
`${relativePath(root, importing.path)} runs ${exam}`
|
|
2022
|
+
)
|
|
2023
|
+
);
|
|
2024
|
+
}
|
|
2013
2025
|
}
|
|
2014
2026
|
return found.length > 0 ? found : [
|
|
2015
2027
|
finding7(
|
|
@@ -2038,6 +2050,7 @@ var installedPluginVersions = (home, name) => {
|
|
|
2038
2050
|
return Object.entries(record.plugins ?? {}).filter(([key]) => (key.split("@")[0] ?? key) === name).flatMap(([, value]) => Array.isArray(value) ? value : []).flatMap((one) => typeof one.version === "string" ? [one.version] : []);
|
|
2039
2051
|
};
|
|
2040
2052
|
var checkClaudePlugin = ({
|
|
2053
|
+
candidate = false,
|
|
2041
2054
|
home = homedir2(),
|
|
2042
2055
|
name = PLUGIN_NAME,
|
|
2043
2056
|
train
|
|
@@ -2056,6 +2069,16 @@ var checkClaudePlugin = ({
|
|
|
2056
2069
|
}
|
|
2057
2070
|
const drifted = installed.filter((one) => one !== train);
|
|
2058
2071
|
if (drifted.length === 0) return [];
|
|
2072
|
+
if (candidate) {
|
|
2073
|
+
return [
|
|
2074
|
+
{
|
|
2075
|
+
check: "loaded",
|
|
2076
|
+
message: `the packages here are ${train}, a candidate the registry does not have, and the plugin is ${drifted.join(", ")} \u2014 the marketplace is where \`claude plugin update\` looks, so the plugin CANNOT follow a candidate and this is not drift. It becomes a real question the moment ${train} publishes`,
|
|
2077
|
+
subject: `${name} (Claude plugin)`,
|
|
2078
|
+
verdict: "OK"
|
|
2079
|
+
}
|
|
2080
|
+
];
|
|
2081
|
+
}
|
|
2059
2082
|
return [
|
|
2060
2083
|
{
|
|
2061
2084
|
check: "loaded",
|
|
@@ -2267,6 +2290,25 @@ var gitHooks = (root) => {
|
|
|
2267
2290
|
);
|
|
2268
2291
|
});
|
|
2269
2292
|
};
|
|
2293
|
+
var OFF_REGISTRY = /^(?:file:|link:|portal:)|\.tgz$/;
|
|
2294
|
+
var installedFromCandidate = (workspaces) => (
|
|
2295
|
+
// A repo that BUILDS the kit is at an unpublished version for the whole window between
|
|
2296
|
+
// `changeset version` and `changeset publish`, and has no override to be recognised by — the same
|
|
2297
|
+
// unactionable question for the same reason, one layer in (#354).
|
|
2298
|
+
workspaces.some((one) => (one.manifest.name ?? "").startsWith(SCOPE2)) || workspaces.some((one) => {
|
|
2299
|
+
const manifest = one.manifest;
|
|
2300
|
+
const blocks2 = [
|
|
2301
|
+
manifest["overrides"],
|
|
2302
|
+
manifest["resolutions"],
|
|
2303
|
+
manifest["pnpm"]?.["overrides"]
|
|
2304
|
+
];
|
|
2305
|
+
return blocks2.some(
|
|
2306
|
+
(block) => typeof block === "object" && block !== null && Object.entries(block).some(
|
|
2307
|
+
([name, spec]) => name.startsWith(SCOPE2) && typeof spec === "string" && OFF_REGISTRY.test(spec)
|
|
2308
|
+
)
|
|
2309
|
+
);
|
|
2310
|
+
})
|
|
2311
|
+
);
|
|
2270
2312
|
var checkLoaded = async ({
|
|
2271
2313
|
configs,
|
|
2272
2314
|
home,
|
|
@@ -2288,7 +2330,13 @@ var checkLoaded = async ({
|
|
|
2288
2330
|
for (const specifier of [...specifiers].toSorted()) {
|
|
2289
2331
|
findings.push(await copiesOf({ root, specifier, workspaces }));
|
|
2290
2332
|
}
|
|
2291
|
-
findings.push(
|
|
2333
|
+
findings.push(
|
|
2334
|
+
...checkClaudePlugin({
|
|
2335
|
+
candidate: installedFromCandidate(workspaces),
|
|
2336
|
+
home,
|
|
2337
|
+
train: await trainVersion(root, [...specifiers])
|
|
2338
|
+
})
|
|
2339
|
+
);
|
|
2292
2340
|
return findings;
|
|
2293
2341
|
};
|
|
2294
2342
|
var trainVersion = async (root, specifiers) => {
|
package/dist/doctor-cli.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -293,7 +293,14 @@ declare const installedPluginVersions: (home: string, name: string) => string[];
|
|
|
293
293
|
* runs the hooks of the one before, and a refusal added this release cannot fire. That is the
|
|
294
294
|
* declared-versus-loaded question one surface out, and it is this check's to ask.
|
|
295
295
|
*/
|
|
296
|
-
declare const checkClaudePlugin: ({ home, name, train, }: {
|
|
296
|
+
declare const checkClaudePlugin: ({ candidate, home, name, train, }: {
|
|
297
|
+
/**
|
|
298
|
+
* Whether the packages here are a RELEASE CANDIDATE the registry has never seen — installed from
|
|
299
|
+
* tarballs or a `file:` override. `claude plugin update` reads the marketplace, so it cannot
|
|
300
|
+
* reach such a version at all, and the drift WARN is then unavoidable and unactionable for the
|
|
301
|
+
* whole life of the candidate: the one window in which a consumer reads this tool hardest (#354).
|
|
302
|
+
*/
|
|
303
|
+
candidate?: boolean;
|
|
297
304
|
home?: string;
|
|
298
305
|
name?: string;
|
|
299
306
|
/** The version the package train is on here, or nothing when this tree declares none. */
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geonosis/doctor",
|
|
3
|
-
"version": "2.3
|
|
3
|
+
"version": "2.4.3",
|
|
4
4
|
"types": "./dist/index.d.ts",
|
|
5
5
|
"description": "The adoption doctor — declared ≠ loaded, enabled ≠ exercised, a baseline that grew, a runner whose exit code is the only verdict.",
|
|
6
6
|
"keywords": [
|
|
@@ -31,14 +31,15 @@
|
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
33
|
"files": [
|
|
34
|
+
"CHANGELOG.md",
|
|
34
35
|
"bin",
|
|
35
36
|
"dist"
|
|
36
37
|
],
|
|
37
38
|
"dependencies": {
|
|
38
|
-
"@geonosis/lint-parity": "2.3
|
|
39
|
+
"@geonosis/lint-parity": "2.4.3"
|
|
39
40
|
},
|
|
40
41
|
"devDependencies": {
|
|
41
|
-
"@geonosis/ratchet": "2.3
|
|
42
|
+
"@geonosis/ratchet": "2.4.3"
|
|
42
43
|
},
|
|
43
44
|
"peerDependencies": {
|
|
44
45
|
"oxlint": ">=1.77"
|