@codyswann/lisa 4.12.0 → 4.13.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.
Files changed (94) hide show
  1. package/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +7 -1
  2. package/dist/cli/doctor-two-channel-drift.d.ts +20 -0
  3. package/dist/cli/doctor-two-channel-drift.d.ts.map +1 -0
  4. package/dist/cli/doctor-two-channel-drift.js +190 -0
  5. package/dist/cli/doctor-two-channel-drift.js.map +1 -0
  6. package/dist/cli/doctor-wiki.d.ts +8 -0
  7. package/dist/cli/doctor-wiki.d.ts.map +1 -0
  8. package/dist/cli/doctor-wiki.js +42 -0
  9. package/dist/cli/doctor-wiki.js.map +1 -0
  10. package/dist/cli/doctor.d.ts.map +1 -1
  11. package/dist/cli/doctor.js +7 -30
  12. package/dist/cli/doctor.js.map +1 -1
  13. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  14. package/dist/core/lisa-owned-hash-ledger.js +3 -0
  15. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  16. package/dist/core/two-channel-delivery-scan.d.ts +81 -0
  17. package/dist/core/two-channel-delivery-scan.d.ts.map +1 -0
  18. package/dist/core/two-channel-delivery-scan.js +191 -0
  19. package/dist/core/two-channel-delivery-scan.js.map +1 -0
  20. package/dist/core/two-channel-delivery.d.ts +215 -0
  21. package/dist/core/two-channel-delivery.d.ts.map +1 -0
  22. package/dist/core/two-channel-delivery.js +259 -0
  23. package/dist/core/two-channel-delivery.js.map +1 -0
  24. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  25. package/dist/core/upstream-evidence-manifest.js +18 -5
  26. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  27. package/dist/opencode/plugin-templates/parity-safety-net.sh +7 -1
  28. package/package.json +4 -2
  29. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  31. package/plugins/lisa/hooks/parity-safety-net.sh +7 -1
  32. package/plugins/lisa/rules/eager/falsifiable-checks.md +1 -1
  33. package/plugins/lisa/rules/reference/falsifiable-checks.md +1 -1
  34. package/plugins/lisa-agy/hooks/parity-safety-net.sh +7 -1
  35. package/plugins/lisa-agy/plugin.json +1 -1
  36. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  38. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  39. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-copilot/hooks/parity-safety-net.sh +7 -1
  43. package/plugins/lisa-copilot/rules/eager/falsifiable-checks.md +1 -1
  44. package/plugins/lisa-copilot/rules/reference/falsifiable-checks.md +1 -1
  45. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-cursor/hooks/parity-safety-net.sh +7 -1
  47. package/plugins/lisa-cursor/rules/falsifiable-checks-reference.mdc +1 -1
  48. package/plugins/lisa-cursor/rules/falsifiable-checks.mdc +1 -1
  49. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  51. package/plugins/lisa-expo-agy/plugin.json +1 -1
  52. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  56. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  57. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  61. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  62. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  64. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  66. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  67. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  69. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  70. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  71. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  72. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  73. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  74. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  75. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  76. package/plugins/lisa-rails-agy/plugin.json +1 -1
  77. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  78. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  79. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  80. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  81. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  82. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  83. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  84. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  85. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  86. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  87. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  88. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  89. package/plugins/src/base/hooks/parity-safety-net.sh +7 -1
  90. package/plugins/src/base/rules/eager/falsifiable-checks.md +1 -1
  91. package/plugins/src/base/rules/reference/falsifiable-checks.md +1 -1
  92. package/scripts/generate-two-channel-couplings.ts +427 -0
  93. package/scripts/two-channel-couplings.json +214 -0
  94. package/typescript/copy-overwrite/scripts/lisa-mutation.mjs +19 -20
@@ -0,0 +1,214 @@
1
+ {
2
+ "ratified": {
3
+ "quality.yml::scripts/check-learnings-budget.ts": "Lisa-source-only branch, and the else arm is the delivery. The step tests for BOTH scripts/check-learnings-budget.ts and src/core/learnings-budget-check.ts, which co-exist only in this repository; every consumer takes the else arm and runs the prover as `bunx @codyswann/lisa@<their own declared version>`, which is the PACKAGE channel and not a caller-tree read at all. A trailing `grep -qE \"learnings budget (passed|saturated)|no learnings file\"` then refuses a run that printed no verdict, so the arm cannot pass vacuously. Ratified because the caller-tree read is unreachable outside this repository, not because the gap is tolerated."
4
+ },
5
+ "inspected": {
6
+ "workflows": 23,
7
+ "steps": 633,
8
+ "couplings": 25,
9
+ "inventory": 55
10
+ },
11
+ "counts": {
12
+ "package-backed": 10,
13
+ "apply-lagged": 14,
14
+ "never-delivered": 0,
15
+ "undelivered": 1
16
+ },
17
+ "couplings": [
18
+ {
19
+ "key": "maestro-native-e2e.yml::scripts/classify-maestro-failures.mjs",
20
+ "workflow": "maestro-native-e2e.yml",
21
+ "path": "scripts/classify-maestro-failures.mjs",
22
+ "channel": "apply",
23
+ "verdict": "apply-lagged",
24
+ "remedy": "run-lisa-apply",
25
+ "guarded": false,
26
+ "lanes": [
27
+ "expo/copy-overwrite"
28
+ ],
29
+ "detail": "`maestro-native-e2e.yml` is delivered at `@main`, so this step is live in every consumer on their next run. It reads `scripts/classify-maestro-failures.mjs` from the CALLER's tree with no package-relative candidate, and Lisa delivers that path on the `lisa apply` channel (expo/copy-overwrite). The two halves land in the wrong order: this step is live everywhere immediately, the file it needs is live only where somebody applied. The read is unguarded, so an absent path fails the job loudly. Visible, which makes it the better of the two failures."
30
+ },
31
+ {
32
+ "key": "nightly-e2e-health.yml::scripts/check-nightly-e2e-health.mjs",
33
+ "workflow": "nightly-e2e-health.yml",
34
+ "path": "scripts/check-nightly-e2e-health.mjs",
35
+ "channel": "apply",
36
+ "verdict": "apply-lagged",
37
+ "remedy": "run-lisa-apply",
38
+ "guarded": false,
39
+ "lanes": [
40
+ "typescript/copy-overwrite"
41
+ ],
42
+ "detail": "`nightly-e2e-health.yml` is delivered at `@main`, so this step is live in every consumer on their next run. It reads `scripts/check-nightly-e2e-health.mjs` from the CALLER's tree with no package-relative candidate, and Lisa delivers that path on the `lisa apply` channel (typescript/copy-overwrite). The two halves land in the wrong order: this step is live everywhere immediately, the file it needs is live only where somebody applied. The read is unguarded, so an absent path fails the job loudly. Visible, which makes it the better of the two failures."
43
+ },
44
+ {
45
+ "key": "nightly-e2e-health.yml::scripts/nightly-e2e-suites.schema.json",
46
+ "workflow": "nightly-e2e-health.yml",
47
+ "path": "scripts/nightly-e2e-suites.schema.json",
48
+ "channel": "apply",
49
+ "verdict": "apply-lagged",
50
+ "remedy": "run-lisa-apply",
51
+ "guarded": false,
52
+ "lanes": [
53
+ "typescript/copy-overwrite"
54
+ ],
55
+ "detail": "`nightly-e2e-health.yml` is delivered at `@main`, so this step is live in every consumer on their next run. It reads `scripts/nightly-e2e-suites.schema.json` from the CALLER's tree with no package-relative candidate, and Lisa delivers that path on the `lisa apply` channel (typescript/copy-overwrite). The two halves land in the wrong order: this step is live everywhere immediately, the file it needs is live only where somebody applied. The read is unguarded, so an absent path fails the job loudly. Visible, which makes it the better of the two failures."
56
+ },
57
+ {
58
+ "key": "nightly-e2e-report.yml::scripts/check-nightly-e2e-health.mjs",
59
+ "workflow": "nightly-e2e-report.yml",
60
+ "path": "scripts/check-nightly-e2e-health.mjs",
61
+ "channel": "apply",
62
+ "verdict": "apply-lagged",
63
+ "remedy": "run-lisa-apply",
64
+ "guarded": false,
65
+ "lanes": [
66
+ "typescript/copy-overwrite"
67
+ ],
68
+ "detail": "`nightly-e2e-report.yml` is delivered at `@main`, so this step is live in every consumer on their next run. It reads `scripts/check-nightly-e2e-health.mjs` from the CALLER's tree with no package-relative candidate, and Lisa delivers that path on the `lisa apply` channel (typescript/copy-overwrite). The two halves land in the wrong order: this step is live everywhere immediately, the file it needs is live only where somebody applied. The read is unguarded, so an absent path fails the job loudly. Visible, which makes it the better of the two failures."
69
+ },
70
+ {
71
+ "key": "quality-rails.yml::scripts/check-threshold-ratchet.mjs",
72
+ "workflow": "quality-rails.yml",
73
+ "path": "scripts/check-threshold-ratchet.mjs",
74
+ "channel": "apply",
75
+ "verdict": "apply-lagged",
76
+ "remedy": "run-lisa-apply",
77
+ "guarded": false,
78
+ "lanes": [
79
+ "rails/copy-overwrite",
80
+ "typescript/copy-overwrite"
81
+ ],
82
+ "detail": "`quality-rails.yml` is delivered at `@main`, so this step is live in every consumer on their next run. It reads `scripts/check-threshold-ratchet.mjs` from the CALLER's tree with no package-relative candidate, and Lisa delivers that path on the `lisa apply` channel (rails/copy-overwrite, typescript/copy-overwrite). The two halves land in the wrong order: this step is live everywhere immediately, the file it needs is live only where somebody applied. The read is unguarded, so an absent path fails the job loudly. Visible, which makes it the better of the two failures."
83
+ },
84
+ {
85
+ "key": "quality-rails.yml::scripts/lisa-mutation.sh",
86
+ "workflow": "quality-rails.yml",
87
+ "path": "scripts/lisa-mutation.sh",
88
+ "channel": "apply",
89
+ "verdict": "apply-lagged",
90
+ "remedy": "run-lisa-apply",
91
+ "guarded": true,
92
+ "lanes": [
93
+ "rails/copy-contents"
94
+ ],
95
+ "detail": "`quality-rails.yml` is delivered at `@main`, so this step is live in every consumer on their next run. It reads `scripts/lisa-mutation.sh` from the CALLER's tree with no package-relative candidate, and Lisa delivers that path on the `lisa apply` channel (rails/copy-contents). The two halves land in the wrong order: this step is live everywhere immediately, the file it needs is live only where somebody applied. The read is guarded by an existence test, so an absent path SKIPS rather than fails — the step posts no context, and an absent required context is not a red one. Nothing reads as broken."
96
+ },
97
+ {
98
+ "key": "quality-rails.yml::scripts/lisa-work-item.mjs",
99
+ "workflow": "quality-rails.yml",
100
+ "path": "scripts/lisa-work-item.mjs",
101
+ "channel": "apply",
102
+ "verdict": "apply-lagged",
103
+ "remedy": "run-lisa-apply",
104
+ "guarded": true,
105
+ "lanes": [
106
+ "all/copy-overwrite"
107
+ ],
108
+ "detail": "`quality-rails.yml` is delivered at `@main`, so this step is live in every consumer on their next run. It reads `scripts/lisa-work-item.mjs` from the CALLER's tree with no package-relative candidate, and Lisa delivers that path on the `lisa apply` channel (all/copy-overwrite). The two halves land in the wrong order: this step is live everywhere immediately, the file it needs is live only where somebody applied. The read is guarded by an existence test, so an absent path SKIPS rather than fails — the step posts no context, and an absent required context is not a red one. Nothing reads as broken."
109
+ },
110
+ {
111
+ "key": "quality.yml::scripts/check-e2e-coverage.mjs",
112
+ "workflow": "quality.yml",
113
+ "path": "scripts/check-e2e-coverage.mjs",
114
+ "channel": "apply",
115
+ "verdict": "apply-lagged",
116
+ "remedy": "run-lisa-apply",
117
+ "guarded": true,
118
+ "lanes": [
119
+ "expo/copy-overwrite"
120
+ ],
121
+ "detail": "`quality.yml` is delivered at `@main`, so this step is live in every consumer on their next run. It reads `scripts/check-e2e-coverage.mjs` from the CALLER's tree with no package-relative candidate, and Lisa delivers that path on the `lisa apply` channel (expo/copy-overwrite). The two halves land in the wrong order: this step is live everywhere immediately, the file it needs is live only where somebody applied. The read is guarded by an existence test, so an absent path SKIPS rather than fails — the step posts no context, and an absent required context is not a red one. Nothing reads as broken."
122
+ },
123
+ {
124
+ "key": "quality.yml::scripts/check-learnings-budget.ts",
125
+ "workflow": "quality.yml",
126
+ "path": "scripts/check-learnings-budget.ts",
127
+ "channel": "undelivered",
128
+ "verdict": "undelivered",
129
+ "remedy": "author-the-artifact",
130
+ "guarded": true,
131
+ "lanes": [],
132
+ "detail": "`quality.yml` is delivered at `@main`, so this step is live in every consumer on their next run. It reads `scripts/check-learnings-budget.ts` from the CALLER's tree, and Lisa ships no such path in any delivery lane — no apply and no bump ever produces it. Either the consumer authors it or this step proves nothing wherever it is absent. The read is guarded by an existence test, so an absent path SKIPS rather than fails — the step posts no context, and an absent required context is not a red one. Nothing reads as broken."
133
+ },
134
+ {
135
+ "key": "quality.yml::scripts/check-skipped-required-checks.mjs",
136
+ "workflow": "quality.yml",
137
+ "path": "scripts/check-skipped-required-checks.mjs",
138
+ "channel": "apply",
139
+ "verdict": "apply-lagged",
140
+ "remedy": "run-lisa-apply",
141
+ "guarded": false,
142
+ "lanes": [
143
+ "typescript/copy-overwrite"
144
+ ],
145
+ "detail": "`quality.yml` is delivered at `@main`, so this step is live in every consumer on their next run. It reads `scripts/check-skipped-required-checks.mjs` from the CALLER's tree with no package-relative candidate, and Lisa delivers that path on the `lisa apply` channel (typescript/copy-overwrite). The two halves land in the wrong order: this step is live everywhere immediately, the file it needs is live only where somebody applied. The read is unguarded, so an absent path fails the job loudly. Visible, which makes it the better of the two failures."
146
+ },
147
+ {
148
+ "key": "quality.yml::scripts/check-state-classification.mjs",
149
+ "workflow": "quality.yml",
150
+ "path": "scripts/check-state-classification.mjs",
151
+ "channel": "apply",
152
+ "verdict": "apply-lagged",
153
+ "remedy": "run-lisa-apply",
154
+ "guarded": true,
155
+ "lanes": [
156
+ "all/copy-overwrite"
157
+ ],
158
+ "detail": "`quality.yml` is delivered at `@main`, so this step is live in every consumer on their next run. It reads `scripts/check-state-classification.mjs` from the CALLER's tree with no package-relative candidate, and Lisa delivers that path on the `lisa apply` channel (all/copy-overwrite). The two halves land in the wrong order: this step is live everywhere immediately, the file it needs is live only where somebody applied. The read is guarded by an existence test, so an absent path SKIPS rather than fails — the step posts no context, and an absent required context is not a red one. Nothing reads as broken."
159
+ },
160
+ {
161
+ "key": "quality.yml::scripts/check-threshold-ratchet.mjs",
162
+ "workflow": "quality.yml",
163
+ "path": "scripts/check-threshold-ratchet.mjs",
164
+ "channel": "apply",
165
+ "verdict": "apply-lagged",
166
+ "remedy": "run-lisa-apply",
167
+ "guarded": false,
168
+ "lanes": [
169
+ "rails/copy-overwrite",
170
+ "typescript/copy-overwrite"
171
+ ],
172
+ "detail": "`quality.yml` is delivered at `@main`, so this step is live in every consumer on their next run. It reads `scripts/check-threshold-ratchet.mjs` from the CALLER's tree with no package-relative candidate, and Lisa delivers that path on the `lisa apply` channel (rails/copy-overwrite, typescript/copy-overwrite). The two halves land in the wrong order: this step is live everywhere immediately, the file it needs is live only where somebody applied. The read is unguarded, so an absent path fails the job loudly. Visible, which makes it the better of the two failures."
173
+ },
174
+ {
175
+ "key": "quality.yml::scripts/check-verification-coverage.mjs",
176
+ "workflow": "quality.yml",
177
+ "path": "scripts/check-verification-coverage.mjs",
178
+ "channel": "apply",
179
+ "verdict": "apply-lagged",
180
+ "remedy": "run-lisa-apply",
181
+ "guarded": false,
182
+ "lanes": [
183
+ "typescript/copy-overwrite"
184
+ ],
185
+ "detail": "`quality.yml` is delivered at `@main`, so this step is live in every consumer on their next run. It reads `scripts/check-verification-coverage.mjs` from the CALLER's tree with no package-relative candidate, and Lisa delivers that path on the `lisa apply` channel (typescript/copy-overwrite). The two halves land in the wrong order: this step is live everywhere immediately, the file it needs is live only where somebody applied. The read is unguarded, so an absent path fails the job loudly. Visible, which makes it the better of the two failures."
186
+ },
187
+ {
188
+ "key": "quality.yml::scripts/lisa-floor-collisions.mjs",
189
+ "workflow": "quality.yml",
190
+ "path": "scripts/lisa-floor-collisions.mjs",
191
+ "channel": "apply",
192
+ "verdict": "apply-lagged",
193
+ "remedy": "run-lisa-apply",
194
+ "guarded": false,
195
+ "lanes": [
196
+ "all/copy-overwrite"
197
+ ],
198
+ "detail": "`quality.yml` is delivered at `@main`, so this step is live in every consumer on their next run. It reads `scripts/lisa-floor-collisions.mjs` from the CALLER's tree with no package-relative candidate, and Lisa delivers that path on the `lisa apply` channel (all/copy-overwrite). The two halves land in the wrong order: this step is live everywhere immediately, the file it needs is live only where somebody applied. The read is unguarded, so an absent path fails the job loudly. Visible, which makes it the better of the two failures."
199
+ },
200
+ {
201
+ "key": "quality.yml::scripts/lisa-work-item.mjs",
202
+ "workflow": "quality.yml",
203
+ "path": "scripts/lisa-work-item.mjs",
204
+ "channel": "apply",
205
+ "verdict": "apply-lagged",
206
+ "remedy": "run-lisa-apply",
207
+ "guarded": true,
208
+ "lanes": [
209
+ "all/copy-overwrite"
210
+ ],
211
+ "detail": "`quality.yml` is delivered at `@main`, so this step is live in every consumer on their next run. It reads `scripts/lisa-work-item.mjs` from the CALLER's tree with no package-relative candidate, and Lisa delivers that path on the `lisa apply` channel (all/copy-overwrite). The two halves land in the wrong order: this step is live everywhere immediately, the file it needs is live only where somebody applied. The read is guarded by an existence test, so an absent path SKIPS rather than fails — the step posts no context, and an absent required context is not a red one. Nothing reads as broken."
212
+ }
213
+ ]
214
+ }
@@ -347,7 +347,6 @@ export const STRYKER_PARSED_EXTENSIONS = Object.freeze([
347
347
  ".jsx",
348
348
  ".cjs",
349
349
  ".mjs",
350
- ".cjsx",
351
350
  ".ts",
352
351
  ".tsx",
353
352
  ".cts",
@@ -387,11 +386,14 @@ export const UNINSTRUMENTABLE_GUARD_EXTENSIONS = Object.freeze([
387
386
  * @returns {string[]} Those whose extension no mutation tool here reaches.
388
387
  */
389
388
  export const uninstrumentableGuards = files =>
390
- files.filter(file =>
391
- UNINSTRUMENTABLE_GUARD_EXTENSIONS.includes(
392
- path.extname(normalizePath(file)).toLowerCase()
393
- )
394
- );
389
+ files.filter(file => {
390
+ const normalized = normalizePath(file);
391
+ const extension = path.extname(normalized).toLowerCase();
392
+ return (
393
+ UNINSTRUMENTABLE_GUARD_EXTENSIONS.includes(extension) ||
394
+ (extension === "" && normalized.startsWith(".husky/"))
395
+ );
396
+ });
395
397
 
396
398
  /**
397
399
  * Whether Stryker can parse a file at all, by extension.
@@ -1313,7 +1315,7 @@ export const selectChangedTargets = (cwd, base, patterns) => {
1313
1315
  const changed = git(cwd, [
1314
1316
  "diff",
1315
1317
  "--name-only",
1316
- "--diff-filter=ACMR",
1318
+ "--diff-filter=ACMRD",
1317
1319
  `${base}...HEAD`,
1318
1320
  ])
1319
1321
  .split("\n")
@@ -1326,9 +1328,7 @@ export const selectChangedTargets = (cwd, base, patterns) => {
1326
1328
  .filter(file => fs.existsSync(path.join(cwd, file))),
1327
1329
  // Reported from the diff, not from the selection, because by construction
1328
1330
  // 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
- ),
1331
+ uninstrumentable: uninstrumentableGuards(changed),
1332
1332
  };
1333
1333
  };
1334
1334
 
@@ -2090,27 +2090,26 @@ export const runGate = (cwd = process.cwd(), argv = []) => {
2090
2090
  return 1;
2091
2091
  }
2092
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.
2093
+ if (scope.uninstrumentable.length > 0) {
2094
+ // Always name the blind part of the diff, even when another selected target
2095
+ // lets Stryker continue. A mixed run measures only the selected files; its
2096
+ // score is not evidence about a shell guard changed beside them.
2099
2097
  const blind = scope.uninstrumentable.map(file => ` • ${file}`).join("\n");
2100
2098
  console.log(
2101
2099
  `⚪ ${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` +
2100
+ ` ${scope.changed} file(s) changed vs ${since}; ${scope.selected.length} of them are mutate targets\n` +
2101
+ ` under the patterns from ${declaration.source}, and ${scope.uninstrumentable.length} of them\n` +
2104
2102
  ` ${scope.uninstrumentable.length === 1 ? "is" : "are"} in a language Stryker cannot instrument in ANY configuration:\n` +
2105
2103
  `${blind}\n` +
2106
- ' This is NOT "nothing relevant changed". NO mutant COULD be generated\n' +
2104
+ " NO mutant COULD be generated for these files. The mutation result\n" +
2105
+ " below, if one runs, covers only the selected targets and says nothing\n" +
2107
2106
  " for these files, so this gate is silent about them by construction —\n" +
2108
2107
  " adding them to `mutate` would abort the run, not measure them.\n" +
2109
2108
  " Their only evidence is a driving test that runs the script against a\n" +
2110
2109
  " payload table and asserts the blocked/allowed verdict, with a control\n" +
2111
2110
  " on both sides. Check that one exists; nothing here did."
2112
2111
  );
2113
- return 0;
2112
+ if (scope.selected.length === 0) return 0;
2114
2113
  }
2115
2114
 
2116
2115
  if (scope.selected.length === 0) {