@geonosis/ratchet 0.4.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +141 -8
- package/dist/{chunk-QVORWCUD.js → chunk-LSYVFUP4.js} +958 -420
- package/dist/cli.js +24 -2
- package/dist/index.d.ts +247 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -178,12 +178,30 @@ counter that reads a file rather than running a command (`lawLineCount`) prints
|
|
|
178
178
|
|
|
179
179
|
### `testFailures` reads the runner's summary, never the exit code
|
|
180
180
|
|
|
181
|
-
The counter looks for the runner's own count
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
181
|
+
The counter looks for the runner's own count and **refuses when it cannot parse one**. It never
|
|
182
|
+
reads the process exit code, because a test runner exiting 0 over a red suite is commoner than
|
|
183
|
+
anyone expects: `@cloudflare/vitest-pool-workers` 0.22 on vitest 4.1 exited 0 with failing tests on
|
|
184
|
+
every workerd suite of a consumer, and every gate that trusted the exit code reported green over red
|
|
185
|
+
for weeks. A refusal is loud and stops the run; a trusted 0 is silent and banks the red as a win.
|
|
186
|
+
|
|
187
|
+
**The lines it reads are the runner's summary lines, whole, and nothing else** — two dialects, both
|
|
188
|
+
captured from the binary rather than guessed at:
|
|
189
|
+
|
|
190
|
+
| Runner | The line | Read as |
|
|
191
|
+
| --- | --- | --- |
|
|
192
|
+
| vitest 3.2.7 | ` Tests 1 failed \| 1 passed (2)` | 1 |
|
|
193
|
+
| vitest 3.2.7 | ` Tests 3 passed (3)` · ` Tests 2 skipped (2)` | 0 |
|
|
194
|
+
| vitest 3.2.7 | ` Tests no tests` (a suite that threw before collecting) | **refused** |
|
|
195
|
+
| bun 1.4.0 | ` 1 fail` on its own line | 1 |
|
|
196
|
+
| bun 1.4.0 | ` 0 fail` | 0 |
|
|
197
|
+
|
|
198
|
+
Nothing else in the output counts. `Test Files 1 failed (1)` is a count of files, not of tests;
|
|
199
|
+
`(fail) one [11.71ms]` is bun naming one; `naming 4 failing test(s)` is some wrapper counting its
|
|
200
|
+
own findings. All three used to be read as a failure count — a **green run regressing a baseline**,
|
|
201
|
+
the false RED mirroring the false green above (backlog #44). A run that prints no summary line at
|
|
202
|
+
all is unmeasured, and the refusal names the two dialects so a third runner's output is a message
|
|
203
|
+
rather than a wrong number. Summaries **add up**: a command that invokes the runner twice prints
|
|
204
|
+
two, and reading the first and stopping banks the second suite's failures as a win.
|
|
187
205
|
|
|
188
206
|
For the same reason, **give every test package its own `testFailures` entry**, each with its own
|
|
189
207
|
`key`. One entry over one workspace measures one workspace; the suites it does not run are not zero
|
|
@@ -196,6 +214,38 @@ failures, they are unmeasured — and unmeasured reads exactly like green.
|
|
|
196
214
|
`examples/during-day.ratchet.json` is four test entries for that reason: `apps/web` plus the three
|
|
197
215
|
workerd packages that were previously outside every gate.
|
|
198
216
|
|
|
217
|
+
### …or the runner's JSON report, which is stronger
|
|
218
|
+
|
|
219
|
+
`report: "vitest-json"` reads the runner's own machine-readable answer instead of its prose:
|
|
220
|
+
|
|
221
|
+
```jsonc
|
|
222
|
+
{
|
|
223
|
+
"counter": "testFailures",
|
|
224
|
+
"key": "testFailuresApi",
|
|
225
|
+
"command": "cd apps/api && bunx vitest run --reporter=json --outputFile={report}",
|
|
226
|
+
"report": "vitest-json"
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
`{report}` is replaced with a path in a temp directory the counter makes and removes; give the entry
|
|
231
|
+
a `reportPath` instead when the report belongs somewhere your CI already collects. A command in this
|
|
232
|
+
mode with neither is refused, naming what is missing — a run whose report goes nowhere is a run
|
|
233
|
+
nobody can read.
|
|
234
|
+
|
|
235
|
+
The counter then reads `numFailedTests`, and **refuses** rather than returning a number when:
|
|
236
|
+
|
|
237
|
+
- the file is not there — a crash before the reporter wrote is not a pass, and it prints no summary
|
|
238
|
+
line either, so the summary parser had nothing to refuse on;
|
|
239
|
+
- the file is not JSON, or has no `numFailedTests` in it;
|
|
240
|
+
- the report says `success: false` and names **0** failing tests — the shape a pool that dies
|
|
241
|
+
mid-run writes. The run did not finish, so there is no number to bank.
|
|
242
|
+
|
|
243
|
+
The summary mode stays the default; nothing changes for an entry that does not ask for a report.
|
|
244
|
+
`--prove` proves all three: `testFailures` ships one probe per reading mode — `summary (vitest)`,
|
|
245
|
+
`summary (bun)`, `vitest-json` — and prints them by name (`PROVEN testFailuresApi (vitest-json)`),
|
|
246
|
+
because proving the mode nobody configured says nothing about the mode they did, and a summary
|
|
247
|
+
dialect nobody proved is a dialect nobody has been shown to read.
|
|
248
|
+
|
|
199
249
|
### `oxlintRule` counts a warned rule twice, on purpose
|
|
200
250
|
|
|
201
251
|
A rule parked at `"warn"` as ratcheted debt appears in two numbers: once inside `oxlintWarnings`,
|
|
@@ -206,12 +256,36 @@ That is not double-counting the total; the second key exists so the debt is visi
|
|
|
206
256
|
of hidden inside a lump sum that a different rule's warning could mask. dielime today: `oxlintWarnings`
|
|
207
257
|
12 → 135 when `no-raw-html-atoms` was armed, with its own key at 123.
|
|
208
258
|
|
|
259
|
+
### `--prove` also proves the lock
|
|
260
|
+
|
|
261
|
+
`--exclusive` is a claim about the machine, so it is measured on the machine, every `--prove`:
|
|
262
|
+
|
|
263
|
+
```
|
|
264
|
+
PROVEN exclusive: two runs of 400ms serialised, the second starting 161ms after the first finished
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
The self-test runs two children of this CLI over `--hold <ms>` — an instrument that takes the lock,
|
|
268
|
+
says when it started, waits, says when it finished, and gives the lock back — and requires the second
|
|
269
|
+
to have started after the first finished. Anything else prints `CANNOT FAIL exclusive: interleaved`
|
|
270
|
+
and exits 2. It uses a lock file of its own in a temp directory, so proving the lock never takes the
|
|
271
|
+
real one out from under the runs it exists to serialise.
|
|
272
|
+
|
|
273
|
+
It is here because the lock did not work for two releases and every gate was green throughout:
|
|
274
|
+
`openSync(path, 'wx')` is atomic about the NAME and not about the holder, and a run polling in that
|
|
275
|
+
window read an unparsable lock, called it stale, and took it. A lock nobody has watched fail has not
|
|
276
|
+
been shown to work — and this one guards the running time of every other gate.
|
|
277
|
+
|
|
209
278
|
## Running it where it will actually run
|
|
210
279
|
|
|
211
280
|
- **Counters run in the caller's environment.** They inherit the shell the ratchet was started in,
|
|
212
281
|
nothing more. If a child command needs `NODE_OPTIONS` — a TypeScript shim, a loader — put it on
|
|
213
282
|
the script that invokes `geonosis-ratchet`, not on the counter's own line, and not only in your
|
|
214
283
|
interactive shell.
|
|
284
|
+
- **A path in a counter's command must be absolute, or `--prove` cannot run it.** A probe runs in a
|
|
285
|
+
scratch directory with none of your repo in it, so a loader or shim named relatively — `node
|
|
286
|
+
--import ./scripts/ts5-shim.mjs …` — resolves to nothing there and the counter comes back
|
|
287
|
+
"command did not run". The command is the same string in both places; write the path as
|
|
288
|
+
`$PWD/scripts/ts5-shim.mjs` and it works in the repo and in the probe alike.
|
|
215
289
|
- **A `typecheckErrors` counter needs the same precondition CI gives it: build the workspace
|
|
216
290
|
packages first.** In a fresh worktree the `dist/*.d.ts` files do not exist yet, and `tsc` reports
|
|
217
291
|
a false +N of missing-module errors that has nothing to do with the change under test. `pnpm build
|
|
@@ -266,15 +340,60 @@ Every counter takes its `command` from the config, so the toolchain stays the re
|
|
|
266
340
|
| `oxlintErrors` / `oxlintWarnings` | findings under any shape oxlint prints — `--format=unix`, the compact `agent` format, the graphical `default` — cross-checked against the tool's own summary | `command`, `expectFormat` |
|
|
267
341
|
| `oxlintRule` | one named rule's findings, counted only on lines the run reported as findings and refused when none of them attributes itself readably; with `config`, after forcing the rule to `error` in a temp copy — `"warn"`, `"off"` and the `["off", { … }]` array form alike — so debt cannot grow behind a downgrade | `rule`, `config`, `command`, `expectFormat` |
|
|
268
342
|
| `typecheckErrors` | `error TS` occurrences, refusing when TS2305/TS2307 name a workspace package of this repo — an unbuilt sibling is a missing build, not debt | `command` |
|
|
269
|
-
| `testFailures` | the runner's own failure summary; throws when
|
|
343
|
+
| `testFailures` | the runner's own failure summary — or, with `report: "vitest-json"`, `numFailedTests` out of the JSON report the command wrote; throws when nothing is readable, when the report is absent, and when the report failed with nothing failing | `command`, `report`, `reportPath` |
|
|
270
344
|
| `unformattedFiles` | paths `--list-different` names that exist on disk | `command` |
|
|
271
345
|
| `cloneCount` | jscpd's `Found N clones` | `command` |
|
|
272
346
|
| `knipIssues` | the totals under knip's unused-* headings | `command`, `headings` |
|
|
273
347
|
| `boundaryIssues` | `N issues found` from a boundary scan | `command` |
|
|
274
|
-
| `archViolations` | lines matching a marker your own architecture scan prints | `command`, `match` |
|
|
348
|
+
| `archViolations` | lines matching a marker your own architecture scan prints, refusing a non-zero exit that printed none of them — a scan that could not run is not a clean scan | `command`, `match` |
|
|
275
349
|
| `sumOfCounts` | the total of one capture group across a per-file census (`grep -rc`) | `command`, `match` |
|
|
276
350
|
| `lawLineCount` | the lines of the law file — a ceiling that can only come down | `path` |
|
|
277
351
|
| `runtimeCodeShipped` | 0 when a change shipped runtime code, 1 when it shipped none | `command`, `patterns` |
|
|
352
|
+
| `disabledCiJobs` | lines of `if: false` across the workflow files — a job switched off to get a release through, still off. A condition that merely mentions `false` is not one. No workflows directory at all reads 0 | `dir` |
|
|
353
|
+
| `bundleBytes` | one integer out of whatever your sizing command printed, separators and all; with `match`, the group that pattern names rather than the last integer, refusing when it matches nothing. **Tolerates.** | `command`, `match`, `tolerance` |
|
|
354
|
+
| `fastTierMs` | `finishedAt − startedAt` from the gate report `geonosis-verify` wrote, refusing a report of another tier rather than timing the wrong gate. **Tolerates.** | `report`, `tier`, `tolerance` |
|
|
355
|
+
| `testsWithoutRunner` | workspaces holding `*.test.*`, `*.spec.*` or `__tests__/` with no `test` script — the suites nobody runs, which read exactly like suites that pass | `script` |
|
|
356
|
+
| `packagesWithoutTypecheck` | workspaces with no `typecheck` script | `script` |
|
|
357
|
+
| `walkFindings` | the defects in the report `geonosis-walk` wrote, over every page; with `classes`, only those classes, refusing a class the walk does not have. A missing or unparsable report is a refusal — the walk writes none when it could not run | `report`, `classes` |
|
|
358
|
+
|
|
359
|
+
### `tolerance`, and the two counters that accept one
|
|
360
|
+
|
|
361
|
+
Most counters count findings, where one more is one too many. `bundleBytes` and `fastTierMs` measure
|
|
362
|
+
something that moves on a dependency patch nobody chose, and a gate that fails on +40 bytes is a
|
|
363
|
+
gate that gets switched off within a week. `tolerance` is the fraction of the baseline such a number
|
|
364
|
+
may drift up before the ratchet calls it growth:
|
|
365
|
+
|
|
366
|
+
```jsonc
|
|
367
|
+
{ "counter": "bundleBytes", "command": "…", "tolerance": 0.1 } // +10 % is noise, +11 % is debt
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
**Only a counter that declares `tolerates` accepts one, and only `bundleBytes` and `fastTierMs` do.**
|
|
371
|
+
Anything else stops the run:
|
|
372
|
+
|
|
373
|
+
```
|
|
374
|
+
geonosis-ratchet: "oxlintErrors" does not accept a tolerance — only a counter that measures a
|
|
375
|
+
quantity declares one
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
That refusal is the point. A band around a findings count is not a tolerance, it is the gate turned
|
|
379
|
+
off from the config file: `"tolerance": 100` on `oxlintErrors` and the run still prints PASS. It is
|
|
380
|
+
law 2 — never downgrade a rule — wearing a friendlier name, and it would be the easiest edit in the
|
|
381
|
+
repo to get past a reviewer. Whether a number is a quantity or a count is the counter's to know, not
|
|
382
|
+
the config's, so the counter declares it.
|
|
383
|
+
|
|
384
|
+
It forgives noise upward, never downward: a shrink of any size is still a shrink and still lowers
|
|
385
|
+
the baseline, or the number would stop following the artefact down. A `tolerance` that is not a
|
|
386
|
+
non-negative number stops the run and says which entry it was — one that silently became `NaN`
|
|
387
|
+
would make every comparison false, which reads exactly like a counter that can never grow.
|
|
388
|
+
|
|
389
|
+
The `geonosis` Claude Code plugin puts any write that introduces or raises one in front of a human
|
|
390
|
+
(`permissionDecision: "ask"`): a band somebody chose and a band somebody's agent chose are not the
|
|
391
|
+
same thing.
|
|
392
|
+
|
|
393
|
+
Tolerance is also why a rewrite lowers only the numbers that **shrank**. While `held` implied
|
|
394
|
+
`now === baseline`, writing every measured key back was a harmless no-op; with a tolerance it is
|
|
395
|
+
not, and a counter that grew inside its tolerance would have had that growth laundered into its new
|
|
396
|
+
floor by the first unrelated win.
|
|
278
397
|
|
|
279
398
|
Two example configs from real repos live in `examples/` in the repository.
|
|
280
399
|
|
|
@@ -289,4 +408,18 @@ process.stdout.write(formatReport(result))
|
|
|
289
408
|
|
|
290
409
|
`counters` is a plain array, so a repo can add its own `{ id, run }` beside the built-ins.
|
|
291
410
|
|
|
411
|
+
## Two things this cannot see about itself
|
|
412
|
+
|
|
413
|
+
A baseline is lowered **in place** when a number shrinks; that is what locks a win in, and it is
|
|
414
|
+
also what lets a branch write any number it likes and stay green on every gate it runs. And a
|
|
415
|
+
workspace whose suite no `testFailures` entry covers is unmeasured, which reads exactly like green.
|
|
416
|
+
|
|
417
|
+
[`@geonosis/doctor`](https://www.npmjs.com/package/@geonosis/doctor) asks both from outside — the
|
|
418
|
+
baseline at HEAD against another ref, and which workspaces have a test script nothing reads a report
|
|
419
|
+
from:
|
|
420
|
+
|
|
421
|
+
```bash
|
|
422
|
+
npx geonosis-doctor --baseline-against origin/main --strict
|
|
423
|
+
```
|
|
424
|
+
|
|
292
425
|
Apache-2.0.
|