@geonosis/ratchet 2.3.0 → 2.4.2
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 +630 -0
- package/bin/geonosis-ratchet.mjs +19 -5
- package/dist/{chunk-E55APSHM.js → chunk-2EAYMSC5.js} +96 -19
- package/dist/cli.js +8 -1
- package/dist/index.d.ts +12 -2
- package/dist/index.js +1 -1
- package/package.json +2 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,630 @@
|
|
|
1
|
+
# @geonosis/ratchet
|
|
2
|
+
|
|
3
|
+
## 2.4.2
|
|
4
|
+
|
|
5
|
+
## 2.4.1
|
|
6
|
+
|
|
7
|
+
## 2.4.0
|
|
8
|
+
|
|
9
|
+
### Minor Changes
|
|
10
|
+
|
|
11
|
+
- 3b7b2ae: A `doctorWarnings` counter: the warnings `geonosis-doctor` reports about the repo it runs in, as a number that may only fall. The kit lints itself with its own preset and ratchets itself with its own counters, and pointed the doctor at consumers only — so its findings reached a session-start hook, where nothing fails a gate, and an unfalsifiable FAIL sat unread for as long as the marker had existed.
|
|
12
|
+
|
|
13
|
+
Its unit is the RULE, not the finding. The doctor reports a rule that cannot be exercised once per config that enables it, which is right for a reader — each config is its own edit — and wrong for a ratchet: a repo loading the plugin in twelve configs would read thirteen rules as one hundred and fifty-six, and adding a legitimate twelfth config would fail the gate for a good change. That is the shape that retired `openBacklogRows`, where a shrink-only number over a quantity that rises honestly teaches people to stop being honest. Measured here, it is the difference between 30 and 17. The check stays part of a warning's identity, so two checks with something to say about one file remain two pieces of debt.
|
|
14
|
+
|
|
15
|
+
The FAIL count is deliberately not this counter's business: the doctor's own exit code is, and a repo runs the doctor as its own gate step so the reader gets its tail rather than a counter's summary of it.
|
|
16
|
+
- 56e0f33: A counter the baseline has never held is ADOPTED rather than refused. It used to throw `has no number for "<key>" — add it before enabling the counter`, and the D-007 write guard refuses the agent exactly that edit, because everything that scores the agent is written by the runner — so two of the kit's own rails together made a counter impossible to enable without breaking one of them. The runner measures it, writes it, and reports `ADOPTED` on its own line.
|
|
17
|
+
|
|
18
|
+
What it does NOT do is call that a pass. The run exits 1 and says the number was measured for the first time and not judged: a first reading can be anything (a mis-escaped `match` reading 0 was banked as a floor once), and a floor nobody has looked at is not a floor anybody agreed to. The second run is the one that judges, against a number a reader has now seen. Nothing is adopted over a refusal, because a run that could not measure one counter has not earned the right to write any number.
|
|
19
|
+
|
|
20
|
+
This loosens a refusal rather than tightening one, so no configuration that worked before stops working.
|
|
21
|
+
|
|
22
|
+
### Patch Changes
|
|
23
|
+
|
|
24
|
+
- 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.
|
|
25
|
+
|
|
26
|
+
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.
|
|
27
|
+
|
|
28
|
+
(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.)
|
|
29
|
+
|
|
30
|
+
## 2.3.1
|
|
31
|
+
|
|
32
|
+
## 2.3.0
|
|
33
|
+
|
|
34
|
+
### Minor Changes
|
|
35
|
+
|
|
36
|
+
- 3dac9a7: **`archViolations` declares `match` as a reading param (#30).** Its shipped probe plants a scanner
|
|
37
|
+
printing `✗` lines, so it proves the command and never a `match` a repo configured for its own
|
|
38
|
+
scanner's shape — the one counter with a reading param that escaped #140's mechanism. A configured
|
|
39
|
+
`match` now reads UNPROVEN until the entry declares `probe: { sample, expect }` beside it, and the
|
|
40
|
+
README says what a probe does and does not prove.
|
|
41
|
+
- 3dac9a7: **A `dx` counter family, fed from the kit's own reports (#93).** `bumpDurationMs` and
|
|
42
|
+
`timeToGreenDoctorMs` read `.geonosis/bump-report.json` — which now records how long the bump took
|
|
43
|
+
and how long each step that RAN took; `openBacklogRows` counts the rows of a register nobody has
|
|
44
|
+
closed, `path` required and `done` the repo's own word. A bump whose doctor was red has no
|
|
45
|
+
time-to-green, and all three refuse rather than reading a zero.
|
|
46
|
+
- 3dac9a7: **One `--since`, and a report a machine can read (#97).** `--since` names a plan number, a day and
|
|
47
|
+
a git ref in three commands, and all three took any string: `plan check --since garbage` exempted
|
|
48
|
+
nothing and printed the verdict it would have printed with no flag, `stats --since notadate` put
|
|
49
|
+
every recorded day outside the window and printed `0 turns` as a pass rate. One reader now declares
|
|
50
|
+
which kind each means and refuses an unreadable value by name. `geonosis-ratchet`,
|
|
51
|
+
`geonosis-verify-arch` and `geonosis-lint-parity` take `--json`: the same report, on stdout,
|
|
52
|
+
whatever the verdict — `--json` with an explicit `--format` is refused rather than resolved.
|
|
53
|
+
|
|
54
|
+
### Patch Changes
|
|
55
|
+
|
|
56
|
+
- 3dac9a7: **Portability measured rather than assumed (#100).** The machine-wide heavy lock honours
|
|
57
|
+
`XDG_CACHE_HOME` instead of hardcoding `~/.cache`, ignoring a relative value as the spec requires.
|
|
58
|
+
`onPath` replaces the containment lookup that joined its argument into a `shell: true` command
|
|
59
|
+
line: a `sandbox-exec`/`bwrap` under a directory with a space in its name read as absent and failed
|
|
60
|
+
the run open.
|
|
61
|
+
- 3dac9a7: **One documented spelling (#90).** Every README of a package the metapackage fronts now names
|
|
62
|
+
`geonosis <tool>` — the door that pins the whole toolchain at one version — beside the bare bin, and
|
|
63
|
+
a `tooling/` test holds it. `#230`'s stamp test lands with it: every flag `docs/orchestration.md`'s
|
|
64
|
+
adoption loop names has to carry the version it first ships in, because the loop once told a
|
|
65
|
+
consumer to run a flag the published version did not have.
|
|
66
|
+
|
|
67
|
+
## 2.2.0
|
|
68
|
+
|
|
69
|
+
### Minor Changes
|
|
70
|
+
|
|
71
|
+
- d15a654: **A floor adoption is measured (#280).** The user's rule — a floor is adopted the day it deletes
|
|
72
|
+
more than it adds — had no instrument: during.day's four db floors came out +222/−225 and its
|
|
73
|
+
workflows floor +1807/−1493, and both numbers were read by nobody until they were asked for. A
|
|
74
|
+
consumer declares `adoption.seams: [globs]` in `geonosis.json` (default none, and a repo that
|
|
75
|
+
declares none gets no figure rather than a figure about everything). `geonosis update` then reports
|
|
76
|
+
lines deleted against lines added over those globs, `git diff --numstat` between the commit the bump
|
|
77
|
+
started from and the working tree, in the verdict and in the bump report. `geonosis-ratchet` gains
|
|
78
|
+
`adoptionSeamLines`, the lines those globs still hold — a number the ratchet already refuses to let
|
|
79
|
+
grow. A snapshot records the same figure for its consumer and `geonosis-release adoption` carries it
|
|
80
|
+
per consumer (MEASURED: a consumer's `.geonosis/` is an excluded directory, so their own bump report
|
|
81
|
+
never reaches a recording — the figure is taken where the tree is read).
|
|
82
|
+
- d15a654: **`floorSurface`: a floor's public surface is a ratchet (#294).** `createConnections` has five doors
|
|
83
|
+
where a consumer needs two, and #248 was fixed by making one of them open its own frame rather than
|
|
84
|
+
by removing a door — every added door is a line to learn, a hole to exam and a promise the next
|
|
85
|
+
major has to keep, and nothing made the growth visible while it happened. The counter reads the
|
|
86
|
+
exported names out of the built declaration file each door in `exports` promises — declared in
|
|
87
|
+
place, re-exported from a chunk, or listed in a brace group, since a bundler exposes a name all
|
|
88
|
+
three ways — and refuses a package whose promised `.d.ts` is not built rather than reading an
|
|
89
|
+
unbuilt surface as empty.
|
|
90
|
+
|
|
91
|
+
### Patch Changes
|
|
92
|
+
|
|
93
|
+
- d15a654: **An envelope writer refuses a root it has been told is not its (#241).** A suite ran one over the
|
|
94
|
+
SHARED repo root mid-run: parallel suites made the comparison racy, and the file it left behind is
|
|
95
|
+
one the next run reads as real. Every one of the seven writer copies now refuses to write when
|
|
96
|
+
`GEONOSIS_ENVELOPES_FORBIDDEN_ROOT` names the root it was pointed at, saying so and pointing at a
|
|
97
|
+
scratch root instead. The kit's own vitest config sets it to the repo root, so a test that writes an
|
|
98
|
+
envelope there names itself — which is how the offender that filed this row was found again.
|
|
99
|
+
- d15a654: **`knipIssues` cites the issues, not just the count (#269).** The same tree read 5 on CI and 6 on a
|
|
100
|
+
laptop and the envelope carried the number alone, so nobody could see which line had moved. The
|
|
101
|
+
counter now cites every issue knip printed under the heading it printed it under — quoted, never
|
|
102
|
+
re-parsed — and the `Unresolved imports` class names what did the resolving: the command and the
|
|
103
|
+
runtime that ran it, because an unresolved import is a fact about a resolver and not about a tree.
|
|
104
|
+
|
|
105
|
+
## 2.1.0
|
|
106
|
+
|
|
107
|
+
## 2.0.0
|
|
108
|
+
|
|
109
|
+
### Minor Changes
|
|
110
|
+
|
|
111
|
+
- 97bc8e9: **A debt marker names the plan that owns it.** New rule `todo-names-its-plan` (preset `tooling`): a marker must cite a plan — `TODO(025):` — or it is work nobody scheduled. The marker words are an option and the plan-id shape is an option, both empty-by-default, and enabled with no markers the rule refuses the run. Comments come from the PARSER, so the word in a string literal or after the `//` of a URL is not a finding. New counter `orphanTodos` counts what a repo already has — markers naming no plan, and markers naming a plan that is not on disk — so the law can be adopted as a ratchet rather than as a sweep. The kit enables the rule on itself; every example in the rule's own prose cites a real plan, because exempting a rule's source from the rule is the exemption that never comes back.
|
|
112
|
+
|
|
113
|
+
### Patch Changes
|
|
114
|
+
|
|
115
|
+
- 74ae3ec: **`knipIssues`'s refusal names the third way out (#206).** A configured `headings` list that drops a heading knip printed is refused — right, #178 — but the text offered only "add it" or "excuse it", and a repo that never meant to narrow anything read it as an instruction to enumerate every heading it has. The refusal now also says: remove `"headings"` altogether and count every heading knip prints.
|
|
116
|
+
|
|
117
|
+
## 1.4.0
|
|
118
|
+
|
|
119
|
+
### Minor Changes
|
|
120
|
+
|
|
121
|
+
- ce2167f: **`suppressionCount` — lawLineCount's sibling (#134).** Occurrences of eslint/oxlint/biome-disable, @ts-expect-error and @ts-ignore across source files (fixtures, deps and prose excluded; `roots` scopes it), shrink-only like every number. A repo arriving with 261 suppressions cannot adopt law 3 as a wall, but it can adopt it as a ratchet.
|
|
122
|
+
- 0d99139: **`--prove` exercises the reading the config asks for (#140), and a match that reads nothing refuses (#141).** during.day's live incident: a mis-escaped `match` read 0 from the real command, prove said PROVEN off a plant the configured match never saw, and the ratchet lowered the baseline — ten pieces of debt erased with a congratulatory message. Now: a counter declares its `readingParams`; a config customizing one must declare `probe: { sample, expect }` beside it (the sample its reading must count) or the prove line says UNPROVEN and the run does not pass; and `sumOfCounts` throws, naming the match and the first line, when output is present and the match reads none of it — nothing-found and could-not-read are different answers.
|
|
123
|
+
- 5df33fd: **The report envelope, balanced at write time.** `writeEnvelope` writes `{ tool, version, considered, read, refused[], excused[], findings[], durationMs }` to `.geonosis/envelopes/<tool>.json` and refuses — throwing, writing nothing — unless `considered === read + refused.length + excused.length`. Four denominator bugs landed in one day, all of them green, all of them green because the tool published the numerator and nobody published the denominator. A run that has lost count of its own inputs now renders no verdict at all rather than a wrong one. `versionOf(import.meta.url)` answers the same in `src/` and in the bundled `dist/`, so an envelope names the build that wrote it.
|
|
124
|
+
- a430c81: **A counter that blew up no longer hides the ones behind it, and the run writes its envelope.** `runRatchet` collects every counter it could not measure into `refusals` instead of throwing on the first, so one broken tool no longer leaves the rest of the gate unrun and unreported — the fix `migrations` already made for an unreadable file. The run still fails, at exit 2, and each refusal now names its cause AND the command that reproduces it, read back off the run rather than guessed from the config. The baseline rewrite is suppressed whenever anything refused: a win locked in over a census that only half happened buys a lower floor with a number nobody took. `geonosis-ratchet` writes `.geonosis/envelopes/ratchet.json` — every configured counter leaves by exactly one door, measured, left out by `--tier`, or refused — and refuses to render a verdict at all if those do not add up.
|
|
125
|
+
- 3de7f28: **One answer to "where does the baseline live" (#120).** `resolveBaseline(cwd)` is exported from `@geonosis/ratchet` — the ratchet config's `baseline`, else the default, never a throw. The rails deny rail now denies the baseline where `geonosis.ratchet.json` says it LIVES (the default alone let a tampering write land, measured on sandbox-exec, on a repo whose baseline is `scripts/gate-baseline.json`); the mcp preamble reads counters from the declared path instead of saying "no counter is ratcheted" over twenty. The layer walls forbid rails and mcp importing ratchet, so each carries a ten-line reader pinned behaviour-equal to `resolveBaseline` by a conformance test — which also pins the class: the literal filename is greppable only in the resolver, the pinned readers, the writers of the default pair, and documented prose.
|
|
126
|
+
|
|
127
|
+
### Patch Changes
|
|
128
|
+
|
|
129
|
+
- 8f5dc0c: **`cloneCount` says when its tool printed nothing at all.** The refusal used to read `no "Found N clones" line:` followed by an empty tail, which tells a reader neither what happened nor what to run — a tool that printed nothing and a tool that printed a paragraph this cannot parse are two different problems. Found by the new refusal-law contract test, which drives each refusal the envelope law introduced and requires the sentence to carry a cause worth acting on and the command that reproduces it.
|
|
130
|
+
- d23aef3: **Each tier records its own gate report (#121).** `geonosis-verify <tier>` writes `.geonosis/gate-report.<tier>.json` beside the shared latest-run report (which stays, for every reader that wants "what ran last"). `fastTierMs` reads its own tier's file first — `fast` straight after `full` refused deterministically while every step printed ok (dielime, reproduced on purpose). The shared file remains a fallback for trees written by an older verify, but a wrong-tier fallback is a refusal naming the file the right tier writes.
|
|
131
|
+
- 565db59: **Regression evidence cites the lines the counter counted (#122).** A counter may now declare `evidence(output, params)`, and the report prints THOSE lines under `<-- REGRESSED` instead of the tail of the run. `oxlintRule` cites findings attributed to its own rule, `oxlintErrors` cites errors and `oxlintWarnings` warnings — dielime read a +2 on one rule with two unrelated WARNING findings underneath it, because those were the last lines oxlint printed while the two real errors sat higher up. The count was right the whole time; the lines sent the reader to the wrong files. A counter that names nothing, or recognises nothing in a run, still falls back to the tail.
|
|
132
|
+
- ef29fc0: READMEs: a tool catalog publishes value vocabularies, not just field names — a zero from a wrong filter reads as good news (#135); and the pairing rule — a scoped fast tier is safe exactly when the full tier is total, every hard cap needs a full-tier counter (#136). Both measured in dielime.
|
|
133
|
+
|
|
134
|
+
## 1.3.0
|
|
135
|
+
|
|
136
|
+
### Minor Changes
|
|
137
|
+
|
|
138
|
+
- 2d1d841: **`geonosis-ratchet --help` ran the full ratchet (#66).** There was no usage, no `--help` branch and
|
|
139
|
+
no unknown-flag rejection: everything unrecognised fell through to a complete measurement — a run
|
|
140
|
+
that can rewrite `gate-baseline.json`, started by a typo. Measured at 141 s on the kit's own tree.
|
|
141
|
+
It now prints its usage at exit 0 for `--help`/`-h`, and refuses any flag outside the known set by
|
|
142
|
+
name at exit 2, before the lock is taken.
|
|
143
|
+
|
|
144
|
+
**It also answers `--print-config-shape` (#75)**: the shape of `geonosis.ratchet.json` and `gate-baseline.json`, including why two counters may not write one key.
|
|
145
|
+
|
|
146
|
+
**The README names the NODE_OPTIONS/nested-pnpm trap (#63)**, with the `env -u NODE_OPTIONS` shape and the doctor line that asks for it.
|
|
147
|
+
|
|
148
|
+
### Patch Changes
|
|
149
|
+
|
|
150
|
+
- db1cebd: **The "a scan that could not run is not a clean scan" answer is pinned, permanently (#80).**
|
|
151
|
+
during.day removed `geonosis.verify-arch.json` deliberately to see what the gate would say, and
|
|
152
|
+
`archViolations` refused rather than reading zero findings. That answer may never regress: the
|
|
153
|
+
failure it prevents is silent and self-reinforcing — a zero the ratchet BANKS, rewriting the
|
|
154
|
+
baseline down off a scan that never happened, green for ever after. The fixture runs the real
|
|
155
|
+
scanner in a directory with no config and the real counter over exactly what it printed; a captured
|
|
156
|
+
sample would prove the regex and not the pair, and the pair is what broke. Shown to fail against a
|
|
157
|
+
planted regression before it was kept.
|
|
158
|
+
- f37950f: Counter output capture no longer drops the tail of large outputs: `spawnSync` replaces the merged subshell, both streams arrive whole (stdout then stderr — cross-stream interleave was never promised on the error path and is no longer promised on success), and a 200k-line output keeps its summary line (dielime Block D, #111).
|
|
159
|
+
|
|
160
|
+
## 1.2.0
|
|
161
|
+
|
|
162
|
+
## 1.1.0
|
|
163
|
+
|
|
164
|
+
## 1.0.1
|
|
165
|
+
|
|
166
|
+
### Patch Changes
|
|
167
|
+
|
|
168
|
+
- 1c2ee0b: **Every package ships its declarations and says where they are.** `@geonosis/visual-diff` and
|
|
169
|
+
`@geonosis/verify` emitted no `.d.ts` at all (TS7016 in a consumer: the comparator every visual test
|
|
170
|
+
calls became `any` at the one seam that decides whether a screenshot regressed), and no package
|
|
171
|
+
declared `types` — the nineteen that worked did so through the sibling-file fallback, which ends the
|
|
172
|
+
day `exports` gains a condition. Every export now carries a `types` condition beside `default`, every
|
|
173
|
+
manifest a `types` field, and `tooling/types.test.ts` holds all twenty-one to it after the build.
|
|
174
|
+
|
|
175
|
+
`geonosis-verify-arch` prints a check once when its label is its id ("direction, orphan", not
|
|
176
|
+
"direction direction").
|
|
177
|
+
|
|
178
|
+
## 1.0.0
|
|
179
|
+
|
|
180
|
+
### Major Changes
|
|
181
|
+
|
|
182
|
+
- e6a54d5: **`archViolations` stops reading "could not measure" as zero.** A scanner that found something exits
|
|
183
|
+
non-zero. So does a scanner that could not run at all, and the counter told them apart by neither —
|
|
184
|
+
it counted marker lines and returned the number, so a run that printed nothing because it never
|
|
185
|
+
happened came back 0.
|
|
186
|
+
|
|
187
|
+
That is the silent zero at its most expensive, because the ratchet BANKS it: a baseline of 4 gets
|
|
188
|
+
rewritten down to 0 and the commit that "earned" it is a config with a typo in it.
|
|
189
|
+
`geonosis-verify-arch` exits **2** for exactly that — no config, an unknown pack, an unknown format
|
|
190
|
+
— and under `--format ratchet` prints nothing while doing so. Its exit 2 is now visible.
|
|
191
|
+
|
|
192
|
+
A non-zero exit with zero matching lines is a `CounterError` naming the command, the status and the
|
|
193
|
+
tail of what it printed. A non-zero exit WITH findings still counts them, which is the normal
|
|
194
|
+
reading and the half that must not become collateral: a scanner exiting 1 because it found four
|
|
195
|
+
things is the tool working.
|
|
196
|
+
|
|
197
|
+
The probe changed with it. It used to `cat` a captured sample, which proved the regex and nothing
|
|
198
|
+
else — a real scanner printing another shape still read 0 and the probe still said PROVEN. It now
|
|
199
|
+
plants a scanner and runs it: findings on stdout, a non-zero exit because it found them, and the
|
|
200
|
+
counter has to come back with the number.
|
|
201
|
+
- 9858ee5: `error-page-status`, a ninth probe from a production incident: a page whose body is an error surface
|
|
202
|
+
and whose status is healthy. Every curl-style health check, uptime monitor and load balancer reads
|
|
203
|
+
such a page as fine while nobody can use it. The status comes from the page's own response, so
|
|
204
|
+
`ProbeContext` gains `status` — a second request would ask a different question of a server that may
|
|
205
|
+
not answer twice the same way. `patterns` is required (D-031); a page that answered 400 or worse is
|
|
206
|
+
a server telling the truth, and the walk refuses that URL on its own. `walkFindings` counts the new
|
|
207
|
+
class.
|
|
208
|
+
- c575f44: Five counters, `tolerance`, and a baseline rewrite that only ever goes down.
|
|
209
|
+
|
|
210
|
+
- `disabledCiJobs` — lines of `if: false` across the workflow files (`dir`). A job switched off to
|
|
211
|
+
get a release through is debt exactly like a lint error: invisible, because the pipeline goes
|
|
212
|
+
green. A condition that merely mentions `false` is not one, and a repo with no workflows
|
|
213
|
+
directory reads 0 rather than refusing; a workflow that is there and cannot be read refuses.
|
|
214
|
+
- `bundleBytes` — one integer out of whatever the repo's sizing command printed (`command`,
|
|
215
|
+
`match`), separators and all. With `match` it takes the group that pattern names and refuses when
|
|
216
|
+
it matches nothing, rather than falling back to any integer and reporting the wrong number as
|
|
217
|
+
confidently as the right one.
|
|
218
|
+
- `fastTierMs` — `finishedAt − startedAt` from the gate report `geonosis-verify` wrote (`report`,
|
|
219
|
+
`tier`). It refuses a report of another tier: the full tier takes minutes, and timing it against
|
|
220
|
+
the fast tier's baseline would report a regression of the wrong thing.
|
|
221
|
+
- `testsWithoutRunner` — workspaces holding tests with no `test` script. Unmeasured and passing are
|
|
222
|
+
indistinguishable from outside, so the count of unmeasured things has to be a number of its own.
|
|
223
|
+
- `packagesWithoutTypecheck` — workspaces with no `typecheck` script (`script`).
|
|
224
|
+
|
|
225
|
+
`tolerance` is new on the verdict: the fraction of the baseline a number may drift UP before the
|
|
226
|
+
ratchet calls it growth. Bytes and milliseconds move on a dependency patch nobody chose, and a gate
|
|
227
|
+
that fails on +40 bytes is a gate that gets switched off. It forgives noise upward only — a shrink
|
|
228
|
+
of any size still lowers the baseline — and a `tolerance` that is not a non-negative number stops
|
|
229
|
+
the run naming the entry, because one that silently became `NaN` reads exactly like a counter that
|
|
230
|
+
can never grow.
|
|
231
|
+
|
|
232
|
+
**Fix:** a baseline rewrite now lowers only the numbers that SHRANK. Writing every measured key back
|
|
233
|
+
was a harmless no-op while `held` implied `now === baseline`. Tolerance breaks that implication, and
|
|
234
|
+
the blanket rewrite would have laundered growth-within-tolerance into the new floor on the first
|
|
235
|
+
unrelated win — a baseline going up on a pass.
|
|
236
|
+
|
|
237
|
+
`examples/dielime.ratchet.json` is dielime's Phase 3 config: 36 keys, one `testFailures` entry per
|
|
238
|
+
workspace. It keeps `--format=unix` and `expectFormat` on the two oxlint counters, which dielime's
|
|
239
|
+
own config drops — the summary cross-check can tell a misparse from a clean run only when the run
|
|
240
|
+
printed a summary, and a warnings-only run in an unreadable shape prints one that says nothing
|
|
241
|
+
about errors. Pinning the format is what closes that window.
|
|
242
|
+
- 90d38ec: **`geonosis-backend` — the landmine list as the mechanical layer for backend work.** Forty-six
|
|
243
|
+
failures that read as success, each as symptom → cause → detection → fix, grouped by the engine it
|
|
244
|
+
is TRUE of: nineteen `medusa`, two `sagaflow-cf`, twenty-five `any`. You arrive with a symptom and
|
|
245
|
+
leave with a fix; that is the whole design, and it is why the entries are a table and not prose.
|
|
246
|
+
|
|
247
|
+
Every entry points at the kit rule or counter that catches it — `emit-declares-attempts`,
|
|
248
|
+
`plugin-route-namespaced`, `named-when-block`, `mutating-step-declares-retry`,
|
|
249
|
+
`store-route-scopes-tenant-data`, `no-d1-transaction`, `step-opens-its-own-cell`,
|
|
250
|
+
`queries-require-org-scope`, `no-unregistered-plugin-dir`, `no-mock-db-in-integration`,
|
|
251
|
+
`layer-walls`, `ssot-no-process-env`, `disabledCiJobs`, `testsWithoutRunner`, `knipIssues` — or says
|
|
252
|
+
**`no gate yet`**, and those rows are the backlog's next rules said out loud rather than omitted
|
|
253
|
+
into what would read as full coverage.
|
|
254
|
+
|
|
255
|
+
Every entry cites a document in this repo: Medusa's seventeen (`docs/analysis-medusa-2026-08.md`
|
|
256
|
+
§4), Midday's eighteen (`docs/analysis-midday-2026-08.md` §4), nine consumer landmines from
|
|
257
|
+
`docs/rules-backlog.md`, and two engine facts `RULES.md` states as the reason two presets exist. A
|
|
258
|
+
landmine that could not be cited was dropped.
|
|
259
|
+
|
|
260
|
+
Pinned by a test that PARSES it rather than reading it: the count, all four fields per entry, a
|
|
261
|
+
source path that exists on disk, and a gate name that the plugin or the ratchet actually exports —
|
|
262
|
+
so a rule renamed in the plugin fails the suite instead of leaving the skill pointing at a gate
|
|
263
|
+
nobody has. The two analyses' own numbering is read from the analyses, so an eighteenth Medusa
|
|
264
|
+
landmine fails the build until the skill carries it.
|
|
265
|
+
|
|
266
|
+
Version-only entry for `@geonosis/ratchet`: the plugin's manifest takes its version from that
|
|
267
|
+
package, and the release group moves together.
|
|
268
|
+
- c418f91: The `geonosis` Claude Code plugin — the method as enforcement rather than advice.
|
|
269
|
+
|
|
270
|
+
`plugin/` ships five hooks, Node ≥ 22, zero dependencies, every tool resolved from the CONSUMER's
|
|
271
|
+
`node_modules/.bin` so a repo that has not adopted a tool is never blocked on a tool it lacks:
|
|
272
|
+
|
|
273
|
+
- **SessionStart** runs `geonosis-doctor` if it is installed and prints a one-line digest of
|
|
274
|
+
`.oxlintrc.json` — which plugins are loaded, how many architecture rules are at `error`. It never
|
|
275
|
+
fails a session.
|
|
276
|
+
- **PreToolUse / Bash** refuses `prettier` (law 4) and `--no-verify` (law 7), and hands a
|
|
277
|
+
`git commit` to `geonosis-ledger` — the message its `-m` flags compose, and whether the staged
|
|
278
|
+
diff delivers anything but comments. The detectors are the ledger's, never reimplemented here; a
|
|
279
|
+
repo without it gets a note and no opinion.
|
|
280
|
+
- **PreToolUse / Edit|Write|MultiEdit** refuses a write that ADDS `eslint-disable`,
|
|
281
|
+
`oxlint-disable`, `@ts-ignore` or `@ts-expect-error` (law 3), any write to `gate-baseline.json` or
|
|
282
|
+
under `.geonosis/` (D-007 — the scored agent never writes the scoreboard), and a lint config that
|
|
283
|
+
sets a rule to `"warn"`, `"off"`, `0` or `1` (law 2). It reads what the call would ADD, never what
|
|
284
|
+
it removes, so the edit that DELETES a suppression is not refused by the guard against them.
|
|
285
|
+
- **PostToolUse / Edit|Write** runs oxfmt on the file just written. It never blocks.
|
|
286
|
+
- **Stop** runs `geonosis-verify fast` and will not let the turn end on a red one, capped at five
|
|
287
|
+
blocks per session — an agent retrying the same broken thing five times has a problem it will not
|
|
288
|
+
solve on the sixth, so the sixth prints the report and lets go.
|
|
289
|
+
|
|
290
|
+
`.claude-plugin/marketplace.json` offers it from `./plugin`. `pnpm generate:skill` now writes the
|
|
291
|
+
plugin's copy of the mechanical skill, the engineer agent with the machine-specific path taken out,
|
|
292
|
+
and `plugin.json` carrying the kit's own version; staleness tests fail when any of the four drift.
|
|
293
|
+
- 5f09ddc: Every hook is driven end to end, as Claude Code drives it.
|
|
294
|
+
|
|
295
|
+
Each script is SPAWNED with real stdin JSON over a throwaway repo carrying a `geonosis.json`, a
|
|
296
|
+
`gate-baseline.json`, an `.oxlintrc.json` and a `node_modules/.bin` linked to the BUILT
|
|
297
|
+
`geonosis-verify` and `geonosis-ratchet`. A hook tested by calling its policy function proves the
|
|
298
|
+
policy; only a spawn proves the exit code, the JSON on stdout and the tool resolution — which is
|
|
299
|
+
the whole of what Claude Code actually reads.
|
|
300
|
+
|
|
301
|
+
The Stop hook is exercised against a real fast tier that really goes red: a law file one line over
|
|
302
|
+
its baseline, so the ratchet grows and the hook blocks with exit 2, the failing step and its tail on
|
|
303
|
+
stderr. Then the sixth block prints the report and exits 0; a green run clears the session's count;
|
|
304
|
+
`stop_hook_active` short-circuits; and a repo with a BACKLOG that did not regress is not blocked at
|
|
305
|
+
all — which is the whole adoption argument, tested rather than asserted.
|
|
306
|
+
|
|
307
|
+
The suite was falsified before it was trusted: making Stop exit 0 instead of 2, dropping the
|
|
308
|
+
delivery check, and raising the cap to 50 each turned exactly the tests that should have gone red,
|
|
309
|
+
red.
|
|
310
|
+
- 60f4d8f: **The law ceiling in both consumers, and an instrument for finding out whether cutting the law
|
|
311
|
+
helped.** `lawLineCount` is now in `examples/dielime.ratchet.json` and
|
|
312
|
+
`examples/during-day.ratchet.json`, in the **fast** tier — it reads one file, and a law that grew is
|
|
313
|
+
exactly the regression a turn should not be allowed to end on. Measured read-only 2026-08-30: those
|
|
314
|
+
two laws are **382** and **1,104** lines against guidance of 200, and those are the baselines each
|
|
315
|
+
repo starts from.
|
|
316
|
+
|
|
317
|
+
`geonosis-verify stats [--since <YYYY-MM-DD>] [--json]` reports the **first-try pass rate**: the
|
|
318
|
+
share of turn-ends whose fast tier was green on the first run. `--since` prints that window AND
|
|
319
|
+
everything before it, so a before/after comes from one command and one instrument.
|
|
320
|
+
|
|
321
|
+
The Stop hook now records what it gated. It had to: `stop-blocks.json` is the block cap's working
|
|
322
|
+
memory and a **green turn deletes its own row from it**, so a session that never blocked and one
|
|
323
|
+
that blocked and recovered are both absent — there was no history to read. The new
|
|
324
|
+
`.geonosis/stop-turns.json` is two integers a day, and the hook writes it itself because a hook is
|
|
325
|
+
fetched as a git tree with no install step and may import nothing; a test reads the hook's own output
|
|
326
|
+
back through `readStats` so the two spellings of the format cannot drift apart.
|
|
327
|
+
|
|
328
|
+
Three refusals, all the same refusal — **an empty record is not a perfect score.** No record says so
|
|
329
|
+
and gives no rate rather than 100 %; a record that will not parse is an error rather than zero turns;
|
|
330
|
+
and a turn the hook never gated (`stop_hook_active`, where the gate does not run) is not counted,
|
|
331
|
+
because there was no first try to have.
|
|
332
|
+
|
|
333
|
+
`docs/law-diet.md` is the M6 procedure end to end: arm the ceiling, record a before, cut in one
|
|
334
|
+
commit on a day you write down, read the after against the same instrument — and write down what
|
|
335
|
+
happened even when nothing moved, because a cut that changed nothing says the law was not the
|
|
336
|
+
binding constraint.
|
|
337
|
+
- 89c6769: The ratchet's own layers are walled, and the walls have a door that is proved open.
|
|
338
|
+
|
|
339
|
+
The three ratchet layers named FILE paths only, so `packages/ratchet/src/index.ts` — the published
|
|
340
|
+
entry — sat in no layer and could import anything, the CLI included, with lint green. It is
|
|
341
|
+
`ratchet-api` now, and the formatter it shares with the CLI has moved into a `ratchet-report` layer
|
|
342
|
+
of its own rather than being counted as part of the CLI it is not.
|
|
343
|
+
|
|
344
|
+
The dogfood suite gains a second kind of plant. A wall that refused everything would be as useless
|
|
345
|
+
as one that refused nothing, so each layer's permitted import — `verify` reaching the lock through
|
|
346
|
+
`@geonosis/ratchet` — is planted and asserted to stay CLEAN. Without that, the next engineer widens
|
|
347
|
+
`mayImport` to get their work through and the wall goes with it.
|
|
348
|
+
- b2d915f: **Fix:** a counter's command no longer loses what it wrote to stderr.
|
|
349
|
+
|
|
350
|
+
`<command> 2>&1` binds the redirect to the LAST command of the string. `echo boom >&2; echo fine`
|
|
351
|
+
therefore merged nothing, and on a ZERO exit `execSync` hands back stdout alone — so a tool that
|
|
352
|
+
prints its findings to stderr and exits 0 read as a tool that found nothing. That is the exact shape
|
|
353
|
+
of a gate that has quietly stopped gating, and it is the one direction in which this kind of bug is
|
|
354
|
+
dangerous: the number comes back lower than the truth and the baseline banks it.
|
|
355
|
+
|
|
356
|
+
On a NON-ZERO exit the line survived, because the failure path concatenates both captured buffers —
|
|
357
|
+
but concatenation put every stderr line after every stdout line, moving each diagnostic away from
|
|
358
|
+
the line it was about, which is what the evidence tail on a regression is for.
|
|
359
|
+
|
|
360
|
+
The command now runs in a subshell, so the shell itself merges the streams in the order they were
|
|
361
|
+
written. Measured before and after: `echo boom >&2` on a zero exit read `""` and now reads `boom`;
|
|
362
|
+
`echo one; echo two >&2; echo three; exit 4` read `one three two` and now reads `one two three`.
|
|
363
|
+
- 7762a01: **`testFailures` in summary mode reads the runner's summary LINE, not the word "fail" anywhere in
|
|
364
|
+
the output.** The regex was unanchored, so anything a command printed on its way to the runner was a
|
|
365
|
+
candidate count. Reproduced: a command that printed `naming 4 failing test(s)` and then a green
|
|
366
|
+
`Tests 3 passed (3)` was read as **four failures** — a green run regressing a baseline. That is the
|
|
367
|
+
false RED mirroring the false green `report: "vitest-json"` exists for; both come from reading
|
|
368
|
+
something that is not the runner's own last word.
|
|
369
|
+
|
|
370
|
+
Two dialects now, and both were captured from the binary rather than guessed at. vitest 3.2.7's
|
|
371
|
+
` Tests 1 failed | 1 passed (2)` — its own label, in its own column, closed by the total in
|
|
372
|
+
parentheses — and bun 1.4.0's ` 1 fail` on a line of its own, which it prints on a green run too,
|
|
373
|
+
saying 0. Nothing else counts: `Test Files 1 failed (1)` counts FILES, `(fail) one [11.71ms]` is
|
|
374
|
+
bun naming one, and a wrapper's prose is a wrapper's prose. All three used to be read as failures.
|
|
375
|
+
|
|
376
|
+
Three consequences beyond the fix. A suite vitest skipped whole (`Tests 2 skipped (2)`) reads 0
|
|
377
|
+
instead of refusing — it is a run that happened and found nothing red. A suite that threw before
|
|
378
|
+
collecting (`Tests no tests`, beside `Test Files 1 failed (1)`) is now **refused** instead of
|
|
379
|
+
being read as one failing test: nothing there is a count of failing tests, and a gate that cannot
|
|
380
|
+
measure has not passed. And summaries **add up**, so a command that invokes the runner twice no
|
|
381
|
+
longer banks the second suite's reds as a win.
|
|
382
|
+
|
|
383
|
+
The refusal names both dialects, so a repo whose runner speaks a third one gets a message instead of
|
|
384
|
+
a wrong number. `--prove` grew a probe: `summary (vitest)`, `summary (bun)` and `vitest-json` are
|
|
385
|
+
each shown reading their own planted finding, because a dialect nobody proved is a dialect nobody
|
|
386
|
+
has been shown to read.
|
|
387
|
+
- 48bce0c: **Fix:** only a counter that measures a quantity accepts a `tolerance`.
|
|
388
|
+
|
|
389
|
+
Any counter took one. `{"counter":"lawLineCount","tolerance":0.5}` over a two-line law with a
|
|
390
|
+
baseline of 2 printed `ratchet PASS` at three lines — a 50 % growth, waved through. The same edit is
|
|
391
|
+
`"tolerance": 100` on `oxlintErrors`: the gate switched off from the config file, with the run still
|
|
392
|
+
reporting green. That is law 2 — never downgrade a rule — wearing a friendlier name, and it was the
|
|
393
|
+
easiest edit in the repo to get past a reviewer.
|
|
394
|
+
|
|
395
|
+
`Counter` gains `tolerates?: true`, declared only by `bundleBytes` and `fastTierMs`. Whether a
|
|
396
|
+
number is a measured quantity or a count of findings is the counter's to know, not the config's.
|
|
397
|
+
Anything else now stops the run, naming the entry:
|
|
398
|
+
|
|
399
|
+
```
|
|
400
|
+
geonosis-ratchet: "oxlintErrors" does not accept a tolerance — only a counter that measures a
|
|
401
|
+
quantity declares one
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
The plugin's write guard gains its first **ask**. A write to `geonosis.ratchet.json` that introduces
|
|
405
|
+
or raises a `"tolerance"` is neither refused nor allowed: it goes in front of the human as
|
|
406
|
+
`permissionDecision: "ask"`. Widening a band is legitimate and is not the agent's call — a band
|
|
407
|
+
somebody chose and a band somebody's agent chose are not the same thing. A tolerance carried through
|
|
408
|
+
an edit unchanged is not a change and says nothing.
|
|
409
|
+
- 461233c: Docs for the two new things: `docs/plugin.md` and `packages/verify/README.md`.
|
|
410
|
+
|
|
411
|
+
`docs/plugin.md` covers installing the plugin from a local path and from the remote once it is
|
|
412
|
+
pushed, what each of the five hooks does and the reason it does it that way, the five-block cap,
|
|
413
|
+
which files the runner owns and why the agent may not write them, what to put in `geonosis.json`,
|
|
414
|
+
and — the section every README skips — what the plugin CANNOT do. A `fast` tier of `["true"]` passes
|
|
415
|
+
for ever; only `geonosis-ratchet --prove` shows a counter can go red.
|
|
416
|
+
|
|
417
|
+
`packages/verify/README.md` is the runner's own: `--prove` first, the config with no default tier,
|
|
418
|
+
the three exit codes and why a report that could not be written is exit 2, the report shape with the
|
|
419
|
+
`<tier>/<n>` id rule, and `--exclusive`.
|
|
420
|
+
|
|
421
|
+
`docs/README.md` indexes both. The ratchet README gains rows for the five new counters, a section on
|
|
422
|
+
`tolerance` and the shrank-only rewrite it forces, and the landmine that cost an afternoon: a
|
|
423
|
+
loader or shim named RELATIVELY in a counter's command resolves to nothing under `--prove`, whose
|
|
424
|
+
probes run in a scratch directory. Write it `$PWD/…` and it works in both.
|
|
425
|
+
- e48501b: `@geonosis/verify` — one gate runner, with a report a hook can read.
|
|
426
|
+
|
|
427
|
+
`geonosis-verify <tier>` runs the shell steps a repo declared under `verify` in `geonosis.json`,
|
|
428
|
+
serially, stopping at the first red, and writes `.geonosis/gate-report.json`:
|
|
429
|
+
`{ tier, startedAt, finishedAt, ok, steps: [{ id, command, ok, exitCode, ms, tail, skipped? }] }`.
|
|
430
|
+
A step whose text is another tier's name is that tier, inlined in place; the inlined steps keep the
|
|
431
|
+
tier that declared them, so `fast/2` is the same command whether the operator ran `fast` or ran the
|
|
432
|
+
`full` tier that inlines it.
|
|
433
|
+
|
|
434
|
+
There is no default tier and there never will be one: no config, no `verify` object, or an unknown
|
|
435
|
+
tier is refused by name with exit 2, and so is a green run whose report could not be written — a
|
|
436
|
+
gate that cannot record has not passed. A failed step is exit 1.
|
|
437
|
+
|
|
438
|
+
`--exclusive` takes the ratchet's machine-wide lock through `@geonosis/ratchet`'s published entry
|
|
439
|
+
(`--exclusive-timeout <seconds>` bounds the wait), so a verify and a ratchet on one laptop serialise
|
|
440
|
+
against each other rather than each against itself. `--prove` plants a tier with one passing step
|
|
441
|
+
and one that exits 3, runs the real binary over it in a temp directory, and checks the failure
|
|
442
|
+
reached the report — a runner that reports green is worth nothing until it has been watched going
|
|
443
|
+
red on purpose.
|
|
444
|
+
|
|
445
|
+
The kit walls the new package with its own `layer-walls` rule: `verify` may import `ratchet-api`
|
|
446
|
+
and nothing behind it, planted against in the dogfood suite like the other walls.
|
|
447
|
+
- 7e8d2c1: `walkFindings`: the defects `geonosis-walk` reported, as debt that may only shrink. Reads the walk's
|
|
448
|
+
JSON report (`report`), counts findings across every page, and scopes to named classes with
|
|
449
|
+
`classes` — refusing a class the walk does not have rather than reporting a confident zero for a
|
|
450
|
+
typo. A missing or unparsable report is a refusal, never a zero (D-030): the walk writes no report
|
|
451
|
+
when it could not run, and answering zero there would make its loudest failure the best number on
|
|
452
|
+
the board.
|
|
453
|
+
|
|
454
|
+
## 0.6.0
|
|
455
|
+
|
|
456
|
+
### Minor Changes
|
|
457
|
+
|
|
458
|
+
- **No counter changed.** The version moves because the packages release as one fixed group.
|
|
459
|
+
|
|
460
|
+
- **Two things this package cannot see about itself now have somewhere to be seen from.** A baseline
|
|
461
|
+
is lowered in place when a number shrinks, which is also what lets a branch raise one back up and
|
|
462
|
+
stay green on every gate it runs; and a workspace whose suite no `testFailures` entry covers is
|
|
463
|
+
unmeasured, which reads exactly like green. `geonosis-doctor --baseline-against <ref>` asks the
|
|
464
|
+
first from outside the checkout, and its `runner` check names the workspaces the second applies
|
|
465
|
+
to. Measured on the two consumers the day it shipped: 28 such workspaces in one, 8 in the other.
|
|
466
|
+
See [`@geonosis/doctor`](https://www.npmjs.com/package/@geonosis/doctor).
|
|
467
|
+
|
|
468
|
+
## 0.5.0
|
|
469
|
+
|
|
470
|
+
### Minor Changes
|
|
471
|
+
|
|
472
|
+
- **New rule `cell-no-cross-feature-organelles` (preset `frontend`).** `organelle-dependency` is scoped
|
|
473
|
+
to `organelles/` and `no-cross-feature-stores` only judges stores, so a CELL importing another
|
|
474
|
+
feature's ORGANELLE passed both while both stayed green. Option `sharedFeatures`, default
|
|
475
|
+
`['shared']` — the same option and the same default as `organelle-dependency`'s, because the two
|
|
476
|
+
rules draw one boundary from two sides. Measured on a consumer: three findings in two files.
|
|
477
|
+
|
|
478
|
+
**The corpus carries both spellings of every direction rule.** A direction rule judges where an
|
|
479
|
+
import lands, so `@/features/billing/x` and `../../billing/x` are one breach; the corpus previously
|
|
480
|
+
carried whichever spelling each rule's author happened to write. Fifteen fixtures added. No rule
|
|
481
|
+
changed.
|
|
482
|
+
|
|
483
|
+
**`geonosis-lint-parity --corpus --expect-changed <rule,…>`.** A changelog line that names a rule is
|
|
484
|
+
a claim; this makes it data the corpus judges, and exits 1 on a claim the corpus is silent about or a
|
|
485
|
+
change nobody claimed. Mandatory for any release whose notes name a rule.
|
|
486
|
+
|
|
487
|
+
**`testFailures` reads the runner's JSON report** with `report: "vitest-json"`, refusing when the
|
|
488
|
+
report is absent, unparsable, missing `numFailedTests`, or says `success: false` while naming none.
|
|
489
|
+
`Counter.probe` takes a list, so `--prove` proves each reading mode by name.
|
|
490
|
+
|
|
491
|
+
**`geonosis-ratchet --prove` proves the lock**, by running two children of the CLI over a slow
|
|
492
|
+
`--hold` and requiring the second to start after the first finished.
|
|
493
|
+
|
|
494
|
+
`@geonosis/ratchet`'s source is layered — `core/`, `counters/`, `report.ts`, `cli.ts` — with no
|
|
495
|
+
change to what `@geonosis/ratchet` exports or to a single line the CLI prints.
|
|
496
|
+
|
|
497
|
+
## 0.4.0
|
|
498
|
+
|
|
499
|
+
### Patch Changes
|
|
500
|
+
|
|
501
|
+
- `--exclusive` did not serialise, and the test that should have said so was blamed for it.
|
|
502
|
+
|
|
503
|
+
`openSync(path, 'wx')` is atomic about the NAME only: the file it creates is empty until the holder
|
|
504
|
+
lands on the next syscall, and a run polling in that window reads a lock nobody can parse, calls it
|
|
505
|
+
stale — `taking over a stale heavy lock (pid unreadable is gone)` — removes it and takes it. Two runs,
|
|
506
|
+
one lock, both certain they held it. It reproduced three times in five on a loaded laptop, which is
|
|
507
|
+
the only kind of machine the lock is for.
|
|
508
|
+
|
|
509
|
+
The holder is now written under a private name and hard-linked into place: `link` fails `EEXIST`
|
|
510
|
+
exactly as `'wx'` did, and when it succeeds there was never an instant at which the lock existed
|
|
511
|
+
without its holder inside it. The staging name is dropped either way, so a contended run leaves
|
|
512
|
+
nothing behind in `~/.cache/geonosis/`.
|
|
513
|
+
|
|
514
|
+
The test's ordering is causal now rather than temporal: the holding run leaves its critical section
|
|
515
|
+
only when the test opens a barrier, and the test opens it when the OTHER run says it is waiting.
|
|
516
|
+
|
|
517
|
+
## 0.3.0
|
|
518
|
+
|
|
519
|
+
## 0.2.1
|
|
520
|
+
|
|
521
|
+
### Patch Changes
|
|
522
|
+
|
|
523
|
+
- Gates prove they can fail (plan 019, K2.1–K2.3).
|
|
524
|
+
|
|
525
|
+
- **`geonosis-ratchet --prove`** — every counter ships a falsification probe: a known-bad input it
|
|
526
|
+
must be able to read. `--prove` runs each configured counter's probe in a temp directory, never in
|
|
527
|
+
the repo, and exits 2 on the first counter that reads 0, misreads, throws, or ships no probe.
|
|
528
|
+
A counter that has never been seen read a planted finding has not been shown to measure (D-029).
|
|
529
|
+
- **`--exclusive`** — a machine-wide lock at `~/.cache/geonosis/heavy.lock` before anything heavy,
|
|
530
|
+
with a bounded printed wait (`--exclusive-timeout`, default 1800s), stale-holder takeover, and
|
|
531
|
+
release on SIGINT/SIGTERM.
|
|
532
|
+
- **`typecheckErrors` refuses on an unbuilt workspace** — TS2305/TS2307 naming a package of this
|
|
533
|
+
workspace is a missing build, not debt, and a number there would be wrong (D-030).
|
|
534
|
+
|
|
535
|
+
The plugin and `@geonosis/lint-parity` carry no change in this release; they move because the three
|
|
536
|
+
packages are a fixed version group.
|
|
537
|
+
|
|
538
|
+
## 0.2.0
|
|
539
|
+
|
|
540
|
+
### Minor Changes
|
|
541
|
+
|
|
542
|
+
- Loose coupling, first slice — and a posture change that will break a config on purpose.
|
|
543
|
+
|
|
544
|
+
**`@geonosis/lint-parity` is a new package.** Findings parity, rule reach, the corpus-manifest
|
|
545
|
+
contract and the `geonosis-lint-parity` bin now live on their own. The tool knows no rule name and
|
|
546
|
+
no plugin: two oxlint configs and a tree are the whole input, and a corpus arrives as a directory
|
|
547
|
+
whose own `manifest.json` says which rules it speaks for. A parity tool welded to one rule plugin
|
|
548
|
+
can only ever check that plugin.
|
|
549
|
+
|
|
550
|
+
**BREAKING (plugin): the `geonosis-lint-parity` bin and the `./parity` export are gone.** Install
|
|
551
|
+
`@geonosis/lint-parity` and pass `--corpus node_modules/@geonosis/oxlint-plugin-biological-architecture/corpus`
|
|
552
|
+
— the plugin still ships the corpus, which is the part that is genuinely its own. `--corpus` now
|
|
553
|
+
requires its directory.
|
|
554
|
+
|
|
555
|
+
**BREAKING (plugin): a rule enabled without the options it needs refuses the run.** `no-brand-names`,
|
|
556
|
+
`no-raw-html-atoms`, `documents-share-one-table`, `document-sagas-are-generic`,
|
|
557
|
+
`step-opens-its-own-cell` and `layer-walls` throw from `create()` naming the missing option, instead
|
|
558
|
+
of returning an empty visitor. A consumer that enabled one bare will fail lint until it configures
|
|
559
|
+
the rule or turns it off — which is the point: one consumer ran `no-raw-html-atoms` at "error" for
|
|
560
|
+
months with no element→atom map, over 136 raw elements, and the gate was green. An enabled rule that
|
|
561
|
+
cannot fire is indistinguishable from compliance. A rule nobody enabled still does nothing.
|
|
562
|
+
|
|
563
|
+
**New rule `layer-walls`, new preset `tooling`.** A repo declares its own layers as data —
|
|
564
|
+
`{ name, paths, mayImport }` — and the rule enforces the direction between them: relative sources
|
|
565
|
+
resolved against the importing file first, `import()`, `require()` and both re-export forms visited,
|
|
566
|
+
externals not walls unless asked. The kit enables it on itself.
|
|
567
|
+
|
|
568
|
+
The plugin also ships type declarations now, for all three entries.
|
|
569
|
+
|
|
570
|
+
## 0.1.2
|
|
571
|
+
|
|
572
|
+
### Patch Changes
|
|
573
|
+
|
|
574
|
+
- Four findings from the 0.1.1 code review, plus a version that disagreed with itself.
|
|
575
|
+
|
|
576
|
+
**`--corpus` scopes its verdict to the rules its corpus can speak for.** It built the list of
|
|
577
|
+
configured rules from every rule id in both configs, but the shipped corpus only exercises this
|
|
578
|
+
plugin's. Any real consumer config also enables `eqeqeq`, `no-throw-literal` and
|
|
579
|
+
`typescript/no-explicit-any`, so `--corpus` reported `FAIL — fired under neither` on every
|
|
580
|
+
adoption. A corpus now declares its own scope in a `manifest.json` generated from the plugin's
|
|
581
|
+
rules export; rules it does not name are listed under "outside this corpus" with no verdict, and
|
|
582
|
+
neither the exit code nor `neither` considers them. `--corpus <dir>` points the run at another
|
|
583
|
+
plugin's corpus. Matching is on full `plugin/rule` ids on both sides, so
|
|
584
|
+
`typescript(no-unused-vars)` can never stand as evidence for somebody else's `no-unused-vars`.
|
|
585
|
+
|
|
586
|
+
**Parity identifies a finding by its place and its rule, not its wording.** `path:line:col` plus
|
|
587
|
+
`plugin(rule)`; message and severity are excluded. Diffing whole lines meant a rule whose message
|
|
588
|
+
was copy-edited read as one finding lost and one gained — exit 1, a stopped adoption over a
|
|
589
|
+
sentence. Those pair up in a new `messageChanged` list, reported under its own heading and written
|
|
590
|
+
to `changed.txt`, and never reach the exit code. Multiset semantics are unchanged.
|
|
591
|
+
|
|
592
|
+
**`oxlintRule` gets the cross-check the error and warning counters already had.** It counted
|
|
593
|
+
`(rule)` anywhere in the output, so a format that printed rule ids differently read as 0 and the
|
|
594
|
+
ratchet banked the "win" by rewriting the baseline down — and a quoted source line containing
|
|
595
|
+
`(a-rule-name)` counted as a finding. The count now comes off recognised finding lines only, and a
|
|
596
|
+
run with findings where none attributes itself readably is refused. The strict-config copy also
|
|
597
|
+
upgrades the `["off", { … }]` array form, which is how every rule with options is parked
|
|
598
|
+
mid-migration.
|
|
599
|
+
|
|
600
|
+
**The warnings-only window is documented and closable.** oxlint exits 0 when a run found only
|
|
601
|
+
warnings, so the counters' non-zero-exit backstop cannot catch an unrecognised format on such a
|
|
602
|
+
run. The README states this plainly and says why `--deny-warnings` is not the fix. New optional
|
|
603
|
+
counter param `expectFormat: "unix" | "agent" | "default"` refuses output in no known shape even on
|
|
604
|
+
a clean exit, once npm's own chatter is stripped. Unset is the default and changes nothing.
|
|
605
|
+
|
|
606
|
+
**The plugin reported `meta.version` 0.1.0 while shipping 0.1.1.** It is read from `package.json`
|
|
607
|
+
now, and a test asserts the two agree.
|
|
608
|
+
|
|
609
|
+
## 0.1.1
|
|
610
|
+
|
|
611
|
+
### Patch Changes
|
|
612
|
+
|
|
613
|
+
- The defects the first real adoption exposed.
|
|
614
|
+
|
|
615
|
+
- **The oxlint counters had never measured anything.** `oxlintErrors` and `oxlintWarnings` matched
|
|
616
|
+
`/: error /`, which is the shape of oxlint's compact `agent` format alone, while every config asks
|
|
617
|
+
for `--format=unix`. They now read all three shapes oxlint prints and cross-check the count
|
|
618
|
+
against the tool's own summary, raising `CounterError` rather than returning a number they cannot
|
|
619
|
+
corroborate — a blind counter reports 0 and lets the ratchet rewrite the baseline DOWN as a win.
|
|
620
|
+
- **`cell-must-not-compose-cell` fired on the barrel `cells-folder-index-is-barrel` demands.**
|
|
621
|
+
dielime's adoption reported 32 errors, every one a `cells/index.ts` re-exporting siblings, which
|
|
622
|
+
is the shape the other rule requires at the same severity. The two rules now read one shared list
|
|
623
|
+
of barrel filenames. A real cell importing or re-exporting another cell still fires.
|
|
624
|
+
- **`geonosis-lint-parity`.** The adoption proof — same tree, two configs, a findings diff — as a
|
|
625
|
+
bin instead of forty tool calls. Exits 1 when a finding the old config caught is missing under the
|
|
626
|
+
new one. `--corpus` answers the prior question first: does each configured rule fire anywhere at
|
|
627
|
+
all? The package now ships the reach corpus that makes that answerable, with a file for every one
|
|
628
|
+
of its 54 rules.
|
|
629
|
+
- **The ratchet shows what grew.** A regressed counter prints the last ten lines of its own command
|
|
630
|
+
output under the `<-- REGRESSED` line, so the report answers the question it used to raise.
|
package/bin/geonosis-ratchet.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-ratchet: 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-ratchet: 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/cli.js')
|
|
@@ -516,11 +516,6 @@ var runRatchet = async ({
|
|
|
516
516
|
continue;
|
|
517
517
|
}
|
|
518
518
|
const limit = baseline[key];
|
|
519
|
-
if (typeof limit !== "number") {
|
|
520
|
-
throw new Error(
|
|
521
|
-
`${config.baseline} has no number for "${key}" \u2014 add it before enabling the counter`
|
|
522
|
-
);
|
|
523
|
-
}
|
|
524
519
|
const tolerance = toleranceOf(entry, key, counter);
|
|
525
520
|
const recorder = recorded(counter, entry, runCommand(cwd, entry.counter));
|
|
526
521
|
let now;
|
|
@@ -534,6 +529,10 @@ var runRatchet = async ({
|
|
|
534
529
|
});
|
|
535
530
|
continue;
|
|
536
531
|
}
|
|
532
|
+
if (typeof limit !== "number") {
|
|
533
|
+
measurements.push({ baseline: now, evidence: [], key, now, verdict: "adopted" });
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
537
536
|
const verdict = verdictOf(now, limit, tolerance);
|
|
538
537
|
measurements.push({
|
|
539
538
|
baseline: limit,
|
|
@@ -545,16 +544,24 @@ var runRatchet = async ({
|
|
|
545
544
|
}
|
|
546
545
|
const grew = measurements.some((one) => one.verdict === "grew");
|
|
547
546
|
const shrank = measurements.some((one) => one.verdict === "shrank");
|
|
548
|
-
|
|
547
|
+
const measuredNew = measurements.flatMap((one) => one.verdict === "adopted" ? [one.key] : []);
|
|
548
|
+
const adopted = refusals.length === 0 ? measuredNew : [];
|
|
549
|
+
if (adopted.length > 0 || shrank && !grew && refusals.length === 0) {
|
|
549
550
|
const next = { ...baseline };
|
|
550
551
|
for (const one of measurements) {
|
|
551
|
-
if (one.verdict === "shrank") next[one.key] = one.now;
|
|
552
|
+
if (one.verdict === "adopted" || one.verdict === "shrank" && !grew) next[one.key] = one.now;
|
|
552
553
|
}
|
|
553
554
|
writeFileSync4(baselinePath, `${JSON.stringify(next, null, 2)}
|
|
554
555
|
`);
|
|
555
|
-
return {
|
|
556
|
+
return {
|
|
557
|
+
adopted,
|
|
558
|
+
considered: config.counters.length,
|
|
559
|
+
measurements,
|
|
560
|
+
refusals,
|
|
561
|
+
rewritten: true
|
|
562
|
+
};
|
|
556
563
|
}
|
|
557
|
-
return { considered: config.counters.length, measurements, refusals, rewritten: false };
|
|
564
|
+
return { adopted, considered: config.counters.length, measurements, refusals, rewritten: false };
|
|
558
565
|
};
|
|
559
566
|
|
|
560
567
|
// src/counters/params.ts
|
|
@@ -707,6 +714,62 @@ var disabledCiJobs = {
|
|
|
707
714
|
}
|
|
708
715
|
};
|
|
709
716
|
|
|
717
|
+
// src/counters/doctor.ts
|
|
718
|
+
var ID = "doctorWarnings";
|
|
719
|
+
var reportIn = (output) => {
|
|
720
|
+
let found;
|
|
721
|
+
for (let at = output.indexOf("{"); at !== -1; at = output.indexOf("{", at + 1)) {
|
|
722
|
+
try {
|
|
723
|
+
const parsed = JSON.parse(output.slice(at));
|
|
724
|
+
if (typeof parsed === "object" && parsed !== null) found = parsed;
|
|
725
|
+
} catch {
|
|
726
|
+
continue;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
return found;
|
|
730
|
+
};
|
|
731
|
+
var CONFIG_AND_RULE = " \u2192 ";
|
|
732
|
+
var about = (one) => {
|
|
733
|
+
const subject = String(one.subject ?? "");
|
|
734
|
+
const at = subject.indexOf(CONFIG_AND_RULE);
|
|
735
|
+
return `${String(one.check ?? "")}:${at === -1 ? subject : subject.slice(at + CONFIG_AND_RULE.length)}`;
|
|
736
|
+
};
|
|
737
|
+
var doctorWarnings = {
|
|
738
|
+
id: ID,
|
|
739
|
+
probe: {
|
|
740
|
+
command: () => `node -p "require('fs').readFileSync('doctor.json','utf8')"`,
|
|
741
|
+
expect: 2,
|
|
742
|
+
input: (dir) => plant(
|
|
743
|
+
dir,
|
|
744
|
+
"doctor.json",
|
|
745
|
+
`${JSON.stringify({
|
|
746
|
+
findings: [
|
|
747
|
+
{ check: "exercised", subject: "a/.oxlintrc.json \u2192 ns/one", verdict: "WARN" },
|
|
748
|
+
{ check: "exercised", subject: "b/.oxlintrc.json \u2192 ns/one", verdict: "WARN" },
|
|
749
|
+
{ check: "drift", subject: "LAW.md", verdict: "WARN" },
|
|
750
|
+
{ check: "drift", subject: "fine.md", verdict: "OK" }
|
|
751
|
+
]
|
|
752
|
+
})}
|
|
753
|
+
`
|
|
754
|
+
)
|
|
755
|
+
},
|
|
756
|
+
run: async ({ params, run }) => {
|
|
757
|
+
const command = stringParam(ID, params, "command", "npx geonosis-doctor --json");
|
|
758
|
+
const output = run(command).output;
|
|
759
|
+
const findings = reportIn(output)?.findings;
|
|
760
|
+
if (!Array.isArray(findings)) {
|
|
761
|
+
throw new CounterError(
|
|
762
|
+
ID,
|
|
763
|
+
`\`${command}\` printed no doctor report with a findings list \u2014 a gate that cannot measure has not passed. Its output began: ${output.trim().slice(0, 200)}`
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
const warned = new Set(
|
|
767
|
+
findings.filter((one) => typeof one === "object" && one !== null).filter((one) => one.verdict === "WARN").map(about)
|
|
768
|
+
);
|
|
769
|
+
return warned.size;
|
|
770
|
+
}
|
|
771
|
+
};
|
|
772
|
+
|
|
710
773
|
// src/counters/duplication.ts
|
|
711
774
|
var CLONES = /Found (\d+) clones?/;
|
|
712
775
|
var cloneCount = {
|
|
@@ -1191,7 +1254,7 @@ import { existsSync as existsSync9, readFileSync as readFileSync10 } from "fs";
|
|
|
1191
1254
|
import { createRequire } from "module";
|
|
1192
1255
|
import { join as join6, resolve as resolve12 } from "path";
|
|
1193
1256
|
import { pathToFileURL } from "url";
|
|
1194
|
-
var
|
|
1257
|
+
var ID2 = "probelessRules";
|
|
1195
1258
|
var OFF = /* @__PURE__ */ new Set([0, "0", "allow", "off", false]);
|
|
1196
1259
|
var severityOf2 = (level) => Array.isArray(level) ? level[0] : level;
|
|
1197
1260
|
var enabledIn = (config, namespace) => {
|
|
@@ -1214,7 +1277,7 @@ var probedBy = async (cwd, plugin) => {
|
|
|
1214
1277
|
entry = createRequire(join6(cwd, "noop.js")).resolve(plugin);
|
|
1215
1278
|
} catch (error) {
|
|
1216
1279
|
throw new CounterError(
|
|
1217
|
-
|
|
1280
|
+
ID2,
|
|
1218
1281
|
`could not resolve ${plugin} from ${cwd} \u2014 ${String(error.message).split("\n")[0]}. A plugin nobody can load says nothing about which rules ship a probe, and it is not zero of them`
|
|
1219
1282
|
);
|
|
1220
1283
|
}
|
|
@@ -1222,12 +1285,12 @@ var probedBy = async (cwd, plugin) => {
|
|
|
1222
1285
|
try {
|
|
1223
1286
|
loaded = await import(pathToFileURL(entry).href);
|
|
1224
1287
|
} catch (error) {
|
|
1225
|
-
throw new CounterError(
|
|
1288
|
+
throw new CounterError(ID2, `could not import ${entry} \u2014 ${error.message}`);
|
|
1226
1289
|
}
|
|
1227
1290
|
const namespace = loaded.default?.meta?.name;
|
|
1228
1291
|
if (typeof namespace !== "string" || namespace === "") {
|
|
1229
1292
|
throw new CounterError(
|
|
1230
|
-
|
|
1293
|
+
ID2,
|
|
1231
1294
|
`${plugin} declares no meta.name, so nothing says which rule ids in the config are its own`
|
|
1232
1295
|
);
|
|
1233
1296
|
}
|
|
@@ -1240,7 +1303,7 @@ var probedBy = async (cwd, plugin) => {
|
|
|
1240
1303
|
};
|
|
1241
1304
|
var PLUGIN = "@geonosis/oxlint-plugin-biological-architecture";
|
|
1242
1305
|
var probelessRules = {
|
|
1243
|
-
id:
|
|
1306
|
+
id: ID2,
|
|
1244
1307
|
probe: {
|
|
1245
1308
|
expect: 1,
|
|
1246
1309
|
input: (dir) => {
|
|
@@ -1266,16 +1329,16 @@ var probelessRules = {
|
|
|
1266
1329
|
params: { plugin: "probe-plugin" }
|
|
1267
1330
|
},
|
|
1268
1331
|
run: async ({ cwd, params }) => {
|
|
1269
|
-
const relative = stringParam(
|
|
1332
|
+
const relative = stringParam(ID2, params, "config", ".oxlintrc.json");
|
|
1270
1333
|
const path = resolve12(cwd, relative);
|
|
1271
|
-
if (!existsSync9(path)) throw new CounterError(
|
|
1334
|
+
if (!existsSync9(path)) throw new CounterError(ID2, `no oxlint config at ${relative}`);
|
|
1272
1335
|
let config;
|
|
1273
1336
|
try {
|
|
1274
1337
|
config = JSON.parse(readFileSync10(path, "utf8"));
|
|
1275
1338
|
} catch (error) {
|
|
1276
|
-
throw new CounterError(
|
|
1339
|
+
throw new CounterError(ID2, `${relative} does not parse: ${error.message}`);
|
|
1277
1340
|
}
|
|
1278
|
-
const { namespace, probed } = await probedBy(cwd, stringParam(
|
|
1341
|
+
const { namespace, probed } = await probedBy(cwd, stringParam(ID2, params, "plugin", PLUGIN));
|
|
1279
1342
|
return enabledIn(config, namespace).filter((rule) => !probed.has(rule)).length;
|
|
1280
1343
|
}
|
|
1281
1344
|
};
|
|
@@ -1956,6 +2019,7 @@ var COUNTERS = [
|
|
|
1956
2019
|
bundleBytes,
|
|
1957
2020
|
cloneCount,
|
|
1958
2021
|
disabledCiJobs,
|
|
2022
|
+
doctorWarnings,
|
|
1959
2023
|
fastTierMs,
|
|
1960
2024
|
floorSurface,
|
|
1961
2025
|
knipIssues,
|
|
@@ -1989,7 +2053,12 @@ var counterById = (id) => {
|
|
|
1989
2053
|
|
|
1990
2054
|
// src/report.ts
|
|
1991
2055
|
var WIDTH = 28;
|
|
1992
|
-
var formatReport = ({
|
|
2056
|
+
var formatReport = ({
|
|
2057
|
+
adopted,
|
|
2058
|
+
measurements,
|
|
2059
|
+
refusals,
|
|
2060
|
+
rewritten
|
|
2061
|
+
}) => {
|
|
1993
2062
|
const lines = measurements.map((one) => {
|
|
1994
2063
|
if (one.verdict === "skipped") {
|
|
1995
2064
|
return ` SKIP ${one.key}: not measured by --tier ${one.tier}`;
|
|
@@ -2000,6 +2069,9 @@ var formatReport = ({ measurements, refusals, rewritten }) => {
|
|
|
2000
2069
|
return [regressed, ...one.evidence.map((line) => ` ${line}`)].join("\n");
|
|
2001
2070
|
}
|
|
2002
2071
|
if (one.verdict === "shrank") return `${head} <-- improved -${one.baseline - one.now}`;
|
|
2072
|
+
if (one.verdict === "adopted") {
|
|
2073
|
+
return `${head} <-- ADOPTED: measured for the first time and written, not judged`;
|
|
2074
|
+
}
|
|
2003
2075
|
return head;
|
|
2004
2076
|
});
|
|
2005
2077
|
lines.push(...refusals.map((one) => ` CANNOT MEASURE ${one.key}: ${one.reason}`));
|
|
@@ -2014,6 +2086,11 @@ var formatReport = ({ measurements, refusals, rewritten }) => {
|
|
|
2014
2086
|
"",
|
|
2015
2087
|
"ratchet FAIL \u2014 debt grew. Fix it, or say in the commit message why the baseline goes up."
|
|
2016
2088
|
);
|
|
2089
|
+
} else if (adopted.length > 0) {
|
|
2090
|
+
lines.push(
|
|
2091
|
+
"",
|
|
2092
|
+
`ratchet DID NOT PASS \u2014 ${adopted.join(", ")} measured for the first time and written, not judged. Nothing shrank and nothing was lowered. Read the number, commit it, and run again.`
|
|
2093
|
+
);
|
|
2017
2094
|
} else if (rewritten) {
|
|
2018
2095
|
lines.push("", "ratchet PASS \u2014 debt shrank; the baseline was lowered to lock the win in.");
|
|
2019
2096
|
} else {
|
package/dist/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
runRatchet,
|
|
8
8
|
versionOf,
|
|
9
9
|
writeEnvelope
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-2EAYMSC5.js";
|
|
11
11
|
|
|
12
12
|
// src/cli.ts
|
|
13
13
|
import process from "process";
|
|
@@ -143,6 +143,13 @@ var measure = async () => {
|
|
|
143
143
|
` : `envelope: ${at}
|
|
144
144
|
`);
|
|
145
145
|
if (result.refusals.length > 0) return 2;
|
|
146
|
+
if (result.adopted.length > 0) {
|
|
147
|
+
process.stdout.write(
|
|
148
|
+
`adopted ${result.adopted.join(", ")} \u2014 measured for the first time and written to the baseline, not judged. Read the number, commit it, and run again: this run does not pass on a floor nobody has seen.
|
|
149
|
+
`
|
|
150
|
+
);
|
|
151
|
+
return 1;
|
|
152
|
+
}
|
|
146
153
|
return result.measurements.some((one) => one.verdict === "grew") ? 1 : 0;
|
|
147
154
|
};
|
|
148
155
|
try {
|
package/dist/index.d.ts
CHANGED
|
@@ -116,7 +116,11 @@ type Measurement = {
|
|
|
116
116
|
evidence: string[];
|
|
117
117
|
key: string;
|
|
118
118
|
now: number;
|
|
119
|
-
|
|
119
|
+
/**
|
|
120
|
+
* `adopted` is a key the baseline had never held: measured, written by the runner, and NOT
|
|
121
|
+
* judged. It carries no `baseline` of its own, because there was none to compare against.
|
|
122
|
+
*/
|
|
123
|
+
verdict: 'adopted' | 'grew' | 'held' | 'shrank';
|
|
120
124
|
} | {
|
|
121
125
|
key: string;
|
|
122
126
|
tier: string;
|
|
@@ -176,6 +180,12 @@ type Refusal = {
|
|
|
176
180
|
reason: string;
|
|
177
181
|
};
|
|
178
182
|
type RatchetResult = {
|
|
183
|
+
/**
|
|
184
|
+
* The keys this run measured for the FIRST time and wrote into the baseline. Never empty on a run
|
|
185
|
+
* that passed: an adopted key makes the run refuse, because a first number nobody has looked at
|
|
186
|
+
* is not a floor anybody agreed to.
|
|
187
|
+
*/
|
|
188
|
+
adopted: string[];
|
|
179
189
|
/**
|
|
180
190
|
* How many counters the config named. The denominator: `considered === measurements.length +
|
|
181
191
|
* refusals.length`, and a run whose two do not add up has lost one somewhere between the config
|
|
@@ -348,7 +358,7 @@ declare const runRatchet: ({ counters, cwd, tier, }: {
|
|
|
348
358
|
* the tier left out says so by name — printing a number for it would be the stale OK this exists
|
|
349
359
|
* to prevent.
|
|
350
360
|
*/
|
|
351
|
-
declare const formatReport: ({ measurements, refusals, rewritten }: RatchetResult) => string;
|
|
361
|
+
declare const formatReport: ({ adopted, measurements, refusals, rewritten, }: RatchetResult) => string;
|
|
352
362
|
declare const formatProve: ({ proofs, proven }: ProveResult) => string;
|
|
353
363
|
|
|
354
364
|
/**
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geonosis/ratchet",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.2",
|
|
4
4
|
"types": "./dist/index.d.ts",
|
|
5
5
|
"description": "Debt as a number that may only shrink — one ratchet, pluggable counters.",
|
|
6
6
|
"keywords": [
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
33
|
"files": [
|
|
34
|
+
"CHANGELOG.md",
|
|
34
35
|
"bin",
|
|
35
36
|
"dist"
|
|
36
37
|
],
|