@geonosis/ratchet 2.1.0 → 2.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 +34 -3
- package/dist/{chunk-H4PLCM5J.js → chunk-E55APSHM.js} +386 -81
- package/dist/cli.js +10 -5
- package/dist/index.d.ts +0 -6
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# @geonosis/ratchet
|
|
2
2
|
|
|
3
|
+
Through the front door: `geonosis ratchet` — the metapackage pins this and every other kit tool at
|
|
4
|
+
ONE version, and passes the exit code through unchanged.
|
|
5
|
+
|
|
3
6
|
Debt as a number that may only shrink. It measures a repo's counters, compares each against a
|
|
4
7
|
committed baseline, and fails **only** on regression — so a repo can adopt a gate the day it is
|
|
5
8
|
written, with the existing debt named rather than forgiven, and nothing new can land behind it.
|
|
@@ -66,6 +69,34 @@ A counter you wrote yourself takes a `probe` beside its `run`: `{ input: (dir) =
|
|
|
66
69
|
(dir) => string, params?, expect: number }`. `expect` is what the planted input is worth, and it is
|
|
67
70
|
at least 1 — a probe that plants nothing proves nothing.
|
|
68
71
|
|
|
72
|
+
### A probe proves the command, not the regex
|
|
73
|
+
|
|
74
|
+
A shipped probe plants an input in the shape the counter's OWN default reads. `archViolations`
|
|
75
|
+
plants a scanner that prints `✗ …` lines and exits non-zero; `sumOfCounts` plants a line its default
|
|
76
|
+
`match` counts. That proves the command is spawned, its output is captured and its exit code is told
|
|
77
|
+
apart from its findings — and it proves nothing whatever about a `match` YOUR config supplies,
|
|
78
|
+
because the planted input was written for the default one.
|
|
79
|
+
|
|
80
|
+
This was measured, not imagined: a consumer's mis-escaped `match` read 0 from a real scan, `--prove`
|
|
81
|
+
said PROVEN off the shipped plant, and the ratchet lowered the baseline — ten pieces of debt erased
|
|
82
|
+
with a congratulatory message (#140).
|
|
83
|
+
|
|
84
|
+
So the counters that take a reading param — `archViolations` (`match`), `sumOfCounts` (`match`),
|
|
85
|
+
`duplication` (`headings`), `todos` (`markers`) — require a sample from the config that supplied it:
|
|
86
|
+
|
|
87
|
+
```jsonc
|
|
88
|
+
{
|
|
89
|
+
"counter": "archViolations",
|
|
90
|
+
"command": "node scripts/scan-architecture.mjs",
|
|
91
|
+
"match": "^VIOLATION ",
|
|
92
|
+
"probe": { "sample": "VIOLATION a\nVIOLATION b\nfine\n", "expect": 2 }
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Without the `probe` block the line reads `UNPROVEN`, naming the param and its value. With it, the
|
|
97
|
+
sample is run through YOUR match and must count `expect` — a sample your match cannot read is a
|
|
98
|
+
refusal that names the match, not a zero.
|
|
99
|
+
|
|
69
100
|
### A baseline is not proof the gate still gates
|
|
70
101
|
|
|
71
102
|
The ratchet compares a number against a number. Swapping the tool that produces it — a vendored lint
|
|
@@ -196,9 +227,9 @@ captured from the binary rather than guessed at:
|
|
|
196
227
|
|
|
197
228
|
| Runner | The line | Read as |
|
|
198
229
|
| --- | --- | --- |
|
|
199
|
-
| vitest 3.2.7 | ` Tests 1 failed \| 1 passed (2)` | 1 |
|
|
200
|
-
| vitest 3.2.7 | ` Tests 3 passed (3)` · ` Tests 2 skipped (2)` | 0 |
|
|
201
|
-
| vitest 3.2.7 | ` Tests no tests` (a suite that threw before collecting) | **refused** |
|
|
230
|
+
| vitest 3.2.7 · 4.1.11 | ` Tests 1 failed \| 1 passed (2)` | 1 |
|
|
231
|
+
| vitest 3.2.7 · 4.1.11 | ` Tests 3 passed (3)` · ` Tests 2 skipped (2)` | 0 |
|
|
232
|
+
| vitest 3.2.7 · 4.1.11 | ` Tests no tests` (a suite that threw before collecting) | **refused** |
|
|
202
233
|
| bun 1.4.0 | ` 1 fail` on its own line | 1 |
|
|
203
234
|
| bun 1.4.0 | ` 0 fail` | 0 |
|
|
204
235
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/core/envelope.ts
|
|
2
2
|
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
-
import { dirname, join } from "path";
|
|
3
|
+
import { dirname, join, resolve } from "path";
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
5
5
|
var ENVELOPES_DIR = ".geonosis/envelopes";
|
|
6
6
|
var envelopePath = (root, tool) => join(root, ENVELOPES_DIR, `${tool}.json`);
|
|
@@ -12,6 +12,14 @@ var UnbalancedEnvelope = class extends Error {
|
|
|
12
12
|
};
|
|
13
13
|
var isCount = (value) => Number.isSafeInteger(value) && value >= 0;
|
|
14
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 FORBIDDEN_ROOT = "GEONOSIS_ENVELOPES_FORBIDDEN_ROOT";
|
|
16
|
+
var refuseForbiddenRoot = (root) => {
|
|
17
|
+
const forbidden = process.env[FORBIDDEN_ROOT];
|
|
18
|
+
if (forbidden === void 0 || resolve(forbidden) !== resolve(root)) return;
|
|
19
|
+
throw new UnbalancedEnvelope(
|
|
20
|
+
`${root} is off limits to envelope writers in this process (${FORBIDDEN_ROOT}) \u2014 a run that writes one into a shared root races every other run reading it, and leaves a file the next one takes for real. Point this at a scratch root of its own: tooling/scratch-dir.ts.`
|
|
21
|
+
);
|
|
22
|
+
};
|
|
15
23
|
var writeEnvelope = ({
|
|
16
24
|
envelope,
|
|
17
25
|
next,
|
|
@@ -35,6 +43,7 @@ var writeEnvelope = ({
|
|
|
35
43
|
if (envelope.considered !== envelope.read + envelope.refused.length + envelope.excused.length) {
|
|
36
44
|
throw new UnbalancedEnvelope(unbalancedMessage(envelope, next));
|
|
37
45
|
}
|
|
46
|
+
refuseForbiddenRoot(root);
|
|
38
47
|
const at = envelopePath(root, envelope.tool);
|
|
39
48
|
mkdirSync(dirname(at), { recursive: true });
|
|
40
49
|
writeFileSync(at, `${JSON.stringify(envelope, void 0, 2)}
|
|
@@ -65,8 +74,14 @@ var versionOf = (moduleUrl) => {
|
|
|
65
74
|
import { randomUUID } from "crypto";
|
|
66
75
|
import { linkSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
67
76
|
import { homedir } from "os";
|
|
68
|
-
import { dirname as dirname2, join as join2 } from "path";
|
|
69
|
-
var heavyLockPath = () =>
|
|
77
|
+
import { dirname as dirname2, isAbsolute, join as join2 } from "path";
|
|
78
|
+
var heavyLockPath = () => {
|
|
79
|
+
const named = process.env.GEONOSIS_HEAVY_LOCK;
|
|
80
|
+
if (named !== void 0 && named !== "") return named;
|
|
81
|
+
const declared = process.env.XDG_CACHE_HOME;
|
|
82
|
+
const cache = declared !== void 0 && isAbsolute(declared) ? declared : join2(homedir(), ".cache");
|
|
83
|
+
return join2(cache, "geonosis", "heavy.lock");
|
|
84
|
+
};
|
|
70
85
|
var sleep = (ms) => new Promise((done) => setTimeout(done, ms));
|
|
71
86
|
var holderOf = (path) => {
|
|
72
87
|
try {
|
|
@@ -153,11 +168,11 @@ var acquireExclusive = async ({
|
|
|
153
168
|
|
|
154
169
|
// src/core/config.ts
|
|
155
170
|
import { existsSync, readFileSync as readFileSync3 } from "fs";
|
|
156
|
-
import { resolve } from "path";
|
|
171
|
+
import { resolve as resolve2 } from "path";
|
|
157
172
|
var CONFIG_FILE = "geonosis.ratchet.json";
|
|
158
173
|
var keyOf = (entry) => entry.key ?? entry.counter;
|
|
159
174
|
var loadConfig = (cwd) => {
|
|
160
|
-
const path =
|
|
175
|
+
const path = resolve2(cwd, CONFIG_FILE);
|
|
161
176
|
if (!existsSync(path)) {
|
|
162
177
|
throw new Error(`no ${CONFIG_FILE} in ${cwd} \u2014 the ratchet has nothing to count`);
|
|
163
178
|
}
|
|
@@ -189,7 +204,7 @@ var loadConfig = (cwd) => {
|
|
|
189
204
|
};
|
|
190
205
|
var resolveBaseline = (cwd) => {
|
|
191
206
|
try {
|
|
192
|
-
const parsed = JSON.parse(readFileSync3(
|
|
207
|
+
const parsed = JSON.parse(readFileSync3(resolve2(cwd, CONFIG_FILE), "utf8"));
|
|
193
208
|
return typeof parsed.baseline === "string" ? parsed.baseline : "gate-baseline.json";
|
|
194
209
|
} catch {
|
|
195
210
|
return "gate-baseline.json";
|
|
@@ -237,11 +252,11 @@ ${output.trim()}`
|
|
|
237
252
|
// src/core/prove.ts
|
|
238
253
|
import { existsSync as existsSync2, mkdtempSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
239
254
|
import { tmpdir } from "os";
|
|
240
|
-
import { delimiter, dirname as dirname4, join as join3, resolve as
|
|
255
|
+
import { delimiter, dirname as dirname4, join as join3, resolve as resolve4 } from "path";
|
|
241
256
|
|
|
242
257
|
// src/core/exclusive.ts
|
|
243
258
|
import { spawn } from "child_process";
|
|
244
|
-
import { dirname as dirname3, resolve as
|
|
259
|
+
import { dirname as dirname3, resolve as resolve3 } from "path";
|
|
245
260
|
var MARK = /^exclusive-hold (start|end) (\d+)$/gm;
|
|
246
261
|
var KEY = "exclusive";
|
|
247
262
|
var spanOf = (output) => {
|
|
@@ -256,7 +271,7 @@ var hold = (cli, holdMs, lockPath) => new Promise((done) => {
|
|
|
256
271
|
process.execPath,
|
|
257
272
|
[cli, "--exclusive", "--exclusive-timeout", "60", "--hold", String(holdMs)],
|
|
258
273
|
{
|
|
259
|
-
cwd: dirname3(
|
|
274
|
+
cwd: dirname3(resolve3(cli)),
|
|
260
275
|
env: { ...process.env, GEONOSIS_HEAVY_LOCK: lockPath },
|
|
261
276
|
stdio: ["ignore", "pipe", "pipe"]
|
|
262
277
|
}
|
|
@@ -306,7 +321,7 @@ ${runs.map((one) => `exit ${String(one.code)}: ${one.output.trim().slice(-300)}`
|
|
|
306
321
|
// src/core/prove.ts
|
|
307
322
|
var toolPath = (cwd) => {
|
|
308
323
|
const dirs = [];
|
|
309
|
-
let dir =
|
|
324
|
+
let dir = resolve4(cwd);
|
|
310
325
|
for (; ; ) {
|
|
311
326
|
const bin = join3(dir, "node_modules", ".bin");
|
|
312
327
|
if (existsSync2(bin)) dirs.push(bin);
|
|
@@ -433,7 +448,7 @@ var runProve = async ({
|
|
|
433
448
|
|
|
434
449
|
// src/core/ratchet.ts
|
|
435
450
|
import { existsSync as existsSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
436
|
-
import { resolve as
|
|
451
|
+
import { resolve as resolve5 } from "path";
|
|
437
452
|
var EVIDENCE_LINES = 10;
|
|
438
453
|
var recorded = (counter, params, run) => {
|
|
439
454
|
let output = "";
|
|
@@ -480,7 +495,7 @@ var runRatchet = async ({
|
|
|
480
495
|
tier
|
|
481
496
|
}) => {
|
|
482
497
|
const config = loadConfig(cwd);
|
|
483
|
-
const baselinePath =
|
|
498
|
+
const baselinePath = resolve5(cwd, config.baseline);
|
|
484
499
|
if (!existsSync3(baselinePath)) {
|
|
485
500
|
throw new Error(`no ${config.baseline} in ${cwd} \u2014 nothing to ratchet against`);
|
|
486
501
|
}
|
|
@@ -597,6 +612,10 @@ var scannerProbe = {
|
|
|
597
612
|
var archViolations = {
|
|
598
613
|
id: "archViolations",
|
|
599
614
|
probe: { ...scannerProbe, expect: 2 },
|
|
615
|
+
// #30: the shipped probe runs a scanner that prints `✗`, so it proves the COMMAND and never a
|
|
616
|
+
// `match` a repo configured for its own scanner's shape. Declared here, a customised match is
|
|
617
|
+
// UNPROVEN until the config declares a sample of its own.
|
|
618
|
+
readingParams: ["match"],
|
|
600
619
|
run: async ({ params, run }) => {
|
|
601
620
|
const command = stringParam("archViolations", params, "command");
|
|
602
621
|
const match = new RegExp(stringParam("archViolations", params, "match", "^\u2717"));
|
|
@@ -649,7 +668,7 @@ var bundleBytes = {
|
|
|
649
668
|
|
|
650
669
|
// src/counters/ci.ts
|
|
651
670
|
import { readdirSync, readFileSync as readFileSync5 } from "fs";
|
|
652
|
-
import { join as join5, resolve as
|
|
671
|
+
import { join as join5, resolve as resolve6 } from "path";
|
|
653
672
|
var DISABLED = /\bif:[ \t]*false\b/;
|
|
654
673
|
var WORKFLOW = /\.ya?ml$/;
|
|
655
674
|
var disabledCiJobs = {
|
|
@@ -664,7 +683,7 @@ var disabledCiJobs = {
|
|
|
664
683
|
},
|
|
665
684
|
run: async ({ cwd, params }) => {
|
|
666
685
|
const relative = stringParam("disabledCiJobs", params, "dir", ".github/workflows");
|
|
667
|
-
const dir =
|
|
686
|
+
const dir = resolve6(cwd, relative);
|
|
668
687
|
let files;
|
|
669
688
|
try {
|
|
670
689
|
files = readdirSync(dir, { withFileTypes: true }).filter((entry) => WORKFLOW.test(entry.name)).map((entry) => join5(dir, entry.name));
|
|
@@ -722,12 +741,40 @@ var excusesIn = (params) => {
|
|
|
722
741
|
typeof declared === "object" && declared !== null && !Array.isArray(declared) ? Object.keys(declared) : []
|
|
723
742
|
);
|
|
724
743
|
};
|
|
744
|
+
var HEADING_LINE = /^([A-Z][A-Za-z ]+?) \((\d+)\)\s*$/;
|
|
745
|
+
var UNRESOLVED = "Unresolved imports";
|
|
746
|
+
var KNIP_DEFAULT = "npx knip --reporter compact";
|
|
747
|
+
var knipEvidence = ({
|
|
748
|
+
output,
|
|
749
|
+
params
|
|
750
|
+
}) => {
|
|
751
|
+
const command = typeof params["command"] === "string" ? params["command"] : KNIP_DEFAULT;
|
|
752
|
+
const said = [];
|
|
753
|
+
let heading;
|
|
754
|
+
for (const line of output.split("\n")) {
|
|
755
|
+
const found = HEADING_LINE.exec(line);
|
|
756
|
+
if (found !== null) {
|
|
757
|
+
heading = found[1];
|
|
758
|
+
said.push(line.trim());
|
|
759
|
+
continue;
|
|
760
|
+
}
|
|
761
|
+
if (heading === void 0 || line.trim() === "") continue;
|
|
762
|
+
said.push(` ${line.trim()}`);
|
|
763
|
+
if (heading === UNRESOLVED) {
|
|
764
|
+
said.push(
|
|
765
|
+
` resolved by \`${command}\` on ${process.version} \u2014 an unresolved import is a fact about the resolver, and this class read one number on CI and another on a laptop over one tree`
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
return said;
|
|
770
|
+
};
|
|
725
771
|
var knipIssues = {
|
|
772
|
+
evidence: knipEvidence,
|
|
726
773
|
id: "knipIssues",
|
|
727
774
|
probe: { ...captured("Unused files (1)\nsrc/gone.ts\n"), expect: 1 },
|
|
728
775
|
readingParams: ["headings"],
|
|
729
776
|
run: async ({ params, run }) => {
|
|
730
|
-
const command = stringParam("knipIssues", params, "command",
|
|
777
|
+
const command = stringParam("knipIssues", params, "command", KNIP_DEFAULT);
|
|
731
778
|
const printed = headingsIn(run(command).output);
|
|
732
779
|
const counted = stringsParam(params, "headings", [...printed.keys()]);
|
|
733
780
|
const excused = excusesIn(params);
|
|
@@ -753,9 +800,119 @@ var boundaryIssues = {
|
|
|
753
800
|
}
|
|
754
801
|
};
|
|
755
802
|
|
|
803
|
+
// src/counters/dx.ts
|
|
804
|
+
import { existsSync as existsSync4, readFileSync as readFileSync6 } from "fs";
|
|
805
|
+
import { resolve as resolve7 } from "path";
|
|
806
|
+
var DEFAULT_BUMP_REPORT = ".geonosis/bump-report.json";
|
|
807
|
+
var bumpFile = (id, cwd, params) => {
|
|
808
|
+
const relative = stringParam(id, params, "report", DEFAULT_BUMP_REPORT);
|
|
809
|
+
const path = resolve7(cwd, relative);
|
|
810
|
+
if (!existsSync4(path)) {
|
|
811
|
+
throw new CounterError(
|
|
812
|
+
id,
|
|
813
|
+
`no bump report at ${relative} \u2014 this repo has never run \`geonosis update\`, and a zero here would read as a bump that took no time`
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
try {
|
|
817
|
+
return JSON.parse(readFileSync6(path, "utf8"));
|
|
818
|
+
} catch (error) {
|
|
819
|
+
throw new CounterError(id, `${relative} is not readable JSON: ${error.message}`);
|
|
820
|
+
}
|
|
821
|
+
};
|
|
822
|
+
var REPORT_WITH_A_GREEN_DOCTOR = JSON.stringify({
|
|
823
|
+
at: "2026-09-03T09:00:00.000Z",
|
|
824
|
+
durationMs: 12e3,
|
|
825
|
+
ok: true,
|
|
826
|
+
steps: [
|
|
827
|
+
{ detail: "", durationMs: 5e3, ok: true, ran: true, step: "install" },
|
|
828
|
+
{ detail: "", durationMs: 7e3, ok: true, ran: true, step: "doctor" }
|
|
829
|
+
],
|
|
830
|
+
to: "2.2.0"
|
|
831
|
+
});
|
|
832
|
+
var bumpDurationMs = {
|
|
833
|
+
id: "bumpDurationMs",
|
|
834
|
+
probe: {
|
|
835
|
+
expect: 12e3,
|
|
836
|
+
input: (dir) => plant(dir, DEFAULT_BUMP_REPORT, REPORT_WITH_A_GREEN_DOCTOR)
|
|
837
|
+
},
|
|
838
|
+
run: async ({ cwd, params }) => {
|
|
839
|
+
const report = bumpFile("bumpDurationMs", cwd, params);
|
|
840
|
+
if (typeof report.durationMs !== "number") {
|
|
841
|
+
throw new CounterError(
|
|
842
|
+
"bumpDurationMs",
|
|
843
|
+
"the bump report carries no durationMs \u2014 it was written by a build that did not time itself"
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
return report.durationMs;
|
|
847
|
+
},
|
|
848
|
+
tolerates: true
|
|
849
|
+
};
|
|
850
|
+
var timeToGreenDoctorMs = {
|
|
851
|
+
id: "timeToGreenDoctorMs",
|
|
852
|
+
probe: {
|
|
853
|
+
expect: 12e3,
|
|
854
|
+
input: (dir) => plant(dir, DEFAULT_BUMP_REPORT, REPORT_WITH_A_GREEN_DOCTOR)
|
|
855
|
+
},
|
|
856
|
+
run: async ({ cwd, params }) => {
|
|
857
|
+
const steps = bumpFile("timeToGreenDoctorMs", cwd, params).steps ?? [];
|
|
858
|
+
const at = steps.findIndex((one) => one.step === "doctor");
|
|
859
|
+
if (at === -1) {
|
|
860
|
+
throw new CounterError(
|
|
861
|
+
"timeToGreenDoctorMs",
|
|
862
|
+
"the bump report names no doctor step, so nothing in it says when this repo first read green"
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
if (steps[at]?.ok !== true) {
|
|
866
|
+
throw new CounterError(
|
|
867
|
+
"timeToGreenDoctorMs",
|
|
868
|
+
"the doctor step of the last bump was not green \u2014 there is no time to a green that never came"
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
return steps.slice(0, at + 1).reduce((total2, one) => total2 + (typeof one.durationMs === "number" ? one.durationMs : 0), 0);
|
|
872
|
+
},
|
|
873
|
+
tolerates: true
|
|
874
|
+
};
|
|
875
|
+
var SEPARATOR = /^\|[\s:|-]*\|\s*$/;
|
|
876
|
+
var ROW = /^\|.*\|\s*$/;
|
|
877
|
+
var cellsOf = (line) => line.replace(/^\|/, "").replace(/\|\s*$/, "").split("|").map((one) => one.trim());
|
|
878
|
+
var openBacklogRows = {
|
|
879
|
+
id: "openBacklogRows",
|
|
880
|
+
probe: {
|
|
881
|
+
expect: 2,
|
|
882
|
+
input: (dir) => plant(
|
|
883
|
+
dir,
|
|
884
|
+
"backlog.md",
|
|
885
|
+
[
|
|
886
|
+
"| # | Finding | Status |",
|
|
887
|
+
"|---|---|---|",
|
|
888
|
+
"| 1 | closed | **DONE** |",
|
|
889
|
+
"| 2 | still here | open |",
|
|
890
|
+
"| 3 | also here | open \u2014 next |",
|
|
891
|
+
""
|
|
892
|
+
].join("\n")
|
|
893
|
+
),
|
|
894
|
+
params: { path: "backlog.md" }
|
|
895
|
+
},
|
|
896
|
+
run: async ({ cwd, params }) => {
|
|
897
|
+
const relative = stringParam("openBacklogRows", params, "path");
|
|
898
|
+
const done = stringParam("openBacklogRows", params, "done", "DONE");
|
|
899
|
+
const at = resolve7(cwd, relative);
|
|
900
|
+
if (!existsSync4(at)) {
|
|
901
|
+
throw new CounterError("openBacklogRows", `no register at ${relative}`);
|
|
902
|
+
}
|
|
903
|
+
const lines = readFileSync6(at, "utf8").split("\n");
|
|
904
|
+
return lines.filter((line, index) => {
|
|
905
|
+
if (!ROW.test(line) || SEPARATOR.test(line)) return false;
|
|
906
|
+
if (SEPARATOR.test(lines[index + 1] ?? "")) return false;
|
|
907
|
+
const status = cellsOf(line).at(-1) ?? "";
|
|
908
|
+
return status !== "" && !status.includes(done);
|
|
909
|
+
}).length;
|
|
910
|
+
}
|
|
911
|
+
};
|
|
912
|
+
|
|
756
913
|
// src/counters/format.ts
|
|
757
|
-
import { existsSync as
|
|
758
|
-
import { resolve as
|
|
914
|
+
import { existsSync as existsSync5 } from "fs";
|
|
915
|
+
import { resolve as resolve8 } from "path";
|
|
759
916
|
var unformattedFiles = {
|
|
760
917
|
id: "unformattedFiles",
|
|
761
918
|
probe: {
|
|
@@ -770,13 +927,13 @@ var unformattedFiles = {
|
|
|
770
927
|
"command",
|
|
771
928
|
"npx oxfmt --config .oxfmtrc.json --list-different ."
|
|
772
929
|
);
|
|
773
|
-
return run(command).output.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 &&
|
|
930
|
+
return run(command).output.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && existsSync5(resolve8(cwd, line))).length;
|
|
774
931
|
}
|
|
775
932
|
};
|
|
776
933
|
|
|
777
934
|
// src/counters/gate-report.ts
|
|
778
|
-
import { existsSync as
|
|
779
|
-
import { resolve as
|
|
935
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
|
|
936
|
+
import { resolve as resolve9 } from "path";
|
|
780
937
|
var DEFAULT_REPORT = ".geonosis/gate-report.json";
|
|
781
938
|
var fastTierMs = {
|
|
782
939
|
id: "fastTierMs",
|
|
@@ -799,9 +956,9 @@ var fastTierMs = {
|
|
|
799
956
|
const wanted = stringParam("fastTierMs", params, "tier", "fast");
|
|
800
957
|
const own = `.geonosis/gate-report.${wanted}.json`;
|
|
801
958
|
const asked = stringParam("fastTierMs", params, "report", "");
|
|
802
|
-
const relative = asked !== "" ? asked :
|
|
803
|
-
const path =
|
|
804
|
-
if (!
|
|
959
|
+
const relative = asked !== "" ? asked : existsSync6(resolve9(cwd, own)) ? own : DEFAULT_REPORT;
|
|
960
|
+
const path = resolve9(cwd, relative);
|
|
961
|
+
if (!existsSync6(path)) {
|
|
805
962
|
throw new CounterError(
|
|
806
963
|
"fastTierMs",
|
|
807
964
|
`no gate report at ${relative} \u2014 run \`geonosis-verify ${wanted}\` before measuring it`
|
|
@@ -809,7 +966,7 @@ var fastTierMs = {
|
|
|
809
966
|
}
|
|
810
967
|
let report;
|
|
811
968
|
try {
|
|
812
|
-
report = JSON.parse(
|
|
969
|
+
report = JSON.parse(readFileSync7(path, "utf8"));
|
|
813
970
|
} catch (error) {
|
|
814
971
|
throw new CounterError(
|
|
815
972
|
"fastTierMs",
|
|
@@ -831,8 +988,8 @@ var fastTierMs = {
|
|
|
831
988
|
};
|
|
832
989
|
|
|
833
990
|
// src/counters/law.ts
|
|
834
|
-
import { existsSync as
|
|
835
|
-
import { resolve as
|
|
991
|
+
import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
|
|
992
|
+
import { resolve as resolve10 } from "path";
|
|
836
993
|
var lawLineCount = {
|
|
837
994
|
id: "lawLineCount",
|
|
838
995
|
probe: {
|
|
@@ -842,15 +999,15 @@ var lawLineCount = {
|
|
|
842
999
|
},
|
|
843
1000
|
run: async ({ cwd, params }) => {
|
|
844
1001
|
const relative = stringParam("lawLineCount", params, "path", "CLAUDE.md");
|
|
845
|
-
const path =
|
|
846
|
-
if (!
|
|
847
|
-
return
|
|
1002
|
+
const path = resolve10(cwd, relative);
|
|
1003
|
+
if (!existsSync7(path)) throw new CounterError("lawLineCount", `no law file at ${relative}`);
|
|
1004
|
+
return readFileSync8(path, "utf8").replace(/\n$/, "").split("\n").length;
|
|
848
1005
|
}
|
|
849
1006
|
};
|
|
850
1007
|
|
|
851
1008
|
// src/counters/oxlint.ts
|
|
852
|
-
import { existsSync as
|
|
853
|
-
import { resolve as
|
|
1009
|
+
import { existsSync as existsSync8, readFileSync as readFileSync9, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
1010
|
+
import { resolve as resolve11 } from "path";
|
|
854
1011
|
var DEFAULT_COMMAND = "npx oxlint --format=unix --config .oxlintrc.json .";
|
|
855
1012
|
var PROBE_COMMAND = "oxlint --format=unix --config .oxlintrc.json .";
|
|
856
1013
|
var oxlintProbe = (severity, rule, source) => ({
|
|
@@ -1013,26 +1170,26 @@ var oxlintRule = {
|
|
|
1013
1170
|
const config = typeof params.config === "string" ? params.config : "";
|
|
1014
1171
|
const expect = expectedFormat("oxlintRule", params);
|
|
1015
1172
|
if (config === "") return countRule(run(command), rule, expect);
|
|
1016
|
-
const source =
|
|
1017
|
-
if (!
|
|
1173
|
+
const source = resolve11(cwd, config);
|
|
1174
|
+
if (!existsSync8(source)) throw new CounterError("oxlintRule", `no config at ${config}`);
|
|
1018
1175
|
const strictName = `.oxlintrc.ratchet-${key}.json`;
|
|
1019
|
-
const strict =
|
|
1176
|
+
const strict = readFileSync9(source, "utf8").replace(
|
|
1020
1177
|
new RegExp(`("[^"]*${escapeForRegex(rule)}"\\s*:\\s*\\[?\\s*)"(warn|off)"`),
|
|
1021
1178
|
'$1"error"'
|
|
1022
1179
|
);
|
|
1023
|
-
writeFileSync6(
|
|
1180
|
+
writeFileSync6(resolve11(cwd, strictName), strict);
|
|
1024
1181
|
try {
|
|
1025
1182
|
return countRule(run(command.replace("{config}", strictName)), rule, expect);
|
|
1026
1183
|
} finally {
|
|
1027
|
-
rmSync3(
|
|
1184
|
+
rmSync3(resolve11(cwd, strictName), { force: true });
|
|
1028
1185
|
}
|
|
1029
1186
|
}
|
|
1030
1187
|
};
|
|
1031
1188
|
|
|
1032
1189
|
// src/counters/probes.ts
|
|
1033
|
-
import { existsSync as
|
|
1190
|
+
import { existsSync as existsSync9, readFileSync as readFileSync10 } from "fs";
|
|
1034
1191
|
import { createRequire } from "module";
|
|
1035
|
-
import { join as join6, resolve as
|
|
1192
|
+
import { join as join6, resolve as resolve12 } from "path";
|
|
1036
1193
|
import { pathToFileURL } from "url";
|
|
1037
1194
|
var ID = "probelessRules";
|
|
1038
1195
|
var OFF = /* @__PURE__ */ new Set([0, "0", "allow", "off", false]);
|
|
@@ -1110,11 +1267,11 @@ var probelessRules = {
|
|
|
1110
1267
|
},
|
|
1111
1268
|
run: async ({ cwd, params }) => {
|
|
1112
1269
|
const relative = stringParam(ID, params, "config", ".oxlintrc.json");
|
|
1113
|
-
const path =
|
|
1114
|
-
if (!
|
|
1270
|
+
const path = resolve12(cwd, relative);
|
|
1271
|
+
if (!existsSync9(path)) throw new CounterError(ID, `no oxlint config at ${relative}`);
|
|
1115
1272
|
let config;
|
|
1116
1273
|
try {
|
|
1117
|
-
config = JSON.parse(
|
|
1274
|
+
config = JSON.parse(readFileSync10(path, "utf8"));
|
|
1118
1275
|
} catch (error) {
|
|
1119
1276
|
throw new CounterError(ID, `${relative} does not parse: ${error.message}`);
|
|
1120
1277
|
}
|
|
@@ -1151,8 +1308,8 @@ import { readdirSync as readdirSync3 } from "fs";
|
|
|
1151
1308
|
import { join as join8 } from "path";
|
|
1152
1309
|
|
|
1153
1310
|
// src/counters/workspace.ts
|
|
1154
|
-
import { existsSync as
|
|
1155
|
-
import { join as join7, resolve as
|
|
1311
|
+
import { existsSync as existsSync10, readdirSync as readdirSync2, readFileSync as readFileSync11 } from "fs";
|
|
1312
|
+
import { join as join7, resolve as resolve13 } from "path";
|
|
1156
1313
|
var SKIP = /^(node_modules|\.)/;
|
|
1157
1314
|
var childDirs = (dir) => {
|
|
1158
1315
|
try {
|
|
@@ -1166,14 +1323,14 @@ var expand = (root, pattern) => {
|
|
|
1166
1323
|
const segments = pattern.split("/").filter((one) => one !== "" && one !== ".");
|
|
1167
1324
|
let dirs = [root];
|
|
1168
1325
|
for (const segment of segments) {
|
|
1169
|
-
dirs = segment === "*" ? dirs.flatMap((dir) => childDirs(dir)) : segment === "**" ? dirs.flatMap((dir) => descendants(dir, 3)) : dirs.map((dir) => join7(dir, segment)).filter((dir) =>
|
|
1326
|
+
dirs = segment === "*" ? dirs.flatMap((dir) => childDirs(dir)) : segment === "**" ? dirs.flatMap((dir) => descendants(dir, 3)) : dirs.map((dir) => join7(dir, segment)).filter((dir) => existsSync10(dir));
|
|
1170
1327
|
}
|
|
1171
1328
|
return dirs;
|
|
1172
1329
|
};
|
|
1173
1330
|
var QUOTED = /^['"]|['"]$/g;
|
|
1174
1331
|
var cleaned = (value) => value.replace(/#.*$/, "").trim().replaceAll(QUOTED, "");
|
|
1175
1332
|
var pnpmPatterns = (path) => {
|
|
1176
|
-
const lines =
|
|
1333
|
+
const lines = readFileSync11(path, "utf8").split("\n");
|
|
1177
1334
|
const at = lines.findIndex((line) => line.startsWith("packages:"));
|
|
1178
1335
|
if (at === -1) return [];
|
|
1179
1336
|
const inline = lines[at]?.slice("packages:".length).trim() ?? "";
|
|
@@ -1192,31 +1349,31 @@ var pnpmPatterns = (path) => {
|
|
|
1192
1349
|
return patterns;
|
|
1193
1350
|
};
|
|
1194
1351
|
var npmPatterns = (path) => {
|
|
1195
|
-
const parsed = JSON.parse(
|
|
1352
|
+
const parsed = JSON.parse(readFileSync11(path, "utf8"));
|
|
1196
1353
|
const declared = Array.isArray(parsed.workspaces) ? parsed.workspaces : parsed.workspaces?.packages ?? [];
|
|
1197
1354
|
return declared.filter((one) => typeof one === "string");
|
|
1198
1355
|
};
|
|
1199
1356
|
var nameOf = (dir) => {
|
|
1200
1357
|
const manifest = join7(dir, "package.json");
|
|
1201
|
-
if (!
|
|
1358
|
+
if (!existsSync10(manifest)) return void 0;
|
|
1202
1359
|
try {
|
|
1203
|
-
const { name } = JSON.parse(
|
|
1360
|
+
const { name } = JSON.parse(readFileSync11(manifest, "utf8"));
|
|
1204
1361
|
return typeof name === "string" && name !== "" ? name : void 0;
|
|
1205
1362
|
} catch {
|
|
1206
1363
|
return void 0;
|
|
1207
1364
|
}
|
|
1208
1365
|
};
|
|
1209
1366
|
var workspaceDirs = (cwd) => {
|
|
1210
|
-
const root =
|
|
1367
|
+
const root = resolve13(cwd);
|
|
1211
1368
|
const pnpm = join7(root, "pnpm-workspace.yaml");
|
|
1212
1369
|
const manifest = join7(root, "package.json");
|
|
1213
|
-
const patterns =
|
|
1214
|
-
const dirs = patterns.filter((pattern) => !pattern.startsWith("!")).flatMap((pattern) => expand(root, pattern)).filter((dir) =>
|
|
1370
|
+
const patterns = existsSync10(pnpm) ? pnpmPatterns(pnpm) : existsSync10(manifest) ? npmPatterns(manifest) : [];
|
|
1371
|
+
const dirs = patterns.filter((pattern) => !pattern.startsWith("!")).flatMap((pattern) => expand(root, pattern)).filter((dir) => existsSync10(join7(dir, "package.json")));
|
|
1215
1372
|
return [...new Set(dirs)];
|
|
1216
1373
|
};
|
|
1217
1374
|
var manifestOf = (dir) => {
|
|
1218
1375
|
try {
|
|
1219
|
-
const parsed = JSON.parse(
|
|
1376
|
+
const parsed = JSON.parse(readFileSync11(join7(dir, "package.json"), "utf8"));
|
|
1220
1377
|
return typeof parsed === "object" && parsed !== null ? parsed : void 0;
|
|
1221
1378
|
} catch {
|
|
1222
1379
|
return void 0;
|
|
@@ -1283,6 +1440,56 @@ var packagesWithoutTypecheck = {
|
|
|
1283
1440
|
}
|
|
1284
1441
|
};
|
|
1285
1442
|
|
|
1443
|
+
// src/counters/seams.ts
|
|
1444
|
+
import { existsSync as existsSync11, readdirSync as readdirSync4, readFileSync as readFileSync12 } from "fs";
|
|
1445
|
+
import { join as join9, resolve as resolve14 } from "path";
|
|
1446
|
+
var CONFIG = "geonosis.json";
|
|
1447
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set([".git", ".turbo", "coverage", "dist", "node_modules"]);
|
|
1448
|
+
var asPattern = (glob) => new RegExp(
|
|
1449
|
+
`^${glob.split("/").map(
|
|
1450
|
+
(part) => part === "**" ? ".*" : part.replaceAll(/[.+^${}()|[\]\\]/g, String.raw`\$&`).replaceAll("*", "[^/]*")
|
|
1451
|
+
).join("/").replaceAll(".*/", "(?:.*/)?")}$`
|
|
1452
|
+
);
|
|
1453
|
+
var filesUnder = (root, at = root) => readdirSync4(at, { withFileTypes: true }).flatMap((entry) => {
|
|
1454
|
+
if (SKIP_DIRS.has(entry.name)) return [];
|
|
1455
|
+
const full = join9(at, entry.name);
|
|
1456
|
+
if (entry.isDirectory()) return filesUnder(root, full);
|
|
1457
|
+
return entry.isFile() ? [full.slice(root.length + 1)] : [];
|
|
1458
|
+
});
|
|
1459
|
+
var seamGlobsIn = (root) => {
|
|
1460
|
+
const at = join9(root, CONFIG);
|
|
1461
|
+
if (!existsSync11(at)) return [];
|
|
1462
|
+
try {
|
|
1463
|
+
const seams = JSON.parse(readFileSync12(at, "utf8")).adoption?.seams;
|
|
1464
|
+
return Array.isArray(seams) ? seams.filter((one) => typeof one === "string") : [];
|
|
1465
|
+
} catch {
|
|
1466
|
+
return [];
|
|
1467
|
+
}
|
|
1468
|
+
};
|
|
1469
|
+
var linesIn = (path) => {
|
|
1470
|
+
const body = readFileSync12(path, "utf8");
|
|
1471
|
+
return body === "" ? 0 : body.replace(/\n$/, "").split("\n").length;
|
|
1472
|
+
};
|
|
1473
|
+
var adoptionSeamLines = {
|
|
1474
|
+
id: "adoptionSeamLines",
|
|
1475
|
+
probe: {
|
|
1476
|
+
expect: 3,
|
|
1477
|
+
input: (dir) => {
|
|
1478
|
+
plant(dir, CONFIG, `${JSON.stringify({ adoption: { seams: ["src/**/*.ts"] } })}
|
|
1479
|
+
`);
|
|
1480
|
+
plant(dir, "src/adapter.ts", "one\ntwo\nthree\n");
|
|
1481
|
+
plant(dir, "src/not-a-seam.md", "four\nfive\n");
|
|
1482
|
+
},
|
|
1483
|
+
params: {}
|
|
1484
|
+
},
|
|
1485
|
+
run: async ({ cwd }) => {
|
|
1486
|
+
const root = resolve14(cwd);
|
|
1487
|
+
const globs = seamGlobsIn(root).map(asPattern);
|
|
1488
|
+
if (globs.length === 0) return 0;
|
|
1489
|
+
return filesUnder(root).filter((one) => globs.some((pattern) => pattern.test(one))).reduce((total2, one) => total2 + linesIn(join9(root, one)), 0);
|
|
1490
|
+
}
|
|
1491
|
+
};
|
|
1492
|
+
|
|
1286
1493
|
// src/counters/sum-of-counts.ts
|
|
1287
1494
|
var sumOfCounts = {
|
|
1288
1495
|
id: "sumOfCounts",
|
|
@@ -1305,15 +1512,15 @@ var sumOfCounts = {
|
|
|
1305
1512
|
};
|
|
1306
1513
|
|
|
1307
1514
|
// src/counters/suppressions.ts
|
|
1308
|
-
import { readdirSync as
|
|
1309
|
-
import { join as
|
|
1515
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync13 } from "fs";
|
|
1516
|
+
import { join as join10, resolve as resolve15 } from "path";
|
|
1310
1517
|
var CODE_FILE = /\.(?:ts|tsx|js|jsx|mjs|cjs|mts|cts|vue|svelte|astro)$/;
|
|
1311
1518
|
var SKIP3 = /^(?:node_modules|dist|coverage|\.[^.]|__fixtures__)/;
|
|
1312
1519
|
var SUPPRESSION = /(?:eslint|oxlint|biome)-(?:disable|ignore)(?:-next-line|-line)?|@ts-expect-error|@ts-ignore/g;
|
|
1313
1520
|
var countIn = (dir, depth = 12) => {
|
|
1314
1521
|
let entries;
|
|
1315
1522
|
try {
|
|
1316
|
-
entries =
|
|
1523
|
+
entries = readdirSync5(dir, { withFileTypes: true });
|
|
1317
1524
|
} catch {
|
|
1318
1525
|
return 0;
|
|
1319
1526
|
}
|
|
@@ -1321,12 +1528,12 @@ var countIn = (dir, depth = 12) => {
|
|
|
1321
1528
|
for (const entry of entries) {
|
|
1322
1529
|
if (SKIP3.test(entry.name)) continue;
|
|
1323
1530
|
if (entry.isDirectory()) {
|
|
1324
|
-
if (depth > 0) found += countIn(
|
|
1531
|
+
if (depth > 0) found += countIn(join10(dir, entry.name), depth - 1);
|
|
1325
1532
|
continue;
|
|
1326
1533
|
}
|
|
1327
1534
|
if (!CODE_FILE.test(entry.name)) continue;
|
|
1328
1535
|
try {
|
|
1329
|
-
found +=
|
|
1536
|
+
found += readFileSync13(join10(dir, entry.name), "utf8").match(SUPPRESSION)?.length ?? 0;
|
|
1330
1537
|
} catch {
|
|
1331
1538
|
}
|
|
1332
1539
|
}
|
|
@@ -1340,14 +1547,107 @@ var suppressionCount = {
|
|
|
1340
1547
|
},
|
|
1341
1548
|
run: async ({ cwd, params }) => {
|
|
1342
1549
|
const roots = stringsParam(params, "roots", ["."]);
|
|
1343
|
-
return roots.reduce((sum, root) => sum + countIn(
|
|
1550
|
+
return roots.reduce((sum, root) => sum + countIn(resolve15(cwd, root)), 0);
|
|
1551
|
+
}
|
|
1552
|
+
};
|
|
1553
|
+
|
|
1554
|
+
// src/counters/surface.ts
|
|
1555
|
+
import { existsSync as existsSync12, readFileSync as readFileSync14 } from "fs";
|
|
1556
|
+
import { join as join11, resolve as resolve16 } from "path";
|
|
1557
|
+
var DECLARED = /^export\s+(?:declare\s+)?(?:abstract\s+)?(?:const|function|class|type|interface|enum|let|var)\s+([$\w]+)/;
|
|
1558
|
+
var BRACED = /^export\s+(?:type\s+)?\{([^}]*)\}(?:\s+from\s+(['"])(.+?)\2)?/;
|
|
1559
|
+
var nameOf2 = (spec) => {
|
|
1560
|
+
const named = spec.trim().replace(/^type\s+/, "");
|
|
1561
|
+
if (named === "") return void 0;
|
|
1562
|
+
const parts = named.split(/\s+as\s+/);
|
|
1563
|
+
return (parts.at(-1) ?? "").trim() || void 0;
|
|
1564
|
+
};
|
|
1565
|
+
var exportedSurfaceIn = (source) => {
|
|
1566
|
+
const found = /* @__PURE__ */ new Map();
|
|
1567
|
+
for (const line of source.split("\n")) {
|
|
1568
|
+
const declared = DECLARED.exec(line);
|
|
1569
|
+
if (declared !== null) {
|
|
1570
|
+
found.set(declared[1] ?? "", line.trim());
|
|
1571
|
+
continue;
|
|
1572
|
+
}
|
|
1573
|
+
const braced = BRACED.exec(line);
|
|
1574
|
+
if (braced === null) continue;
|
|
1575
|
+
const from = braced[3];
|
|
1576
|
+
for (const spec of (braced[1] ?? "").split(",")) {
|
|
1577
|
+
const name = nameOf2(spec);
|
|
1578
|
+
if (name === void 0) continue;
|
|
1579
|
+
found.set(
|
|
1580
|
+
name,
|
|
1581
|
+
from === void 0 ? `export { ${name} }` : `export { ${name} } from '${from}'`
|
|
1582
|
+
);
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
return found;
|
|
1586
|
+
};
|
|
1587
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1588
|
+
var declarationEntriesOf = (dir) => {
|
|
1589
|
+
const at = join11(dir, "package.json");
|
|
1590
|
+
if (!existsSync12(at)) {
|
|
1591
|
+
throw new CounterError("floorSurface", `no package.json in ${dir} \u2014 that is not a package`);
|
|
1592
|
+
}
|
|
1593
|
+
const manifest = JSON.parse(readFileSync14(at, "utf8"));
|
|
1594
|
+
const found = [];
|
|
1595
|
+
const walk = (value) => {
|
|
1596
|
+
if (typeof value === "string" && value.endsWith(".d.ts")) found.push(value);
|
|
1597
|
+
if (isRecord(value)) for (const one of Object.values(value)) walk(one);
|
|
1598
|
+
};
|
|
1599
|
+
walk({ exports: manifest["exports"], types: manifest["types"] });
|
|
1600
|
+
return [...new Set(found)];
|
|
1601
|
+
};
|
|
1602
|
+
var floorSurface = {
|
|
1603
|
+
evidence: ({ params }) => [`the doors declared by ${String(params["dir"] ?? "this package")}`],
|
|
1604
|
+
id: "floorSurface",
|
|
1605
|
+
probe: {
|
|
1606
|
+
expect: 3,
|
|
1607
|
+
input: (dir) => {
|
|
1608
|
+
plant(
|
|
1609
|
+
dir,
|
|
1610
|
+
"floor/package.json",
|
|
1611
|
+
`${JSON.stringify({ exports: { ".": { types: "./dist/index.d.ts" } }, name: "floor" })}
|
|
1612
|
+
`
|
|
1613
|
+
);
|
|
1614
|
+
plant(
|
|
1615
|
+
dir,
|
|
1616
|
+
"floor/dist/index.d.ts",
|
|
1617
|
+
"export declare const open: () => void;\nexport type Handle = { id: string };\nexport { c as close } from './chunk.js';\ndeclare const hidden: number;\n"
|
|
1618
|
+
);
|
|
1619
|
+
},
|
|
1620
|
+
params: { dir: "floor" }
|
|
1621
|
+
},
|
|
1622
|
+
run: async ({ cwd, params }) => {
|
|
1623
|
+
const relative = stringParam("floorSurface", params, "dir");
|
|
1624
|
+
const dir = resolve16(cwd, relative);
|
|
1625
|
+
const entries = declarationEntriesOf(dir);
|
|
1626
|
+
if (entries.length === 0) {
|
|
1627
|
+
throw new CounterError(
|
|
1628
|
+
"floorSurface",
|
|
1629
|
+
`${relative} promises no .d.ts in its exports map \u2014 a package whose surface cannot be read is not a package this can hold`
|
|
1630
|
+
);
|
|
1631
|
+
}
|
|
1632
|
+
const names = /* @__PURE__ */ new Set();
|
|
1633
|
+
for (const entry of entries) {
|
|
1634
|
+
const at = resolve16(dir, entry);
|
|
1635
|
+
if (!existsSync12(at)) {
|
|
1636
|
+
throw new CounterError(
|
|
1637
|
+
"floorSurface",
|
|
1638
|
+
`${relative} promises ${entry} and it is not there \u2014 build before measuring, or the surface reads as empty`
|
|
1639
|
+
);
|
|
1640
|
+
}
|
|
1641
|
+
for (const name of exportedSurfaceIn(readFileSync14(at, "utf8")).keys()) names.add(name);
|
|
1642
|
+
}
|
|
1643
|
+
return names.size;
|
|
1344
1644
|
}
|
|
1345
1645
|
};
|
|
1346
1646
|
|
|
1347
1647
|
// src/counters/tests.ts
|
|
1348
|
-
import { existsSync as
|
|
1648
|
+
import { existsSync as existsSync13, mkdtempSync as mkdtempSync2, readFileSync as readFileSync15, rmSync as rmSync4 } from "fs";
|
|
1349
1649
|
import { tmpdir as tmpdir2 } from "os";
|
|
1350
|
-
import { join as
|
|
1650
|
+
import { join as join12, resolve as resolve17 } from "path";
|
|
1351
1651
|
var COUNTER = "testFailures";
|
|
1352
1652
|
var VITEST_LINE = /^\s*Tests {2,}(.+?)\s*$/;
|
|
1353
1653
|
var VITEST_TOTAL = /\(\d+\)$/;
|
|
@@ -1379,7 +1679,7 @@ ${output.trim().slice(-500)}`
|
|
|
1379
1679
|
);
|
|
1380
1680
|
};
|
|
1381
1681
|
var fromReport = (path) => {
|
|
1382
|
-
if (!
|
|
1682
|
+
if (!existsSync13(path)) {
|
|
1383
1683
|
throw new CounterError(
|
|
1384
1684
|
COUNTER,
|
|
1385
1685
|
`the runner wrote no report at ${path} \u2014 a crash before the reporter is not a pass`
|
|
@@ -1387,7 +1687,7 @@ var fromReport = (path) => {
|
|
|
1387
1687
|
}
|
|
1388
1688
|
let report;
|
|
1389
1689
|
try {
|
|
1390
|
-
report = JSON.parse(
|
|
1690
|
+
report = JSON.parse(readFileSync15(path, "utf8"));
|
|
1391
1691
|
} catch (error) {
|
|
1392
1692
|
throw new CounterError(
|
|
1393
1693
|
COUNTER,
|
|
@@ -1411,14 +1711,14 @@ var fromReport = (path) => {
|
|
|
1411
1711
|
};
|
|
1412
1712
|
var reportPathFor = (cwd, params, command) => {
|
|
1413
1713
|
const named = params.reportPath;
|
|
1414
|
-
if (typeof named === "string" && named !== "") return { own: false, path:
|
|
1714
|
+
if (typeof named === "string" && named !== "") return { own: false, path: resolve17(cwd, named) };
|
|
1415
1715
|
if (!command.includes(PLACEHOLDER)) {
|
|
1416
1716
|
throw new CounterError(
|
|
1417
1717
|
COUNTER,
|
|
1418
1718
|
`report: "${VITEST_JSON}" needs somewhere to put the report \u2014 write ${PLACEHOLDER} into the command (--outputFile=${PLACEHOLDER}) or give the entry a "reportPath"`
|
|
1419
1719
|
);
|
|
1420
1720
|
}
|
|
1421
|
-
return { own: true, path:
|
|
1721
|
+
return { own: true, path: join12(mkdtempSync2(join12(tmpdir2(), "geonosis-report-")), "report.json") };
|
|
1422
1722
|
};
|
|
1423
1723
|
var testFailures = {
|
|
1424
1724
|
id: COUNTER,
|
|
@@ -1467,21 +1767,21 @@ var testFailures = {
|
|
|
1467
1767
|
run(command.replaceAll(PLACEHOLDER, path));
|
|
1468
1768
|
return fromReport(path);
|
|
1469
1769
|
} finally {
|
|
1470
|
-
if (own) rmSync4(
|
|
1770
|
+
if (own) rmSync4(join12(path, ".."), { force: true, recursive: true });
|
|
1471
1771
|
}
|
|
1472
1772
|
}
|
|
1473
1773
|
};
|
|
1474
1774
|
|
|
1475
1775
|
// src/counters/todos.ts
|
|
1476
|
-
import { existsSync as
|
|
1477
|
-
import { join as
|
|
1776
|
+
import { existsSync as existsSync14, readdirSync as readdirSync6, readFileSync as readFileSync16 } from "fs";
|
|
1777
|
+
import { join as join13, resolve as resolve18 } from "path";
|
|
1478
1778
|
var CODE_FILE2 = /\.(?:ts|tsx|js|jsx|mjs|cjs|mts|cts|vue|svelte|astro)$/;
|
|
1479
1779
|
var SKIP4 = /^(?:node_modules|dist|coverage|\.[^.]|__fixtures__)/;
|
|
1480
1780
|
var PLAN_FILE = /^(\d{3,})-[a-z\d][a-z\d.-]*\.md$/;
|
|
1481
1781
|
var plansIn = (dir) => {
|
|
1482
|
-
if (!
|
|
1782
|
+
if (!existsSync14(dir)) return /* @__PURE__ */ new Set();
|
|
1483
1783
|
const found = /* @__PURE__ */ new Set();
|
|
1484
|
-
for (const name of
|
|
1784
|
+
for (const name of readdirSync6(dir)) {
|
|
1485
1785
|
const number = PLAN_FILE.exec(name)?.[1];
|
|
1486
1786
|
if (number !== void 0) found.add(String(Number(number)));
|
|
1487
1787
|
}
|
|
@@ -1490,7 +1790,7 @@ var plansIn = (dir) => {
|
|
|
1490
1790
|
var countIn2 = (dir, marker, plans, depth = 12) => {
|
|
1491
1791
|
let entries;
|
|
1492
1792
|
try {
|
|
1493
|
-
entries =
|
|
1793
|
+
entries = readdirSync6(dir, { withFileTypes: true });
|
|
1494
1794
|
} catch {
|
|
1495
1795
|
return 0;
|
|
1496
1796
|
}
|
|
@@ -1498,13 +1798,13 @@ var countIn2 = (dir, marker, plans, depth = 12) => {
|
|
|
1498
1798
|
for (const entry of entries) {
|
|
1499
1799
|
if (SKIP4.test(entry.name)) continue;
|
|
1500
1800
|
if (entry.isDirectory()) {
|
|
1501
|
-
if (depth > 0) found += countIn2(
|
|
1801
|
+
if (depth > 0) found += countIn2(join13(dir, entry.name), marker, plans, depth - 1);
|
|
1502
1802
|
continue;
|
|
1503
1803
|
}
|
|
1504
1804
|
if (!CODE_FILE2.test(entry.name)) continue;
|
|
1505
1805
|
let text = "";
|
|
1506
1806
|
try {
|
|
1507
|
-
text =
|
|
1807
|
+
text = readFileSync16(join13(dir, entry.name), "utf8");
|
|
1508
1808
|
} catch {
|
|
1509
1809
|
continue;
|
|
1510
1810
|
}
|
|
@@ -1528,22 +1828,22 @@ var orphanTodos = {
|
|
|
1528
1828
|
readingParams: ["markers"],
|
|
1529
1829
|
run: async ({ cwd, params }) => {
|
|
1530
1830
|
const markers = stringsParam(params, "markers", ["TODO", "FIXME"]);
|
|
1531
|
-
const plans = plansIn(
|
|
1831
|
+
const plans = plansIn(resolve18(cwd, stringParam("orphanTodos", params, "plans", "plans")));
|
|
1532
1832
|
const marker = new RegExp(
|
|
1533
1833
|
`\\b(?:${markers.map(escapeForRegex).join("|")})\\b(?:\\((\\d+)\\))?`,
|
|
1534
1834
|
"g"
|
|
1535
1835
|
);
|
|
1536
1836
|
const roots = stringsParam(params, "roots", ["."]);
|
|
1537
|
-
return roots.reduce((sum, root) => sum + countIn2(
|
|
1837
|
+
return roots.reduce((sum, root) => sum + countIn2(resolve18(cwd, root), marker, plans), 0);
|
|
1538
1838
|
}
|
|
1539
1839
|
};
|
|
1540
1840
|
|
|
1541
1841
|
// src/counters/typecheck.ts
|
|
1542
|
-
var
|
|
1842
|
+
var UNRESOLVED2 = /error TS(?:2305|2307): ([^\n]*)/g;
|
|
1543
1843
|
var SPECIFIER = /'([^']+)'/g;
|
|
1544
1844
|
var specifiersIn = (message) => [...message.matchAll(SPECIFIER)].map(([, quoted]) => (quoted ?? "").replaceAll('"', ""));
|
|
1545
1845
|
var unbuiltIn = (cwd, output) => {
|
|
1546
|
-
const messages = [...output.matchAll(
|
|
1846
|
+
const messages = [...output.matchAll(UNRESOLVED2)].map(([, message]) => message ?? "");
|
|
1547
1847
|
if (messages.length === 0) return [];
|
|
1548
1848
|
const packages = workspacePackageNames(cwd);
|
|
1549
1849
|
const named = messages.flatMap((message) => specifiersIn(message)).flatMap(
|
|
@@ -1573,8 +1873,8 @@ var typecheckErrors = {
|
|
|
1573
1873
|
};
|
|
1574
1874
|
|
|
1575
1875
|
// src/counters/walk.ts
|
|
1576
|
-
import { existsSync as
|
|
1577
|
-
import { resolve as
|
|
1876
|
+
import { existsSync as existsSync15, readFileSync as readFileSync17 } from "fs";
|
|
1877
|
+
import { resolve as resolve19 } from "path";
|
|
1578
1878
|
var DEFAULT_REPORT2 = ".geonosis/walk-report.json";
|
|
1579
1879
|
var CLASSES = /* @__PURE__ */ new Set([
|
|
1580
1880
|
"buy-box-above-fold",
|
|
@@ -1618,8 +1918,8 @@ var walkFindings = {
|
|
|
1618
1918
|
},
|
|
1619
1919
|
run: async ({ cwd, params }) => {
|
|
1620
1920
|
const relative = stringParam("walkFindings", params, "report", DEFAULT_REPORT2);
|
|
1621
|
-
const path =
|
|
1622
|
-
if (!
|
|
1921
|
+
const path = resolve19(cwd, relative);
|
|
1922
|
+
if (!existsSync15(path)) {
|
|
1623
1923
|
throw new CounterError(
|
|
1624
1924
|
"walkFindings",
|
|
1625
1925
|
`no walk report at ${relative} \u2014 run \`geonosis-walk\` before measuring it`
|
|
@@ -1627,7 +1927,7 @@ var walkFindings = {
|
|
|
1627
1927
|
}
|
|
1628
1928
|
let report;
|
|
1629
1929
|
try {
|
|
1630
|
-
report = JSON.parse(
|
|
1930
|
+
report = JSON.parse(readFileSync17(path, "utf8"));
|
|
1631
1931
|
} catch (error) {
|
|
1632
1932
|
throw new CounterError(
|
|
1633
1933
|
"walkFindings",
|
|
@@ -1649,14 +1949,18 @@ var walkFindings = {
|
|
|
1649
1949
|
|
|
1650
1950
|
// src/counters/index.ts
|
|
1651
1951
|
var COUNTERS = [
|
|
1952
|
+
adoptionSeamLines,
|
|
1652
1953
|
archViolations,
|
|
1653
1954
|
boundaryIssues,
|
|
1955
|
+
bumpDurationMs,
|
|
1654
1956
|
bundleBytes,
|
|
1655
1957
|
cloneCount,
|
|
1656
1958
|
disabledCiJobs,
|
|
1657
1959
|
fastTierMs,
|
|
1960
|
+
floorSurface,
|
|
1658
1961
|
knipIssues,
|
|
1659
1962
|
lawLineCount,
|
|
1963
|
+
openBacklogRows,
|
|
1660
1964
|
suppressionCount,
|
|
1661
1965
|
oxlintErrors,
|
|
1662
1966
|
oxlintRule,
|
|
@@ -1668,6 +1972,7 @@ var COUNTERS = [
|
|
|
1668
1972
|
sumOfCounts,
|
|
1669
1973
|
testFailures,
|
|
1670
1974
|
testsWithoutRunner,
|
|
1975
|
+
timeToGreenDoctorMs,
|
|
1671
1976
|
typecheckErrors,
|
|
1672
1977
|
unformattedFiles,
|
|
1673
1978
|
walkFindings
|
package/dist/cli.js
CHANGED
|
@@ -7,11 +7,11 @@ import {
|
|
|
7
7
|
runRatchet,
|
|
8
8
|
versionOf,
|
|
9
9
|
writeEnvelope
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-E55APSHM.js";
|
|
11
11
|
|
|
12
12
|
// src/cli.ts
|
|
13
13
|
import process from "process";
|
|
14
|
-
var USAGE = `geonosis-ratchet [--cwd <dir>] [--tier <name>] [--prove] [--exclusive]
|
|
14
|
+
var USAGE = `geonosis-ratchet [--cwd <dir>] [--tier <name>] [--prove] [--exclusive] [--json]
|
|
15
15
|
|
|
16
16
|
Debt as a number that may only shrink. Runs the counters named in geonosis.ratchet.json, compares
|
|
17
17
|
each against gate-baseline.json, and REWRITES the baseline down when a number shrank \u2014 so the win is
|
|
@@ -23,6 +23,7 @@ locked in the same commit that earned it.
|
|
|
23
23
|
--exclusive hold the machine-wide heavy lock for the run
|
|
24
24
|
--exclusive-timeout <secs> how long to wait for that lock
|
|
25
25
|
--hold <ms> take the lock, wait, give it back \u2014 the --prove self-test's slow thing
|
|
26
|
+
--json the run's own report on stdout, for a caller that parses it
|
|
26
27
|
--help, -h this text
|
|
27
28
|
|
|
28
29
|
Exit codes: 0 no counter grew \xB7 1 a counter grew \xB7 2 the run could not measure.
|
|
@@ -35,6 +36,7 @@ var KNOWN = /* @__PURE__ */ new Set([
|
|
|
35
36
|
"--exclusive",
|
|
36
37
|
"--exclusive-timeout",
|
|
37
38
|
"--hold",
|
|
39
|
+
"--json",
|
|
38
40
|
"--prove",
|
|
39
41
|
"--tier"
|
|
40
42
|
]);
|
|
@@ -91,6 +93,7 @@ var tierFlag = process.argv.indexOf("--tier");
|
|
|
91
93
|
var tier = tierFlag === -1 ? void 0 : process.argv[tierFlag + 1];
|
|
92
94
|
var proving = process.argv.includes("--prove");
|
|
93
95
|
var exclusive = process.argv.includes("--exclusive");
|
|
96
|
+
var json = process.argv.includes("--json");
|
|
94
97
|
var timeoutFlag = process.argv.indexOf("--exclusive-timeout");
|
|
95
98
|
var timeoutSeconds = timeoutFlag === -1 ? void 0 : Number(process.argv[timeoutFlag + 1] ?? Number.NaN);
|
|
96
99
|
var holdMs = numberAfter("--hold");
|
|
@@ -124,18 +127,20 @@ var measure = async () => {
|
|
|
124
127
|
exclusiveVia: process.argv[1],
|
|
125
128
|
tier
|
|
126
129
|
});
|
|
127
|
-
process.stdout.write(
|
|
130
|
+
process.stdout.write(json ? `${JSON.stringify(proof, void 0, 2)}
|
|
131
|
+
` : formatProve(proof));
|
|
128
132
|
return proof.proven ? 0 : 2;
|
|
129
133
|
}
|
|
130
134
|
const startedAt = Date.now();
|
|
131
135
|
const result = await runRatchet({ counters: COUNTERS, cwd, tier });
|
|
132
|
-
process.stdout.write(formatReport(result));
|
|
136
|
+
if (!json) process.stdout.write(formatReport(result));
|
|
133
137
|
const at = writeEnvelope({
|
|
134
138
|
envelope: envelopeOf(result, Date.now() - startedAt),
|
|
135
139
|
next: ENVELOPE_NEXT,
|
|
136
140
|
root: cwd
|
|
137
141
|
});
|
|
138
|
-
process.stdout.write(
|
|
142
|
+
process.stdout.write(json ? `${JSON.stringify(result, void 0, 2)}
|
|
143
|
+
` : `envelope: ${at}
|
|
139
144
|
`);
|
|
140
145
|
if (result.refusals.length > 0) return 2;
|
|
141
146
|
return result.measurements.some((one) => one.verdict === "grew") ? 1 : 0;
|
package/dist/index.d.ts
CHANGED
|
@@ -259,12 +259,6 @@ declare const envelopePath: (root: string, tool: string) => string;
|
|
|
259
259
|
declare class UnbalancedEnvelope extends Error {
|
|
260
260
|
constructor(message: string);
|
|
261
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
262
|
declare const writeEnvelope: ({ envelope, next, root, }: {
|
|
269
263
|
envelope: ReportEnvelope;
|
|
270
264
|
/** The command a reader should run to see the accounting for themselves. */
|
package/dist/index.js
CHANGED