@codyswann/lisa 4.10.13 → 4.10.14
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/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +104 -20
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +4 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +7 -5
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/opencode/plugin-templates/parity-safety-net.sh +104 -20
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/hooks/parity-safety-net.sh +104 -20
- package/plugins/lisa/rules/eager/falsifiable-checks.md +1 -0
- package/plugins/lisa/rules/reference/falsifiable-checks.md +14 -0
- package/plugins/lisa-agy/hooks/parity-safety-net.sh +104 -20
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/hooks/parity-safety-net.sh +104 -20
- package/plugins/lisa-copilot/rules/eager/falsifiable-checks.md +1 -0
- package/plugins/lisa-copilot/rules/reference/falsifiable-checks.md +14 -0
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/parity-safety-net.sh +104 -20
- package/plugins/lisa-cursor/rules/falsifiable-checks-reference.mdc +14 -0
- package/plugins/lisa-cursor/rules/falsifiable-checks.mdc +1 -0
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/hooks/parity-safety-net.sh +104 -20
- package/plugins/src/base/rules/eager/falsifiable-checks.md +1 -0
- package/plugins/src/base/rules/reference/falsifiable-checks.md +14 -0
- package/typescript/copy-overwrite/scripts/lisa-mutation.mjs +176 -1
|
@@ -49,6 +49,27 @@
|
|
|
49
49
|
* a misconfigured gate, permanently inert, green forever. Reported as
|
|
50
50
|
* `inert-mutate-config` and it FAILS, exit 1. Distinguishing the two costs
|
|
51
51
|
* one `git ls-files`.
|
|
52
|
+
* - **Something changed that no mutation tool can reach** — a shell guard.
|
|
53
|
+
* Reported as `uninstrumentable-language`, exit 0, because it is not a test
|
|
54
|
+
* failure. It is separated from `nothing-to-mutate` because the two are
|
|
55
|
+
* opposite claims wearing the same grey line: one says nothing this gate
|
|
56
|
+
* cares about changed, the other says a guard changed and this gate is
|
|
57
|
+
* structurally blind to it.
|
|
58
|
+
*
|
|
59
|
+
* ## Some guards are outside this gate by construction, not by selection
|
|
60
|
+
*
|
|
61
|
+
* Stryker's instrumenter is per-language and shell has no parser. That makes
|
|
62
|
+
* the gap unfixable by widening `mutate`: a `.sh` path in `mutate` does not
|
|
63
|
+
* produce zero mutants quietly, it aborts the ENTIRE run with
|
|
64
|
+
* `Unable to parse …. No parser registered for .sh!` — measured on this
|
|
65
|
+
* repository — so one such entry takes every other guard's score with it.
|
|
66
|
+
* Both halves are handled here: `uninstrumentable-mutate-target` refuses the
|
|
67
|
+
* config edit before Stryker can crash on it, and `uninstrumentable-language`
|
|
68
|
+
* refuses to let a shell-guard change be read as a measured pass.
|
|
69
|
+
*
|
|
70
|
+
* What DOES measure a shell guard is a driving test: run the script against a
|
|
71
|
+
* payload table and assert the blocked/allowed verdict, with controls on both
|
|
72
|
+
* sides. That is the evidence to look for; this gate will never supply it.
|
|
52
73
|
*
|
|
53
74
|
* ## A timeout is not a score
|
|
54
75
|
*
|
|
@@ -153,7 +174,9 @@ export const OUTCOMES = Object.freeze({
|
|
|
153
174
|
disabled: "mutation-gate: disabled",
|
|
154
175
|
noBase: "mutation-gate: no-diff-base",
|
|
155
176
|
nothingToMutate: "mutation-gate: nothing-to-mutate",
|
|
177
|
+
uninstrumentableLanguage: "mutation-gate: uninstrumentable-language",
|
|
156
178
|
inertConfig: "mutation-gate: inert-mutate-config",
|
|
179
|
+
uninstrumentableTarget: "mutation-gate: uninstrumentable-mutate-target",
|
|
157
180
|
unrepresentablePath: "mutation-gate: unrepresentable-path",
|
|
158
181
|
diffFailed: "mutation-gate: diff-failed",
|
|
159
182
|
scoped: "mutation-gate: scoped-run",
|
|
@@ -304,6 +327,82 @@ export const isMutateTarget = (file, patterns) => {
|
|
|
304
327
|
return !patterns.exclude.some(rule => rule.test(candidate));
|
|
305
328
|
};
|
|
306
329
|
|
|
330
|
+
/**
|
|
331
|
+
* Extensions Stryker's instrumenter has a parser for.
|
|
332
|
+
*
|
|
333
|
+
* Taken from `@stryker-mutator/instrumenter`'s own parser registry rather than
|
|
334
|
+
* guessed: the TypeScript/JavaScript family, plus the HTML and Svelte parsers
|
|
335
|
+
* that delegate to it. Anything outside this set does not degrade to zero
|
|
336
|
+
* mutants — `createParser` throws `No parser registered for <ext>!` and the run
|
|
337
|
+
* aborts before a single mutant is tried.
|
|
338
|
+
*
|
|
339
|
+
* Kept as a denial list rather than an allow list on purpose. A new Stryker
|
|
340
|
+
* parser being missing here is a false refusal a maintainer can see and delete;
|
|
341
|
+
* the opposite mistake — an unparseable extension quietly permitted — is a
|
|
342
|
+
* crash that reads as a broken gate and gets "fixed" by disabling it.
|
|
343
|
+
* @type {readonly string[]}
|
|
344
|
+
*/
|
|
345
|
+
export const STRYKER_PARSED_EXTENSIONS = Object.freeze([
|
|
346
|
+
".js",
|
|
347
|
+
".jsx",
|
|
348
|
+
".cjs",
|
|
349
|
+
".mjs",
|
|
350
|
+
".cjsx",
|
|
351
|
+
".ts",
|
|
352
|
+
".tsx",
|
|
353
|
+
".cts",
|
|
354
|
+
".mts",
|
|
355
|
+
".html",
|
|
356
|
+
".htm",
|
|
357
|
+
".vue",
|
|
358
|
+
".svelte",
|
|
359
|
+
]);
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Extensions that carry guard logic no mutation tool in this toolchain reaches.
|
|
363
|
+
*
|
|
364
|
+
* Deliberately the shell family and nothing else. The general question — "does
|
|
365
|
+
* Stryker have a parser for this?" — is answered by
|
|
366
|
+
* {@link STRYKER_PARSED_EXTENSIONS}, and it is the right question to ask of a
|
|
367
|
+
* `mutate` entry, where any unparseable extension is a crash. It is the WRONG
|
|
368
|
+
* question to ask of a diff: every markdown, JSON and workflow file would
|
|
369
|
+
* answer no, and a docs-only branch would be told its guards are unmeasured.
|
|
370
|
+
*
|
|
371
|
+
* So this narrower set names the languages this repository actually writes
|
|
372
|
+
* enforcement in and Stryker cannot instrument. It exists to make one specific
|
|
373
|
+
* confusion impossible: a branch whose only guard change is a `.sh` file
|
|
374
|
+
* reporting the same grey line as a branch that changed a README.
|
|
375
|
+
* @type {readonly string[]}
|
|
376
|
+
*/
|
|
377
|
+
export const UNINSTRUMENTABLE_GUARD_EXTENSIONS = Object.freeze([
|
|
378
|
+
".sh",
|
|
379
|
+
".bash",
|
|
380
|
+
".ksh",
|
|
381
|
+
".zsh",
|
|
382
|
+
]);
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* The subset of `files` written in a language this gate cannot instrument.
|
|
386
|
+
* @param {readonly string[]} files - Repository-relative paths.
|
|
387
|
+
* @returns {string[]} Those whose extension no mutation tool here reaches.
|
|
388
|
+
*/
|
|
389
|
+
export const uninstrumentableGuards = files =>
|
|
390
|
+
files.filter(file =>
|
|
391
|
+
UNINSTRUMENTABLE_GUARD_EXTENSIONS.includes(
|
|
392
|
+
path.extname(normalizePath(file)).toLowerCase()
|
|
393
|
+
)
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Whether Stryker can parse a file at all, by extension.
|
|
398
|
+
* @param {string} file - Repository-relative path.
|
|
399
|
+
* @returns {boolean} True when Stryker has a parser registered for it.
|
|
400
|
+
*/
|
|
401
|
+
export const isStrykerParseable = file =>
|
|
402
|
+
STRYKER_PARSED_EXTENSIONS.includes(
|
|
403
|
+
path.extname(normalizePath(file)).toLowerCase()
|
|
404
|
+
);
|
|
405
|
+
|
|
307
406
|
/**
|
|
308
407
|
* Read and parse a Stryker JSON config, or report why it could not be used.
|
|
309
408
|
* @param {string} cwd - Project root.
|
|
@@ -1177,12 +1276,38 @@ export const countMutateTargetsInRepo = (cwd, patterns) => {
|
|
|
1177
1276
|
}
|
|
1178
1277
|
};
|
|
1179
1278
|
|
|
1279
|
+
/**
|
|
1280
|
+
* Tracked files the patterns select that Stryker cannot parse at all.
|
|
1281
|
+
*
|
|
1282
|
+
* This is not a coverage question, it is a liveness one. Stryker does not skip
|
|
1283
|
+
* a file it has no parser for — it throws out of instrumentation, so ONE such
|
|
1284
|
+
* entry in `mutate` destroys the score of every other guard in the list. The
|
|
1285
|
+
* check exists because widening `mutate` is the intuitive fix for the shell
|
|
1286
|
+
* gap and it is the one edit that must never be made.
|
|
1287
|
+
* @param {string} cwd - Project root.
|
|
1288
|
+
* @param {{include: RegExp[], exclude: RegExp[]}} patterns - Compiled matchers.
|
|
1289
|
+
* @returns {string[]} Selected tracked files with no Stryker parser.
|
|
1290
|
+
*/
|
|
1291
|
+
export const selectUninstrumentableMutateTargets = (cwd, patterns) => {
|
|
1292
|
+
try {
|
|
1293
|
+
return git(cwd, ["ls-files"])
|
|
1294
|
+
.split("\n")
|
|
1295
|
+
.filter(file => file && isMutateTarget(file, patterns))
|
|
1296
|
+
.filter(file => !isStrykerParseable(file));
|
|
1297
|
+
} catch {
|
|
1298
|
+
// Same reasoning as the count above: an unreadable index says nothing
|
|
1299
|
+
// about the config, and blocking a push on it would be a lie about why.
|
|
1300
|
+
return [];
|
|
1301
|
+
}
|
|
1302
|
+
};
|
|
1303
|
+
|
|
1180
1304
|
/**
|
|
1181
1305
|
* Files changed on this branch that this project mutates.
|
|
1182
1306
|
* @param {string} cwd - Project root.
|
|
1183
1307
|
* @param {string} base - Merge-base sha.
|
|
1184
1308
|
* @param {{include: RegExp[], exclude: RegExp[]}} patterns - Compiled matchers.
|
|
1185
|
-
* @returns {{changed: number, selected: string[]}}
|
|
1309
|
+
* @returns {{changed: number, selected: string[], uninstrumentable: string[]}}
|
|
1310
|
+
* Totals, the selection, and the changed files no mutation tool here reaches.
|
|
1186
1311
|
*/
|
|
1187
1312
|
export const selectChangedTargets = (cwd, base, patterns) => {
|
|
1188
1313
|
const changed = git(cwd, [
|
|
@@ -1199,6 +1324,11 @@ export const selectChangedTargets = (cwd, base, patterns) => {
|
|
|
1199
1324
|
selected: changed
|
|
1200
1325
|
.filter(file => isMutateTarget(file, patterns))
|
|
1201
1326
|
.filter(file => fs.existsSync(path.join(cwd, file))),
|
|
1327
|
+
// Reported from the diff, not from the selection, because by construction
|
|
1328
|
+
// these can never BE in the selection — that is the whole point of them.
|
|
1329
|
+
uninstrumentable: uninstrumentableGuards(changed).filter(file =>
|
|
1330
|
+
fs.existsSync(path.join(cwd, file))
|
|
1331
|
+
),
|
|
1202
1332
|
};
|
|
1203
1333
|
};
|
|
1204
1334
|
|
|
@@ -1897,6 +2027,28 @@ export const runGate = (cwd = process.cwd(), argv = []) => {
|
|
|
1897
2027
|
return 1;
|
|
1898
2028
|
}
|
|
1899
2029
|
|
|
2030
|
+
const unparseable = selectUninstrumentableMutateTargets(cwd, patterns);
|
|
2031
|
+
if (unparseable.length > 0) {
|
|
2032
|
+
const listed = unparseable
|
|
2033
|
+
.map(file => ` • ${file} (${path.extname(file)})`)
|
|
2034
|
+
.join("\n");
|
|
2035
|
+
console.error(
|
|
2036
|
+
`❌ ${OUTCOMES.uninstrumentableTarget}\n` +
|
|
2037
|
+
` The mutate patterns from ${declaration.source} select tracked files\n` +
|
|
2038
|
+
" Stryker has no parser for:\n" +
|
|
2039
|
+
`${listed}\n` +
|
|
2040
|
+
" Stryker does not skip these. It aborts the whole run with\n" +
|
|
2041
|
+
' "No parser registered for <ext>!", so ONE such entry takes the score\n' +
|
|
2042
|
+
" of EVERY other guard in the list with it.\n" +
|
|
2043
|
+
" No configuration makes them produce mutants: the instrumenter is\n" +
|
|
2044
|
+
" per-language and these languages have none. Remove them from `mutate`.\n" +
|
|
2045
|
+
" A shell guard's bite is evidenced by a driving test that runs the\n" +
|
|
2046
|
+
" script against a payload table and asserts the blocked/allowed\n" +
|
|
2047
|
+
" verdict — never by this gate."
|
|
2048
|
+
);
|
|
2049
|
+
return 1;
|
|
2050
|
+
}
|
|
2051
|
+
|
|
1900
2052
|
if (argv.includes(WHOLE_LIST_FLAG)) {
|
|
1901
2053
|
console.log(
|
|
1902
2054
|
`🧬 ${OUTCOMES.wholeList} — Stryker over every pattern in ` +
|
|
@@ -1938,6 +2090,29 @@ export const runGate = (cwd = process.cwd(), argv = []) => {
|
|
|
1938
2090
|
return 1;
|
|
1939
2091
|
}
|
|
1940
2092
|
|
|
2093
|
+
if (scope.selected.length === 0 && scope.uninstrumentable.length > 0) {
|
|
2094
|
+
// Exit 0, like `nothing-to-mutate`, because nothing here is a test failure
|
|
2095
|
+
// — but under its own marker, because the claim is the opposite one. That
|
|
2096
|
+
// branch says nothing this gate cares about changed. This one says a guard
|
|
2097
|
+
// changed and this gate is structurally blind to it, so the absence of red
|
|
2098
|
+
// is a property of the toolchain rather than of the tests.
|
|
2099
|
+
const blind = scope.uninstrumentable.map(file => ` • ${file}`).join("\n");
|
|
2100
|
+
console.log(
|
|
2101
|
+
`⚪ ${OUTCOMES.uninstrumentableLanguage}\n` +
|
|
2102
|
+
` ${scope.changed} file(s) changed vs ${since}; 0 of them are mutate targets\n` +
|
|
2103
|
+
` under the patterns from ${declaration.source} — but ${scope.uninstrumentable.length} of them\n` +
|
|
2104
|
+
` ${scope.uninstrumentable.length === 1 ? "is" : "are"} in a language Stryker cannot instrument in ANY configuration:\n` +
|
|
2105
|
+
`${blind}\n` +
|
|
2106
|
+
' This is NOT "nothing relevant changed". NO mutant COULD be generated\n' +
|
|
2107
|
+
" for these files, so this gate is silent about them by construction —\n" +
|
|
2108
|
+
" adding them to `mutate` would abort the run, not measure them.\n" +
|
|
2109
|
+
" Their only evidence is a driving test that runs the script against a\n" +
|
|
2110
|
+
" payload table and asserts the blocked/allowed verdict, with a control\n" +
|
|
2111
|
+
" on both sides. Check that one exists; nothing here did."
|
|
2112
|
+
);
|
|
2113
|
+
return 0;
|
|
2114
|
+
}
|
|
2115
|
+
|
|
1941
2116
|
if (scope.selected.length === 0) {
|
|
1942
2117
|
console.log(
|
|
1943
2118
|
`⚪ ${OUTCOMES.nothingToMutate}\n` +
|