@geonosis/ratchet 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -1
- package/dist/{chunk-LSYVFUP4.js → chunk-HBM6F3GO.js} +108 -30
- package/dist/cli.js +72 -1
- package/dist/index.d.ts +27 -1
- package/dist/index.js +3 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -172,10 +172,17 @@ lines) is refused **even on a clean exit**. `npm ERR!` is not stripped — that
|
|
|
172
172
|
command never ran, and it must never read as zero. Unset is the default and changes nothing, so a repo that never opts in keeps exactly
|
|
173
173
|
the behaviour it had. Valid values: `unix`, `agent`, `default`.
|
|
174
174
|
|
|
175
|
-
When a number **grew**,
|
|
175
|
+
When a number **grew**, up to ten lines of that counter's command output print under the
|
|
176
176
|
`<-- REGRESSED` line, indented — so the report says what grew, not only that something did. A
|
|
177
177
|
counter that reads a file rather than running a command (`lawLineCount`) prints nothing extra.
|
|
178
178
|
|
|
179
|
+
The lines are the ones the counter says its number came from, not the tail of the run. `oxlintRule`
|
|
180
|
+
cites the findings attributed to ITS rule; `oxlintErrors` cites errors and `oxlintWarnings`
|
|
181
|
+
warnings. The tail was wrong for exactly the run that needs it most: a consumer's +2 on one rule
|
|
182
|
+
printed two unrelated **warnings** as its evidence, because those were the last lines oxlint wrote
|
|
183
|
+
and the two real errors sat higher up. A counter written outside this package that names nothing —
|
|
184
|
+
or that recognises nothing in a run — falls back to the tail, as before.
|
|
185
|
+
|
|
179
186
|
### `testFailures` reads the runner's summary, never the exit code
|
|
180
187
|
|
|
181
188
|
The counter looks for the runner's own count and **refuses when it cannot parse one**. It never
|
|
@@ -281,6 +288,19 @@ been shown to work — and this one guards the running time of every other gate.
|
|
|
281
288
|
nothing more. If a child command needs `NODE_OPTIONS` — a TypeScript shim, a loader — put it on
|
|
282
289
|
the script that invokes `geonosis-ratchet`, not on the counter's own line, and not only in your
|
|
283
290
|
interactive shell.
|
|
291
|
+
- **`NODE_OPTIONS` in the parent script + a counter that shells out to `pnpm` = the counter dies.**
|
|
292
|
+
The nested `pnpm` INHERITS the option. dielime preloads a TypeScript-5 shim through
|
|
293
|
+
`NODE_OPTIONS` in its `lint` script; run the ratchet from inside that script and the nested pnpm
|
|
294
|
+
goes looking for a `.pnpmfile.mjs` that is not there and exits non-zero. The ratchet refuses —
|
|
295
|
+
correctly, a command that cannot run is never a silent zero — but nothing in the message is near
|
|
296
|
+
the cause. Run the ratchet outside that script, or unset `NODE_OPTIONS` for the nested call:
|
|
297
|
+
|
|
298
|
+
```json
|
|
299
|
+
{ "counter": "archViolations", "command": "env -u NODE_OPTIONS pnpm --silent verify:arch" }
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
`geonosis-doctor --only drift` asks for this shape by name: a manifest script carrying
|
|
303
|
+
`NODE_OPTIONS` beside a counter whose command shells to `pnpm`.
|
|
284
304
|
- **A path in a counter's command must be absolute, or `--prove` cannot run it.** A probe runs in a
|
|
285
305
|
scratch directory with none of your repo in it, so a loader or shim named relatively — `node
|
|
286
306
|
--import ./scripts/ts5-shim.mjs …` — resolves to nothing there and the counter comes back
|
|
@@ -423,3 +443,10 @@ npx geonosis-doctor --baseline-against origin/main --strict
|
|
|
423
443
|
```
|
|
424
444
|
|
|
425
445
|
Apache-2.0.
|
|
446
|
+
|
|
447
|
+
## The pairing rule (#136)
|
|
448
|
+
|
|
449
|
+
A scoped fast tier (changed files only) is safe exactly when the full tier is TOTAL: every hard cap
|
|
450
|
+
— max-lines, bundle bytes, suppression counts — needs a full-tier counter watching the whole tree,
|
|
451
|
+
or it is a cap in prose that a file can sit over indefinitely. `oxlintRule`, `suppressionCount`,
|
|
452
|
+
`bundleBytes` and friends exist to be that counter.
|
|
@@ -124,6 +124,14 @@ var loadConfig = (cwd) => {
|
|
|
124
124
|
}
|
|
125
125
|
return { baseline: parsed.baseline ?? "gate-baseline.json", counters: parsed.counters };
|
|
126
126
|
};
|
|
127
|
+
var resolveBaseline = (cwd) => {
|
|
128
|
+
try {
|
|
129
|
+
const parsed = JSON.parse(readFileSync2(resolve(cwd, CONFIG_FILE), "utf8"));
|
|
130
|
+
return typeof parsed.baseline === "string" ? parsed.baseline : "gate-baseline.json";
|
|
131
|
+
} catch {
|
|
132
|
+
return "gate-baseline.json";
|
|
133
|
+
}
|
|
134
|
+
};
|
|
127
135
|
|
|
128
136
|
// src/core/types.ts
|
|
129
137
|
var CounterError = class extends Error {
|
|
@@ -136,23 +144,21 @@ var CounterError = class extends Error {
|
|
|
136
144
|
};
|
|
137
145
|
|
|
138
146
|
// src/core/shell.ts
|
|
139
|
-
import {
|
|
147
|
+
import { spawnSync } from "child_process";
|
|
140
148
|
var ANSI = /\[[0-9;]*m/g;
|
|
141
149
|
var runCommand = (cwd, counterId, env) => (command) => {
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
${command}
|
|
145
|
-
) 2>&1`, {
|
|
150
|
+
{
|
|
151
|
+
const run = spawnSync(command, {
|
|
146
152
|
cwd,
|
|
147
153
|
encoding: "utf8",
|
|
148
154
|
env,
|
|
149
155
|
maxBuffer: 64 * 1024 * 1024,
|
|
156
|
+
shell: true,
|
|
150
157
|
stdio: ["ignore", "pipe", "pipe"]
|
|
151
158
|
});
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const failed =
|
|
155
|
-
const output = `${failed.stdout ?? ""}${failed.stderr ?? ""}`.replaceAll(ANSI, "");
|
|
159
|
+
const output = `${run.stdout ?? ""}${run.stderr ?? ""}`.replaceAll(ANSI, "");
|
|
160
|
+
if (run.error === void 0 && (run.status ?? 0) === 0) return { code: 0, output };
|
|
161
|
+
const failed = { status: run.status ?? 1, stderr: "", stdout: "" };
|
|
156
162
|
const code = failed.status ?? -1;
|
|
157
163
|
if (code === 126 || code === 127 || code === -1) {
|
|
158
164
|
throw new CounterError(
|
|
@@ -334,10 +340,14 @@ var runProve = async ({
|
|
|
334
340
|
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
335
341
|
import { resolve as resolve4 } from "path";
|
|
336
342
|
var EVIDENCE_LINES = 10;
|
|
337
|
-
var recorded = (run) => {
|
|
343
|
+
var recorded = (counter, params, run) => {
|
|
338
344
|
let output = "";
|
|
345
|
+
const trimmed = (lines) => lines.map((line) => line.trimEnd()).filter((line) => line !== "").slice(-EVIDENCE_LINES);
|
|
339
346
|
return {
|
|
340
|
-
|
|
347
|
+
cited: () => {
|
|
348
|
+
const counted = counter.evidence === void 0 ? [] : counter.evidence({ output, params });
|
|
349
|
+
return trimmed(counted.length > 0 ? counted : output.split("\n"));
|
|
350
|
+
},
|
|
341
351
|
run: (command) => {
|
|
342
352
|
const result = run(command);
|
|
343
353
|
output = result.output;
|
|
@@ -398,12 +408,12 @@ var runRatchet = async ({
|
|
|
398
408
|
);
|
|
399
409
|
}
|
|
400
410
|
const tolerance = toleranceOf(entry, key, counter);
|
|
401
|
-
const recorder = recorded(runCommand(cwd, entry.counter));
|
|
411
|
+
const recorder = recorded(counter, entry, runCommand(cwd, entry.counter));
|
|
402
412
|
const now = await counter.run({ cwd, key, params: entry, run: recorder.run });
|
|
403
413
|
const verdict = verdictOf(now, limit, tolerance);
|
|
404
414
|
measurements.push({
|
|
405
415
|
baseline: limit,
|
|
406
|
-
evidence: verdict === "grew" ? recorder.
|
|
416
|
+
evidence: verdict === "grew" ? recorder.cited() : [],
|
|
407
417
|
key,
|
|
408
418
|
now,
|
|
409
419
|
verdict
|
|
@@ -646,7 +656,7 @@ var fastTierMs = {
|
|
|
646
656
|
expect: 4500,
|
|
647
657
|
input: (dir) => plant(
|
|
648
658
|
dir,
|
|
649
|
-
|
|
659
|
+
".geonosis/gate-report.fast.json",
|
|
650
660
|
JSON.stringify({
|
|
651
661
|
finishedAt: "2026-08-30T10:00:04.500Z",
|
|
652
662
|
ok: true,
|
|
@@ -657,8 +667,10 @@ var fastTierMs = {
|
|
|
657
667
|
)
|
|
658
668
|
},
|
|
659
669
|
run: async ({ cwd, params }) => {
|
|
660
|
-
const relative = stringParam("fastTierMs", params, "report", DEFAULT_REPORT);
|
|
661
670
|
const wanted = stringParam("fastTierMs", params, "tier", "fast");
|
|
671
|
+
const own = `.geonosis/gate-report.${wanted}.json`;
|
|
672
|
+
const asked = stringParam("fastTierMs", params, "report", "");
|
|
673
|
+
const relative = asked !== "" ? asked : existsSync5(resolve7(cwd, own)) ? own : DEFAULT_REPORT;
|
|
662
674
|
const path = resolve7(cwd, relative);
|
|
663
675
|
if (!existsSync5(path)) {
|
|
664
676
|
throw new CounterError(
|
|
@@ -678,7 +690,7 @@ var fastTierMs = {
|
|
|
678
690
|
if (report.tier !== wanted) {
|
|
679
691
|
throw new CounterError(
|
|
680
692
|
"fastTierMs",
|
|
681
|
-
`${relative} is a report of tier "${String(report.tier)}", not "${wanted}"`
|
|
693
|
+
`${relative} is a report of tier "${String(report.tier)}", not "${wanted}" \u2014 run \`geonosis-verify ${wanted}\`, which records ${own}`
|
|
682
694
|
);
|
|
683
695
|
}
|
|
684
696
|
const spent = Date.parse(String(report.finishedAt)) - Date.parse(String(report.startedAt));
|
|
@@ -729,12 +741,32 @@ var UNIX_FINDING = /^\S[^\n]*:\d+:\d+: .*\[(Error|Warning)\/[^\]\n]+\]$/gm;
|
|
|
729
741
|
var UNIX_SUMMARY = /^(\d+) problems?$/m;
|
|
730
742
|
var AGENT_FINDING = /^\S[^\n]*:\d+:\d+: (error|warning) /gm;
|
|
731
743
|
var DEFAULT_SUMMARY = /^Found (\d+) warnings? and (\d+) errors?\.$/m;
|
|
732
|
-
var DEFAULT_FINDING = /^\s*[x!] [^\s(]+\([^)\n]+\): /m;
|
|
744
|
+
var DEFAULT_FINDING = /^\s*([x!]) [^\s(]+\([^)\n]+\): /m;
|
|
733
745
|
var FINDING_SHAPES = [UNIX_FINDING, AGENT_FINDING, DEFAULT_FINDING].map(
|
|
734
746
|
(shape) => new RegExp(shape.source)
|
|
735
747
|
);
|
|
736
748
|
var RULE_TOKEN = /\([^()\n]+\)/;
|
|
737
|
-
var
|
|
749
|
+
var SEVERITY_BY_TOKEN = {
|
|
750
|
+
"!": "warning",
|
|
751
|
+
Error: "error",
|
|
752
|
+
error: "error",
|
|
753
|
+
Warning: "warning",
|
|
754
|
+
warning: "warning",
|
|
755
|
+
x: "error"
|
|
756
|
+
};
|
|
757
|
+
var severityOf = (line) => {
|
|
758
|
+
for (const shape of FINDING_SHAPES) {
|
|
759
|
+
const token = shape.exec(line)?.[1];
|
|
760
|
+
if (token !== void 0) return SEVERITY_BY_TOKEN[token];
|
|
761
|
+
}
|
|
762
|
+
return void 0;
|
|
763
|
+
};
|
|
764
|
+
var findingLinesOf = (output) => output.split("\n").filter((line) => severityOf(line) !== void 0);
|
|
765
|
+
var linesOfSeverity = (output, want) => output.split("\n").filter((line) => severityOf(line) === want);
|
|
766
|
+
var linesOfRule = (output, rule) => {
|
|
767
|
+
const named = new RegExp(`\\(${escapeForRegex(rule)}\\)`);
|
|
768
|
+
return findingLinesOf(output).filter((line) => named.test(line));
|
|
769
|
+
};
|
|
738
770
|
var FORMATS = ["agent", "default", "unix"];
|
|
739
771
|
var isFormat = (value) => typeof value === "string" && FORMATS.some((one) => one === value);
|
|
740
772
|
var expectedFormat = (counter, params) => {
|
|
@@ -793,6 +825,7 @@ ${output.trim()}`);
|
|
|
793
825
|
return refuse2(`the tool exited ${code} and printed no findings and no summary`);
|
|
794
826
|
};
|
|
795
827
|
var oxlintErrors = {
|
|
828
|
+
evidence: ({ output }) => linesOfSeverity(output, "error"),
|
|
796
829
|
id: "oxlintErrors",
|
|
797
830
|
probe: {
|
|
798
831
|
...oxlintProbe("error", "typescript/no-explicit-any", ONE_ANY),
|
|
@@ -806,6 +839,7 @@ var oxlintErrors = {
|
|
|
806
839
|
}
|
|
807
840
|
};
|
|
808
841
|
var oxlintWarnings = {
|
|
842
|
+
evidence: ({ output }) => linesOfSeverity(output, "warning"),
|
|
809
843
|
id: "oxlintWarnings",
|
|
810
844
|
// A warning, so the run exits 0 — the window `expectFormat` exists to close is also the window a
|
|
811
845
|
// probe has to survive.
|
|
@@ -830,10 +864,12 @@ var countRule = (result, rule, expect) => {
|
|
|
830
864
|
${result.output.trim()}`
|
|
831
865
|
);
|
|
832
866
|
}
|
|
833
|
-
|
|
834
|
-
return lines.filter((line) => named.test(line)).length;
|
|
867
|
+
return linesOfRule(result.output, rule).length;
|
|
835
868
|
};
|
|
836
869
|
var oxlintRule = {
|
|
870
|
+
// The rule is a required param, so a run that got here always has one; a citation is not the
|
|
871
|
+
// place to raise that, and the report falls back to the tail rather than losing the regression.
|
|
872
|
+
evidence: ({ output, params }) => typeof params.rule === "string" ? linesOfRule(output, params.rule) : [],
|
|
837
873
|
id: "oxlintRule",
|
|
838
874
|
// The probe names its OWN rule: which rule a repo tracks is its business, and a probe that had to
|
|
839
875
|
// make the repo's rule fire would need the repo's plugin loadable from a scratch directory.
|
|
@@ -1035,10 +1071,50 @@ var sumOfCounts = {
|
|
|
1035
1071
|
}
|
|
1036
1072
|
};
|
|
1037
1073
|
|
|
1074
|
+
// src/counters/suppressions.ts
|
|
1075
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync9 } from "fs";
|
|
1076
|
+
import { join as join7, resolve as resolve11 } from "path";
|
|
1077
|
+
var CODE_FILE = /\.(?:ts|tsx|js|jsx|mjs|cjs|mts|cts|vue|svelte|astro)$/;
|
|
1078
|
+
var SKIP3 = /^(?:node_modules|dist|coverage|\.[^.]|__fixtures__)/;
|
|
1079
|
+
var SUPPRESSION = /(?:eslint|oxlint|biome)-(?:disable|ignore)(?:-next-line|-line)?|@ts-expect-error|@ts-ignore/g;
|
|
1080
|
+
var countIn = (dir, depth = 12) => {
|
|
1081
|
+
let entries;
|
|
1082
|
+
try {
|
|
1083
|
+
entries = readdirSync4(dir, { withFileTypes: true });
|
|
1084
|
+
} catch {
|
|
1085
|
+
return 0;
|
|
1086
|
+
}
|
|
1087
|
+
let found = 0;
|
|
1088
|
+
for (const entry of entries) {
|
|
1089
|
+
if (SKIP3.test(entry.name)) continue;
|
|
1090
|
+
if (entry.isDirectory()) {
|
|
1091
|
+
if (depth > 0) found += countIn(join7(dir, entry.name), depth - 1);
|
|
1092
|
+
continue;
|
|
1093
|
+
}
|
|
1094
|
+
if (!CODE_FILE.test(entry.name)) continue;
|
|
1095
|
+
try {
|
|
1096
|
+
found += readFileSync9(join7(dir, entry.name), "utf8").match(SUPPRESSION)?.length ?? 0;
|
|
1097
|
+
} catch {
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
return found;
|
|
1101
|
+
};
|
|
1102
|
+
var suppressionCount = {
|
|
1103
|
+
id: "suppressionCount",
|
|
1104
|
+
probe: {
|
|
1105
|
+
expect: 2,
|
|
1106
|
+
input: (dir) => plant(dir, "src/planted.ts", "// eslint-disable-next-line x\n// @ts-expect-error y\n")
|
|
1107
|
+
},
|
|
1108
|
+
run: async ({ cwd, params }) => {
|
|
1109
|
+
const roots = stringsParam(params, "roots", ["."]);
|
|
1110
|
+
return roots.reduce((sum, root) => sum + countIn(resolve11(cwd, root)), 0);
|
|
1111
|
+
}
|
|
1112
|
+
};
|
|
1113
|
+
|
|
1038
1114
|
// src/counters/tests.ts
|
|
1039
|
-
import { existsSync as existsSync9, mkdtempSync as mkdtempSync2, readFileSync as
|
|
1115
|
+
import { existsSync as existsSync9, mkdtempSync as mkdtempSync2, readFileSync as readFileSync10, rmSync as rmSync4 } from "fs";
|
|
1040
1116
|
import { tmpdir as tmpdir2 } from "os";
|
|
1041
|
-
import { join as
|
|
1117
|
+
import { join as join8, resolve as resolve12 } from "path";
|
|
1042
1118
|
var COUNTER = "testFailures";
|
|
1043
1119
|
var VITEST_LINE = /^\s*Tests {2,}(.+?)\s*$/;
|
|
1044
1120
|
var VITEST_TOTAL = /\(\d+\)$/;
|
|
@@ -1078,7 +1154,7 @@ var fromReport = (path) => {
|
|
|
1078
1154
|
}
|
|
1079
1155
|
let report;
|
|
1080
1156
|
try {
|
|
1081
|
-
report = JSON.parse(
|
|
1157
|
+
report = JSON.parse(readFileSync10(path, "utf8"));
|
|
1082
1158
|
} catch (error) {
|
|
1083
1159
|
throw new CounterError(
|
|
1084
1160
|
COUNTER,
|
|
@@ -1102,14 +1178,14 @@ var fromReport = (path) => {
|
|
|
1102
1178
|
};
|
|
1103
1179
|
var reportPathFor = (cwd, params, command) => {
|
|
1104
1180
|
const named = params.reportPath;
|
|
1105
|
-
if (typeof named === "string" && named !== "") return { own: false, path:
|
|
1181
|
+
if (typeof named === "string" && named !== "") return { own: false, path: resolve12(cwd, named) };
|
|
1106
1182
|
if (!command.includes(PLACEHOLDER)) {
|
|
1107
1183
|
throw new CounterError(
|
|
1108
1184
|
COUNTER,
|
|
1109
1185
|
`report: "${VITEST_JSON}" needs somewhere to put the report \u2014 write ${PLACEHOLDER} into the command (--outputFile=${PLACEHOLDER}) or give the entry a "reportPath"`
|
|
1110
1186
|
);
|
|
1111
1187
|
}
|
|
1112
|
-
return { own: true, path:
|
|
1188
|
+
return { own: true, path: join8(mkdtempSync2(join8(tmpdir2(), "geonosis-report-")), "report.json") };
|
|
1113
1189
|
};
|
|
1114
1190
|
var testFailures = {
|
|
1115
1191
|
id: COUNTER,
|
|
@@ -1158,7 +1234,7 @@ var testFailures = {
|
|
|
1158
1234
|
run(command.replaceAll(PLACEHOLDER, path));
|
|
1159
1235
|
return fromReport(path);
|
|
1160
1236
|
} finally {
|
|
1161
|
-
if (own) rmSync4(
|
|
1237
|
+
if (own) rmSync4(join8(path, ".."), { force: true, recursive: true });
|
|
1162
1238
|
}
|
|
1163
1239
|
}
|
|
1164
1240
|
};
|
|
@@ -1198,8 +1274,8 @@ var typecheckErrors = {
|
|
|
1198
1274
|
};
|
|
1199
1275
|
|
|
1200
1276
|
// src/counters/walk.ts
|
|
1201
|
-
import { existsSync as existsSync10, readFileSync as
|
|
1202
|
-
import { resolve as
|
|
1277
|
+
import { existsSync as existsSync10, readFileSync as readFileSync11 } from "fs";
|
|
1278
|
+
import { resolve as resolve13 } from "path";
|
|
1203
1279
|
var DEFAULT_REPORT2 = ".geonosis/walk-report.json";
|
|
1204
1280
|
var CLASSES = /* @__PURE__ */ new Set([
|
|
1205
1281
|
"buy-box-above-fold",
|
|
@@ -1243,7 +1319,7 @@ var walkFindings = {
|
|
|
1243
1319
|
},
|
|
1244
1320
|
run: async ({ cwd, params }) => {
|
|
1245
1321
|
const relative = stringParam("walkFindings", params, "report", DEFAULT_REPORT2);
|
|
1246
|
-
const path =
|
|
1322
|
+
const path = resolve13(cwd, relative);
|
|
1247
1323
|
if (!existsSync10(path)) {
|
|
1248
1324
|
throw new CounterError(
|
|
1249
1325
|
"walkFindings",
|
|
@@ -1252,7 +1328,7 @@ var walkFindings = {
|
|
|
1252
1328
|
}
|
|
1253
1329
|
let report;
|
|
1254
1330
|
try {
|
|
1255
|
-
report = JSON.parse(
|
|
1331
|
+
report = JSON.parse(readFileSync11(path, "utf8"));
|
|
1256
1332
|
} catch (error) {
|
|
1257
1333
|
throw new CounterError(
|
|
1258
1334
|
"walkFindings",
|
|
@@ -1282,6 +1358,7 @@ var COUNTERS = [
|
|
|
1282
1358
|
fastTierMs,
|
|
1283
1359
|
knipIssues,
|
|
1284
1360
|
lawLineCount,
|
|
1361
|
+
suppressionCount,
|
|
1285
1362
|
oxlintErrors,
|
|
1286
1363
|
oxlintRule,
|
|
1287
1364
|
oxlintWarnings,
|
|
@@ -1361,6 +1438,7 @@ export {
|
|
|
1361
1438
|
CONFIG_FILE,
|
|
1362
1439
|
keyOf,
|
|
1363
1440
|
loadConfig,
|
|
1441
|
+
resolveBaseline,
|
|
1364
1442
|
CounterError,
|
|
1365
1443
|
runCommand,
|
|
1366
1444
|
runProve,
|
package/dist/cli.js
CHANGED
|
@@ -5,9 +5,80 @@ import {
|
|
|
5
5
|
formatReport,
|
|
6
6
|
runProve,
|
|
7
7
|
runRatchet
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-HBM6F3GO.js";
|
|
9
9
|
|
|
10
10
|
// src/cli.ts
|
|
11
|
+
import process from "process";
|
|
12
|
+
var USAGE = `geonosis-ratchet [--cwd <dir>] [--tier <name>] [--prove] [--exclusive]
|
|
13
|
+
|
|
14
|
+
Debt as a number that may only shrink. Runs the counters named in geonosis.ratchet.json, compares
|
|
15
|
+
each against gate-baseline.json, and REWRITES the baseline down when a number shrank \u2014 so the win is
|
|
16
|
+
locked in the same commit that earned it.
|
|
17
|
+
|
|
18
|
+
--cwd <dir> the repo to measure (default: the working directory)
|
|
19
|
+
--tier <name> only the counters in that tier (default: every counter)
|
|
20
|
+
--prove plant a finding for each counter and require it to be read
|
|
21
|
+
--exclusive hold the machine-wide heavy lock for the run
|
|
22
|
+
--exclusive-timeout <secs> how long to wait for that lock
|
|
23
|
+
--hold <ms> take the lock, wait, give it back \u2014 the --prove self-test's slow thing
|
|
24
|
+
--help, -h this text
|
|
25
|
+
|
|
26
|
+
Exit codes: 0 no counter grew \xB7 1 a counter grew \xB7 2 the run could not measure.
|
|
27
|
+
|
|
28
|
+
Every flag it does not know is REFUSED. This bin used to let anything unrecognised fall through to a
|
|
29
|
+
full measurement, so \`--help\` ran the whole ratchet \u2014 a run that can rewrite the baseline, started
|
|
30
|
+
by a typo.`;
|
|
31
|
+
var KNOWN = /* @__PURE__ */ new Set([
|
|
32
|
+
"--cwd",
|
|
33
|
+
"--exclusive",
|
|
34
|
+
"--exclusive-timeout",
|
|
35
|
+
"--hold",
|
|
36
|
+
"--prove",
|
|
37
|
+
"--tier"
|
|
38
|
+
]);
|
|
39
|
+
var TAKES_A_VALUE = /* @__PURE__ */ new Set(["--cwd", "--exclusive-timeout", "--hold", "--tier"]);
|
|
40
|
+
var CONFIG_SHAPE = `geonosis-ratchet reads geonosis.ratchet.json and gate-baseline.json
|
|
41
|
+
|
|
42
|
+
geonosis.ratchet.json
|
|
43
|
+
baseline string? the file holding the numbers (default "gate-baseline.json")
|
|
44
|
+
counters CounterEntry[] required, and every entry is:
|
|
45
|
+
counter string required \u2014 the id of a counter this build ships
|
|
46
|
+
key string? the baseline key it writes (default: the counter id).
|
|
47
|
+
Two entries writing one key are refused: the loser's debt
|
|
48
|
+
would vanish into the baseline
|
|
49
|
+
tiers string[]? the tiers it runs in (default: all of them). A malformed
|
|
50
|
+
list is refused, never quietly matched to no tier
|
|
51
|
+
\u2026 each counter reads its own further keys
|
|
52
|
+
|
|
53
|
+
gate-baseline.json
|
|
54
|
+
<key> number one number per counter key. Written DOWN in place when a
|
|
55
|
+
number shrank, so the win lands in the commit that earned it`;
|
|
56
|
+
var argv = process.argv.slice(2);
|
|
57
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
58
|
+
process.stdout.write(`${USAGE}
|
|
59
|
+
`);
|
|
60
|
+
process.exit(0);
|
|
61
|
+
}
|
|
62
|
+
if (argv.includes("--print-config-shape")) {
|
|
63
|
+
process.stdout.write(`${CONFIG_SHAPE}
|
|
64
|
+
`);
|
|
65
|
+
process.exit(0);
|
|
66
|
+
}
|
|
67
|
+
var refuse = (message) => {
|
|
68
|
+
process.stderr.write(`geonosis-ratchet: ${message}
|
|
69
|
+
`);
|
|
70
|
+
process.exit(2);
|
|
71
|
+
};
|
|
72
|
+
for (let at = 0; at < argv.length; at += 1) {
|
|
73
|
+
const arg = argv[at];
|
|
74
|
+
if (KNOWN.has(arg)) {
|
|
75
|
+
if (TAKES_A_VALUE.has(arg)) at += 1;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
refuse(
|
|
79
|
+
`${arg} is not an option it takes. Run geonosis-ratchet --help for the ones it does \u2014 an unrecognised argument is refused rather than measured, because a run here can rewrite the baseline.`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
11
82
|
var numberAfter = (flag) => {
|
|
12
83
|
const at = process.argv.indexOf(flag);
|
|
13
84
|
return at === -1 ? void 0 : Number(process.argv[at + 1] ?? Number.NaN);
|
package/dist/index.d.ts
CHANGED
|
@@ -41,6 +41,22 @@ type CounterProbe = {
|
|
|
41
41
|
params?: Record<string, unknown>;
|
|
42
42
|
};
|
|
43
43
|
type Counter = {
|
|
44
|
+
/**
|
|
45
|
+
* Which lines of a run this counter actually COUNTED, for the report to cite when the number
|
|
46
|
+
* grew. Absent means the tail of the run is shown, which is right for a counter whose tool prints
|
|
47
|
+
* nothing but findings and wrong for every counter that reads one rule or one severity out of a
|
|
48
|
+
* mixed run: the tail is whatever printed LAST, and dielime got two unrelated warnings under a
|
|
49
|
+
* +2 on one rule while the two real errors sat further up.
|
|
50
|
+
*
|
|
51
|
+
* It takes the output rather than being handed the lines during the run so that it stays a pure
|
|
52
|
+
* function of what the tool said — the same reading the count was taken from, testable on its
|
|
53
|
+
* own. Returning nothing falls back to the tail: a citation nobody could make is no reason to
|
|
54
|
+
* show the reader nothing at all.
|
|
55
|
+
*/
|
|
56
|
+
evidence?: (context: {
|
|
57
|
+
output: string;
|
|
58
|
+
params: Record<string, unknown>;
|
|
59
|
+
}) => string[];
|
|
44
60
|
id: string;
|
|
45
61
|
/**
|
|
46
62
|
* Whether this counter's number is a measured QUANTITY — bytes, milliseconds — rather than a
|
|
@@ -151,6 +167,16 @@ declare const CONFIG_FILE = "geonosis.ratchet.json";
|
|
|
151
167
|
/** The baseline key an entry writes. Defaults to the counter's own id. */
|
|
152
168
|
declare const keyOf: (entry: CounterConfig) => string;
|
|
153
169
|
declare const loadConfig: (cwd: string) => RatchetConfig;
|
|
170
|
+
/**
|
|
171
|
+
* #120: the ONE answer to "where does the baseline live". Three tools guessed instead of asking —
|
|
172
|
+
* rails' deny rail rendered `<root>/gate-baseline.json` over a repo whose baseline is
|
|
173
|
+
* `scripts/gate-baseline.json` (measured on sandbox-exec: the tampering write LANDED), and mcp's
|
|
174
|
+
* preamble said "no gate-baseline.json here" over twenty ratcheted counters. Anything that SPEAKS
|
|
175
|
+
* about the baseline resolves it here; `loadConfig` stays the strict door for anything that RUNS
|
|
176
|
+
* counters. Never throws: a missing or unreadable config means the default, because naming the
|
|
177
|
+
* baseline must work in repos that have not adopted the ratchet yet.
|
|
178
|
+
*/
|
|
179
|
+
declare const resolveBaseline: (cwd: string) => string;
|
|
154
180
|
|
|
155
181
|
declare const COUNTERS: Counter[];
|
|
156
182
|
declare const counterById: (id: string) => Counter;
|
|
@@ -244,4 +270,4 @@ declare const formatProve: ({ proofs, proven }: ProveResult) => string;
|
|
|
244
270
|
*/
|
|
245
271
|
declare const runCommand: (cwd: string, counterId: string, env?: NodeJS.ProcessEnv) => (command: string) => CommandResult;
|
|
246
272
|
|
|
247
|
-
export { CONFIG_FILE, COUNTERS, type CommandResult, type Counter, type CounterConfig, type CounterContext, CounterError, type CounterProbe, type Holder, type Measurement, type Proof, type ProveResult, type RatchetConfig, type RatchetResult, type Verdict, acquireExclusive, counterById, formatProve, formatReport, heavyLockPath, keyOf, loadConfig, runCommand, runProve, runRatchet };
|
|
273
|
+
export { CONFIG_FILE, COUNTERS, type CommandResult, type Counter, type CounterConfig, type CounterContext, CounterError, type CounterProbe, type Holder, type Measurement, type Proof, type ProveResult, type RatchetConfig, type RatchetResult, type Verdict, acquireExclusive, counterById, formatProve, formatReport, heavyLockPath, keyOf, loadConfig, resolveBaseline, runCommand, runProve, runRatchet };
|
package/dist/index.js
CHANGED
|
@@ -9,10 +9,11 @@ import {
|
|
|
9
9
|
heavyLockPath,
|
|
10
10
|
keyOf,
|
|
11
11
|
loadConfig,
|
|
12
|
+
resolveBaseline,
|
|
12
13
|
runCommand,
|
|
13
14
|
runProve,
|
|
14
15
|
runRatchet
|
|
15
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-HBM6F3GO.js";
|
|
16
17
|
export {
|
|
17
18
|
CONFIG_FILE,
|
|
18
19
|
COUNTERS,
|
|
@@ -24,6 +25,7 @@ export {
|
|
|
24
25
|
heavyLockPath,
|
|
25
26
|
keyOf,
|
|
26
27
|
loadConfig,
|
|
28
|
+
resolveBaseline,
|
|
27
29
|
runCommand,
|
|
28
30
|
runProve,
|
|
29
31
|
runRatchet
|