@geonosis/ratchet 1.3.0 → 1.4.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/dist/{chunk-HBM6F3GO.js → chunk-ESN6ULZZ.js} +204 -72
- package/dist/cli.js +26 -2
- package/dist/index.d.ts +105 -2
- package/dist/index.js +13 -3
- package/package.json +1 -1
|
@@ -1,13 +1,76 @@
|
|
|
1
|
+
// src/core/envelope.ts
|
|
2
|
+
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
+
import { dirname, join } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
var ENVELOPES_DIR = ".geonosis/envelopes";
|
|
6
|
+
var envelopePath = (root, tool) => join(root, ENVELOPES_DIR, `${tool}.json`);
|
|
7
|
+
var UnbalancedEnvelope = class extends Error {
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "UnbalancedEnvelope";
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
var isCount = (value) => Number.isSafeInteger(value) && value >= 0;
|
|
14
|
+
var unbalancedMessage = (envelope, next) => `${envelope.tool}: considered ${envelope.considered} but accounts for ${envelope.read + envelope.refused.length + envelope.excused.length} \u2014 ${envelope.read} read + ${envelope.refused.length} refused + ${envelope.excused.length} excused. A run that has lost count of its own inputs cannot say what it measured, so no verdict was rendered and no envelope was written. Next: ${next}`;
|
|
15
|
+
var writeEnvelope = ({
|
|
16
|
+
envelope,
|
|
17
|
+
next,
|
|
18
|
+
root
|
|
19
|
+
}) => {
|
|
20
|
+
if (envelope.tool.trim() === "") {
|
|
21
|
+
throw new UnbalancedEnvelope(
|
|
22
|
+
`an envelope with no tool name cannot be filed or reported against. Next: ${next}`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
if (envelope.version.trim() === "") {
|
|
26
|
+
throw new UnbalancedEnvelope(
|
|
27
|
+
`${envelope.tool}: an envelope that cannot name the build that wrote it dates nothing, and a stale one reads exactly like a fresh one. Next: ${next}`
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
if (!isCount(envelope.considered) || !isCount(envelope.read)) {
|
|
31
|
+
throw new UnbalancedEnvelope(
|
|
32
|
+
`${envelope.tool}: considered ${envelope.considered} and read ${envelope.read} \u2014 a census is a whole number of things, and arithmetic over anything else balances by accident. Next: ${next}`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
if (envelope.considered !== envelope.read + envelope.refused.length + envelope.excused.length) {
|
|
36
|
+
throw new UnbalancedEnvelope(unbalancedMessage(envelope, next));
|
|
37
|
+
}
|
|
38
|
+
const at = envelopePath(root, envelope.tool);
|
|
39
|
+
mkdirSync(dirname(at), { recursive: true });
|
|
40
|
+
writeFileSync(at, `${JSON.stringify(envelope, void 0, 2)}
|
|
41
|
+
`);
|
|
42
|
+
return at;
|
|
43
|
+
};
|
|
44
|
+
var UNKNOWN = "unknown";
|
|
45
|
+
var versionIn = (dir) => {
|
|
46
|
+
try {
|
|
47
|
+
const manifest = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
|
|
48
|
+
return typeof manifest.version === "string" ? manifest.version : void 0;
|
|
49
|
+
} catch {
|
|
50
|
+
return void 0;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
var versionOf = (moduleUrl) => {
|
|
54
|
+
let dir = dirname(fileURLToPath(moduleUrl));
|
|
55
|
+
for (; ; ) {
|
|
56
|
+
const found = versionIn(dir);
|
|
57
|
+
if (found !== void 0) return found;
|
|
58
|
+
const up = dirname(dir);
|
|
59
|
+
if (up === dir) return UNKNOWN;
|
|
60
|
+
dir = up;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
1
64
|
// src/core/lock.ts
|
|
2
65
|
import { randomUUID } from "crypto";
|
|
3
|
-
import { linkSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
66
|
+
import { linkSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
4
67
|
import { homedir } from "os";
|
|
5
|
-
import { dirname, join } from "path";
|
|
6
|
-
var heavyLockPath = () => process.env.GEONOSIS_HEAVY_LOCK ??
|
|
68
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
69
|
+
var heavyLockPath = () => process.env.GEONOSIS_HEAVY_LOCK ?? join2(homedir(), ".cache", "geonosis", "heavy.lock");
|
|
7
70
|
var sleep = (ms) => new Promise((done) => setTimeout(done, ms));
|
|
8
71
|
var holderOf = (path) => {
|
|
9
72
|
try {
|
|
10
|
-
const parsed = JSON.parse(
|
|
73
|
+
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
11
74
|
return typeof parsed.pid === "number" ? {
|
|
12
75
|
cwd: parsed.cwd ?? "somewhere",
|
|
13
76
|
pid: parsed.pid,
|
|
@@ -31,7 +94,7 @@ var heldFor = (holder) => {
|
|
|
31
94
|
return `${Math.round((Date.now() - since) / 1e3)}s`;
|
|
32
95
|
};
|
|
33
96
|
var write = (path) => {
|
|
34
|
-
|
|
97
|
+
mkdirSync2(dirname2(path), { recursive: true });
|
|
35
98
|
const mine = {
|
|
36
99
|
cwd: process.cwd(),
|
|
37
100
|
pid: process.pid,
|
|
@@ -39,7 +102,7 @@ var write = (path) => {
|
|
|
39
102
|
};
|
|
40
103
|
const staging = `${path}.${process.pid}.${randomUUID()}`;
|
|
41
104
|
try {
|
|
42
|
-
|
|
105
|
+
writeFileSync2(staging, JSON.stringify(mine));
|
|
43
106
|
linkSync(staging, path);
|
|
44
107
|
return true;
|
|
45
108
|
} catch (error) {
|
|
@@ -89,7 +152,7 @@ var acquireExclusive = async ({
|
|
|
89
152
|
};
|
|
90
153
|
|
|
91
154
|
// src/core/config.ts
|
|
92
|
-
import { existsSync, readFileSync as
|
|
155
|
+
import { existsSync, readFileSync as readFileSync3 } from "fs";
|
|
93
156
|
import { resolve } from "path";
|
|
94
157
|
var CONFIG_FILE = "geonosis.ratchet.json";
|
|
95
158
|
var keyOf = (entry) => entry.key ?? entry.counter;
|
|
@@ -98,7 +161,7 @@ var loadConfig = (cwd) => {
|
|
|
98
161
|
if (!existsSync(path)) {
|
|
99
162
|
throw new Error(`no ${CONFIG_FILE} in ${cwd} \u2014 the ratchet has nothing to count`);
|
|
100
163
|
}
|
|
101
|
-
const parsed = JSON.parse(
|
|
164
|
+
const parsed = JSON.parse(readFileSync3(path, "utf8"));
|
|
102
165
|
if (!Array.isArray(parsed.counters)) {
|
|
103
166
|
throw new Error(`${CONFIG_FILE} has no "counters" array`);
|
|
104
167
|
}
|
|
@@ -126,7 +189,7 @@ var loadConfig = (cwd) => {
|
|
|
126
189
|
};
|
|
127
190
|
var resolveBaseline = (cwd) => {
|
|
128
191
|
try {
|
|
129
|
-
const parsed = JSON.parse(
|
|
192
|
+
const parsed = JSON.parse(readFileSync3(resolve(cwd, CONFIG_FILE), "utf8"));
|
|
130
193
|
return typeof parsed.baseline === "string" ? parsed.baseline : "gate-baseline.json";
|
|
131
194
|
} catch {
|
|
132
195
|
return "gate-baseline.json";
|
|
@@ -172,13 +235,13 @@ ${output.trim()}`
|
|
|
172
235
|
};
|
|
173
236
|
|
|
174
237
|
// src/core/prove.ts
|
|
175
|
-
import { existsSync as existsSync2, mkdtempSync, rmSync as rmSync2 } from "fs";
|
|
238
|
+
import { existsSync as existsSync2, mkdtempSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
176
239
|
import { tmpdir } from "os";
|
|
177
|
-
import { delimiter, dirname as
|
|
240
|
+
import { delimiter, dirname as dirname4, join as join3, resolve as resolve3 } from "path";
|
|
178
241
|
|
|
179
242
|
// src/core/exclusive.ts
|
|
180
243
|
import { spawn } from "child_process";
|
|
181
|
-
import { dirname as
|
|
244
|
+
import { dirname as dirname3, resolve as resolve2 } from "path";
|
|
182
245
|
var MARK = /^exclusive-hold (start|end) (\d+)$/gm;
|
|
183
246
|
var KEY = "exclusive";
|
|
184
247
|
var spanOf = (output) => {
|
|
@@ -193,7 +256,7 @@ var hold = (cli, holdMs, lockPath) => new Promise((done) => {
|
|
|
193
256
|
process.execPath,
|
|
194
257
|
[cli, "--exclusive", "--exclusive-timeout", "60", "--hold", String(holdMs)],
|
|
195
258
|
{
|
|
196
|
-
cwd:
|
|
259
|
+
cwd: dirname3(resolve2(cli)),
|
|
197
260
|
env: { ...process.env, GEONOSIS_HEAVY_LOCK: lockPath },
|
|
198
261
|
stdio: ["ignore", "pipe", "pipe"]
|
|
199
262
|
}
|
|
@@ -245,9 +308,9 @@ var toolPath = (cwd) => {
|
|
|
245
308
|
const dirs = [];
|
|
246
309
|
let dir = resolve3(cwd);
|
|
247
310
|
for (; ; ) {
|
|
248
|
-
const bin =
|
|
311
|
+
const bin = join3(dir, "node_modules", ".bin");
|
|
249
312
|
if (existsSync2(bin)) dirs.push(bin);
|
|
250
|
-
const parent =
|
|
313
|
+
const parent = dirname4(dir);
|
|
251
314
|
if (parent === dir) break;
|
|
252
315
|
dir = parent;
|
|
253
316
|
}
|
|
@@ -255,7 +318,7 @@ var toolPath = (cwd) => {
|
|
|
255
318
|
};
|
|
256
319
|
var NO_PROBE = "no probe \u2014 a counter nobody has seen read a planted finding has not been shown to measure";
|
|
257
320
|
var oneProofOf = async (counter, key, path, probe) => {
|
|
258
|
-
const dir = mkdtempSync(
|
|
321
|
+
const dir = mkdtempSync(join3(tmpdir(), "geonosis-prove-"));
|
|
259
322
|
try {
|
|
260
323
|
probe.input(dir);
|
|
261
324
|
const command = probe.command?.(dir);
|
|
@@ -322,11 +385,43 @@ var runProve = async ({
|
|
|
322
385
|
if (taken.some((one) => one.verdict !== "proven" && one.verdict !== "skipped")) {
|
|
323
386
|
return { proofs, proven: false };
|
|
324
387
|
}
|
|
388
|
+
const customized = (counter.readingParams ?? []).filter((one) => entry[one] !== void 0);
|
|
389
|
+
if (customized.length > 0) {
|
|
390
|
+
const declared = entry.probe;
|
|
391
|
+
const named = customized.map((one) => `${one} ${JSON.stringify(entry[one])}`).join(", ");
|
|
392
|
+
if (declared === void 0 || typeof declared.sample !== "string" || typeof declared.expect !== "number") {
|
|
393
|
+
proofs.push({
|
|
394
|
+
counter: counter.id,
|
|
395
|
+
key,
|
|
396
|
+
reason: `the configured ${named} is not exercised by this counter's shipped probe \u2014 declare probe: { sample, expect } beside it, a sample this reading must count`,
|
|
397
|
+
verdict: "unproven"
|
|
398
|
+
});
|
|
399
|
+
return { proofs, proven: false };
|
|
400
|
+
}
|
|
401
|
+
const sample = declared.sample;
|
|
402
|
+
const entryProbe = {
|
|
403
|
+
command: () => "cat sample.txt",
|
|
404
|
+
expect: declared.expect,
|
|
405
|
+
input: (dir) => writeFileSync3(join3(dir, "sample.txt"), sample),
|
|
406
|
+
name: "configured reading",
|
|
407
|
+
params: Object.fromEntries(customized.map((one) => [one, entry[one]]))
|
|
408
|
+
};
|
|
409
|
+
const proof = await oneProofOf(counter, key, path, entryProbe);
|
|
410
|
+
const said = proof.verdict === "cannot-fail" ? {
|
|
411
|
+
counter: counter.id,
|
|
412
|
+
expected: declared.expect,
|
|
413
|
+
key,
|
|
414
|
+
reading: 0,
|
|
415
|
+
verdict: "misread"
|
|
416
|
+
} : proof;
|
|
417
|
+
proofs.push(said);
|
|
418
|
+
if (said.verdict !== "proven") return { proofs, proven: false };
|
|
419
|
+
}
|
|
325
420
|
}
|
|
326
421
|
if (exclusiveVia !== void 0) {
|
|
327
|
-
const dir = mkdtempSync(
|
|
422
|
+
const dir = mkdtempSync(join3(tmpdir(), "geonosis-lock-"));
|
|
328
423
|
try {
|
|
329
|
-
const proof = await proveExclusive({ cli: exclusiveVia, lockPath:
|
|
424
|
+
const proof = await proveExclusive({ cli: exclusiveVia, lockPath: join3(dir, "heavy.lock") });
|
|
330
425
|
proofs.push(proof);
|
|
331
426
|
if (proof.verdict !== "serialised") return { proofs, proven: false };
|
|
332
427
|
} finally {
|
|
@@ -337,20 +432,23 @@ var runProve = async ({
|
|
|
337
432
|
};
|
|
338
433
|
|
|
339
434
|
// src/core/ratchet.ts
|
|
340
|
-
import { existsSync as existsSync3, readFileSync as
|
|
435
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
341
436
|
import { resolve as resolve4 } from "path";
|
|
342
437
|
var EVIDENCE_LINES = 10;
|
|
343
438
|
var recorded = (counter, params, run) => {
|
|
344
439
|
let output = "";
|
|
440
|
+
let lastCommand;
|
|
345
441
|
const trimmed = (lines) => lines.map((line) => line.trimEnd()).filter((line) => line !== "").slice(-EVIDENCE_LINES);
|
|
346
442
|
return {
|
|
347
443
|
cited: () => {
|
|
348
444
|
const counted = counter.evidence === void 0 ? [] : counter.evidence({ output, params });
|
|
349
445
|
return trimmed(counted.length > 0 ? counted : output.split("\n"));
|
|
350
446
|
},
|
|
447
|
+
lastCommand: () => lastCommand,
|
|
351
448
|
run: (command) => {
|
|
352
449
|
const result = run(command);
|
|
353
450
|
output = result.output;
|
|
451
|
+
lastCommand = command;
|
|
354
452
|
return result;
|
|
355
453
|
}
|
|
356
454
|
};
|
|
@@ -386,9 +484,10 @@ var runRatchet = async ({
|
|
|
386
484
|
if (!existsSync3(baselinePath)) {
|
|
387
485
|
throw new Error(`no ${config.baseline} in ${cwd} \u2014 nothing to ratchet against`);
|
|
388
486
|
}
|
|
389
|
-
const baseline = JSON.parse(
|
|
487
|
+
const baseline = JSON.parse(readFileSync4(baselinePath, "utf8"));
|
|
390
488
|
const byId = new Map(counters.map((one) => [one.id, one]));
|
|
391
489
|
const measurements = [];
|
|
490
|
+
const refusals = [];
|
|
392
491
|
for (const entry of config.counters) {
|
|
393
492
|
const counter = byId.get(entry.counter);
|
|
394
493
|
if (counter === void 0) {
|
|
@@ -409,7 +508,17 @@ var runRatchet = async ({
|
|
|
409
508
|
}
|
|
410
509
|
const tolerance = toleranceOf(entry, key, counter);
|
|
411
510
|
const recorder = recorded(counter, entry, runCommand(cwd, entry.counter));
|
|
412
|
-
|
|
511
|
+
let now;
|
|
512
|
+
try {
|
|
513
|
+
now = await counter.run({ cwd, key, params: entry, run: recorder.run });
|
|
514
|
+
} catch (error) {
|
|
515
|
+
const ran = recorder.lastCommand();
|
|
516
|
+
refusals.push({
|
|
517
|
+
key,
|
|
518
|
+
reason: `${error.message}${ran === void 0 ? "" : `. Next: run \`${ran}\` in ${cwd} and read what it prints`}`
|
|
519
|
+
});
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
413
522
|
const verdict = verdictOf(now, limit, tolerance);
|
|
414
523
|
measurements.push({
|
|
415
524
|
baseline: limit,
|
|
@@ -421,16 +530,16 @@ var runRatchet = async ({
|
|
|
421
530
|
}
|
|
422
531
|
const grew = measurements.some((one) => one.verdict === "grew");
|
|
423
532
|
const shrank = measurements.some((one) => one.verdict === "shrank");
|
|
424
|
-
if (shrank && !grew) {
|
|
533
|
+
if (shrank && !grew && refusals.length === 0) {
|
|
425
534
|
const next = { ...baseline };
|
|
426
535
|
for (const one of measurements) {
|
|
427
536
|
if (one.verdict === "shrank") next[one.key] = one.now;
|
|
428
537
|
}
|
|
429
|
-
|
|
538
|
+
writeFileSync4(baselinePath, `${JSON.stringify(next, null, 2)}
|
|
430
539
|
`);
|
|
431
|
-
return { measurements, rewritten: true };
|
|
540
|
+
return { considered: config.counters.length, measurements, refusals, rewritten: true };
|
|
432
541
|
}
|
|
433
|
-
return { measurements, rewritten: false };
|
|
542
|
+
return { considered: config.counters.length, measurements, refusals, rewritten: false };
|
|
434
543
|
};
|
|
435
544
|
|
|
436
545
|
// src/counters/params.ts
|
|
@@ -449,12 +558,12 @@ var countMatches = (text, pattern) => text.match(new RegExp(pattern.source, `${p
|
|
|
449
558
|
var escapeForRegex = (value) => value.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
450
559
|
|
|
451
560
|
// src/counters/plant.ts
|
|
452
|
-
import { mkdirSync as
|
|
453
|
-
import { dirname as
|
|
561
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
562
|
+
import { dirname as dirname5, join as join4 } from "path";
|
|
454
563
|
var plant = (dir, relative, contents) => {
|
|
455
|
-
const path =
|
|
456
|
-
|
|
457
|
-
|
|
564
|
+
const path = join4(dir, relative);
|
|
565
|
+
mkdirSync3(dirname5(path), { recursive: true });
|
|
566
|
+
writeFileSync5(path, contents);
|
|
458
567
|
};
|
|
459
568
|
var captured = (sample) => ({
|
|
460
569
|
command: () => "cat sample.txt",
|
|
@@ -539,8 +648,8 @@ var bundleBytes = {
|
|
|
539
648
|
};
|
|
540
649
|
|
|
541
650
|
// src/counters/ci.ts
|
|
542
|
-
import { readdirSync, readFileSync as
|
|
543
|
-
import { join as
|
|
651
|
+
import { readdirSync, readFileSync as readFileSync5 } from "fs";
|
|
652
|
+
import { join as join5, resolve as resolve5 } from "path";
|
|
544
653
|
var DISABLED = /\bif:[ \t]*false\b/;
|
|
545
654
|
var WORKFLOW = /\.ya?ml$/;
|
|
546
655
|
var disabledCiJobs = {
|
|
@@ -558,7 +667,7 @@ var disabledCiJobs = {
|
|
|
558
667
|
const dir = resolve5(cwd, relative);
|
|
559
668
|
let files;
|
|
560
669
|
try {
|
|
561
|
-
files = readdirSync(dir, { withFileTypes: true }).filter((entry) => WORKFLOW.test(entry.name)).map((entry) =>
|
|
670
|
+
files = readdirSync(dir, { withFileTypes: true }).filter((entry) => WORKFLOW.test(entry.name)).map((entry) => join5(dir, entry.name));
|
|
562
671
|
} catch {
|
|
563
672
|
return 0;
|
|
564
673
|
}
|
|
@@ -566,7 +675,7 @@ var disabledCiJobs = {
|
|
|
566
675
|
for (const file of files) {
|
|
567
676
|
let contents;
|
|
568
677
|
try {
|
|
569
|
-
contents =
|
|
678
|
+
contents = readFileSync5(file, "utf8");
|
|
570
679
|
} catch (error) {
|
|
571
680
|
throw new CounterError(
|
|
572
681
|
"disabledCiJobs",
|
|
@@ -589,10 +698,11 @@ var cloneCount = {
|
|
|
589
698
|
const output = run(command).output;
|
|
590
699
|
const found = output.match(CLONES)?.[1];
|
|
591
700
|
if (found === void 0) {
|
|
701
|
+
const said = output.trim();
|
|
592
702
|
throw new CounterError(
|
|
593
703
|
"cloneCount",
|
|
594
|
-
`no "Found N clones" line:
|
|
595
|
-
${
|
|
704
|
+
said === "" ? 'the command printed nothing at all, so there is no "Found N clones" line to read and no output to show \u2014 it is not installed, or it never ran' : `no "Found N clones" line in what it printed:
|
|
705
|
+
${said.slice(-500)}`
|
|
596
706
|
);
|
|
597
707
|
}
|
|
598
708
|
return Number(found);
|
|
@@ -646,7 +756,7 @@ var unformattedFiles = {
|
|
|
646
756
|
};
|
|
647
757
|
|
|
648
758
|
// src/counters/gate-report.ts
|
|
649
|
-
import { existsSync as existsSync5, readFileSync as
|
|
759
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
|
|
650
760
|
import { resolve as resolve7 } from "path";
|
|
651
761
|
var DEFAULT_REPORT = ".geonosis/gate-report.json";
|
|
652
762
|
var fastTierMs = {
|
|
@@ -680,7 +790,7 @@ var fastTierMs = {
|
|
|
680
790
|
}
|
|
681
791
|
let report;
|
|
682
792
|
try {
|
|
683
|
-
report = JSON.parse(
|
|
793
|
+
report = JSON.parse(readFileSync6(path, "utf8"));
|
|
684
794
|
} catch (error) {
|
|
685
795
|
throw new CounterError(
|
|
686
796
|
"fastTierMs",
|
|
@@ -702,7 +812,7 @@ var fastTierMs = {
|
|
|
702
812
|
};
|
|
703
813
|
|
|
704
814
|
// src/counters/law.ts
|
|
705
|
-
import { existsSync as existsSync6, readFileSync as
|
|
815
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
|
|
706
816
|
import { resolve as resolve8 } from "path";
|
|
707
817
|
var lawLineCount = {
|
|
708
818
|
id: "lawLineCount",
|
|
@@ -715,12 +825,12 @@ var lawLineCount = {
|
|
|
715
825
|
const relative = stringParam("lawLineCount", params, "path", "CLAUDE.md");
|
|
716
826
|
const path = resolve8(cwd, relative);
|
|
717
827
|
if (!existsSync6(path)) throw new CounterError("lawLineCount", `no law file at ${relative}`);
|
|
718
|
-
return
|
|
828
|
+
return readFileSync7(path, "utf8").replace(/\n$/, "").split("\n").length;
|
|
719
829
|
}
|
|
720
830
|
};
|
|
721
831
|
|
|
722
832
|
// src/counters/oxlint.ts
|
|
723
|
-
import { existsSync as existsSync7, readFileSync as
|
|
833
|
+
import { existsSync as existsSync7, readFileSync as readFileSync8, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
724
834
|
import { resolve as resolve9 } from "path";
|
|
725
835
|
var DEFAULT_COMMAND = "npx oxlint --format=unix --config .oxlintrc.json .";
|
|
726
836
|
var PROBE_COMMAND = "oxlint --format=unix --config .oxlintrc.json .";
|
|
@@ -887,11 +997,11 @@ var oxlintRule = {
|
|
|
887
997
|
const source = resolve9(cwd, config);
|
|
888
998
|
if (!existsSync7(source)) throw new CounterError("oxlintRule", `no config at ${config}`);
|
|
889
999
|
const strictName = `.oxlintrc.ratchet-${key}.json`;
|
|
890
|
-
const strict =
|
|
1000
|
+
const strict = readFileSync8(source, "utf8").replace(
|
|
891
1001
|
new RegExp(`("[^"]*${escapeForRegex(rule)}"\\s*:\\s*\\[?\\s*)"(warn|off)"`),
|
|
892
1002
|
'$1"error"'
|
|
893
1003
|
);
|
|
894
|
-
|
|
1004
|
+
writeFileSync6(resolve9(cwd, strictName), strict);
|
|
895
1005
|
try {
|
|
896
1006
|
return countRule(run(command.replace("{config}", strictName)), rule, expect);
|
|
897
1007
|
} finally {
|
|
@@ -925,15 +1035,15 @@ var runtimeCodeShipped = {
|
|
|
925
1035
|
|
|
926
1036
|
// src/counters/scripts.ts
|
|
927
1037
|
import { readdirSync as readdirSync3 } from "fs";
|
|
928
|
-
import { join as
|
|
1038
|
+
import { join as join7 } from "path";
|
|
929
1039
|
|
|
930
1040
|
// src/counters/workspace.ts
|
|
931
|
-
import { existsSync as existsSync8, readdirSync as readdirSync2, readFileSync as
|
|
932
|
-
import { join as
|
|
1041
|
+
import { existsSync as existsSync8, readdirSync as readdirSync2, readFileSync as readFileSync9 } from "fs";
|
|
1042
|
+
import { join as join6, resolve as resolve10 } from "path";
|
|
933
1043
|
var SKIP = /^(node_modules|\.)/;
|
|
934
1044
|
var childDirs = (dir) => {
|
|
935
1045
|
try {
|
|
936
|
-
return readdirSync2(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !SKIP.test(entry.name)).map((entry) =>
|
|
1046
|
+
return readdirSync2(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !SKIP.test(entry.name)).map((entry) => join6(dir, entry.name));
|
|
937
1047
|
} catch {
|
|
938
1048
|
return [];
|
|
939
1049
|
}
|
|
@@ -943,14 +1053,14 @@ var expand = (root, pattern) => {
|
|
|
943
1053
|
const segments = pattern.split("/").filter((one) => one !== "" && one !== ".");
|
|
944
1054
|
let dirs = [root];
|
|
945
1055
|
for (const segment of segments) {
|
|
946
|
-
dirs = segment === "*" ? dirs.flatMap((dir) => childDirs(dir)) : segment === "**" ? dirs.flatMap((dir) => descendants(dir, 3)) : dirs.map((dir) =>
|
|
1056
|
+
dirs = segment === "*" ? dirs.flatMap((dir) => childDirs(dir)) : segment === "**" ? dirs.flatMap((dir) => descendants(dir, 3)) : dirs.map((dir) => join6(dir, segment)).filter((dir) => existsSync8(dir));
|
|
947
1057
|
}
|
|
948
1058
|
return dirs;
|
|
949
1059
|
};
|
|
950
1060
|
var QUOTED = /^['"]|['"]$/g;
|
|
951
1061
|
var cleaned = (value) => value.replace(/#.*$/, "").trim().replaceAll(QUOTED, "");
|
|
952
1062
|
var pnpmPatterns = (path) => {
|
|
953
|
-
const lines =
|
|
1063
|
+
const lines = readFileSync9(path, "utf8").split("\n");
|
|
954
1064
|
const at = lines.findIndex((line) => line.startsWith("packages:"));
|
|
955
1065
|
if (at === -1) return [];
|
|
956
1066
|
const inline = lines[at]?.slice("packages:".length).trim() ?? "";
|
|
@@ -969,15 +1079,15 @@ var pnpmPatterns = (path) => {
|
|
|
969
1079
|
return patterns;
|
|
970
1080
|
};
|
|
971
1081
|
var npmPatterns = (path) => {
|
|
972
|
-
const parsed = JSON.parse(
|
|
1082
|
+
const parsed = JSON.parse(readFileSync9(path, "utf8"));
|
|
973
1083
|
const declared = Array.isArray(parsed.workspaces) ? parsed.workspaces : parsed.workspaces?.packages ?? [];
|
|
974
1084
|
return declared.filter((one) => typeof one === "string");
|
|
975
1085
|
};
|
|
976
1086
|
var nameOf = (dir) => {
|
|
977
|
-
const manifest =
|
|
1087
|
+
const manifest = join6(dir, "package.json");
|
|
978
1088
|
if (!existsSync8(manifest)) return void 0;
|
|
979
1089
|
try {
|
|
980
|
-
const { name } = JSON.parse(
|
|
1090
|
+
const { name } = JSON.parse(readFileSync9(manifest, "utf8"));
|
|
981
1091
|
return typeof name === "string" && name !== "" ? name : void 0;
|
|
982
1092
|
} catch {
|
|
983
1093
|
return void 0;
|
|
@@ -985,15 +1095,15 @@ var nameOf = (dir) => {
|
|
|
985
1095
|
};
|
|
986
1096
|
var workspaceDirs = (cwd) => {
|
|
987
1097
|
const root = resolve10(cwd);
|
|
988
|
-
const pnpm =
|
|
989
|
-
const manifest =
|
|
1098
|
+
const pnpm = join6(root, "pnpm-workspace.yaml");
|
|
1099
|
+
const manifest = join6(root, "package.json");
|
|
990
1100
|
const patterns = existsSync8(pnpm) ? pnpmPatterns(pnpm) : existsSync8(manifest) ? npmPatterns(manifest) : [];
|
|
991
|
-
const dirs = patterns.filter((pattern) => !pattern.startsWith("!")).flatMap((pattern) => expand(root, pattern)).filter((dir) => existsSync8(
|
|
1101
|
+
const dirs = patterns.filter((pattern) => !pattern.startsWith("!")).flatMap((pattern) => expand(root, pattern)).filter((dir) => existsSync8(join6(dir, "package.json")));
|
|
992
1102
|
return [...new Set(dirs)];
|
|
993
1103
|
};
|
|
994
1104
|
var manifestOf = (dir) => {
|
|
995
1105
|
try {
|
|
996
|
-
const parsed = JSON.parse(
|
|
1106
|
+
const parsed = JSON.parse(readFileSync9(join6(dir, "package.json"), "utf8"));
|
|
997
1107
|
return typeof parsed === "object" && parsed !== null ? parsed : void 0;
|
|
998
1108
|
} catch {
|
|
999
1109
|
return void 0;
|
|
@@ -1023,7 +1133,7 @@ var holdsTests = (dir, depth = 6) => {
|
|
|
1023
1133
|
if (entry.isDirectory()) {
|
|
1024
1134
|
if (entry.name === TEST_DIR) return true;
|
|
1025
1135
|
if (SKIP2.test(entry.name) || depth === 0) continue;
|
|
1026
|
-
if (holdsTests(
|
|
1136
|
+
if (holdsTests(join7(dir, entry.name), depth - 1)) return true;
|
|
1027
1137
|
continue;
|
|
1028
1138
|
}
|
|
1029
1139
|
if (TEST_FILE.test(entry.name)) return true;
|
|
@@ -1064,16 +1174,26 @@ var packagesWithoutTypecheck = {
|
|
|
1064
1174
|
var sumOfCounts = {
|
|
1065
1175
|
id: "sumOfCounts",
|
|
1066
1176
|
probe: { ...captured("src/a.ts:1\nsrc/b.ts:0\n"), expect: 1 },
|
|
1177
|
+
readingParams: ["match"],
|
|
1067
1178
|
run: async ({ params, run }) => {
|
|
1068
1179
|
const command = stringParam("sumOfCounts", params, "command");
|
|
1069
|
-
const
|
|
1070
|
-
|
|
1180
|
+
const pattern = stringParam("sumOfCounts", params, "match", ":(\\d+)$");
|
|
1181
|
+
const match = new RegExp(pattern, "gm");
|
|
1182
|
+
const output = run(command).output;
|
|
1183
|
+
const found = [...output.matchAll(match)];
|
|
1184
|
+
if (found.length === 0 && output.trim() !== "") {
|
|
1185
|
+
throw new CounterError(
|
|
1186
|
+
"sumOfCounts",
|
|
1187
|
+
`its match ${pattern} read nothing from ${output.trim().split("\n").length} line(s) of output (first: ${JSON.stringify(output.trim().split("\n")[0])}) \u2014 a match that cannot read the tool is not a zero`
|
|
1188
|
+
);
|
|
1189
|
+
}
|
|
1190
|
+
return found.map(([, digits]) => Number(digits ?? 0)).filter((count) => Number.isFinite(count)).reduce((sum, count) => sum + count, 0);
|
|
1071
1191
|
}
|
|
1072
1192
|
};
|
|
1073
1193
|
|
|
1074
1194
|
// src/counters/suppressions.ts
|
|
1075
|
-
import { readdirSync as readdirSync4, readFileSync as
|
|
1076
|
-
import { join as
|
|
1195
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync10 } from "fs";
|
|
1196
|
+
import { join as join8, resolve as resolve11 } from "path";
|
|
1077
1197
|
var CODE_FILE = /\.(?:ts|tsx|js|jsx|mjs|cjs|mts|cts|vue|svelte|astro)$/;
|
|
1078
1198
|
var SKIP3 = /^(?:node_modules|dist|coverage|\.[^.]|__fixtures__)/;
|
|
1079
1199
|
var SUPPRESSION = /(?:eslint|oxlint|biome)-(?:disable|ignore)(?:-next-line|-line)?|@ts-expect-error|@ts-ignore/g;
|
|
@@ -1088,12 +1208,12 @@ var countIn = (dir, depth = 12) => {
|
|
|
1088
1208
|
for (const entry of entries) {
|
|
1089
1209
|
if (SKIP3.test(entry.name)) continue;
|
|
1090
1210
|
if (entry.isDirectory()) {
|
|
1091
|
-
if (depth > 0) found += countIn(
|
|
1211
|
+
if (depth > 0) found += countIn(join8(dir, entry.name), depth - 1);
|
|
1092
1212
|
continue;
|
|
1093
1213
|
}
|
|
1094
1214
|
if (!CODE_FILE.test(entry.name)) continue;
|
|
1095
1215
|
try {
|
|
1096
|
-
found +=
|
|
1216
|
+
found += readFileSync10(join8(dir, entry.name), "utf8").match(SUPPRESSION)?.length ?? 0;
|
|
1097
1217
|
} catch {
|
|
1098
1218
|
}
|
|
1099
1219
|
}
|
|
@@ -1112,9 +1232,9 @@ var suppressionCount = {
|
|
|
1112
1232
|
};
|
|
1113
1233
|
|
|
1114
1234
|
// src/counters/tests.ts
|
|
1115
|
-
import { existsSync as existsSync9, mkdtempSync as mkdtempSync2, readFileSync as
|
|
1235
|
+
import { existsSync as existsSync9, mkdtempSync as mkdtempSync2, readFileSync as readFileSync11, rmSync as rmSync4 } from "fs";
|
|
1116
1236
|
import { tmpdir as tmpdir2 } from "os";
|
|
1117
|
-
import { join as
|
|
1237
|
+
import { join as join9, resolve as resolve12 } from "path";
|
|
1118
1238
|
var COUNTER = "testFailures";
|
|
1119
1239
|
var VITEST_LINE = /^\s*Tests {2,}(.+?)\s*$/;
|
|
1120
1240
|
var VITEST_TOTAL = /\(\d+\)$/;
|
|
@@ -1154,7 +1274,7 @@ var fromReport = (path) => {
|
|
|
1154
1274
|
}
|
|
1155
1275
|
let report;
|
|
1156
1276
|
try {
|
|
1157
|
-
report = JSON.parse(
|
|
1277
|
+
report = JSON.parse(readFileSync11(path, "utf8"));
|
|
1158
1278
|
} catch (error) {
|
|
1159
1279
|
throw new CounterError(
|
|
1160
1280
|
COUNTER,
|
|
@@ -1185,7 +1305,7 @@ var reportPathFor = (cwd, params, command) => {
|
|
|
1185
1305
|
`report: "${VITEST_JSON}" needs somewhere to put the report \u2014 write ${PLACEHOLDER} into the command (--outputFile=${PLACEHOLDER}) or give the entry a "reportPath"`
|
|
1186
1306
|
);
|
|
1187
1307
|
}
|
|
1188
|
-
return { own: true, path:
|
|
1308
|
+
return { own: true, path: join9(mkdtempSync2(join9(tmpdir2(), "geonosis-report-")), "report.json") };
|
|
1189
1309
|
};
|
|
1190
1310
|
var testFailures = {
|
|
1191
1311
|
id: COUNTER,
|
|
@@ -1234,7 +1354,7 @@ var testFailures = {
|
|
|
1234
1354
|
run(command.replaceAll(PLACEHOLDER, path));
|
|
1235
1355
|
return fromReport(path);
|
|
1236
1356
|
} finally {
|
|
1237
|
-
if (own) rmSync4(
|
|
1357
|
+
if (own) rmSync4(join9(path, ".."), { force: true, recursive: true });
|
|
1238
1358
|
}
|
|
1239
1359
|
}
|
|
1240
1360
|
};
|
|
@@ -1274,7 +1394,7 @@ var typecheckErrors = {
|
|
|
1274
1394
|
};
|
|
1275
1395
|
|
|
1276
1396
|
// src/counters/walk.ts
|
|
1277
|
-
import { existsSync as existsSync10, readFileSync as
|
|
1397
|
+
import { existsSync as existsSync10, readFileSync as readFileSync12 } from "fs";
|
|
1278
1398
|
import { resolve as resolve13 } from "path";
|
|
1279
1399
|
var DEFAULT_REPORT2 = ".geonosis/walk-report.json";
|
|
1280
1400
|
var CLASSES = /* @__PURE__ */ new Set([
|
|
@@ -1328,7 +1448,7 @@ var walkFindings = {
|
|
|
1328
1448
|
}
|
|
1329
1449
|
let report;
|
|
1330
1450
|
try {
|
|
1331
|
-
report = JSON.parse(
|
|
1451
|
+
report = JSON.parse(readFileSync12(path, "utf8"));
|
|
1332
1452
|
} catch (error) {
|
|
1333
1453
|
throw new CounterError(
|
|
1334
1454
|
"walkFindings",
|
|
@@ -1383,7 +1503,7 @@ var counterById = (id) => {
|
|
|
1383
1503
|
|
|
1384
1504
|
// src/report.ts
|
|
1385
1505
|
var WIDTH = 28;
|
|
1386
|
-
var formatReport = ({ measurements, rewritten }) => {
|
|
1506
|
+
var formatReport = ({ measurements, refusals, rewritten }) => {
|
|
1387
1507
|
const lines = measurements.map((one) => {
|
|
1388
1508
|
if (one.verdict === "skipped") {
|
|
1389
1509
|
return ` SKIP ${one.key}: not measured by --tier ${one.tier}`;
|
|
@@ -1396,8 +1516,14 @@ var formatReport = ({ measurements, rewritten }) => {
|
|
|
1396
1516
|
if (one.verdict === "shrank") return `${head} <-- improved -${one.baseline - one.now}`;
|
|
1397
1517
|
return head;
|
|
1398
1518
|
});
|
|
1519
|
+
lines.push(...refusals.map((one) => ` CANNOT MEASURE ${one.key}: ${one.reason}`));
|
|
1399
1520
|
const grew = measurements.filter((one) => one.verdict === "grew");
|
|
1400
|
-
if (
|
|
1521
|
+
if (refusals.length > 0) {
|
|
1522
|
+
lines.push(
|
|
1523
|
+
"",
|
|
1524
|
+
`ratchet FAIL \u2014 ${refusals.length} counter(s) could not be measured, and a gate that cannot measure has not passed. The baseline was left exactly as it was. Next: run each counter's own command by hand \u2014 ${refusals.map((one) => one.key).join(", ")} \u2014 and fix what it prints.`
|
|
1525
|
+
);
|
|
1526
|
+
} else if (grew.length > 0) {
|
|
1401
1527
|
lines.push(
|
|
1402
1528
|
"",
|
|
1403
1529
|
"ratchet FAIL \u2014 debt grew. Fix it, or say in the commit message why the baseline goes up."
|
|
@@ -1419,6 +1545,7 @@ var formatProve = ({ proofs, proven }) => {
|
|
|
1419
1545
|
}
|
|
1420
1546
|
if (one.verdict === "cannot-measure") return ` CANNOT MEASURE ${one.key}: ${one.reason}`;
|
|
1421
1547
|
if (one.verdict === "cannot-fail") return ` CANNOT FAIL ${one.key}: read 0`;
|
|
1548
|
+
if (one.verdict === "unproven") return ` UNPROVEN ${one.key}: ${one.reason}`;
|
|
1422
1549
|
if (one.verdict === "misread") {
|
|
1423
1550
|
return ` MISREAD ${one.key}: read ${one.reading} where its probe planted ${one.expected}`;
|
|
1424
1551
|
}
|
|
@@ -1433,6 +1560,11 @@ var formatProve = ({ proofs, proven }) => {
|
|
|
1433
1560
|
};
|
|
1434
1561
|
|
|
1435
1562
|
export {
|
|
1563
|
+
ENVELOPES_DIR,
|
|
1564
|
+
envelopePath,
|
|
1565
|
+
UnbalancedEnvelope,
|
|
1566
|
+
writeEnvelope,
|
|
1567
|
+
versionOf,
|
|
1436
1568
|
heavyLockPath,
|
|
1437
1569
|
acquireExclusive,
|
|
1438
1570
|
CONFIG_FILE,
|
package/dist/cli.js
CHANGED
|
@@ -4,8 +4,10 @@ import {
|
|
|
4
4
|
formatProve,
|
|
5
5
|
formatReport,
|
|
6
6
|
runProve,
|
|
7
|
-
runRatchet
|
|
8
|
-
|
|
7
|
+
runRatchet,
|
|
8
|
+
versionOf,
|
|
9
|
+
writeEnvelope
|
|
10
|
+
} from "./chunk-ESN6ULZZ.js";
|
|
9
11
|
|
|
10
12
|
// src/cli.ts
|
|
11
13
|
import process from "process";
|
|
@@ -100,6 +102,19 @@ var holdTheLock = async (ms) => {
|
|
|
100
102
|
`);
|
|
101
103
|
return 0;
|
|
102
104
|
};
|
|
105
|
+
var ENVELOPE_NEXT = "geonosis-ratchet --print-config-shape, then count the entries in geonosis.ratchet.json \u2014 a run that measured fewer counters than the config named is a bug in this tool, not in the tree it measured";
|
|
106
|
+
var envelopeOf = (result, durationMs) => ({
|
|
107
|
+
considered: result.considered,
|
|
108
|
+
durationMs,
|
|
109
|
+
excused: result.measurements.flatMap(
|
|
110
|
+
(one) => one.verdict === "skipped" ? [{ path: one.key, reason: `not measured by --tier ${one.tier}` }] : []
|
|
111
|
+
),
|
|
112
|
+
findings: result.measurements.filter((one) => one.verdict === "grew"),
|
|
113
|
+
read: result.measurements.filter((one) => one.verdict !== "skipped").length,
|
|
114
|
+
refused: result.refusals.map((one) => ({ path: one.key, reason: one.reason })),
|
|
115
|
+
tool: "ratchet",
|
|
116
|
+
version: versionOf(import.meta.url)
|
|
117
|
+
});
|
|
103
118
|
var measure = async () => {
|
|
104
119
|
if (holdMs !== void 0) return holdTheLock(holdMs);
|
|
105
120
|
if (proving) {
|
|
@@ -112,8 +127,17 @@ var measure = async () => {
|
|
|
112
127
|
process.stdout.write(formatProve(proof));
|
|
113
128
|
return proof.proven ? 0 : 2;
|
|
114
129
|
}
|
|
130
|
+
const startedAt = Date.now();
|
|
115
131
|
const result = await runRatchet({ counters: COUNTERS, cwd, tier });
|
|
116
132
|
process.stdout.write(formatReport(result));
|
|
133
|
+
const at = writeEnvelope({
|
|
134
|
+
envelope: envelopeOf(result, Date.now() - startedAt),
|
|
135
|
+
next: ENVELOPE_NEXT,
|
|
136
|
+
root: cwd
|
|
137
|
+
});
|
|
138
|
+
process.stdout.write(`envelope: ${at}
|
|
139
|
+
`);
|
|
140
|
+
if (result.refusals.length > 0) return 2;
|
|
117
141
|
return result.measurements.some((one) => one.verdict === "grew") ? 1 : 0;
|
|
118
142
|
};
|
|
119
143
|
try {
|
package/dist/index.d.ts
CHANGED
|
@@ -76,6 +76,14 @@ type Counter = {
|
|
|
76
76
|
* nobody configured says nothing about the mode they did.
|
|
77
77
|
*/
|
|
78
78
|
probe?: CounterProbe | CounterProbe[];
|
|
79
|
+
/**
|
|
80
|
+
* The params that change WHAT this counter reads (a `match`), as opposed to where it looks.
|
|
81
|
+
* #140: a configured reading the probe never exercises can be wrong in a way the probe cannot
|
|
82
|
+
* see — during.day's mis-escaped match read 0, was certified PROVEN, and the false win was
|
|
83
|
+
* banked into the baseline. A config customizing one of these must declare `probe: { sample,
|
|
84
|
+
* expect }` beside it, or the prove line says UNPROVEN.
|
|
85
|
+
*/
|
|
86
|
+
readingParams?: string[];
|
|
79
87
|
run: (context: CounterContext) => Promise<number>;
|
|
80
88
|
};
|
|
81
89
|
/** One line of `geonosis.ratchet.json`'s `counters` array. */
|
|
@@ -134,6 +142,11 @@ type Proof = {
|
|
|
134
142
|
key: string;
|
|
135
143
|
reason: string;
|
|
136
144
|
verdict: 'cannot-measure';
|
|
145
|
+
} | {
|
|
146
|
+
counter: string;
|
|
147
|
+
key: string;
|
|
148
|
+
reason: string;
|
|
149
|
+
verdict: 'unproven';
|
|
137
150
|
}
|
|
138
151
|
/**
|
|
139
152
|
* Not a counter: `--exclusive` itself, measured by running two of it. A lock is a claim about the
|
|
@@ -157,8 +170,24 @@ type ProveResult = {
|
|
|
157
170
|
/** False as soon as one counter could not be shown to read its own planted finding. */
|
|
158
171
|
proven: boolean;
|
|
159
172
|
};
|
|
173
|
+
/** A counter whose tool could not be read. Never a number, and never a silent zero. */
|
|
174
|
+
type Refusal = {
|
|
175
|
+
key: string;
|
|
176
|
+
reason: string;
|
|
177
|
+
};
|
|
160
178
|
type RatchetResult = {
|
|
179
|
+
/**
|
|
180
|
+
* How many counters the config named. The denominator: `considered === measurements.length +
|
|
181
|
+
* refusals.length`, and a run whose two do not add up has lost one somewhere between the config
|
|
182
|
+
* and the report.
|
|
183
|
+
*/
|
|
184
|
+
considered: number;
|
|
161
185
|
measurements: Measurement[];
|
|
186
|
+
/**
|
|
187
|
+
* Every counter that blew up, not just the first. The throw that used to leave the run at the
|
|
188
|
+
* first failure also left every counter behind it unrun and unreported.
|
|
189
|
+
*/
|
|
190
|
+
refusals: Refusal[];
|
|
162
191
|
/** True when the baseline file was rewritten because something shrank. */
|
|
163
192
|
rewritten: boolean;
|
|
164
193
|
};
|
|
@@ -178,6 +207,80 @@ declare const loadConfig: (cwd: string) => RatchetConfig;
|
|
|
178
207
|
*/
|
|
179
208
|
declare const resolveBaseline: (cwd: string) => string;
|
|
180
209
|
|
|
210
|
+
/**
|
|
211
|
+
* The report envelope: what a tool CONSIDERED, against what it actually read.
|
|
212
|
+
*
|
|
213
|
+
* Four denominator bugs landed in one day — a migrations run that reported on three of four files,
|
|
214
|
+
* a plan check that printed `PASS — 0 plan(s)` over a directory of twenty-one, a parity run over a
|
|
215
|
+
* tree the second config ignored, a chains validator that walked a list it had already filtered.
|
|
216
|
+
* Every one of them was GREEN, and every one of them was green because the tool published the
|
|
217
|
+
* numerator and nobody published the denominator.
|
|
218
|
+
*
|
|
219
|
+
* So every gate writes this, and the law is arithmetic:
|
|
220
|
+
*
|
|
221
|
+
* considered === read + refused.length + excused.length
|
|
222
|
+
*
|
|
223
|
+
* checked HERE, at write time. A tool whose own accounting does not add up has not measured what it
|
|
224
|
+
* says it measured, so it renders no verdict at all: the write throws, the file is not created, and
|
|
225
|
+
* the bin exits 2. That is deliberately harsher than a failing gate — a wrong verdict is worse than
|
|
226
|
+
* no verdict, and a tool that has lost count of its own inputs cannot tell which it is.
|
|
227
|
+
*
|
|
228
|
+
* `refused` and `excused` are both "considered and NOT read". They differ in whose fault it is:
|
|
229
|
+
* refused is the tool unable to read the thing (an unparseable migration, a counter whose command
|
|
230
|
+
* blew up), excused is the thing legitimately exempt (a contract-migration marker the gate believed,
|
|
231
|
+
* a step the run never reached, a plan written before the contract). Both carry a reason, because a
|
|
232
|
+
* bucket without reasons is just a smaller silence.
|
|
233
|
+
*/
|
|
234
|
+
type EnvelopeEntry = {
|
|
235
|
+
/** What was not read — a file, a counter key, a step id. Whatever the tool names its inputs by. */
|
|
236
|
+
path: string;
|
|
237
|
+
/** Why it was not read, in a sentence a reader can act on. */
|
|
238
|
+
reason: string;
|
|
239
|
+
};
|
|
240
|
+
type ReportEnvelope = {
|
|
241
|
+
/** Everything this run took as its input. The denominator. */
|
|
242
|
+
considered: number;
|
|
243
|
+
durationMs: number;
|
|
244
|
+
excused: EnvelopeEntry[];
|
|
245
|
+
/** Whatever the tool found. Shape is the tool's own; nothing here reads into it. */
|
|
246
|
+
findings: unknown[];
|
|
247
|
+
/** How many of `considered` the run actually read. The numerator. */
|
|
248
|
+
read: number;
|
|
249
|
+
refused: EnvelopeEntry[];
|
|
250
|
+
/** The name the envelope is filed under, and the name a refusal is reported against. */
|
|
251
|
+
tool: string;
|
|
252
|
+
/** The build that wrote it. An envelope that cannot name its build dates nothing. */
|
|
253
|
+
version: string;
|
|
254
|
+
};
|
|
255
|
+
/** Where every tool writes, and the one directory the doctor's envelope check reads. */
|
|
256
|
+
declare const ENVELOPES_DIR = ".geonosis/envelopes";
|
|
257
|
+
declare const envelopePath: (root: string, tool: string) => string;
|
|
258
|
+
/** A run whose accounting does not add up. Never a verdict — the refusal to render one. */
|
|
259
|
+
declare class UnbalancedEnvelope extends Error {
|
|
260
|
+
constructor(message: string);
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Writes the envelope, or refuses. Returns the path written so a caller can print it.
|
|
264
|
+
*
|
|
265
|
+
* The check is before the write and not after it, because a file on disk is a verdict: the doctor
|
|
266
|
+
* would read a half-true envelope and report on numbers nobody stands behind.
|
|
267
|
+
*/
|
|
268
|
+
declare const writeEnvelope: ({ envelope, next, root, }: {
|
|
269
|
+
envelope: ReportEnvelope;
|
|
270
|
+
/** The command a reader should run to see the accounting for themselves. */
|
|
271
|
+
next: string;
|
|
272
|
+
root: string;
|
|
273
|
+
}) => string;
|
|
274
|
+
/**
|
|
275
|
+
* The version of the package a module belongs to, found by walking up to the nearest manifest.
|
|
276
|
+
*
|
|
277
|
+
* Called with `import.meta.url`, it answers the same in `src/` under vitest and in the bundled
|
|
278
|
+
* `dist/` a bin loads — which is the whole point, because the envelope a consumer's CI writes is
|
|
279
|
+
* written by the build, and a version baked in at compile time would be the version of whoever
|
|
280
|
+
* bundled it.
|
|
281
|
+
*/
|
|
282
|
+
declare const versionOf: (moduleUrl: string) => string;
|
|
283
|
+
|
|
181
284
|
declare const COUNTERS: Counter[];
|
|
182
285
|
declare const counterById: (id: string) => Counter;
|
|
183
286
|
|
|
@@ -251,7 +354,7 @@ declare const runRatchet: ({ counters, cwd, tier, }: {
|
|
|
251
354
|
* the tier left out says so by name — printing a number for it would be the stale OK this exists
|
|
252
355
|
* to prevent.
|
|
253
356
|
*/
|
|
254
|
-
declare const formatReport: ({ measurements, rewritten }: RatchetResult) => string;
|
|
357
|
+
declare const formatReport: ({ measurements, refusals, rewritten }: RatchetResult) => string;
|
|
255
358
|
declare const formatProve: ({ proofs, proven }: ProveResult) => string;
|
|
256
359
|
|
|
257
360
|
/**
|
|
@@ -270,4 +373,4 @@ declare const formatProve: ({ proofs, proven }: ProveResult) => string;
|
|
|
270
373
|
*/
|
|
271
374
|
declare const runCommand: (cwd: string, counterId: string, env?: NodeJS.ProcessEnv) => (command: string) => CommandResult;
|
|
272
375
|
|
|
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 };
|
|
376
|
+
export { CONFIG_FILE, COUNTERS, type CommandResult, type Counter, type CounterConfig, type CounterContext, CounterError, type CounterProbe, ENVELOPES_DIR, type EnvelopeEntry, type Holder, type Measurement, type Proof, type ProveResult, type RatchetConfig, type RatchetResult, type Refusal, type ReportEnvelope, UnbalancedEnvelope, type Verdict, acquireExclusive, counterById, envelopePath, formatProve, formatReport, heavyLockPath, keyOf, loadConfig, resolveBaseline, runCommand, runProve, runRatchet, versionOf, writeEnvelope };
|
package/dist/index.js
CHANGED
|
@@ -2,8 +2,11 @@ import {
|
|
|
2
2
|
CONFIG_FILE,
|
|
3
3
|
COUNTERS,
|
|
4
4
|
CounterError,
|
|
5
|
+
ENVELOPES_DIR,
|
|
6
|
+
UnbalancedEnvelope,
|
|
5
7
|
acquireExclusive,
|
|
6
8
|
counterById,
|
|
9
|
+
envelopePath,
|
|
7
10
|
formatProve,
|
|
8
11
|
formatReport,
|
|
9
12
|
heavyLockPath,
|
|
@@ -12,14 +15,19 @@ import {
|
|
|
12
15
|
resolveBaseline,
|
|
13
16
|
runCommand,
|
|
14
17
|
runProve,
|
|
15
|
-
runRatchet
|
|
16
|
-
|
|
18
|
+
runRatchet,
|
|
19
|
+
versionOf,
|
|
20
|
+
writeEnvelope
|
|
21
|
+
} from "./chunk-ESN6ULZZ.js";
|
|
17
22
|
export {
|
|
18
23
|
CONFIG_FILE,
|
|
19
24
|
COUNTERS,
|
|
20
25
|
CounterError,
|
|
26
|
+
ENVELOPES_DIR,
|
|
27
|
+
UnbalancedEnvelope,
|
|
21
28
|
acquireExclusive,
|
|
22
29
|
counterById,
|
|
30
|
+
envelopePath,
|
|
23
31
|
formatProve,
|
|
24
32
|
formatReport,
|
|
25
33
|
heavyLockPath,
|
|
@@ -28,5 +36,7 @@ export {
|
|
|
28
36
|
resolveBaseline,
|
|
29
37
|
runCommand,
|
|
30
38
|
runProve,
|
|
31
|
-
runRatchet
|
|
39
|
+
runRatchet,
|
|
40
|
+
versionOf,
|
|
41
|
+
writeEnvelope
|
|
32
42
|
};
|