@dzhechkov/harness-core 0.3.149 → 0.4.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/.dz-manifest.json +399 -55
- package/README.md +80 -3
- package/dist/agentdb-index.d.ts.map +1 -1
- package/dist/agentdb-index.js +10 -2
- package/dist/agentdb-index.js.map +1 -1
- package/dist/backlog-embed.d.ts +94 -0
- package/dist/backlog-embed.d.ts.map +1 -0
- package/dist/backlog-embed.js +138 -0
- package/dist/backlog-embed.js.map +1 -0
- package/dist/backlog.d.ts +180 -7
- package/dist/backlog.d.ts.map +1 -1
- package/dist/backlog.js +429 -26
- package/dist/backlog.js.map +1 -1
- package/dist/challenge-panel.d.ts +3 -0
- package/dist/challenge-panel.d.ts.map +1 -1
- package/dist/challenge-panel.js +3 -0
- package/dist/challenge-panel.js.map +1 -1
- package/dist/export-holdout.d.ts +149 -0
- package/dist/export-holdout.d.ts.map +1 -0
- package/dist/export-holdout.js +198 -0
- package/dist/export-holdout.js.map +1 -0
- package/dist/feature-adr-checkpoints.d.ts +82 -0
- package/dist/feature-adr-checkpoints.d.ts.map +1 -1
- package/dist/feature-adr-checkpoints.js +138 -1
- package/dist/feature-adr-checkpoints.js.map +1 -1
- package/dist/feature-adr-routing.d.ts +3 -0
- package/dist/feature-adr-routing.d.ts.map +1 -1
- package/dist/feature-adr-routing.js +3 -0
- package/dist/feature-adr-routing.js.map +1 -1
- package/dist/guard.d.ts +42 -0
- package/dist/guard.d.ts.map +1 -1
- package/dist/guard.js +73 -1
- package/dist/guard.js.map +1 -1
- package/dist/index.d.ts +17 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -1
- package/dist/index.js.map +1 -1
- package/dist/loop-blobs.generated.d.ts +33 -0
- package/dist/loop-blobs.generated.d.ts.map +1 -0
- package/dist/loop-blobs.generated.js +101 -0
- package/dist/loop-blobs.generated.js.map +1 -0
- package/dist/loop-lint.d.ts +63 -0
- package/dist/loop-lint.d.ts.map +1 -0
- package/dist/loop-lint.js +606 -0
- package/dist/loop-lint.js.map +1 -0
- package/dist/loop-plan.d.ts +416 -0
- package/dist/loop-plan.d.ts.map +1 -0
- package/dist/loop-plan.js +1151 -0
- package/dist/loop-plan.js.map +1 -0
- package/dist/loop-render.d.ts +104 -0
- package/dist/loop-render.d.ts.map +1 -0
- package/dist/loop-render.js +989 -0
- package/dist/loop-render.js.map +1 -0
- package/dist/loop-trace.d.ts +204 -0
- package/dist/loop-trace.d.ts.map +1 -0
- package/dist/loop-trace.js +550 -0
- package/dist/loop-trace.js.map +1 -0
- package/dist/mutation-gate.d.ts +247 -0
- package/dist/mutation-gate.d.ts.map +1 -0
- package/dist/mutation-gate.js +535 -0
- package/dist/mutation-gate.js.map +1 -0
- package/dist/no-stubs.d.ts +53 -0
- package/dist/no-stubs.d.ts.map +1 -0
- package/dist/no-stubs.js +190 -0
- package/dist/no-stubs.js.map +1 -0
- package/dist/package-skill-layouts.d.ts +67 -0
- package/dist/package-skill-layouts.d.ts.map +1 -0
- package/dist/package-skill-layouts.js +81 -0
- package/dist/package-skill-layouts.js.map +1 -0
- package/dist/patterns.d.ts.map +1 -1
- package/dist/patterns.js +156 -75
- package/dist/patterns.js.map +1 -1
- package/dist/recall-domain-boost.d.ts +96 -0
- package/dist/recall-domain-boost.d.ts.map +1 -0
- package/dist/recall-domain-boost.js +158 -0
- package/dist/recall-domain-boost.js.map +1 -0
- package/dist/store-lock.d.ts +108 -0
- package/dist/store-lock.d.ts.map +1 -0
- package/dist/store-lock.js +231 -0
- package/dist/store-lock.js.map +1 -0
- package/dist/workflows.d.ts +16 -22
- package/dist/workflows.d.ts.map +1 -1
- package/dist/workflows.js +17 -98
- package/dist/workflows.js.map +1 -1
- package/package.json +7 -5
- package/sbom.json +1062 -202
- package/src/agentdb-index.ts +10 -1
- package/src/backlog-embed.ts +156 -0
- package/src/backlog.ts +536 -28
- package/src/challenge-panel.ts +4 -0
- package/src/export-holdout.ts +235 -0
- package/src/feature-adr-checkpoints.ts +192 -1
- package/src/feature-adr-routing.ts +4 -0
- package/src/guard.ts +106 -1
- package/src/index.ts +72 -1
- package/src/loop-blobs.generated.ts +114 -0
- package/src/loop-lint.ts +643 -0
- package/src/loop-plan.ts +1419 -0
- package/src/loop-render.ts +1050 -0
- package/src/loop-trace.ts +650 -0
- package/src/mutation-gate.ts +701 -0
- package/src/no-stubs.ts +204 -0
- package/src/package-skill-layouts.ts +107 -0
- package/src/patterns.ts +135 -60
- package/src/recall-domain-boost.ts +178 -0
- package/src/store-lock.ts +258 -0
- package/src/workflows.ts +18 -117
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
// Mutation gate (feature ha-mutation-gate — features/ha-mutation-gate/SPEC.md).
|
|
2
|
+
//
|
|
3
|
+
// A test that passes proves the code works; it does NOT prove the test would have noticed the
|
|
4
|
+
// protection being deleted. Three QE rounds on @dzhechkov/health-advisor produced 53 → 14 → 14
|
|
5
|
+
// findings while the whole suite stayed green — including the exact exploit string a code comment
|
|
6
|
+
// records as MEASURED. The gate generalises the one measurement that proved a protection real in
|
|
7
|
+
// those rounds: DELETE the protection, run the suite, REQUIRE red.
|
|
8
|
+
//
|
|
9
|
+
// This module is PURE, in the house style of discrimination-gate.ts / challenge-panel.ts: it
|
|
10
|
+
// parses the declarative registry, applies a mutation to file TEXT, parses failing-test counts
|
|
11
|
+
// from runner output, and classifies observed outcomes. The copy-tree / run-suite / restore I/O is
|
|
12
|
+
// performed by the CLI executor (`dz mutation-gate` in harness-cli), which feeds observations back
|
|
13
|
+
// into `classifyMutationOutcome`. Deterministic and unit-testable without a filesystem or runner.
|
|
14
|
+
//
|
|
15
|
+
// The four rules the gate itself must obey (SPEC.md §"Four rules"):
|
|
16
|
+
// 1. A mutation that does not apply — `find` absent, or present more than once — is a FAILURE,
|
|
17
|
+
// never a skip (inconclusive ≠ pass, the dz skills-verify lesson).
|
|
18
|
+
// 2. A mutation whose suite stays GREEN is a FAILURE, and the report names the property.
|
|
19
|
+
// 3. Never mutate the working tree (enforced by the executor: scratch copy only).
|
|
20
|
+
// 4. The gate has its own discrimination proof (the deliberately-undefended fixture in
|
|
21
|
+
// harness-cli/test/fixtures/mutation-gate-undefended — excluded from any real registry).
|
|
22
|
+
// ── Registry parsing — declarative DATA, validated loudly ─────────────────────────────────────
|
|
23
|
+
const SAFE_ID = /^[a-z0-9][a-z0-9-]{0,79}$/;
|
|
24
|
+
/** same shape as discrimination-gate's path rule: package-relative, no traversal, no metacharacters. */
|
|
25
|
+
const UNSAFE_FILE = /(^\/)|(^[A-Za-z]:)|(^~)|(^-)|(\/-)|(\.\.(\/|\\|$))|[\0`$;&|<>*?"'\n\r\t\\]/;
|
|
26
|
+
/** Parse + validate a registry JSON text. Accepts a bare array of entries or `{testCommand, entries}`. */
|
|
27
|
+
export function parseMutationRegistry(text) {
|
|
28
|
+
let raw;
|
|
29
|
+
try {
|
|
30
|
+
raw = JSON.parse(text);
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
return { registry: null, errors: [`registry is not valid JSON: ${String(e.message).slice(0, 120)}`] };
|
|
34
|
+
}
|
|
35
|
+
let entriesRaw;
|
|
36
|
+
let testCommand;
|
|
37
|
+
if (Array.isArray(raw)) {
|
|
38
|
+
entriesRaw = raw;
|
|
39
|
+
}
|
|
40
|
+
else if (raw && typeof raw === 'object') {
|
|
41
|
+
const obj = raw;
|
|
42
|
+
entriesRaw = obj.entries;
|
|
43
|
+
if (obj.testCommand !== undefined) {
|
|
44
|
+
if (typeof obj.testCommand !== 'string' || obj.testCommand.trim() === '') {
|
|
45
|
+
return { registry: null, errors: ['testCommand must be a non-empty string when present'] };
|
|
46
|
+
}
|
|
47
|
+
testCommand = obj.testCommand.trim();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (!Array.isArray(entriesRaw)) {
|
|
51
|
+
return { registry: null, errors: ['registry must be an array of entries or {testCommand?, entries: [...]}'] };
|
|
52
|
+
}
|
|
53
|
+
if (entriesRaw.length === 0) {
|
|
54
|
+
// An empty registry "passes" by testing nothing — the same silent hole as a skipped mutation.
|
|
55
|
+
return { registry: null, errors: ['registry has no entries — an empty registry proves nothing and is refused'] };
|
|
56
|
+
}
|
|
57
|
+
const errors = [];
|
|
58
|
+
const entries = [];
|
|
59
|
+
const seen = new Set();
|
|
60
|
+
entriesRaw.forEach((e, i) => {
|
|
61
|
+
const at = `entries[${i}]`;
|
|
62
|
+
if (!e || typeof e !== 'object') {
|
|
63
|
+
errors.push(`${at}: not an object`);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const o = e;
|
|
67
|
+
const id = typeof o['id'] === 'string' ? o['id'] : '';
|
|
68
|
+
if (!SAFE_ID.test(id)) {
|
|
69
|
+
errors.push(`${at}: id must be kebab-case [a-z0-9-], got ${JSON.stringify(o['id'])}`);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (seen.has(id)) {
|
|
73
|
+
errors.push(`${at}: duplicate id '${id}'`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
seen.add(id);
|
|
77
|
+
if (typeof o['property'] !== 'string' || o['property'].trim() === '') {
|
|
78
|
+
errors.push(`${id}: property (the claimed sentence) is required`);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const file = typeof o['file'] === 'string' ? o['file'] : '';
|
|
82
|
+
if (file === '' || UNSAFE_FILE.test(file)) {
|
|
83
|
+
errors.push(`${id}: file must be a plain package-relative path, got ${JSON.stringify(o['file'])}`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
// A registry file under node_modules/ is refused OUTRIGHT (F-2): the registry names protections
|
|
87
|
+
// in the package's OWN code — a dependency's file is not this package's protection — and the
|
|
88
|
+
// scratch copy intentionally SHARES node_modules with the real tree (symlinked for
|
|
89
|
+
// runnability), so such an entry could never be mutated without writing through the link into
|
|
90
|
+
// the real working tree (SPEC rule 3). The executor's realpath containment is the belt; this
|
|
91
|
+
// is the cheaper layer-1 refusal for the case that is always a mistake.
|
|
92
|
+
if (file.split('/').includes('node_modules')) {
|
|
93
|
+
errors.push(`${id}: file targets node_modules/ (${JSON.stringify(file)}) — refused: a dependency file is not this package's protection, and the scratch copy shares node_modules with the REAL tree (rule 3: never mutate the working tree)`);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const mut = o['mutation'];
|
|
97
|
+
if (!mut || typeof mut !== 'object' || typeof mut.find !== 'string' || mut.find.length === 0 || typeof mut.replace !== 'string') {
|
|
98
|
+
errors.push(`${id}: mutation must be {find: <non-empty string>, replace: <string>}`);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (mut.find === mut.replace) {
|
|
102
|
+
errors.push(`${id}: mutation.replace equals mutation.find — a no-op mutation tests nothing`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
let minFailing = 1;
|
|
106
|
+
if (o['minFailing'] !== undefined) {
|
|
107
|
+
if (typeof o['minFailing'] !== 'number' || !Number.isInteger(o['minFailing']) || o['minFailing'] < 1) {
|
|
108
|
+
errors.push(`${id}: minFailing must be a positive integer`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
minFailing = o['minFailing'];
|
|
112
|
+
}
|
|
113
|
+
let observed;
|
|
114
|
+
if (o['observed'] !== undefined) {
|
|
115
|
+
if (typeof o['observed'] !== 'number' || !Number.isInteger(o['observed']) || o['observed'] < 1) {
|
|
116
|
+
errors.push(`${id}: observed must be a positive integer when present`);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
observed = o['observed'];
|
|
120
|
+
}
|
|
121
|
+
let maxFailing;
|
|
122
|
+
if (o['maxFailing'] !== undefined) {
|
|
123
|
+
if (typeof o['maxFailing'] !== 'number' || !Number.isInteger(o['maxFailing']) || o['maxFailing'] < 1) {
|
|
124
|
+
errors.push(`${id}: maxFailing must be a positive integer when present`);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (o['maxFailing'] < minFailing) {
|
|
128
|
+
errors.push(`${id}: maxFailing (${o['maxFailing']}) must be >= minFailing (${minFailing}) — a contradictory bound can never pass`);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
maxFailing = o['maxFailing'];
|
|
132
|
+
}
|
|
133
|
+
const entry = {
|
|
134
|
+
id,
|
|
135
|
+
property: o['property'].trim(),
|
|
136
|
+
file,
|
|
137
|
+
mutation: { find: mut.find, replace: mut.replace },
|
|
138
|
+
minFailing,
|
|
139
|
+
...(observed !== undefined ? { observed } : {}),
|
|
140
|
+
...(maxFailing !== undefined ? { maxFailing } : {}),
|
|
141
|
+
};
|
|
142
|
+
entries.push(entry);
|
|
143
|
+
});
|
|
144
|
+
if (errors.length > 0)
|
|
145
|
+
return { registry: null, errors };
|
|
146
|
+
return testCommand !== undefined
|
|
147
|
+
? { registry: { testCommand, entries }, errors: [] }
|
|
148
|
+
: { registry: { entries }, errors: [] };
|
|
149
|
+
}
|
|
150
|
+
/** Count NON-OVERLAPPING occurrences and apply only when the count is exactly 1. */
|
|
151
|
+
export function applyMutationToText(source, find, replace) {
|
|
152
|
+
if (find.length === 0)
|
|
153
|
+
return { ok: false, occurrences: 0 };
|
|
154
|
+
const occurrences = source.split(find).length - 1;
|
|
155
|
+
if (occurrences !== 1)
|
|
156
|
+
return { ok: false, occurrences };
|
|
157
|
+
// The replacer FUNCTION makes the replacement LITERAL: with a plain string, `$&`/`` $` ``/`$'`/
|
|
158
|
+
// `$$` are substitution patterns and the gate would silently apply a DIFFERENT mutation than
|
|
159
|
+
// declared (G-3, MEASURED: replace 'X$&Y' on find 'GUARD' produced 'XGUARDY').
|
|
160
|
+
return { ok: true, occurrences, text: source.replace(find, () => replace) };
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* The route-c upper bound: the most failing tests a mutation may cause and still be read as
|
|
164
|
+
* BEHAVIOURAL redness attributable to this one protection. Explicit `maxFailing` wins; otherwise
|
|
165
|
+
* `max(anchor*5, anchor+10)` where anchor = `observed` (the measured blast radius) falling back to
|
|
166
|
+
* `minFailing`. k=5 / N=10 justification, MEASURED on the live data: every health-advisor registry
|
|
167
|
+
* entry has observed 1–4 ⇒ bounds 11–20, so legitimate growth (more tests covering the property —
|
|
168
|
+
* 5× proportional, or +10 absolute so tiny `observed` is not strangled) still passes, while the
|
|
169
|
+
* two measured structural blow-ups (route a: whole 484-test suite dead on a parse error; route c:
|
|
170
|
+
* 200+ failing vs observed 1) exceed the bound by an order of magnitude. NEVER unbounded — an
|
|
171
|
+
* entry with no `observed` anchors on its own minFailing contract.
|
|
172
|
+
*/
|
|
173
|
+
export function effectiveMaxFailing(entry) {
|
|
174
|
+
if (entry.maxFailing !== undefined)
|
|
175
|
+
return entry.maxFailing;
|
|
176
|
+
const anchor = entry.observed ?? entry.minFailing ?? 1;
|
|
177
|
+
return Math.max(anchor * 5, anchor + 10);
|
|
178
|
+
}
|
|
179
|
+
// ── Failing-test count — best-effort SECONDARY signal (never decides red/green) ───────────────
|
|
180
|
+
/**
|
|
181
|
+
* Parse a failing-test count from runner output. Order of preference:
|
|
182
|
+
* 1. node --test / TAP summary `# fail N` (authoritative for TAP);
|
|
183
|
+
* 2. vitest/jest summary `Tests N failed` / `N failed`;
|
|
184
|
+
* 3. top-level (unindented) `not ok` line count.
|
|
185
|
+
* Returns null when nothing parses — the verdict then rests on the exit code ALONE, by design:
|
|
186
|
+
* count parsing failure must never flip red/green (SPEC design decision).
|
|
187
|
+
*/
|
|
188
|
+
export function countFailingTests(rawOutput) {
|
|
189
|
+
// vitest colours its summary even when piped (MEASURED: `npm test` piped still carries SGR
|
|
190
|
+
// codes), which would hide `Tests N failed` from every regex below — strip SGR first.
|
|
191
|
+
const output = stripSgr(rawOutput);
|
|
192
|
+
const tap = /^#\s*fail(?:ing)?\s+(\d+)\s*$/m.exec(output);
|
|
193
|
+
if (tap && tap[1] !== undefined)
|
|
194
|
+
return Number(tap[1]);
|
|
195
|
+
// the `Tests` line, NOT `Test Files` — a failed file count under-reports the failing tests.
|
|
196
|
+
const vitest = /^\s*Tests\s+[^|\n]*?(\d+)\s+failed/m.exec(output);
|
|
197
|
+
if (vitest && vitest[1] !== undefined)
|
|
198
|
+
return Number(vitest[1]);
|
|
199
|
+
const jest = /^\s*Tests:\s+(?:[^,\n]+,\s*)?(\d+)\s+failed/m.exec(output);
|
|
200
|
+
if (jest && jest[1] !== undefined)
|
|
201
|
+
return Number(jest[1]);
|
|
202
|
+
const notOk = output.match(/^not ok\b/gm);
|
|
203
|
+
if (notOk !== null && notOk.length > 0)
|
|
204
|
+
return notOk.length;
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
// ── Run-failure classification — the route-a′ signal, from THE RUN ITSELF (round-6 rework) ────
|
|
208
|
+
/** ANSI SGR sequences (colour). vitest emits them even when piped. */
|
|
209
|
+
// eslint-disable-next-line no-control-regex
|
|
210
|
+
const SGR = /\x1b\[[0-9;]*m/g;
|
|
211
|
+
function stripSgr(s) {
|
|
212
|
+
return s.replace(SGR, '');
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Classify a RED suite run's output: did a test FILE fail to LOAD (structural) or did test
|
|
216
|
+
* ASSERTIONS fail (behavioural)? This is the round-6 replacement for the isolated-child
|
|
217
|
+
* `import()` load-check, whose three false-PASS routes were ALL artifacts of the isolated import
|
|
218
|
+
* being a DIFFERENT environment than the test runner (env-dependent module goals, unsettled
|
|
219
|
+
* top-level await draining a child's event loop, non-deterministic load aborts dodging a second
|
|
220
|
+
* spawn). The correct signal comes from the SAME run that produced the failing count — no second
|
|
221
|
+
* child, no environment mismatch, nothing to disagree with itself.
|
|
222
|
+
*
|
|
223
|
+
* Runner shapes, both MEASURED (node v22.22.0, vitest 3.2.4 — reproducers in
|
|
224
|
+
* harness-cli/test/mutation-gate-cli.test.ts and this file's unit tests):
|
|
225
|
+
*
|
|
226
|
+
* node --test (flat TAP): every failure is a column-0 `not ok N - <name>` followed by an indented
|
|
227
|
+
* YAML diagnostic block. A test FILE that dies (throw at load, `process.exit` at import, unsettled
|
|
228
|
+
* top-level await ⇒ exit 13, dead require) is reported as a file-named test point whose block
|
|
229
|
+
* carries an `exitCode:` field (and `signal:` when killed) — `failureType: 'testCodeFailure'`,
|
|
230
|
+
* `error: 'test failed'`, `code: 'ERR_TEST_FAILURE'`. An ASSERTION failure — and equally a plain
|
|
231
|
+
* throw INSIDE a running test — NEVER carries `exitCode:`/`signal:`: those fields describe the
|
|
232
|
+
* spawned per-file process, which only appears when the file itself died. That asymmetry is the
|
|
233
|
+
* discriminator, and it is exactly the honest-mutation boundary: delete a guard clause and the
|
|
234
|
+
* file loads, assertions fire, no `exitCode:` field ⇒ behavioural.
|
|
235
|
+
*
|
|
236
|
+
* vitest (`vitest run`): a load/transform/collection error is reported under a `Failed Suites N`
|
|
237
|
+
* section as `FAIL <path> [ <path> ]` (the bracketed suite name repeats the path) with the error
|
|
238
|
+
* where a test name would be, and the summary counts it under `Test Files N failed` while `Tests`
|
|
239
|
+
* shows `no tests` for that file. Assertion failures appear under `Failed Tests N` as
|
|
240
|
+
* `FAIL <path> > <test name>` with `Tests N failed` in the summary. (An unsettled top-level await
|
|
241
|
+
* HANGS vitest rather than failing the file — the suite times out, exitCode null, INCONCLUSIVE —
|
|
242
|
+
* measured, and honestly out of scope for this classifier.)
|
|
243
|
+
*
|
|
244
|
+
* Runner detection is from the OUTPUT SHAPE, not the command (a `npm test` alias hides the
|
|
245
|
+
* runner): node --test requires the TAP header AND node's `# duration_ms` trailer (tape emits TAP
|
|
246
|
+
* + `# fail` but not `# duration_ms`); vitest requires its `RUN v<semver>` banner or `Test Files`
|
|
247
|
+
* summary line. Anything else — jest, mocha, tape, a bare script — is 'unknown'/'unrecognised':
|
|
248
|
+
* the honest, narrowed gap. It is about THIS TOOL's runner coverage, fails LOUD (INCONCLUSIVE),
|
|
249
|
+
* and never silently passes on output it cannot read.
|
|
250
|
+
*/
|
|
251
|
+
export function classifyRunFailure(rawOutput) {
|
|
252
|
+
const output = stripSgr(rawOutput);
|
|
253
|
+
// node --test (flat TAP, node ≥ 20 shape measured on v22.22.0)
|
|
254
|
+
if (/^TAP version \d+/m.test(output) && /^#\s*duration_ms\s+[\d.]+/m.test(output)) {
|
|
255
|
+
const lines = output.split('\n');
|
|
256
|
+
let sawNotOk = false;
|
|
257
|
+
for (let i = 0; i < lines.length; i++) {
|
|
258
|
+
const point = /^not ok \d+ - (.*)$/.exec(lines[i] ?? '');
|
|
259
|
+
if (point === null)
|
|
260
|
+
continue;
|
|
261
|
+
sawNotOk = true;
|
|
262
|
+
// Scan this point's YAML diagnostic block (indented, closed by ` ...`) for the per-file
|
|
263
|
+
// process fields. Nested subtest points are indented and never match the column-0 anchor.
|
|
264
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
265
|
+
const l = lines[j] ?? '';
|
|
266
|
+
if (!/^\s{2}/.test(l) || /^\s{2}\.\.\.\s*$/.test(l))
|
|
267
|
+
break;
|
|
268
|
+
const exit = /^\s+exitCode:\s*(-?\d+)/.exec(l);
|
|
269
|
+
const signal = /^\s+signal:\s*'([^']+)'/.exec(l);
|
|
270
|
+
if (exit !== null || signal !== null) {
|
|
271
|
+
const how = exit !== null ? `exitCode ${exit[1]}` : `signal ${signal?.[1]}`;
|
|
272
|
+
return {
|
|
273
|
+
runner: 'node-test',
|
|
274
|
+
kind: 'file-load',
|
|
275
|
+
evidence: `node --test reported the test FILE '${(point[1] ?? '').trim()}' failing as a whole (${how}) — the file (or a module it imports) died at load, not an assertion`,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const tapFail = /^#\s*fail(?:ing)?\s+(\d+)\s*$/m.exec(output);
|
|
281
|
+
if (sawNotOk || (tapFail !== null && Number(tapFail[1]) > 0)) {
|
|
282
|
+
return { runner: 'node-test', kind: 'assertions' };
|
|
283
|
+
}
|
|
284
|
+
return { runner: 'node-test', kind: 'unrecognised', evidence: 'red node --test run with no failing TAP test point — the redness has no classifiable source' };
|
|
285
|
+
}
|
|
286
|
+
// vitest (`vitest run`, shape measured on 3.2.4)
|
|
287
|
+
if (/^\s*RUN\s+v\d+\./m.test(output) || /^\s*Test Files\s/m.test(output)) {
|
|
288
|
+
const failedSuites = /\bFailed Suites\s+(\d+)\b/.exec(output);
|
|
289
|
+
const suiteFail = /^\s*FAIL\s+(\S+)\s+\[\s*\1\s*\]/m.exec(output);
|
|
290
|
+
if (failedSuites !== null || suiteFail !== null) {
|
|
291
|
+
const what = suiteFail !== null ? `'${suiteFail[1]}'` : `${failedSuites?.[1]} suite(s)`;
|
|
292
|
+
return {
|
|
293
|
+
runner: 'vitest',
|
|
294
|
+
kind: 'file-load',
|
|
295
|
+
evidence: `vitest reported ${what} failing to load (Failed Suites — a collection/import error, not a test assertion)`,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
if (/^\s*Tests\s+[^|\n]*?\d+\s+failed/m.test(output) || /\bFailed Tests\s+\d+\b/.test(output)) {
|
|
299
|
+
return { runner: 'vitest', kind: 'assertions' };
|
|
300
|
+
}
|
|
301
|
+
return { runner: 'vitest', kind: 'unrecognised', evidence: 'red vitest run with neither Failed Suites nor failed tests in the output — the redness has no classifiable source (unhandled error outside any test?)' };
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
runner: 'unknown',
|
|
305
|
+
kind: 'unrecognised',
|
|
306
|
+
evidence: 'output matches neither node --test (TAP + # duration_ms) nor vitest (RUN v / Test Files) — no classifier for this runner; structural-vs-behavioural redness cannot be told apart, so the verdict may not be PROVEN',
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* A RED baseline in the scratch copy is a SETUP error, never a mutation result: every subsequent
|
|
311
|
+
* "red under mutation" would be noise, and every "green" a lie about an unrunnable copy.
|
|
312
|
+
*/
|
|
313
|
+
export function classifyBaseline(exitCode) {
|
|
314
|
+
if (exitCode === 0)
|
|
315
|
+
return { ok: true, detail: 'baseline suite green in the scratch copy' };
|
|
316
|
+
if (exitCode === null) {
|
|
317
|
+
return { ok: false, detail: 'baseline suite produced no exit code (timeout/spawn failure) — the copy is not runnable; fix the copy, do not read this as a mutation result' };
|
|
318
|
+
}
|
|
319
|
+
return { ok: false, detail: `baseline suite RED (exit ${exitCode}) in the UNMUTATED scratch copy — a broken copy cannot prove anything; fix the copy (node_modules link? path-dependent test?) before trusting any verdict` };
|
|
320
|
+
}
|
|
321
|
+
// ── Classification (rules 1 + 2, and the drop decision) ───────────────────────────────────────
|
|
322
|
+
/**
|
|
323
|
+
* Classify one entry's observation.
|
|
324
|
+
*
|
|
325
|
+
* PRECEDENCE LATTICE (checked top to bottom; each verdict is justified by what it invalidates
|
|
326
|
+
* BELOW it — a verdict may only outrank another when it makes that other's evidence meaningless):
|
|
327
|
+
* 1. NOT_APPLIED — nothing was mutated, so no run observation means anything;
|
|
328
|
+
* 2. MUTATION_UNPARSEABLE — the mutated file is not even syntactically a module; a non-parsing
|
|
329
|
+
* file never RUNS, so no load- or count-signal can exist (which is
|
|
330
|
+
* why it sits above MUTATION_LOAD_FATAL);
|
|
331
|
+
* 3. MUTATION_LOAD_FATAL — the run happened, but its own output says a test FILE died at
|
|
332
|
+
* load: the redness is structural and every count from that same run
|
|
333
|
+
* is non-attributable — so it outranks every count-based verdict AND
|
|
334
|
+
* the flaky-rebaseline check (a structurally-broken run needs no
|
|
335
|
+
* attribution analysis);
|
|
336
|
+
* 4. INCONCLUSIVE (no exit) — the run produced nothing to classify at all;
|
|
337
|
+
* 5. UNDEFENDED (exit 0) — the disjoint GREEN arm: mutually exclusive with every red-based
|
|
338
|
+
* verdict below;
|
|
339
|
+
* 6. INCONCLUSIVE (unrecognised output) — red, but the shape is unreadable: counts parsed out of
|
|
340
|
+
* unrecognised output must not reach BELOW_MIN/OVER_FAILING/PROVEN;
|
|
341
|
+
* 7. INCONCLUSIVE (flaky rebaseline) — red, readable, but not attributable;
|
|
342
|
+
* 8. BELOW_MIN → 9. OVER_FAILING — reliable-count contract checks, both failing;
|
|
343
|
+
* 10. PROVEN — applied, red, behavioural, attributable, within bounds.
|
|
344
|
+
*
|
|
345
|
+
* THE DROP DECISION (SPEC §Reporting, decided here + justified): `failing < observed` but still
|
|
346
|
+
* `>= minFailing` is a LOUD WARNING, not a failure. Two reasons, both load-bearing:
|
|
347
|
+
* • the count is a BEST-EFFORT secondary parsed from runner output — making it verdict-deciding
|
|
348
|
+
* would let a count-parse failure flip red/green, which the design decisions forbid;
|
|
349
|
+
* • `observed` is a historical measurement, `minFailing` is the entry's declared CONTRACT. A
|
|
350
|
+
* contract violation fails (BELOW_MIN — only ever on a reliable count); history drifting down
|
|
351
|
+
* while the contract still holds is the early warning, reported loudly so a human re-pins
|
|
352
|
+
* `observed` or investigates — silently normalising it would erase the signal.
|
|
353
|
+
*/
|
|
354
|
+
export function classifyMutationOutcome(obs) {
|
|
355
|
+
const e = obs.entry;
|
|
356
|
+
const minFailing = e.minFailing ?? 1;
|
|
357
|
+
const base = {
|
|
358
|
+
id: e.id,
|
|
359
|
+
property: e.property,
|
|
360
|
+
file: e.file,
|
|
361
|
+
occurrences: obs.occurrences,
|
|
362
|
+
exitCode: obs.exitCode,
|
|
363
|
+
failingCount: obs.failingCount,
|
|
364
|
+
// an `observed` anchor is the ONLY thing that makes a drop detectable at all (round-7 honesty)
|
|
365
|
+
dropComparable: e.observed !== undefined,
|
|
366
|
+
};
|
|
367
|
+
if (obs.occurrences !== 1) {
|
|
368
|
+
return {
|
|
369
|
+
...base,
|
|
370
|
+
applied: false,
|
|
371
|
+
verdict: 'NOT_APPLIED',
|
|
372
|
+
drop: false,
|
|
373
|
+
detail: obs.occurrences === 0
|
|
374
|
+
? `mutation did not apply: find-text absent from ${e.file} (code drifted?) — NOTHING was tested; a skipped mutation may not report the property as proven (rule 1)`
|
|
375
|
+
: `mutation did not apply: find-text occurs ${obs.occurrences} times in ${e.file} (must be exactly once) — ambiguous surgery is refused, not guessed (rule 1)`,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
// Route (a) — STRUCTURAL redness: the mutated file does not parse, so the suite (or its import
|
|
379
|
+
// chain) dies wholesale. That says nothing about the named protection. A registry mutation must
|
|
380
|
+
// delete the protection while keeping the file loadable; this is a bad entry, and it wins even
|
|
381
|
+
// over a red exit + a count, because that red is exactly the false signal being refused.
|
|
382
|
+
if (obs.parseError !== undefined) {
|
|
383
|
+
return {
|
|
384
|
+
...base,
|
|
385
|
+
applied: true,
|
|
386
|
+
verdict: 'MUTATION_UNPARSEABLE',
|
|
387
|
+
drop: false,
|
|
388
|
+
detail: `the MUTATED ${e.file} does not parse (${obs.parseError.slice(0, 160)}) — a SETUP/registry error, not a discrimination proof: whole-suite redness from a load failure is not attributable to the protection; rewrite the mutation to delete the protection while keeping the file loadable`,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
// Route (a′) — the run-derived file-load signal (round-6 rework): THE SUITE RUN's own output
|
|
392
|
+
// reported a test FILE failing to load under the mutation. Structural redness — the mutation
|
|
393
|
+
// broke the module, every count the same run produced is not attributable to the protection —
|
|
394
|
+
// so this outranks every count-based verdict AND the rebaseline check, exactly like
|
|
395
|
+
// MUTATION_UNPARSEABLE does. Unlike the replaced isolated-import load-check there is no second
|
|
396
|
+
// environment to disagree: the signal and the count come from ONE run.
|
|
397
|
+
if (obs.fileLoadFailure !== undefined) {
|
|
398
|
+
return {
|
|
399
|
+
...base,
|
|
400
|
+
applied: true,
|
|
401
|
+
verdict: 'MUTATION_LOAD_FATAL',
|
|
402
|
+
drop: false,
|
|
403
|
+
detail: `the suite run reports a test FILE failing to LOAD under the mutation (${obs.fileLoadFailure.slice(0, 220)}) — structural, not behavioural: the mutation broke the module, so the redness is not attributable to the protection; rewrite the mutation to delete the protection while keeping the module evaluable`,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
if (obs.exitCode === null) {
|
|
407
|
+
return {
|
|
408
|
+
...base,
|
|
409
|
+
applied: true,
|
|
410
|
+
verdict: 'INCONCLUSIVE',
|
|
411
|
+
drop: false,
|
|
412
|
+
detail: 'suite produced NO exit code under the mutation (timeout / spawn failure) — inconclusive is a FAILURE, never a pass',
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
if (obs.exitCode === 0) {
|
|
416
|
+
return {
|
|
417
|
+
...base,
|
|
418
|
+
applied: true,
|
|
419
|
+
verdict: 'UNDEFENDED',
|
|
420
|
+
drop: false,
|
|
421
|
+
detail: `suite stayed GREEN with the protection deleted — property UNDEFENDED: "${e.property}" (${e.file}). The suite would not notice this protection regressing (rule 2).`,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
// Unrecognised output shape on a RED run — a runner-coverage gap of THIS TOOL (jest, mocha, an
|
|
425
|
+
// opaque wrapper script…): structural-vs-behavioural cannot be told apart, so nothing below
|
|
426
|
+
// (counts, bounds, PROVEN) may run. INCONCLUSIVE, loud, before every count-based branch.
|
|
427
|
+
if (obs.outputUnrecognised !== undefined) {
|
|
428
|
+
return {
|
|
429
|
+
...base,
|
|
430
|
+
applied: true,
|
|
431
|
+
verdict: 'INCONCLUSIVE',
|
|
432
|
+
drop: false,
|
|
433
|
+
detail: `suite red but the runner output is UNRECOGNISED (${obs.outputUnrecognised.slice(0, 220)}) — this tool has no classifier for the output shape, so file-load redness cannot be told from assertion redness; INCONCLUSIVE is a FAILURE, never a pass (run the gate with a node --test or vitest test command, or extend classifyRunFailure)`,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
// Route (b) — SPURIOUS redness: the suite went red under the mutation, but the RESTORED tree did
|
|
437
|
+
// not come back green, so the suite is flaky and an unrelated neighbour may be what went red.
|
|
438
|
+
// Not attributable ⇒ INCONCLUSIVE (a failure, never a pass).
|
|
439
|
+
if (obs.rebaselineExitCode !== undefined && obs.rebaselineExitCode !== 0) {
|
|
440
|
+
return {
|
|
441
|
+
...base,
|
|
442
|
+
applied: true,
|
|
443
|
+
verdict: 'INCONCLUSIVE',
|
|
444
|
+
drop: false,
|
|
445
|
+
detail: `suite red under the mutation BUT the restored baseline did not reproduce green (${obs.rebaselineExitCode === null ? 'no exit code' : `exit ${obs.rebaselineExitCode}`}) — the suite is flaky; the redness is not attributable to the protection and may be an unrelated neighbour`,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
if (obs.failingCount !== null && obs.failingCount < minFailing) {
|
|
449
|
+
return {
|
|
450
|
+
...base,
|
|
451
|
+
applied: true,
|
|
452
|
+
verdict: 'BELOW_MIN',
|
|
453
|
+
drop: false,
|
|
454
|
+
detail: `suite red, but only ${obs.failingCount} failing test(s) — below the entry's own minFailing contract of ${minFailing}`,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
// Route (c) — the symmetric arm of the drop warning: a RELIABLE count wildly ABOVE the entry's
|
|
458
|
+
// bound means the mutation broke far more than the protection's own tests (a structural blow-up
|
|
459
|
+
// that still parsed — dead export surface, poisoned shared helper). Not attributable ⇒ FAILURE.
|
|
460
|
+
const bound = effectiveMaxFailing(e);
|
|
461
|
+
if (obs.failingCount !== null && obs.failingCount > bound) {
|
|
462
|
+
return {
|
|
463
|
+
...base,
|
|
464
|
+
applied: true,
|
|
465
|
+
verdict: 'OVER_FAILING',
|
|
466
|
+
drop: false,
|
|
467
|
+
detail: `suite red with ${obs.failingCount} failing test(s) — far more than this protection's own tests (bound ${bound}${e.maxFailing !== undefined ? ', explicit maxFailing' : `, default from ${e.observed !== undefined ? `observed ${e.observed}` : `minFailing ${minFailing}`}`}); the redness is not attributable to the protection — narrow the mutation or pin maxFailing if the blast radius is genuinely this large`,
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
const drop = obs.failingCount !== null && e.observed !== undefined && obs.failingCount < e.observed;
|
|
471
|
+
return {
|
|
472
|
+
...base,
|
|
473
|
+
applied: true,
|
|
474
|
+
verdict: 'PROVEN',
|
|
475
|
+
drop,
|
|
476
|
+
detail: drop
|
|
477
|
+
? `suite red (${obs.failingCount} failing vs ${e.observed} observed when written) — COVERAGE DROP: still defended, but fewer tests notice; investigate or re-pin observed`
|
|
478
|
+
: `suite red under the mutation (${obs.failingCount === null ? 'failing count unavailable — exit code is the verdict' : `${obs.failingCount} failing`}) — the test discriminates`,
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
/** Verdicts that fail the gate. INCONCLUSIVE and NOT_APPLIED fail (inconclusive ≠ pass). */
|
|
482
|
+
const FAILING_VERDICTS = new Set(['UNDEFENDED', 'NOT_APPLIED', 'BELOW_MIN', 'MUTATION_UNPARSEABLE', 'MUTATION_LOAD_FATAL', 'OVER_FAILING', 'INCONCLUSIVE']);
|
|
483
|
+
/** Exit contract: 0 all proven · 1 any entry failed (or red baseline) · (2 = usage/setup, CLI-side). */
|
|
484
|
+
export function mutationGateExitCode(results, baselineOk) {
|
|
485
|
+
if (!baselineOk)
|
|
486
|
+
return 1;
|
|
487
|
+
if (results.length === 0)
|
|
488
|
+
return 1; // nothing ran ⇒ nothing proven
|
|
489
|
+
return results.some((r) => FAILING_VERDICTS.has(r.verdict)) ? 1 : 0;
|
|
490
|
+
}
|
|
491
|
+
export function summarizeMutationResults(results) {
|
|
492
|
+
return {
|
|
493
|
+
total: results.length,
|
|
494
|
+
proven: results.filter((r) => r.verdict === 'PROVEN').length,
|
|
495
|
+
undefended: results.filter((r) => r.verdict === 'UNDEFENDED').length,
|
|
496
|
+
notApplied: results.filter((r) => r.verdict === 'NOT_APPLIED').length,
|
|
497
|
+
belowMin: results.filter((r) => r.verdict === 'BELOW_MIN').length,
|
|
498
|
+
unparseable: results.filter((r) => r.verdict === 'MUTATION_UNPARSEABLE').length,
|
|
499
|
+
loadFatal: results.filter((r) => r.verdict === 'MUTATION_LOAD_FATAL').length,
|
|
500
|
+
overFailing: results.filter((r) => r.verdict === 'OVER_FAILING').length,
|
|
501
|
+
inconclusive: results.filter((r) => r.verdict === 'INCONCLUSIVE').length,
|
|
502
|
+
drops: results.filter((r) => r.drop).length,
|
|
503
|
+
dropComparable: results.filter((r) => r.dropComparable).length,
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
const VERDICT_MARK = {
|
|
507
|
+
PROVEN: '✓',
|
|
508
|
+
UNDEFENDED: '✗',
|
|
509
|
+
NOT_APPLIED: '✗',
|
|
510
|
+
BELOW_MIN: '✗',
|
|
511
|
+
MUTATION_UNPARSEABLE: '✗',
|
|
512
|
+
MUTATION_LOAD_FATAL: '✗',
|
|
513
|
+
OVER_FAILING: '✗',
|
|
514
|
+
INCONCLUSIVE: '✗',
|
|
515
|
+
};
|
|
516
|
+
export function renderMutationReport(results, baseline, packageDir) {
|
|
517
|
+
const lines = [];
|
|
518
|
+
lines.push(`mutation-gate: ${packageDir}`);
|
|
519
|
+
lines.push(` baseline: ${baseline.ok ? 'GREEN' : 'ERROR'} — ${baseline.detail}`);
|
|
520
|
+
if (!baseline.ok)
|
|
521
|
+
return lines.join('\n');
|
|
522
|
+
for (const r of results) {
|
|
523
|
+
const count = r.failingCount === null ? 'n/a' : String(r.failingCount);
|
|
524
|
+
lines.push(` ${VERDICT_MARK[r.verdict]} ${r.id} applied=${r.applied ? 'yes' : 'no'} failing=${count} ${r.verdict}${r.drop ? ' (COVERAGE DROP)' : ''}`);
|
|
525
|
+
if (r.verdict !== 'PROVEN' || r.drop)
|
|
526
|
+
lines.push(` ${r.detail}`);
|
|
527
|
+
}
|
|
528
|
+
const s = summarizeMutationResults(results);
|
|
529
|
+
lines.push(` summary: ${s.proven}/${s.total} proven · ${s.undefended} undefended · ${s.notApplied} not-applied · ${s.belowMin} below-min · ${s.unparseable} unparseable · ${s.loadFatal} load-fatal · ${s.overFailing} over-failing · ${s.inconclusive} inconclusive · ${s.drops} coverage drop(s) among ${s.dropComparable}/${s.total} observed-anchored entries (a drop is undetectable without an \`observed\` anchor)`);
|
|
530
|
+
lines.push(mutationGateExitCode(results, baseline.ok) === 0
|
|
531
|
+
? ' verdict: PASS — every named protection has a test that goes red when the protection is deleted'
|
|
532
|
+
: ' verdict: FAIL — at least one named protection is undefended, unmutable, or unproven');
|
|
533
|
+
return lines.join('\n');
|
|
534
|
+
}
|
|
535
|
+
//# sourceMappingURL=mutation-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mutation-gate.js","sourceRoot":"","sources":["../src/mutation-gate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bare stub markers, ASSEMBLED so this module never fires on itself. Matched case-SENSITIVELY
|
|
3
|
+
* (measured: case-insensitivity doubles hits and adds only prose) with hard word boundaries on both
|
|
4
|
+
* sides, so a marker embedded in a word (a codename, a longer identifier) does not fire.
|
|
5
|
+
*/
|
|
6
|
+
export declare const STUB_MARKERS: readonly string[];
|
|
7
|
+
/**
|
|
8
|
+
* Stub PHRASES: matched case-insensitively (a phrase is English, not a convention), with letter
|
|
9
|
+
* boundaries. Assembled for the same self-exemption reason.
|
|
10
|
+
*/
|
|
11
|
+
export declare const STUB_PHRASES: readonly string[];
|
|
12
|
+
/**
|
|
13
|
+
* File extensions the scan reads. EXPLICIT and testable — never a silent glob. Fixture/test paths
|
|
14
|
+
* are NOT auto-exempt: a marker you just added to a fixture is still a decision, and the waiver is
|
|
15
|
+
* where that decision gets its reason.
|
|
16
|
+
*/
|
|
17
|
+
export declare const STUB_SCAN_EXTENSIONS: readonly string[];
|
|
18
|
+
/** Is this path one the stub scan reads? (extension allowlist, case-insensitive on the extension) */
|
|
19
|
+
export declare function scannableStubPath(path: unknown): boolean;
|
|
20
|
+
export interface StubFinding {
|
|
21
|
+
readonly path: string;
|
|
22
|
+
/** 1-based line number. */
|
|
23
|
+
readonly line: number;
|
|
24
|
+
readonly kind: 'marker' | 'reasonless-waiver';
|
|
25
|
+
/** which marker/phrase matched (for 'marker'), or the refusal text (for 'reasonless-waiver'). */
|
|
26
|
+
readonly detail: string;
|
|
27
|
+
}
|
|
28
|
+
/** A config waiver: path-keyed, reason MANDATORY. A reasonless entry is refused, never honoured. */
|
|
29
|
+
export interface StubWaiver {
|
|
30
|
+
readonly path?: string;
|
|
31
|
+
readonly reason?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Scan ONE file's text. Case-sensitive bare markers + case-insensitive phrases, word-bounded.
|
|
35
|
+
* Markdown gets PROSE scoping: fenced code blocks and inline backtick spans are QUOTES of a marker,
|
|
36
|
+
* not stubs (the claim-check backtick-literal convention) — a doc explaining this very gate scans
|
|
37
|
+
* clean, while a naked stub line in doc prose still fires. Code files are scanned in full: a marker
|
|
38
|
+
* a code file must legitimately carry (another gate's source, a fixture) takes an inline
|
|
39
|
+
* `no-stubs: <reason>` waiver — visible, reasoned, greppable.
|
|
40
|
+
*/
|
|
41
|
+
export declare function scanStubs(path: string, text: unknown): StubFinding[];
|
|
42
|
+
/**
|
|
43
|
+
* The aggregate check the guard rule calls: scan the CHANGED files whose contents were gathered,
|
|
44
|
+
* then apply config waivers. Missing contents for a changed file ⇒ that file reports nothing
|
|
45
|
+
* (fail-open on missing evidence — the standing guard contract; a deleted file has no contents).
|
|
46
|
+
*
|
|
47
|
+
* Config-waiver semantics: an entry with a non-empty `path` AND a non-empty `reason` exempts that
|
|
48
|
+
* exact repo-relative path. An entry with a path but NO reason is REFUSED as its own finding and
|
|
49
|
+
* exempts nothing. An entry with no path at all is inert garbage and is ignored (there is nothing
|
|
50
|
+
* it could exempt, and inventing a finding for it would punish a stray comma).
|
|
51
|
+
*/
|
|
52
|
+
export declare function checkNoStubs(files: readonly string[], contents: Readonly<Record<string, string>> | undefined, waivers: readonly StubWaiver[] | undefined): StubFinding[];
|
|
53
|
+
//# sourceMappingURL=no-stubs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-stubs.d.ts","sourceRoot":"","sources":["../src/no-stubs.ts"],"names":[],"mappings":"AAyDA;;;;GAIG;AACH,eAAO,MAAM,YAAY,EAAE,SAAS,MAAM,EAMzC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,YAAY,EAAE,SAAS,MAAM,EAA6B,CAAC;AAkBxE;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAE,SAAS,MAAM,EAKjD,CAAC;AAEF,qGAAqG;AACrG,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAIxD;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2BAA2B;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,mBAAmB,CAAC;IAC9C,iGAAiG;IACjG,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,oGAAoG;AACpG,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAYD;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,WAAW,EAAE,CA2BpE;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,SAAS,EACtD,OAAO,EAAE,SAAS,UAAU,EAAE,GAAG,SAAS,GACzC,WAAW,EAAE,CAgBf"}
|