@geonosis/ratchet 2.2.0 → 2.3.1
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-IQ6TXLWC.js → chunk-E55APSHM.js} +196 -73
- package/dist/cli.js +10 -5
- 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
|
|
|
@@ -74,8 +74,14 @@ var versionOf = (moduleUrl) => {
|
|
|
74
74
|
import { randomUUID } from "crypto";
|
|
75
75
|
import { linkSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
76
76
|
import { homedir } from "os";
|
|
77
|
-
import { dirname as dirname2, join as join2 } from "path";
|
|
78
|
-
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
|
+
};
|
|
79
85
|
var sleep = (ms) => new Promise((done) => setTimeout(done, ms));
|
|
80
86
|
var holderOf = (path) => {
|
|
81
87
|
try {
|
|
@@ -606,6 +612,10 @@ var scannerProbe = {
|
|
|
606
612
|
var archViolations = {
|
|
607
613
|
id: "archViolations",
|
|
608
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"],
|
|
609
619
|
run: async ({ params, run }) => {
|
|
610
620
|
const command = stringParam("archViolations", params, "command");
|
|
611
621
|
const match = new RegExp(stringParam("archViolations", params, "match", "^\u2717"));
|
|
@@ -790,9 +800,119 @@ var boundaryIssues = {
|
|
|
790
800
|
}
|
|
791
801
|
};
|
|
792
802
|
|
|
793
|
-
// src/counters/
|
|
794
|
-
import { existsSync as existsSync4 } from "fs";
|
|
803
|
+
// src/counters/dx.ts
|
|
804
|
+
import { existsSync as existsSync4, readFileSync as readFileSync6 } from "fs";
|
|
795
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
|
+
|
|
913
|
+
// src/counters/format.ts
|
|
914
|
+
import { existsSync as existsSync5 } from "fs";
|
|
915
|
+
import { resolve as resolve8 } from "path";
|
|
796
916
|
var unformattedFiles = {
|
|
797
917
|
id: "unformattedFiles",
|
|
798
918
|
probe: {
|
|
@@ -807,13 +927,13 @@ var unformattedFiles = {
|
|
|
807
927
|
"command",
|
|
808
928
|
"npx oxfmt --config .oxfmtrc.json --list-different ."
|
|
809
929
|
);
|
|
810
|
-
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;
|
|
811
931
|
}
|
|
812
932
|
};
|
|
813
933
|
|
|
814
934
|
// src/counters/gate-report.ts
|
|
815
|
-
import { existsSync as
|
|
816
|
-
import { resolve as
|
|
935
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
|
|
936
|
+
import { resolve as resolve9 } from "path";
|
|
817
937
|
var DEFAULT_REPORT = ".geonosis/gate-report.json";
|
|
818
938
|
var fastTierMs = {
|
|
819
939
|
id: "fastTierMs",
|
|
@@ -836,9 +956,9 @@ var fastTierMs = {
|
|
|
836
956
|
const wanted = stringParam("fastTierMs", params, "tier", "fast");
|
|
837
957
|
const own = `.geonosis/gate-report.${wanted}.json`;
|
|
838
958
|
const asked = stringParam("fastTierMs", params, "report", "");
|
|
839
|
-
const relative = asked !== "" ? asked :
|
|
840
|
-
const path =
|
|
841
|
-
if (!
|
|
959
|
+
const relative = asked !== "" ? asked : existsSync6(resolve9(cwd, own)) ? own : DEFAULT_REPORT;
|
|
960
|
+
const path = resolve9(cwd, relative);
|
|
961
|
+
if (!existsSync6(path)) {
|
|
842
962
|
throw new CounterError(
|
|
843
963
|
"fastTierMs",
|
|
844
964
|
`no gate report at ${relative} \u2014 run \`geonosis-verify ${wanted}\` before measuring it`
|
|
@@ -846,7 +966,7 @@ var fastTierMs = {
|
|
|
846
966
|
}
|
|
847
967
|
let report;
|
|
848
968
|
try {
|
|
849
|
-
report = JSON.parse(
|
|
969
|
+
report = JSON.parse(readFileSync7(path, "utf8"));
|
|
850
970
|
} catch (error) {
|
|
851
971
|
throw new CounterError(
|
|
852
972
|
"fastTierMs",
|
|
@@ -868,8 +988,8 @@ var fastTierMs = {
|
|
|
868
988
|
};
|
|
869
989
|
|
|
870
990
|
// src/counters/law.ts
|
|
871
|
-
import { existsSync as
|
|
872
|
-
import { resolve as
|
|
991
|
+
import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
|
|
992
|
+
import { resolve as resolve10 } from "path";
|
|
873
993
|
var lawLineCount = {
|
|
874
994
|
id: "lawLineCount",
|
|
875
995
|
probe: {
|
|
@@ -879,15 +999,15 @@ var lawLineCount = {
|
|
|
879
999
|
},
|
|
880
1000
|
run: async ({ cwd, params }) => {
|
|
881
1001
|
const relative = stringParam("lawLineCount", params, "path", "CLAUDE.md");
|
|
882
|
-
const path =
|
|
883
|
-
if (!
|
|
884
|
-
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;
|
|
885
1005
|
}
|
|
886
1006
|
};
|
|
887
1007
|
|
|
888
1008
|
// src/counters/oxlint.ts
|
|
889
|
-
import { existsSync as
|
|
890
|
-
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";
|
|
891
1011
|
var DEFAULT_COMMAND = "npx oxlint --format=unix --config .oxlintrc.json .";
|
|
892
1012
|
var PROBE_COMMAND = "oxlint --format=unix --config .oxlintrc.json .";
|
|
893
1013
|
var oxlintProbe = (severity, rule, source) => ({
|
|
@@ -1050,26 +1170,26 @@ var oxlintRule = {
|
|
|
1050
1170
|
const config = typeof params.config === "string" ? params.config : "";
|
|
1051
1171
|
const expect = expectedFormat("oxlintRule", params);
|
|
1052
1172
|
if (config === "") return countRule(run(command), rule, expect);
|
|
1053
|
-
const source =
|
|
1054
|
-
if (!
|
|
1173
|
+
const source = resolve11(cwd, config);
|
|
1174
|
+
if (!existsSync8(source)) throw new CounterError("oxlintRule", `no config at ${config}`);
|
|
1055
1175
|
const strictName = `.oxlintrc.ratchet-${key}.json`;
|
|
1056
|
-
const strict =
|
|
1176
|
+
const strict = readFileSync9(source, "utf8").replace(
|
|
1057
1177
|
new RegExp(`("[^"]*${escapeForRegex(rule)}"\\s*:\\s*\\[?\\s*)"(warn|off)"`),
|
|
1058
1178
|
'$1"error"'
|
|
1059
1179
|
);
|
|
1060
|
-
writeFileSync6(
|
|
1180
|
+
writeFileSync6(resolve11(cwd, strictName), strict);
|
|
1061
1181
|
try {
|
|
1062
1182
|
return countRule(run(command.replace("{config}", strictName)), rule, expect);
|
|
1063
1183
|
} finally {
|
|
1064
|
-
rmSync3(
|
|
1184
|
+
rmSync3(resolve11(cwd, strictName), { force: true });
|
|
1065
1185
|
}
|
|
1066
1186
|
}
|
|
1067
1187
|
};
|
|
1068
1188
|
|
|
1069
1189
|
// src/counters/probes.ts
|
|
1070
|
-
import { existsSync as
|
|
1190
|
+
import { existsSync as existsSync9, readFileSync as readFileSync10 } from "fs";
|
|
1071
1191
|
import { createRequire } from "module";
|
|
1072
|
-
import { join as join6, resolve as
|
|
1192
|
+
import { join as join6, resolve as resolve12 } from "path";
|
|
1073
1193
|
import { pathToFileURL } from "url";
|
|
1074
1194
|
var ID = "probelessRules";
|
|
1075
1195
|
var OFF = /* @__PURE__ */ new Set([0, "0", "allow", "off", false]);
|
|
@@ -1147,11 +1267,11 @@ var probelessRules = {
|
|
|
1147
1267
|
},
|
|
1148
1268
|
run: async ({ cwd, params }) => {
|
|
1149
1269
|
const relative = stringParam(ID, params, "config", ".oxlintrc.json");
|
|
1150
|
-
const path =
|
|
1151
|
-
if (!
|
|
1270
|
+
const path = resolve12(cwd, relative);
|
|
1271
|
+
if (!existsSync9(path)) throw new CounterError(ID, `no oxlint config at ${relative}`);
|
|
1152
1272
|
let config;
|
|
1153
1273
|
try {
|
|
1154
|
-
config = JSON.parse(
|
|
1274
|
+
config = JSON.parse(readFileSync10(path, "utf8"));
|
|
1155
1275
|
} catch (error) {
|
|
1156
1276
|
throw new CounterError(ID, `${relative} does not parse: ${error.message}`);
|
|
1157
1277
|
}
|
|
@@ -1188,8 +1308,8 @@ import { readdirSync as readdirSync3 } from "fs";
|
|
|
1188
1308
|
import { join as join8 } from "path";
|
|
1189
1309
|
|
|
1190
1310
|
// src/counters/workspace.ts
|
|
1191
|
-
import { existsSync as
|
|
1192
|
-
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";
|
|
1193
1313
|
var SKIP = /^(node_modules|\.)/;
|
|
1194
1314
|
var childDirs = (dir) => {
|
|
1195
1315
|
try {
|
|
@@ -1203,14 +1323,14 @@ var expand = (root, pattern) => {
|
|
|
1203
1323
|
const segments = pattern.split("/").filter((one) => one !== "" && one !== ".");
|
|
1204
1324
|
let dirs = [root];
|
|
1205
1325
|
for (const segment of segments) {
|
|
1206
|
-
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));
|
|
1207
1327
|
}
|
|
1208
1328
|
return dirs;
|
|
1209
1329
|
};
|
|
1210
1330
|
var QUOTED = /^['"]|['"]$/g;
|
|
1211
1331
|
var cleaned = (value) => value.replace(/#.*$/, "").trim().replaceAll(QUOTED, "");
|
|
1212
1332
|
var pnpmPatterns = (path) => {
|
|
1213
|
-
const lines =
|
|
1333
|
+
const lines = readFileSync11(path, "utf8").split("\n");
|
|
1214
1334
|
const at = lines.findIndex((line) => line.startsWith("packages:"));
|
|
1215
1335
|
if (at === -1) return [];
|
|
1216
1336
|
const inline = lines[at]?.slice("packages:".length).trim() ?? "";
|
|
@@ -1229,31 +1349,31 @@ var pnpmPatterns = (path) => {
|
|
|
1229
1349
|
return patterns;
|
|
1230
1350
|
};
|
|
1231
1351
|
var npmPatterns = (path) => {
|
|
1232
|
-
const parsed = JSON.parse(
|
|
1352
|
+
const parsed = JSON.parse(readFileSync11(path, "utf8"));
|
|
1233
1353
|
const declared = Array.isArray(parsed.workspaces) ? parsed.workspaces : parsed.workspaces?.packages ?? [];
|
|
1234
1354
|
return declared.filter((one) => typeof one === "string");
|
|
1235
1355
|
};
|
|
1236
1356
|
var nameOf = (dir) => {
|
|
1237
1357
|
const manifest = join7(dir, "package.json");
|
|
1238
|
-
if (!
|
|
1358
|
+
if (!existsSync10(manifest)) return void 0;
|
|
1239
1359
|
try {
|
|
1240
|
-
const { name } = JSON.parse(
|
|
1360
|
+
const { name } = JSON.parse(readFileSync11(manifest, "utf8"));
|
|
1241
1361
|
return typeof name === "string" && name !== "" ? name : void 0;
|
|
1242
1362
|
} catch {
|
|
1243
1363
|
return void 0;
|
|
1244
1364
|
}
|
|
1245
1365
|
};
|
|
1246
1366
|
var workspaceDirs = (cwd) => {
|
|
1247
|
-
const root =
|
|
1367
|
+
const root = resolve13(cwd);
|
|
1248
1368
|
const pnpm = join7(root, "pnpm-workspace.yaml");
|
|
1249
1369
|
const manifest = join7(root, "package.json");
|
|
1250
|
-
const patterns =
|
|
1251
|
-
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")));
|
|
1252
1372
|
return [...new Set(dirs)];
|
|
1253
1373
|
};
|
|
1254
1374
|
var manifestOf = (dir) => {
|
|
1255
1375
|
try {
|
|
1256
|
-
const parsed = JSON.parse(
|
|
1376
|
+
const parsed = JSON.parse(readFileSync11(join7(dir, "package.json"), "utf8"));
|
|
1257
1377
|
return typeof parsed === "object" && parsed !== null ? parsed : void 0;
|
|
1258
1378
|
} catch {
|
|
1259
1379
|
return void 0;
|
|
@@ -1321,8 +1441,8 @@ var packagesWithoutTypecheck = {
|
|
|
1321
1441
|
};
|
|
1322
1442
|
|
|
1323
1443
|
// src/counters/seams.ts
|
|
1324
|
-
import { existsSync as
|
|
1325
|
-
import { join as join9, resolve as
|
|
1444
|
+
import { existsSync as existsSync11, readdirSync as readdirSync4, readFileSync as readFileSync12 } from "fs";
|
|
1445
|
+
import { join as join9, resolve as resolve14 } from "path";
|
|
1326
1446
|
var CONFIG = "geonosis.json";
|
|
1327
1447
|
var SKIP_DIRS = /* @__PURE__ */ new Set([".git", ".turbo", "coverage", "dist", "node_modules"]);
|
|
1328
1448
|
var asPattern = (glob) => new RegExp(
|
|
@@ -1338,16 +1458,16 @@ var filesUnder = (root, at = root) => readdirSync4(at, { withFileTypes: true }).
|
|
|
1338
1458
|
});
|
|
1339
1459
|
var seamGlobsIn = (root) => {
|
|
1340
1460
|
const at = join9(root, CONFIG);
|
|
1341
|
-
if (!
|
|
1461
|
+
if (!existsSync11(at)) return [];
|
|
1342
1462
|
try {
|
|
1343
|
-
const seams = JSON.parse(
|
|
1463
|
+
const seams = JSON.parse(readFileSync12(at, "utf8")).adoption?.seams;
|
|
1344
1464
|
return Array.isArray(seams) ? seams.filter((one) => typeof one === "string") : [];
|
|
1345
1465
|
} catch {
|
|
1346
1466
|
return [];
|
|
1347
1467
|
}
|
|
1348
1468
|
};
|
|
1349
1469
|
var linesIn = (path) => {
|
|
1350
|
-
const body =
|
|
1470
|
+
const body = readFileSync12(path, "utf8");
|
|
1351
1471
|
return body === "" ? 0 : body.replace(/\n$/, "").split("\n").length;
|
|
1352
1472
|
};
|
|
1353
1473
|
var adoptionSeamLines = {
|
|
@@ -1363,7 +1483,7 @@ var adoptionSeamLines = {
|
|
|
1363
1483
|
params: {}
|
|
1364
1484
|
},
|
|
1365
1485
|
run: async ({ cwd }) => {
|
|
1366
|
-
const root =
|
|
1486
|
+
const root = resolve14(cwd);
|
|
1367
1487
|
const globs = seamGlobsIn(root).map(asPattern);
|
|
1368
1488
|
if (globs.length === 0) return 0;
|
|
1369
1489
|
return filesUnder(root).filter((one) => globs.some((pattern) => pattern.test(one))).reduce((total2, one) => total2 + linesIn(join9(root, one)), 0);
|
|
@@ -1392,8 +1512,8 @@ var sumOfCounts = {
|
|
|
1392
1512
|
};
|
|
1393
1513
|
|
|
1394
1514
|
// src/counters/suppressions.ts
|
|
1395
|
-
import { readdirSync as readdirSync5, readFileSync as
|
|
1396
|
-
import { join as join10, resolve as
|
|
1515
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync13 } from "fs";
|
|
1516
|
+
import { join as join10, resolve as resolve15 } from "path";
|
|
1397
1517
|
var CODE_FILE = /\.(?:ts|tsx|js|jsx|mjs|cjs|mts|cts|vue|svelte|astro)$/;
|
|
1398
1518
|
var SKIP3 = /^(?:node_modules|dist|coverage|\.[^.]|__fixtures__)/;
|
|
1399
1519
|
var SUPPRESSION = /(?:eslint|oxlint|biome)-(?:disable|ignore)(?:-next-line|-line)?|@ts-expect-error|@ts-ignore/g;
|
|
@@ -1413,7 +1533,7 @@ var countIn = (dir, depth = 12) => {
|
|
|
1413
1533
|
}
|
|
1414
1534
|
if (!CODE_FILE.test(entry.name)) continue;
|
|
1415
1535
|
try {
|
|
1416
|
-
found +=
|
|
1536
|
+
found += readFileSync13(join10(dir, entry.name), "utf8").match(SUPPRESSION)?.length ?? 0;
|
|
1417
1537
|
} catch {
|
|
1418
1538
|
}
|
|
1419
1539
|
}
|
|
@@ -1427,13 +1547,13 @@ var suppressionCount = {
|
|
|
1427
1547
|
},
|
|
1428
1548
|
run: async ({ cwd, params }) => {
|
|
1429
1549
|
const roots = stringsParam(params, "roots", ["."]);
|
|
1430
|
-
return roots.reduce((sum, root) => sum + countIn(
|
|
1550
|
+
return roots.reduce((sum, root) => sum + countIn(resolve15(cwd, root)), 0);
|
|
1431
1551
|
}
|
|
1432
1552
|
};
|
|
1433
1553
|
|
|
1434
1554
|
// src/counters/surface.ts
|
|
1435
|
-
import { existsSync as
|
|
1436
|
-
import { join as join11, resolve as
|
|
1555
|
+
import { existsSync as existsSync12, readFileSync as readFileSync14 } from "fs";
|
|
1556
|
+
import { join as join11, resolve as resolve16 } from "path";
|
|
1437
1557
|
var DECLARED = /^export\s+(?:declare\s+)?(?:abstract\s+)?(?:const|function|class|type|interface|enum|let|var)\s+([$\w]+)/;
|
|
1438
1558
|
var BRACED = /^export\s+(?:type\s+)?\{([^}]*)\}(?:\s+from\s+(['"])(.+?)\2)?/;
|
|
1439
1559
|
var nameOf2 = (spec) => {
|
|
@@ -1467,10 +1587,10 @@ var exportedSurfaceIn = (source) => {
|
|
|
1467
1587
|
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1468
1588
|
var declarationEntriesOf = (dir) => {
|
|
1469
1589
|
const at = join11(dir, "package.json");
|
|
1470
|
-
if (!
|
|
1590
|
+
if (!existsSync12(at)) {
|
|
1471
1591
|
throw new CounterError("floorSurface", `no package.json in ${dir} \u2014 that is not a package`);
|
|
1472
1592
|
}
|
|
1473
|
-
const manifest = JSON.parse(
|
|
1593
|
+
const manifest = JSON.parse(readFileSync14(at, "utf8"));
|
|
1474
1594
|
const found = [];
|
|
1475
1595
|
const walk = (value) => {
|
|
1476
1596
|
if (typeof value === "string" && value.endsWith(".d.ts")) found.push(value);
|
|
@@ -1501,7 +1621,7 @@ var floorSurface = {
|
|
|
1501
1621
|
},
|
|
1502
1622
|
run: async ({ cwd, params }) => {
|
|
1503
1623
|
const relative = stringParam("floorSurface", params, "dir");
|
|
1504
|
-
const dir =
|
|
1624
|
+
const dir = resolve16(cwd, relative);
|
|
1505
1625
|
const entries = declarationEntriesOf(dir);
|
|
1506
1626
|
if (entries.length === 0) {
|
|
1507
1627
|
throw new CounterError(
|
|
@@ -1511,23 +1631,23 @@ var floorSurface = {
|
|
|
1511
1631
|
}
|
|
1512
1632
|
const names = /* @__PURE__ */ new Set();
|
|
1513
1633
|
for (const entry of entries) {
|
|
1514
|
-
const at =
|
|
1515
|
-
if (!
|
|
1634
|
+
const at = resolve16(dir, entry);
|
|
1635
|
+
if (!existsSync12(at)) {
|
|
1516
1636
|
throw new CounterError(
|
|
1517
1637
|
"floorSurface",
|
|
1518
1638
|
`${relative} promises ${entry} and it is not there \u2014 build before measuring, or the surface reads as empty`
|
|
1519
1639
|
);
|
|
1520
1640
|
}
|
|
1521
|
-
for (const name of exportedSurfaceIn(
|
|
1641
|
+
for (const name of exportedSurfaceIn(readFileSync14(at, "utf8")).keys()) names.add(name);
|
|
1522
1642
|
}
|
|
1523
1643
|
return names.size;
|
|
1524
1644
|
}
|
|
1525
1645
|
};
|
|
1526
1646
|
|
|
1527
1647
|
// src/counters/tests.ts
|
|
1528
|
-
import { existsSync as
|
|
1648
|
+
import { existsSync as existsSync13, mkdtempSync as mkdtempSync2, readFileSync as readFileSync15, rmSync as rmSync4 } from "fs";
|
|
1529
1649
|
import { tmpdir as tmpdir2 } from "os";
|
|
1530
|
-
import { join as join12, resolve as
|
|
1650
|
+
import { join as join12, resolve as resolve17 } from "path";
|
|
1531
1651
|
var COUNTER = "testFailures";
|
|
1532
1652
|
var VITEST_LINE = /^\s*Tests {2,}(.+?)\s*$/;
|
|
1533
1653
|
var VITEST_TOTAL = /\(\d+\)$/;
|
|
@@ -1559,7 +1679,7 @@ ${output.trim().slice(-500)}`
|
|
|
1559
1679
|
);
|
|
1560
1680
|
};
|
|
1561
1681
|
var fromReport = (path) => {
|
|
1562
|
-
if (!
|
|
1682
|
+
if (!existsSync13(path)) {
|
|
1563
1683
|
throw new CounterError(
|
|
1564
1684
|
COUNTER,
|
|
1565
1685
|
`the runner wrote no report at ${path} \u2014 a crash before the reporter is not a pass`
|
|
@@ -1567,7 +1687,7 @@ var fromReport = (path) => {
|
|
|
1567
1687
|
}
|
|
1568
1688
|
let report;
|
|
1569
1689
|
try {
|
|
1570
|
-
report = JSON.parse(
|
|
1690
|
+
report = JSON.parse(readFileSync15(path, "utf8"));
|
|
1571
1691
|
} catch (error) {
|
|
1572
1692
|
throw new CounterError(
|
|
1573
1693
|
COUNTER,
|
|
@@ -1591,7 +1711,7 @@ var fromReport = (path) => {
|
|
|
1591
1711
|
};
|
|
1592
1712
|
var reportPathFor = (cwd, params, command) => {
|
|
1593
1713
|
const named = params.reportPath;
|
|
1594
|
-
if (typeof named === "string" && named !== "") return { own: false, path:
|
|
1714
|
+
if (typeof named === "string" && named !== "") return { own: false, path: resolve17(cwd, named) };
|
|
1595
1715
|
if (!command.includes(PLACEHOLDER)) {
|
|
1596
1716
|
throw new CounterError(
|
|
1597
1717
|
COUNTER,
|
|
@@ -1653,13 +1773,13 @@ var testFailures = {
|
|
|
1653
1773
|
};
|
|
1654
1774
|
|
|
1655
1775
|
// src/counters/todos.ts
|
|
1656
|
-
import { existsSync as
|
|
1657
|
-
import { join as join13, resolve as
|
|
1776
|
+
import { existsSync as existsSync14, readdirSync as readdirSync6, readFileSync as readFileSync16 } from "fs";
|
|
1777
|
+
import { join as join13, resolve as resolve18 } from "path";
|
|
1658
1778
|
var CODE_FILE2 = /\.(?:ts|tsx|js|jsx|mjs|cjs|mts|cts|vue|svelte|astro)$/;
|
|
1659
1779
|
var SKIP4 = /^(?:node_modules|dist|coverage|\.[^.]|__fixtures__)/;
|
|
1660
1780
|
var PLAN_FILE = /^(\d{3,})-[a-z\d][a-z\d.-]*\.md$/;
|
|
1661
1781
|
var plansIn = (dir) => {
|
|
1662
|
-
if (!
|
|
1782
|
+
if (!existsSync14(dir)) return /* @__PURE__ */ new Set();
|
|
1663
1783
|
const found = /* @__PURE__ */ new Set();
|
|
1664
1784
|
for (const name of readdirSync6(dir)) {
|
|
1665
1785
|
const number = PLAN_FILE.exec(name)?.[1];
|
|
@@ -1684,7 +1804,7 @@ var countIn2 = (dir, marker, plans, depth = 12) => {
|
|
|
1684
1804
|
if (!CODE_FILE2.test(entry.name)) continue;
|
|
1685
1805
|
let text = "";
|
|
1686
1806
|
try {
|
|
1687
|
-
text =
|
|
1807
|
+
text = readFileSync16(join13(dir, entry.name), "utf8");
|
|
1688
1808
|
} catch {
|
|
1689
1809
|
continue;
|
|
1690
1810
|
}
|
|
@@ -1708,13 +1828,13 @@ var orphanTodos = {
|
|
|
1708
1828
|
readingParams: ["markers"],
|
|
1709
1829
|
run: async ({ cwd, params }) => {
|
|
1710
1830
|
const markers = stringsParam(params, "markers", ["TODO", "FIXME"]);
|
|
1711
|
-
const plans = plansIn(
|
|
1831
|
+
const plans = plansIn(resolve18(cwd, stringParam("orphanTodos", params, "plans", "plans")));
|
|
1712
1832
|
const marker = new RegExp(
|
|
1713
1833
|
`\\b(?:${markers.map(escapeForRegex).join("|")})\\b(?:\\((\\d+)\\))?`,
|
|
1714
1834
|
"g"
|
|
1715
1835
|
);
|
|
1716
1836
|
const roots = stringsParam(params, "roots", ["."]);
|
|
1717
|
-
return roots.reduce((sum, root) => sum + countIn2(
|
|
1837
|
+
return roots.reduce((sum, root) => sum + countIn2(resolve18(cwd, root), marker, plans), 0);
|
|
1718
1838
|
}
|
|
1719
1839
|
};
|
|
1720
1840
|
|
|
@@ -1753,8 +1873,8 @@ var typecheckErrors = {
|
|
|
1753
1873
|
};
|
|
1754
1874
|
|
|
1755
1875
|
// src/counters/walk.ts
|
|
1756
|
-
import { existsSync as
|
|
1757
|
-
import { resolve as
|
|
1876
|
+
import { existsSync as existsSync15, readFileSync as readFileSync17 } from "fs";
|
|
1877
|
+
import { resolve as resolve19 } from "path";
|
|
1758
1878
|
var DEFAULT_REPORT2 = ".geonosis/walk-report.json";
|
|
1759
1879
|
var CLASSES = /* @__PURE__ */ new Set([
|
|
1760
1880
|
"buy-box-above-fold",
|
|
@@ -1798,8 +1918,8 @@ var walkFindings = {
|
|
|
1798
1918
|
},
|
|
1799
1919
|
run: async ({ cwd, params }) => {
|
|
1800
1920
|
const relative = stringParam("walkFindings", params, "report", DEFAULT_REPORT2);
|
|
1801
|
-
const path =
|
|
1802
|
-
if (!
|
|
1921
|
+
const path = resolve19(cwd, relative);
|
|
1922
|
+
if (!existsSync15(path)) {
|
|
1803
1923
|
throw new CounterError(
|
|
1804
1924
|
"walkFindings",
|
|
1805
1925
|
`no walk report at ${relative} \u2014 run \`geonosis-walk\` before measuring it`
|
|
@@ -1807,7 +1927,7 @@ var walkFindings = {
|
|
|
1807
1927
|
}
|
|
1808
1928
|
let report;
|
|
1809
1929
|
try {
|
|
1810
|
-
report = JSON.parse(
|
|
1930
|
+
report = JSON.parse(readFileSync17(path, "utf8"));
|
|
1811
1931
|
} catch (error) {
|
|
1812
1932
|
throw new CounterError(
|
|
1813
1933
|
"walkFindings",
|
|
@@ -1832,6 +1952,7 @@ var COUNTERS = [
|
|
|
1832
1952
|
adoptionSeamLines,
|
|
1833
1953
|
archViolations,
|
|
1834
1954
|
boundaryIssues,
|
|
1955
|
+
bumpDurationMs,
|
|
1835
1956
|
bundleBytes,
|
|
1836
1957
|
cloneCount,
|
|
1837
1958
|
disabledCiJobs,
|
|
@@ -1839,6 +1960,7 @@ var COUNTERS = [
|
|
|
1839
1960
|
floorSurface,
|
|
1840
1961
|
knipIssues,
|
|
1841
1962
|
lawLineCount,
|
|
1963
|
+
openBacklogRows,
|
|
1842
1964
|
suppressionCount,
|
|
1843
1965
|
oxlintErrors,
|
|
1844
1966
|
oxlintRule,
|
|
@@ -1850,6 +1972,7 @@ var COUNTERS = [
|
|
|
1850
1972
|
sumOfCounts,
|
|
1851
1973
|
testFailures,
|
|
1852
1974
|
testsWithoutRunner,
|
|
1975
|
+
timeToGreenDoctorMs,
|
|
1853
1976
|
typecheckErrors,
|
|
1854
1977
|
unformattedFiles,
|
|
1855
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.js
CHANGED