@codyswann/lisa 4.79.2 → 4.80.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 (91) hide show
  1. package/all/copy-overwrite/scripts/lib/npm-update-runtime-archive.mjs +5 -1
  2. package/all/copy-overwrite/scripts/lib/npm-update-worker-lifecycle.mjs +7 -9
  3. package/all/copy-overwrite/scripts/lisa-gates.mjs +101 -34
  4. package/all/copy-overwrite/scripts/npm-updater-helper-graph.json +3 -3
  5. package/dist/cli/doctor-skip-jobs-migration.d.ts.map +1 -1
  6. package/dist/cli/doctor-skip-jobs-migration.js +9 -0
  7. package/dist/cli/doctor-skip-jobs-migration.js.map +1 -1
  8. package/dist/cli/ui-ci-quality-jobs-compute.d.ts.map +1 -1
  9. package/dist/cli/ui-ci-quality-jobs-compute.js +48 -13
  10. package/dist/cli/ui-ci-quality-jobs-compute.js.map +1 -1
  11. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  12. package/dist/core/lisa-owned-hash-ledger.js +20 -0
  13. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  14. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  15. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  16. package/dist/core/upstream-evidence-manifest.js +12 -7
  17. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  18. package/package.json +4 -4
  19. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  20. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  21. package/plugins/lisa-agy/plugin.json +1 -1
  22. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  24. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  25. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  26. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  27. package/plugins/lisa-cdk-devin/.claude-plugin/plugin.json +1 -1
  28. package/plugins/lisa-cdk-devin/.devin-plugin/plugin.json +1 -1
  29. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  31. package/plugins/lisa-devin/.claude-plugin/plugin.json +1 -1
  32. package/plugins/lisa-devin/.devin-plugin/plugin.json +1 -1
  33. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  34. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  35. package/plugins/lisa-expo-agy/plugin.json +1 -1
  36. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-expo-devin/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa-expo-devin/.devin-plugin/plugin.json +1 -1
  40. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  42. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  43. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-harper-fabric-devin/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-harper-fabric-devin/.devin-plugin/plugin.json +1 -1
  47. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  49. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  50. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-nestjs-devin/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-nestjs-devin/.devin-plugin/plugin.json +1 -1
  54. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  56. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  57. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-openclaw-devin/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-openclaw-devin/.devin-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-phaser-devin/.claude-plugin/plugin.json +1 -1
  67. package/plugins/lisa-phaser-devin/.devin-plugin/plugin.json +1 -1
  68. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  69. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  70. package/plugins/lisa-rails-agy/plugin.json +1 -1
  71. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  72. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  73. package/plugins/lisa-rails-devin/.claude-plugin/plugin.json +1 -1
  74. package/plugins/lisa-rails-devin/.devin-plugin/plugin.json +1 -1
  75. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  76. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  77. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  78. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  79. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  80. package/plugins/lisa-typescript-devin/.claude-plugin/plugin.json +1 -1
  81. package/plugins/lisa-typescript-devin/.devin-plugin/plugin.json +1 -1
  82. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  83. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  84. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  85. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  86. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  87. package/plugins/lisa-wiki-devin/.claude-plugin/plugin.json +1 -1
  88. package/plugins/lisa-wiki-devin/.devin-plugin/plugin.json +1 -1
  89. package/scripts/check-required-check-promotions.mjs +110 -3
  90. package/scripts/two-channel-couplings.json +1 -1
  91. package/typescript/copy-overwrite/.prettierignore +114 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.79.2",
3
+ "version": "4.80.1",
4
4
  "description": "Distributable LLM Wiki kernel — ingest, query, lint, and maintain a git-native markdown knowledge base across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.79.2",
3
+ "version": "4.80.1",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.79.2",
3
+ "version": "4.80.1",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.79.2",
3
+ "version": "4.80.1",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.79.2",
3
+ "version": "4.80.1",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.79.2",
3
+ "version": "4.80.1",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -139,7 +139,11 @@ import fs from "node:fs";
139
139
  import path from "node:path";
140
140
  import yaml from "js-yaml";
141
141
 
142
- import { readGates } from "../all/copy-overwrite/scripts/lisa-gates.mjs";
142
+ import {
143
+ REGISTRY,
144
+ jobBackedGates,
145
+ readGates,
146
+ } from "../all/copy-overwrite/scripts/lisa-gates.mjs";
143
147
  import { invokedAsScript } from "./lib/invoked-as-script.mjs";
144
148
  import { buildRulesetPayload } from "./lisa-ruleset-payload.mjs";
145
149
 
@@ -363,7 +367,7 @@ export function collectDeclaredContexts(root) {
363
367
  * jobs it declares, and whether it actually runs on every pull request.
364
368
  *
365
369
  * @param {string} absolute - absolute path to the workflow YAML.
366
- * @returns {{ jobs: Map<string, string>, onPullRequest: boolean, pathFilterKeys: string[] } | null}
370
+ * @returns {{ jobs: Map<string, string>, matrixJobs: Set<string>, onPullRequest: boolean, pathFilterKeys: string[] } | null}
367
371
  * the parsed facts, or null when the file is absent or unparseable.
368
372
  */
369
373
  export function readWorkflow(absolute) {
@@ -395,10 +399,13 @@ export function readWorkflow(absolute) {
395
399
  ? ["paths", "paths-ignore"].filter(key => key in pullRequest)
396
400
  : [];
397
401
  const jobs = new Map();
402
+ const matrixJobs = new Set();
398
403
  for (const [id, job] of Object.entries(doc.jobs ?? {})) {
399
404
  jobs.set(id, typeof job?.name === "string" ? job.name : id);
405
+ const matrix = job?.strategy?.matrix;
406
+ if (typeof matrix === "object" && matrix !== null) matrixJobs.add(id);
400
407
  }
401
- return { jobs, onPullRequest, pathFilterKeys };
408
+ return { jobs, matrixJobs, onPullRequest, pathFilterKeys };
402
409
  }
403
410
 
404
411
  /**
@@ -674,9 +681,109 @@ function calledJobProblems(entry, root, calledName) {
674
681
  },
675
682
  ];
676
683
  }
684
+ if (entry.job_id === DECLARED_GATES_JOB) {
685
+ return declaredGateLegProblems(called, entry, calledName, root);
686
+ }
677
687
  return jobNameProblems(called, entry.job_id, calledName, "called");
678
688
  }
679
689
 
690
+ /** The matrix job that posts one context per declared gate leg. */
691
+ const DECLARED_GATES_JOB = "declared_gates";
692
+
693
+ /** The literal `name:` that makes each matrix leg post its gate's label. */
694
+ const MATRIX_LABEL_NAME = "${{ matrix.label }}";
695
+
696
+ /**
697
+ * Verify a context posted by a `declared_gates` matrix leg rather than by a
698
+ * hand-written job (#3052).
699
+ *
700
+ * A leg has no static `name:` to compare — it is named by the registry label
701
+ * of the gate it runs. So the entry names that gate, and the guard checks the
702
+ * three facts that together mean the leg posts this exact string: the matrix
703
+ * job exists and is named by `matrix.label`, the gate's registry label is the
704
+ * context half, and no hand-written job still owns the gate. The last one
705
+ * matters because `momentLegs` emits no leg for a job-backed gate, so a ledger
706
+ * pointing at a leg that cannot exist would vouch for a context nothing posts.
707
+ *
708
+ * @param {{ jobs: Map<string, string>, matrixJobs: Set<string> }} called - parsed called workflow.
709
+ * @param {object} entry - the ledger entry.
710
+ * @param {string} calledName - the expected called-job display name.
711
+ * @param {string} root - absolute repository root.
712
+ * @returns {{ rule: string, detail: string }[]} problems, empty when sound.
713
+ */
714
+ function declaredGateLegProblems(called, entry, calledName, root) {
715
+ // A job named by `matrix.label` with no `strategy.matrix` has no legs, so it
716
+ // posts no context at all — the name alone proves nothing.
717
+ if (
718
+ called.jobs.get(DECLARED_GATES_JOB) !== MATRIX_LABEL_NAME ||
719
+ !called.matrixJobs.has(DECLARED_GATES_JOB)
720
+ ) {
721
+ return [
722
+ {
723
+ rule: "called-job-missing",
724
+ detail: `no '${DECLARED_GATES_JOB}' matrix job named '${MATRIX_LABEL_NAME}' in the called workflow, so no declared gate leg posts '${calledName}'`,
725
+ },
726
+ ];
727
+ }
728
+ const gate = entry.gate;
729
+ const label =
730
+ typeof gate === "string" && Object.hasOwn(REGISTRY, gate)
731
+ ? REGISTRY[gate].label
732
+ : undefined;
733
+ if (label === undefined) {
734
+ return [
735
+ {
736
+ rule: "leg-gate-missing",
737
+ detail: `a '${DECLARED_GATES_JOB}' entry must name the registry gate whose leg posts the context; got ${JSON.stringify(gate)}`,
738
+ },
739
+ ];
740
+ }
741
+ if (label !== calledName) {
742
+ return [
743
+ {
744
+ rule: "called-job-name-mismatch",
745
+ detail: `gate '${gate}' posts '${label}' but the context expects '${calledName}'`,
746
+ },
747
+ ];
748
+ }
749
+ if (jobBackedGates().includes(gate)) {
750
+ return [
751
+ {
752
+ rule: "leg-gate-job-backed",
753
+ detail: `gate '${gate}' is still owned by a hand-written job, so the runner emits no leg for it`,
754
+ },
755
+ ];
756
+ }
757
+ return legDeclarationProblems(root, gate, calledName);
758
+ }
759
+
760
+ /**
761
+ * Whether this repository declares the gate at a level that makes its leg run.
762
+ *
763
+ * A leg exists only for a gate the project DECLARES at pull-request. A ledger
764
+ * entry pointing at a leg for an undeclared gate vouches for a context nothing
765
+ * posts, and a required context that never posts holds every pull request.
766
+ *
767
+ * @param {string} root - absolute repository root.
768
+ * @param {string} gate - the gate whose leg posts the context.
769
+ * @param {string} calledName - the expected called-job display name.
770
+ * @returns {{ rule: string, detail: string }[]} problems, empty when sound.
771
+ */
772
+ function legDeclarationProblems(root, gate, calledName) {
773
+ const declared = readGates(root).gates?.[gate]?.["pull-request"];
774
+ const level =
775
+ typeof declared === "object" && declared !== null
776
+ ? declared.level
777
+ : declared;
778
+ if (level === "required" || level === "optional") return [];
779
+ return [
780
+ {
781
+ rule: "leg-gate-undeclared",
782
+ detail: `gate '${gate}' is not declared at pull-request in .lisa.config.json (found ${JSON.stringify(declared ?? null)}), so no leg posts '${calledName}'`,
783
+ },
784
+ ];
785
+ }
786
+
680
787
  /**
681
788
  * Read the promotion ledger.
682
789
  *
@@ -103,7 +103,7 @@
103
103
  },
104
104
  "inspected": {
105
105
  "workflows": 26,
106
- "steps": 824,
106
+ "steps": 765,
107
107
  "couplings": 36,
108
108
  "inventory": 186
109
109
  },
@@ -109,6 +109,120 @@ scripts/reconcile-nightly-e2e-tracking-cli.mjs
109
109
  scripts/reconcile-nightly-e2e-tracking.mjs
110
110
  scripts/threshold-ratchet-compare.mjs
111
111
  scripts/threshold-ratchet-families.mjs
112
+ #
113
+ # Nested helpers are payload too. Listing only top-level scripts left every
114
+ # scripts/lib/ module exposed: a host whose Prettier resolved a newer minor
115
+ # than Lisa's own reformatted one of them in its pre-commit hook
116
+ # (CodySwannGT/lisa#4450). Still enumerated, for the same reason as above: a
117
+ # scripts/lib/ glob would also stop formatting a host's own helpers.
118
+ scripts/bdd/baseline.mjs
119
+ scripts/bdd/ci-summary.mjs
120
+ scripts/bdd/contract.mjs
121
+ scripts/bdd/discover.mjs
122
+ scripts/bdd/envelope.mjs
123
+ scripts/bdd/markdown-cell.mjs
124
+ scripts/bdd/parse.mjs
125
+ scripts/bdd/projection-model.mjs
126
+ scripts/bdd/projection-ownership.mjs
127
+ scripts/bdd/projection-paths.mjs
128
+ scripts/bdd/projection-render.mjs
129
+ scripts/bdd/projection-write.mjs
130
+ scripts/bdd/projection.mjs
131
+ scripts/bdd/render.mjs
132
+ scripts/bdd/report.mjs
133
+ scripts/bdd/validate.mjs
134
+ scripts/bdd/waivers.mjs
135
+ scripts/lib/automation-provenance-contract.mjs
136
+ scripts/lib/automation-provenance-files.mjs
137
+ scripts/lib/automation-provenance-local.mjs
138
+ scripts/lib/bounded-spawn.mjs
139
+ scripts/lib/gate-failure-diagnosis.mjs
140
+ scripts/lib/github-attestation-provider.mjs
141
+ scripts/lib/github-attestation-recovery.mjs
142
+ scripts/lib/github-attestation-verifier.mjs
143
+ scripts/lib/history-secret-evidence-shape.mjs
144
+ scripts/lib/history-secret-evidence.mjs
145
+ scripts/lib/history-secret-git.mjs
146
+ scripts/lib/history-secret-policy.mjs
147
+ scripts/lib/history-secret-scanner.mjs
148
+ scripts/lib/invoked-as-script.mjs
149
+ scripts/lib/kill-marks.mjs
150
+ scripts/lib/npm-update-allocate.mjs
151
+ scripts/lib/npm-update-authorization.mjs
152
+ scripts/lib/npm-update-broker-client.mjs
153
+ scripts/lib/npm-update-bun.mjs
154
+ scripts/lib/npm-update-cancel-origin.mjs
155
+ scripts/lib/npm-update-cancellation-proof.mjs
156
+ scripts/lib/npm-update-cancellation.mjs
157
+ scripts/lib/npm-update-checkpoint-codec.mjs
158
+ scripts/lib/npm-update-checkpoint.mjs
159
+ scripts/lib/npm-update-classifier-cache.mjs
160
+ scripts/lib/npm-update-contract.mjs
161
+ scripts/lib/npm-update-controller-broker.mjs
162
+ scripts/lib/npm-update-controller-factory.mjs
163
+ scripts/lib/npm-update-controller-recipe.mjs
164
+ scripts/lib/npm-update-execution-adapter.mjs
165
+ scripts/lib/npm-update-gate-hooks.mjs
166
+ scripts/lib/npm-update-gate-install.mjs
167
+ scripts/lib/npm-update-gate-proof.mjs
168
+ scripts/lib/npm-update-gate.mjs
169
+ scripts/lib/npm-update-gh-dispatch.mjs
170
+ scripts/lib/npm-update-gh-grants.mjs
171
+ scripts/lib/npm-update-gh-requests.mjs
172
+ scripts/lib/npm-update-github.mjs
173
+ scripts/lib/npm-update-helper-graph.mjs
174
+ scripts/lib/npm-update-helper-inventory.mjs
175
+ scripts/lib/npm-update-helper.mjs
176
+ scripts/lib/npm-update-hook-installation.mjs
177
+ scripts/lib/npm-update-hook-preload.mjs
178
+ scripts/lib/npm-update-hook-provider.mjs
179
+ scripts/lib/npm-update-hook-read-client.mjs
180
+ scripts/lib/npm-update-hosted-gate.mjs
181
+ scripts/lib/npm-update-hosted-hook.mjs
182
+ scripts/lib/npm-update-hosted-scope.mjs
183
+ scripts/lib/npm-update-invariants.mjs
184
+ scripts/lib/npm-update-isolation.mjs
185
+ scripts/lib/npm-update-leaf-contract.mjs
186
+ scripts/lib/npm-update-leaf.mjs
187
+ scripts/lib/npm-update-native-process.mjs
188
+ scripts/lib/npm-update-npm.mjs
189
+ scripts/lib/npm-update-object.mjs
190
+ scripts/lib/npm-update-orchestrator.mjs
191
+ scripts/lib/npm-update-owner.mjs
192
+ scripts/lib/npm-update-policy.mjs
193
+ scripts/lib/npm-update-prepare.mjs
194
+ scripts/lib/npm-update-process-core.mjs
195
+ scripts/lib/npm-update-process.mjs
196
+ scripts/lib/npm-update-publication.mjs
197
+ scripts/lib/npm-update-publish.mjs
198
+ scripts/lib/npm-update-quality.mjs
199
+ scripts/lib/npm-update-rails-mysql-daemon.mjs
200
+ scripts/lib/npm-update-rails-mysql-resource.mjs
201
+ scripts/lib/npm-update-rails-mysql-storage.mjs
202
+ scripts/lib/npm-update-rails-mysql.mjs
203
+ scripts/lib/npm-update-rails-runtime-contract.mjs
204
+ scripts/lib/npm-update-rails-tool-downloads.mjs
205
+ scripts/lib/npm-update-rails-tool-identity.mjs
206
+ scripts/lib/npm-update-rails-tools.mjs
207
+ scripts/lib/npm-update-recovery.mjs
208
+ scripts/lib/npm-update-runtime-archive.mjs
209
+ scripts/lib/npm-update-runtime-graph.mjs
210
+ scripts/lib/npm-update-runtime-transport.mjs
211
+ scripts/lib/npm-update-runtime.mjs
212
+ scripts/lib/npm-update-supersession.mjs
213
+ scripts/lib/npm-update-tool-launcher.mjs
214
+ scripts/lib/npm-update-worker-environment.mjs
215
+ scripts/lib/npm-update-worker-inspection.mjs
216
+ scripts/lib/npm-update-worker-lifecycle.mjs
217
+ scripts/lib/npm-update-worker-policy.mjs
218
+ scripts/lib/placeholder-expiry.mjs
219
+ scripts/lib/process-tree-runner.mjs
220
+ scripts/lib/reusable-workflow-load-adapter.mjs
221
+ scripts/lib/reusable-workflow-load-failure.mjs
222
+ scripts/lib/reusable-workflow-load-scan.mjs
223
+ scripts/lib/windows-process-job.mjs
224
+ scripts/lib/worktree-dependencies.mjs
225
+ scripts/lisa-hooks/worktree-binding-guard.mjs
112
226
 
113
227
  # Mutation gate sandbox: a full second copy of the tree written by
114
228
  # `test:mutation`. Formatting or linting it reports every source file twice, and