probatio 0.3.0 → 0.5.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.
Files changed (74) hide show
  1. package/README.md +10 -198
  2. package/bin/probatio.js +52 -0
  3. package/package.json +21 -29
  4. package/LICENSE +0 -21
  5. package/dist/cli.js +0 -721
  6. package/dist/contract.js +0 -32
  7. package/dist/flags.js +0 -87
  8. package/dist/golden/check.js +0 -188
  9. package/dist/golden/record.js +0 -395
  10. package/dist/golden/recorder.mjs +0 -78
  11. package/dist/ledger/build.js +0 -348
  12. package/dist/ledger/check.js +0 -133
  13. package/dist/matrix/report.js +0 -256
  14. package/dist/mcp.js +0 -188
  15. package/dist/memory/state.js +0 -222
  16. package/dist/mutate/affected.js +0 -173
  17. package/dist/mutate/child-lines.mjs +0 -39
  18. package/dist/mutate/coverage-map.js +0 -229
  19. package/dist/mutate/csharp-coverage.js +0 -161
  20. package/dist/mutate/fail-reporter.mjs +0 -65
  21. package/dist/mutate/find.js +0 -13
  22. package/dist/mutate/generate.js +0 -156
  23. package/dist/mutate/go-coverage.js +0 -100
  24. package/dist/mutate/ids.js +0 -22
  25. package/dist/mutate/java-coverage.js +0 -217
  26. package/dist/mutate/kill-label.js +0 -34
  27. package/dist/mutate/load-failure.mjs +0 -40
  28. package/dist/mutate/mocha-coverage.cjs +0 -77
  29. package/dist/mutate/node-batch-loader.mjs +0 -99
  30. package/dist/mutate/node-batch.mjs +0 -167
  31. package/dist/mutate/node-coverage.mjs +0 -77
  32. package/dist/mutate/operators.js +0 -181
  33. package/dist/mutate/patch.js +0 -136
  34. package/dist/mutate/precise-lines.mjs +0 -304
  35. package/dist/mutate/probatio-jacoco-run.java +0 -516
  36. package/dist/mutate/project-config.js +0 -58
  37. package/dist/mutate/py-pytest-reporter.py +0 -145
  38. package/dist/mutate/py-reporter.py +0 -79
  39. package/dist/mutate/run.js +0 -1335
  40. package/dist/mutate/rust-coverage.js +0 -144
  41. package/dist/mutate/sealed.js +0 -118
  42. package/dist/mutate/suite-decision.js +0 -130
  43. package/dist/mutate/suites.js +0 -1524
  44. package/dist/mutate/tally.js +0 -240
  45. package/dist/mutate/text-operators.js +0 -637
  46. package/dist/mutate/titles.js +0 -23
  47. package/dist/node-version.js +0 -13
  48. package/dist/swarm/check-kill.js +0 -114
  49. package/dist/swarm/queue.js +0 -68
  50. package/dist/verify/change.js +0 -340
  51. package/schemas/check-kill.schema.json +0 -93
  52. package/schemas/findings.add.schema.json +0 -53
  53. package/schemas/gap.fix.schema.json +0 -53
  54. package/schemas/gap.revert.schema.json +0 -53
  55. package/schemas/golden.check.schema.json +0 -89
  56. package/schemas/golden.compare.schema.json +0 -151
  57. package/schemas/golden.record.schema.json +0 -126
  58. package/schemas/guard.check.schema.json +0 -53
  59. package/schemas/ledger.build.schema.json +0 -132
  60. package/schemas/ledger.check.schema.json +0 -143
  61. package/schemas/matrix.report.schema.json +0 -129
  62. package/schemas/mcp.schema.json +0 -53
  63. package/schemas/mutate.generate.schema.json +0 -127
  64. package/schemas/mutate.help.schema.json +0 -53
  65. package/schemas/mutate.run.schema.json +0 -272
  66. package/schemas/mutate.sealed.schema.json +0 -53
  67. package/schemas/mutate.tally.schema.json +0 -137
  68. package/schemas/queue.claim.schema.json +0 -53
  69. package/schemas/queue.reap.schema.json +0 -71
  70. package/schemas/queue.seed.schema.json +0 -71
  71. package/schemas/schema.schema.json +0 -74
  72. package/schemas/seal.schema.json +0 -69
  73. package/schemas/status.schema.json +0 -103
  74. package/schemas/verify-change.schema.json +0 -222
package/dist/contract.js DELETED
@@ -1,32 +0,0 @@
1
- import { homedir } from "node:os";
2
- /** Bumped when a field is renamed, removed, or changes meaning. 2: `drop` became `noKillsYet`, and a compile or link failure became `unviable`, not `killed`. */
3
- export const SCHEMA_VERSION = 2;
4
- /** Replace the home directory so a result never carries a user's name or home path. */
5
- export function scrub(value, home = homedir(), keepPaths = false) {
6
- if (typeof value === "string") {
7
- if (keepPaths || !home || !value.includes(home))
8
- return value;
9
- return value.split(home).join("~");
10
- }
11
- if (Array.isArray(value))
12
- return value.map((item) => scrub(item, home, keepPaths));
13
- if (value && typeof value === "object") {
14
- const out = {};
15
- for (const [key, item] of Object.entries(value)) {
16
- // nextCall.argv is executed. A tilde is not expanded by spawn or by MCP.
17
- out[key] = scrub(item, home, keepPaths || key === "nextCall");
18
- }
19
- return out;
20
- }
21
- return value;
22
- }
23
- export function render(envelope, human, home = homedir()) {
24
- const clean = scrub(envelope, home);
25
- if (!human)
26
- return `${JSON.stringify(clean, null, 2)}\n`;
27
- const lines = [clean.summary, clean.next];
28
- const call = clean.nextCall;
29
- if (call && Array.isArray(call.argv))
30
- lines.push(`next call: probatio ${call.argv.join(" ")}`);
31
- return `${lines.filter((line) => line.length > 0).join("\n")}\n`;
32
- }
package/dist/flags.js DELETED
@@ -1,87 +0,0 @@
1
- /** `--key value`, `--key=value`, `--flag`, `--no-flag`. Words before the first flag are the command. */
2
- export function parseArgs(argv) {
3
- const command = [];
4
- const flags = new Map();
5
- let seenFlag = false;
6
- for (let i = 0; i < argv.length; i++) {
7
- const token = argv[i];
8
- if (token === "--") {
9
- seenFlag = true;
10
- continue;
11
- }
12
- if (!token.startsWith("--")) {
13
- if (!seenFlag)
14
- command.push(token);
15
- else
16
- push(flags, "_", token);
17
- continue;
18
- }
19
- seenFlag = true;
20
- const body = token.slice(2);
21
- const eq = body.indexOf("=");
22
- if (eq !== -1) {
23
- push(flags, body.slice(0, eq), body.slice(eq + 1));
24
- continue;
25
- }
26
- if (body.startsWith("no-") && body.length > 3) {
27
- push(flags, body.slice(3), false);
28
- continue;
29
- }
30
- const next = argv[i + 1];
31
- if (next === undefined || next.startsWith("--"))
32
- push(flags, body, true);
33
- else {
34
- push(flags, body, next);
35
- i++;
36
- }
37
- }
38
- const human = flags.get("human")?.some((value) => value === true) ?? false;
39
- return { command, flags, human };
40
- }
41
- function push(flags, key, value) {
42
- const list = flags.get(key);
43
- if (list)
44
- list.push(value);
45
- else
46
- flags.set(key, [value]);
47
- }
48
- export function text(flags, key) {
49
- const values = flags.get(key);
50
- if (!values || values.length === 0)
51
- return undefined;
52
- const last = values[values.length - 1];
53
- if (typeof last === "boolean")
54
- return undefined;
55
- return last;
56
- }
57
- export function texts(flags, key) {
58
- return (flags.get(key) ?? []).filter((value) => typeof value === "string");
59
- }
60
- export function bool(flags, key, fallback) {
61
- const values = flags.get(key);
62
- if (!values || values.length === 0)
63
- return fallback;
64
- const last = values[values.length - 1];
65
- if (typeof last === "boolean")
66
- return last;
67
- if (last === "true")
68
- return true;
69
- if (last === "false")
70
- return false;
71
- return fallback;
72
- }
73
- export function int(flags, key) {
74
- const raw = text(flags, key);
75
- if (raw === undefined)
76
- return undefined;
77
- const value = Number(raw);
78
- if (!Number.isInteger(value) || value < 0)
79
- throw new Error(`--${key} must be a non-negative integer`);
80
- return value;
81
- }
82
- export function requireText(flags, key) {
83
- const value = text(flags, key);
84
- if (!value)
85
- throw new Error(`--${key} is required`);
86
- return value;
87
- }
@@ -1,188 +0,0 @@
1
- import { readFileSync, writeFileSync } from "node:fs";
2
- import { homedir } from "node:os";
3
- import { SCHEMA_VERSION } from "../contract.js";
4
- const WORDING_KEYS = ["next", "nextLead"];
5
- /** `Golden-Change: <row>: <why>`. The why starts after the first colon-and-space, so a row may contain colons. */
6
- export function goldenChanges(message) {
7
- const named = new Map();
8
- for (const match of message.matchAll(/^Golden-Change:\s*(.+?):\s+(.+)$/gm))
9
- named.set(match[1], match[2]);
10
- return named;
11
- }
12
- export function readGolden(file) {
13
- return asTable(readFileSync(file, "utf8"));
14
- }
15
- export function checkGolden(input) {
16
- const home = input.home ?? homedir();
17
- const recorded = normalizeTable(input.recorded, input.roots);
18
- const actual = normalizeTable(input.actual, input.roots);
19
- const ids = [...new Set([...Object.keys(recorded.rows), ...Object.keys(actual.rows)])].sort();
20
- const wording = [];
21
- const contract = [];
22
- const invariant = [];
23
- for (const id of ids) {
24
- const before = recorded.rows[id];
25
- const after = actual.rows[id];
26
- if (after) {
27
- const violation = invariantOf(id, after, home);
28
- if (violation)
29
- invariant.push(violation);
30
- }
31
- if (JSON.stringify(wordingOf(before)) !== JSON.stringify(wordingOf(after)))
32
- wording.push(id);
33
- if (JSON.stringify(contractOf(before)) !== JSON.stringify(contractOf(after)))
34
- contract.push(id);
35
- }
36
- const allowed = goldenChanges(input.message);
37
- const unauthorized = contract.filter((id) => !allowed.has(id));
38
- const canWrite = input.update !== null && invariant.length === 0 && unauthorized.length === 0;
39
- const wordingWrite = canWrite && (input.update === "wording" || input.update === "all");
40
- const contractWrite = canWrite && contract.every((id) => allowed.has(id));
41
- const table = structuredClone(input.recorded);
42
- if (wordingWrite || (canWrite && contractWrite && contract.length > 0)) {
43
- for (const id of ids) {
44
- const after = input.actual.rows[id];
45
- const contractRow = contract.includes(id);
46
- const wordingRow = wording.includes(id);
47
- if (contractRow && contractWrite) {
48
- if (!after)
49
- delete table.rows[id];
50
- else
51
- table.rows[id] = after;
52
- }
53
- else if (wordingRow && wordingWrite && after && table.rows[id]) {
54
- table.rows[id] = { ...table.rows[id], ...wordingOf(after) };
55
- }
56
- else if (wordingRow && wordingWrite && after && !table.rows[id] && !contractRow) {
57
- table.rows[id] = wordingOf(after);
58
- }
59
- }
60
- }
61
- const write = invariant.length === 0 && unauthorized.length === 0 && input.update !== null && (wording.length > 0 || contract.length > 0);
62
- const ok = invariant.length === 0 && unauthorized.length === 0 && (wording.length === 0 || input.update !== null);
63
- const summary = ok
64
- ? wording.length === 0 && contract.length === 0
65
- ? "Golden rows match."
66
- : `Re-recorded ${wording.length} wording row(s) and ${contract.length} contract row(s).`
67
- : invariant.length > 0
68
- ? `Invariant failed: ${invariant.join("; ")}.`
69
- : unauthorized.length > 0
70
- ? `Contract changed for ${unauthorized.join(", ")} without a Golden-Change trailer.`
71
- : `Wording changed for ${wording.join(", ")}. Re-record with --update wording.`;
72
- return {
73
- write: write && ok,
74
- table,
75
- envelope: {
76
- schemaVersion: SCHEMA_VERSION,
77
- ok,
78
- command: "golden.check",
79
- summary,
80
- next: ok ? "Review the re-recorded rows." : "The table was not written.",
81
- nextCall: null,
82
- wording,
83
- contract,
84
- invariant,
85
- updated: write && ok,
86
- },
87
- };
88
- }
89
- export function goldenShape(text) {
90
- if (!text.trim())
91
- return "rows";
92
- let parsed;
93
- try {
94
- parsed = JSON.parse(text);
95
- }
96
- catch {
97
- return "rows";
98
- }
99
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
100
- return "flat";
101
- const rows = parsed.rows;
102
- if (rows && typeof rows === "object" && !Array.isArray(rows))
103
- return "rows";
104
- return "flat";
105
- }
106
- export function writeGolden(file, table, shape = "rows") {
107
- const rows = shape === "flat" ? table.rows : sortRows(table.rows);
108
- const payload = shape === "flat" ? rows : { rows };
109
- writeFileSync(file, `${JSON.stringify(payload, null, 2)}\n`);
110
- }
111
- function invariantOf(id, row, home) {
112
- if (row.ok === true && (typeof row.reason !== "string" || row.reason.trim().length === 0)) {
113
- return `${id} has ok without a reason`;
114
- }
115
- if (home && containsHome(row, home))
116
- return `${id} contains a home path`;
117
- return null;
118
- }
119
- function containsHome(value, home) {
120
- if (typeof value === "string")
121
- return value.includes(home);
122
- if (Array.isArray(value))
123
- return value.some((item) => containsHome(item, home));
124
- if (value && typeof value === "object")
125
- return Object.values(value).some((item) => containsHome(item, home));
126
- return false;
127
- }
128
- function wordingOf(row) {
129
- if (!row || typeof row !== "object" || Array.isArray(row))
130
- return {};
131
- const out = {};
132
- for (const key of WORDING_KEYS) {
133
- if (Object.prototype.hasOwnProperty.call(row, key))
134
- out[key] = row[key];
135
- }
136
- return out;
137
- }
138
- function contractOf(row) {
139
- if (!row)
140
- return {};
141
- const out = {};
142
- for (const key of Object.keys(row).sort()) {
143
- if (WORDING_KEYS.includes(key))
144
- continue;
145
- out[key] = row[key];
146
- }
147
- return out;
148
- }
149
- function normalizeTable(table, roots) {
150
- return { rows: normalize(table.rows, roots) };
151
- }
152
- function normalize(value, roots) {
153
- if (typeof value === "string") {
154
- let out = value;
155
- for (const root of roots) {
156
- if (root.length > 1 && out.includes(root))
157
- out = out.split(root).join("{root}");
158
- }
159
- return out;
160
- }
161
- if (Array.isArray(value))
162
- return value.map((item) => normalize(item, roots));
163
- if (value && typeof value === "object") {
164
- const out = {};
165
- for (const [key, item] of Object.entries(value))
166
- out[key] = normalize(item, roots);
167
- return out;
168
- }
169
- return value;
170
- }
171
- export function asTable(text) {
172
- if (!text.trim())
173
- return { rows: {} };
174
- const parsed = JSON.parse(text);
175
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
176
- return { rows: {} };
177
- const record = parsed;
178
- const rows = record.rows && typeof record.rows === "object" && !Array.isArray(record.rows)
179
- ? record.rows
180
- : record;
181
- return { rows };
182
- }
183
- function sortRows(rows) {
184
- const out = {};
185
- for (const key of Object.keys(rows).sort())
186
- out[key] = rows[key];
187
- return out;
188
- }