@codyswann/lisa 4.10.13 → 4.10.15

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 (87) hide show
  1. package/all/copy-overwrite/scripts/lisa-hooks/block-no-verify.sh +4 -1
  2. package/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +233 -60
  3. package/dist/codex/scripts/block-no-verify.sh +4 -1
  4. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  5. package/dist/core/lisa-owned-hash-ledger.js +8 -0
  6. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  7. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  8. package/dist/core/upstream-evidence-manifest.js +12 -9
  9. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  10. package/dist/opencode/plugin-templates/block-no-verify.sh +4 -1
  11. package/dist/opencode/plugin-templates/parity-safety-net.sh +233 -60
  12. package/package.json +1 -1
  13. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  14. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  15. package/plugins/lisa/hooks/block-no-verify.agy.sh +4 -1
  16. package/plugins/lisa/hooks/block-no-verify.sh +4 -1
  17. package/plugins/lisa/hooks/parity-safety-net.sh +233 -60
  18. package/plugins/lisa/rules/eager/falsifiable-checks.md +1 -0
  19. package/plugins/lisa/rules/reference/base-rules.md +1 -1
  20. package/plugins/lisa/rules/reference/falsifiable-checks.md +14 -0
  21. package/plugins/lisa-agy/hooks/block-no-verify.agy.sh +4 -1
  22. package/plugins/lisa-agy/hooks/parity-safety-net.sh +233 -60
  23. package/plugins/lisa-agy/plugin.json +1 -1
  24. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  25. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  26. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  27. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  28. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  29. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa-copilot/hooks/block-no-verify.sh +4 -1
  31. package/plugins/lisa-copilot/hooks/parity-safety-net.sh +233 -60
  32. package/plugins/lisa-copilot/rules/eager/falsifiable-checks.md +1 -0
  33. package/plugins/lisa-copilot/rules/reference/base-rules.md +1 -1
  34. package/plugins/lisa-copilot/rules/reference/falsifiable-checks.md +14 -0
  35. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lisa-cursor/hooks/block-no-verify.sh +4 -1
  37. package/plugins/lisa-cursor/hooks/parity-safety-net.sh +233 -60
  38. package/plugins/lisa-cursor/rules/base-rules-reference.mdc +1 -1
  39. package/plugins/lisa-cursor/rules/falsifiable-checks-reference.mdc +14 -0
  40. package/plugins/lisa-cursor/rules/falsifiable-checks.mdc +1 -0
  41. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  43. package/plugins/lisa-expo-agy/plugin.json +1 -1
  44. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  48. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  49. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  53. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  54. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  58. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  59. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  63. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  64. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  67. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  68. package/plugins/lisa-rails-agy/plugin.json +1 -1
  69. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  70. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  71. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  72. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  73. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  74. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  75. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  76. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  77. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  78. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  79. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  80. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  81. package/plugins/src/base/hooks/block-no-verify.agy.sh +4 -1
  82. package/plugins/src/base/hooks/block-no-verify.sh +4 -1
  83. package/plugins/src/base/hooks/parity-safety-net.sh +233 -60
  84. package/plugins/src/base/rules/eager/falsifiable-checks.md +1 -0
  85. package/plugins/src/base/rules/reference/base-rules.md +1 -1
  86. package/plugins/src/base/rules/reference/falsifiable-checks.md +14 -0
  87. 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[]}} Totals and selection.
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` +