@codyswann/lisa 3.33.4 → 3.33.6

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 (108) hide show
  1. package/all/copy-overwrite/scripts/check-state-classification.mjs +18 -4
  2. package/all/copy-overwrite/scripts/lisa-gates.mjs +47 -46
  3. package/all/copy-overwrite/scripts/lisa-run-gates.mjs +3 -2
  4. package/dist/configs/eslint/base.d.ts +21 -0
  5. package/dist/configs/eslint/base.d.ts.map +1 -1
  6. package/dist/configs/eslint/base.js +78 -2
  7. package/dist/configs/eslint/base.js.map +1 -1
  8. package/dist/configs/eslint/cdk.d.ts.map +1 -1
  9. package/dist/configs/eslint/cdk.js +5 -1
  10. package/dist/configs/eslint/cdk.js.map +1 -1
  11. package/dist/configs/eslint/expo.d.ts.map +1 -1
  12. package/dist/configs/eslint/expo.js +5 -1
  13. package/dist/configs/eslint/expo.js.map +1 -1
  14. package/dist/configs/eslint/harper-fabric.d.ts.map +1 -1
  15. package/dist/configs/eslint/harper-fabric.js +6 -1
  16. package/dist/configs/eslint/harper-fabric.js.map +1 -1
  17. package/dist/configs/eslint/nestjs.d.ts.map +1 -1
  18. package/dist/configs/eslint/nestjs.js +5 -1
  19. package/dist/configs/eslint/nestjs.js.map +1 -1
  20. package/dist/configs/eslint/phaser.d.ts.map +1 -1
  21. package/dist/configs/eslint/phaser.js +6 -1
  22. package/dist/configs/eslint/phaser.js.map +1 -1
  23. package/dist/configs/eslint/typescript.d.ts +2 -2
  24. package/dist/configs/eslint/typescript.d.ts.map +1 -1
  25. package/dist/configs/eslint/typescript.js +6 -2
  26. package/dist/configs/eslint/typescript.js.map +1 -1
  27. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  28. package/dist/core/lisa-owned-hash-ledger.js +11 -0
  29. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  30. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  31. package/dist/core/upstream-evidence-manifest.js +29 -28
  32. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  33. package/expo/copy-overwrite/scripts/bdd/baseline.mjs +11 -4
  34. package/expo/copy-overwrite/scripts/bdd/discover.mjs +12 -6
  35. package/expo/copy-overwrite/scripts/bdd/waivers.mjs +7 -4
  36. package/expo/copy-overwrite/scripts/bdd-matrix.mjs +1 -0
  37. package/expo/copy-overwrite/scripts/check-bdd-coverage.mjs +26 -17
  38. package/expo/copy-overwrite/scripts/check-e2e-coverage.mjs +12 -8
  39. package/package.json +1 -1
  40. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  42. package/plugins/lisa-agy/plugin.json +1 -1
  43. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  45. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  46. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  52. package/plugins/lisa-expo-agy/plugin.json +1 -1
  53. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  57. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  58. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  62. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  63. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  64. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  67. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  68. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  69. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  70. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  71. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  72. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  73. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  74. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  75. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  76. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  77. package/plugins/lisa-rails-agy/plugin.json +1 -1
  78. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  79. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  80. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  81. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  82. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  83. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  84. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  85. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  86. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  87. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  88. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  89. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  90. package/scripts/check-conflict-markers.mjs +1 -1
  91. package/scripts/check-required-check-promotions.mjs +8 -4
  92. package/scripts/detect-stale-workflow-inputs.mjs +1 -3
  93. package/scripts/fix-namespace-test-assertions.mjs +1 -1
  94. package/scripts/generate-agy-plugin-artifacts.mjs +12 -7
  95. package/scripts/generate-codex-plugin-artifacts.mjs +14 -9
  96. package/scripts/generate-copilot-plugin-artifacts.mjs +1 -1
  97. package/scripts/generate-cursor-plugin-artifacts.mjs +2 -2
  98. package/scripts/generate-upstream-evidence-manifest.mjs +1 -1
  99. package/scripts/lisa-assert-eas-profile.mjs +3 -3
  100. package/scripts/plugin-parity-drift.mjs +29 -13
  101. package/scripts/plugin-routing-validate.mjs +8 -2
  102. package/scripts/update-node-version.ts +7 -3
  103. package/scripts/verify-health-agentic-built.mjs +27 -20
  104. package/scripts/verify-health-consumer-built.mjs +7 -3
  105. package/scripts/verify-health-deterministic-built.mjs +24 -19
  106. package/typescript/copy-overwrite/eslint.ignore.config.json +0 -2
  107. package/typescript/copy-overwrite/scripts/check-nightly-e2e-health.mjs +9 -4
  108. package/typescript/copy-overwrite/scripts/lisa-mutation.mjs +1 -3
@@ -17,6 +17,14 @@ import {
17
17
  import { tmpdir } from "node:os";
18
18
  import path from "node:path";
19
19
 
20
+ // Literals named once — each was repeated enough times that a typo in one
21
+ // copy would diverge silently.
22
+ const TEMPLATES_MANAGED = "templates.managed";
23
+ const AGENTIC_CI_SKIP_LINT = "agentic.ci.skip.lint";
24
+ const ESLINT_CONFIG_TS = "eslint.config.ts";
25
+ const AGENTIC_INTENTIONAL_DRIFT = "agentic.intentional-drift";
26
+ const HARPER_APP = "harper-app";
27
+
20
28
  let assertions = 0;
21
29
  const check = (condition, message) => {
22
30
  assertions += 1;
@@ -241,8 +249,7 @@ function artifactEvaluator(request, signal) {
241
249
  const mutationDisabled =
242
250
  request.config.quality.mutation.gate.enabled === false;
243
251
  const managedDrift = request.deterministicFindings.find(
244
- finding =>
245
- finding.check === "templates.managed" && finding.status === "fail"
252
+ finding => finding.check === TEMPLATES_MANAGED && finding.status === "fail"
246
253
  );
247
254
 
248
255
  if (mutationDisabled) {
@@ -268,19 +275,19 @@ function artifactEvaluator(request, signal) {
268
275
  !/\b(?:reason|justification)\b/iu.test(workflow.content)
269
276
  ) {
270
277
  judgments.push({
271
- check: "agentic.ci.skip.lint",
278
+ check: AGENTIC_CI_SKIP_LINT,
272
279
  reason: `${workflow.path} skips lint without a recorded justification.`,
273
280
  });
274
281
  }
275
282
  }
276
283
  if (
277
- managedDrift?.reason.includes("eslint.config.ts") === true &&
284
+ managedDrift?.reason.includes(ESLINT_CONFIG_TS) === true &&
278
285
  /\b(?:reason|justification)\b/iu.test(override?.content ?? "") &&
279
286
  managedArtifact?.content.includes("no-explicit-any") === true &&
280
287
  /["']off["']/u.test(managedArtifact.content)
281
288
  ) {
282
289
  judgments.push({
283
- check: "agentic.intentional-drift",
290
+ check: AGENTIC_INTENTIONAL_DRIFT,
284
291
  reason:
285
292
  "The bounded managed ESLint drift disables no-explicit-any despite the recorded temporary justification.",
286
293
  });
@@ -316,13 +323,13 @@ try {
316
323
  2
317
324
  )}\n`
318
325
  );
319
- await mkdir(path.join(project, "harper-app"), { recursive: true });
326
+ await mkdir(path.join(project, HARPER_APP), { recursive: true });
320
327
  await writeFile(
321
- path.join(project, "harper-app", "config.yaml"),
328
+ path.join(project, HARPER_APP, "config.yaml"),
322
329
  "graphqlSchema: ./schema.graphql\njsResource: true\nstatic: ./web\n"
323
330
  );
324
331
  await writeFile(
325
- path.join(project, "harper-app", "schema.graphql"),
332
+ path.join(project, HARPER_APP, "schema.graphql"),
326
333
  "type Query { health: Boolean! }\n"
327
334
  );
328
335
  git("init", "--initial-branch=main");
@@ -525,7 +532,7 @@ try {
525
532
  check(
526
533
  unreasonedSkip.findings.some(
527
534
  finding =>
528
- finding.check === "agentic.ci.skip.lint" &&
535
+ finding.check === AGENTIC_CI_SKIP_LINT &&
529
536
  finding.reason.includes("ci.yml")
530
537
  ),
531
538
  "unreasoned skipped job produces a named warning"
@@ -545,22 +552,22 @@ try {
545
552
  );
546
553
  check(
547
554
  !reasonedSkip.findings.some(
548
- finding => finding.check === "agentic.ci.skip.lint"
555
+ finding => finding.check === AGENTIC_CI_SKIP_LINT
549
556
  ),
550
557
  "recorded skip justification clears the job warning"
551
558
  );
552
559
  printStateDump("skipped-job-reason-clears", {
553
560
  undocumented: unreasonedSkip.findings
554
- .filter(finding => finding.check === "agentic.ci.skip.lint")
561
+ .filter(finding => finding.check === AGENTIC_CI_SKIP_LINT)
555
562
  .map(findingState),
556
563
  documented: reasonedSkip.findings
557
- .filter(finding => finding.check === "agentic.ci.skip.lint")
564
+ .filter(finding => finding.check === AGENTIC_CI_SKIP_LINT)
558
565
  .map(findingState),
559
566
  });
560
567
  console.log("[EVIDENCE: state-dump: skipped-job-reason-clears]");
561
568
  await writeFile(ciPath, ciOriginal);
562
569
 
563
- const managedEslintPath = path.join(project, "eslint.config.ts");
570
+ const managedEslintPath = path.join(project, ESLINT_CONFIG_TS);
564
571
  const managedEslintOriginal = await readFile(managedEslintPath);
565
572
  const managedJustification =
566
573
  '// justification: temporary vendor migration\nexport default [{ rules: { "@typescript-eslint/no-explicit-any": "off" } }];\n';
@@ -599,7 +606,7 @@ try {
599
606
  );
600
607
  equal(
601
608
  observedManagedArtifacts.map(artifact => artifact?.path),
602
- ["eslint.config.ts", "eslint.config.ts"],
609
+ [ESLINT_CONFIG_TS, ESLINT_CONFIG_TS],
603
610
  "both drift judgments inspect the bounded managed ESLint artifact"
604
611
  );
605
612
  equal(
@@ -613,18 +620,18 @@ try {
613
620
  check(
614
621
  benignDrift.findings.some(
615
622
  finding =>
616
- finding.check === "templates.managed" && finding.status === "fail"
623
+ finding.check === TEMPLATES_MANAGED && finding.status === "fail"
617
624
  ),
618
625
  "benign agentic judgment cannot erase deterministic managed drift"
619
626
  );
620
627
  check(
621
628
  !benignDrift.findings.some(
622
- finding => finding.check === "agentic.intentional-drift"
629
+ finding => finding.check === AGENTIC_INTENTIONAL_DRIFT
623
630
  ),
624
631
  "benign managed content produces no drift judgment"
625
632
  );
626
633
  const suspiciousDriftWarning = suspiciousDrift.findings.find(
627
- finding => finding.check === "agentic.intentional-drift"
634
+ finding => finding.check === AGENTIC_INTENTIONAL_DRIFT
628
635
  );
629
636
  equal(
630
637
  suspiciousDriftWarning?.status,
@@ -643,7 +650,7 @@ try {
643
650
  .update(benignManagedContent)
644
651
  .digest("hex"),
645
652
  warnings: benignDrift.findings
646
- .filter(finding => finding.check === "agentic.intentional-drift")
653
+ .filter(finding => finding.check === AGENTIC_INTENTIONAL_DRIFT)
647
654
  .map(findingState),
648
655
  },
649
656
  suspicious: {
@@ -651,7 +658,7 @@ try {
651
658
  .update(suspiciousManagedContent)
652
659
  .digest("hex"),
653
660
  warnings: suspiciousDrift.findings
654
- .filter(finding => finding.check === "agentic.intentional-drift")
661
+ .filter(finding => finding.check === AGENTIC_INTENTIONAL_DRIFT)
655
662
  .map(findingState),
656
663
  },
657
664
  });
@@ -678,7 +685,7 @@ try {
678
685
  name: "malformed",
679
686
  evaluator: async () => ({
680
687
  status: "completed",
681
- judgments: [{ check: "templates.managed", reason: "collision" }],
688
+ judgments: [{ check: TEMPLATES_MANAGED, reason: "collision" }],
682
689
  }),
683
690
  timeoutMs: 1_000,
684
691
  },
@@ -14,6 +14,10 @@ import {
14
14
  import { tmpdir } from "node:os";
15
15
  import path from "node:path";
16
16
 
17
+ // Literals named once — each was repeated enough times that a typo in one
18
+ // copy would diverge silently.
19
+ const LATEST_JSON = "latest.json";
20
+
17
21
  let assertions = 0;
18
22
  const check = (condition, message) => {
19
23
  assertions += 1;
@@ -64,7 +68,7 @@ function runCli(project, argv, input) {
64
68
 
65
69
  /** Return whether the canonical result exists without throwing on absence. */
66
70
  async function resultExists(project) {
67
- return lstat(path.join(project, ".lisa", "health", "latest.json"))
71
+ return lstat(path.join(project, ".lisa", "health", LATEST_JSON))
68
72
  .then(() => true)
69
73
  .catch(() => false);
70
74
  }
@@ -91,14 +95,14 @@ async function prepare(project) {
91
95
 
92
96
  /** Validate one final CLI result against its exact persisted representation. */
93
97
  async function validateFinal(project, stdout, expectedMode) {
94
- const resultPath = path.join(project, ".lisa", "health", "latest.json");
98
+ const resultPath = path.join(project, ".lisa", "health", LATEST_JSON);
95
99
  const persisted = await readFile(resultPath, "utf8");
96
100
  equal(stdout, persisted, `${expectedMode} stdout equals persisted bytes`);
97
101
  const result = JSON.parse(stdout);
98
102
  equal(result.mode, expectedMode, `final result is ${expectedMode}`);
99
103
  equal(
100
104
  (await readdir(path.dirname(resultPath))).sort(),
101
- ["latest.json"],
105
+ [LATEST_JSON],
102
106
  "one final result remains with no temporary or lock artifact"
103
107
  );
104
108
  return result;
@@ -18,6 +18,14 @@ import {
18
18
  import { tmpdir } from "node:os";
19
19
  import path from "node:path";
20
20
 
21
+ // Literals named once — each was repeated enough times that a typo in one
22
+ // copy would diverge silently.
23
+ const PACKAGE_JSON = "package.json";
24
+ const LISA_CONFIG_JSON = ".lisa.config.json";
25
+ const HARPER_APP = "harper-app";
26
+ const TEMPLATES_MANAGED = "templates.managed";
27
+ const PRETTIERIGNORE = ".prettierignore";
28
+
21
29
  let assertions = 0;
22
30
  const check = (condition, message) => {
23
31
  assertions += 1;
@@ -149,7 +157,7 @@ try {
149
157
  await mkdir(isolatedBin, { recursive: true });
150
158
  await symlink(gitExecutable, path.join(isolatedBin, "git"));
151
159
  await writeFile(
152
- path.join(project, "package.json"),
160
+ path.join(project, PACKAGE_JSON),
153
161
  `${JSON.stringify(
154
162
  {
155
163
  name: "deterministic-health-fixture",
@@ -161,7 +169,7 @@ try {
161
169
  )}\n`
162
170
  );
163
171
  await writeFile(
164
- path.join(project, ".lisa.config.json"),
172
+ path.join(project, LISA_CONFIG_JSON),
165
173
  `${JSON.stringify(
166
174
  {
167
175
  tracker: "github",
@@ -172,13 +180,13 @@ try {
172
180
  2
173
181
  )}\n`
174
182
  );
175
- await mkdir(path.join(project, "harper-app"), { recursive: true });
183
+ await mkdir(path.join(project, HARPER_APP), { recursive: true });
176
184
  await writeFile(
177
- path.join(project, "harper-app", "config.yaml"),
185
+ path.join(project, HARPER_APP, "config.yaml"),
178
186
  "graphqlSchema: ./schema.graphql\njsResource: true\nstatic: ./web\n"
179
187
  );
180
188
  await writeFile(
181
- path.join(project, "harper-app", "schema.graphql"),
189
+ path.join(project, HARPER_APP, "schema.graphql"),
182
190
  "type Query { health: Boolean! }\n"
183
191
  );
184
192
  git("init", "--initial-branch=main");
@@ -220,7 +228,7 @@ try {
220
228
  }
221
229
  );
222
230
  const version = JSON.parse(
223
- await readFile(path.join(root, "package.json"), "utf8")
231
+ await readFile(path.join(root, PACKAGE_JSON), "utf8")
224
232
  ).version;
225
233
  await writeFile(
226
234
  path.join(project, ".claude", ".lisa-plugins-synced"),
@@ -256,13 +264,12 @@ try {
256
264
  equal(clean.summary.counts.fail, 0, "clean fixture has zero failures");
257
265
  equal(clean.summary.verdict, "in band", "clean fixture is in band");
258
266
  equal(
259
- clean.findings.find(finding => finding.check === "templates.managed")
260
- .status,
267
+ clean.findings.find(finding => finding.check === TEMPLATES_MANAGED).status,
261
268
  "pass",
262
269
  "the real TypeScript plus Harper child template stack composes cleanly"
263
270
  );
264
271
  check(
265
- (await readFile(path.join(project, ".prettierignore"), "utf8")).includes(
272
+ (await readFile(path.join(project, PRETTIERIGNORE), "utf8")).includes(
266
273
  "AI GUARDRAILS HARPER-FABRIC"
267
274
  ),
268
275
  "the real child copy-contents block survives its parent overwrite"
@@ -298,14 +305,14 @@ try {
298
305
  );
299
306
  await writeFile(callerPath, callerOriginal);
300
307
 
301
- const prettierPath = path.join(project, ".prettierignore");
308
+ const prettierPath = path.join(project, PRETTIERIGNORE);
302
309
  const prettierOriginal = await readFile(prettierPath);
303
310
  await writeFile(path.join(project, ".lisaignore"), ".prettierignore\n");
304
311
  await writeFile(prettierPath, "project-owned while ignored\n");
305
312
  const ignoredTemplate = await assertPureCollection(health, options);
306
313
  equal(
307
314
  ignoredTemplate.findings.find(
308
- finding => finding.check === "templates.managed"
315
+ finding => finding.check === TEMPLATES_MANAGED
309
316
  ).status,
310
317
  "pass",
311
318
  "ignored templates are outside Lisa ownership"
@@ -337,9 +344,8 @@ try {
337
344
  await writeFile(learningsPath, customLearnings);
338
345
  const learningsOwned = await assertPureCollection(health, options);
339
346
  equal(
340
- learningsOwned.findings.find(
341
- finding => finding.check === "templates.managed"
342
- ).status,
347
+ learningsOwned.findings.find(finding => finding.check === TEMPLATES_MANAGED)
348
+ .status,
343
349
  "pass",
344
350
  "project learnings remain host-owned after their initial seed"
345
351
  );
@@ -396,8 +402,7 @@ try {
396
402
  );
397
403
  const drift = await assertPureCollection(health, options);
398
404
  const managedDrift = drift.findings.filter(
399
- finding =>
400
- finding.check === "templates.managed" && finding.status === "fail"
405
+ finding => finding.check === TEMPLATES_MANAGED && finding.status === "fail"
401
406
  );
402
407
  equal(
403
408
  managedDrift.length,
@@ -422,7 +427,7 @@ try {
422
427
  console.log("[EVIDENCE: clean-project-in-band]");
423
428
  console.log("[EVIDENCE: managed-file-restoration-in-band]");
424
429
 
425
- const configPath = path.join(project, ".lisa.config.json");
430
+ const configPath = path.join(project, LISA_CONFIG_JSON);
426
431
  const config = JSON.parse(await readFile(configPath, "utf8"));
427
432
  delete config.github.repo;
428
433
  await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`);
@@ -458,10 +463,10 @@ try {
458
463
  const unsafeProject = path.join(workspace, "unsafe-host");
459
464
  await mkdir(unsafeProject, { recursive: true });
460
465
  await writeFile(
461
- path.join(unsafeProject, ".lisa.config.json"),
466
+ path.join(unsafeProject, LISA_CONFIG_JSON),
462
467
  '{"tracker":"github","harness":"codex"}\n'
463
468
  );
464
- execFileSync("mkfifo", [path.join(unsafeProject, "package.json")]);
469
+ execFileSync("mkfifo", [path.join(unsafeProject, PACKAGE_JSON)]);
465
470
  const unsafeStarted = Date.now();
466
471
  const unsafe = await health.runDeterministicHealth(unsafeProject, {
467
472
  lisaRoot: root,
@@ -72,8 +72,6 @@
72
72
  "*.d.ts",
73
73
  "esbuild.plugins.js",
74
74
 
75
- "projects/**/scripts/**",
76
- "scripts/**",
77
75
 
78
76
  "lib/**/*.js",
79
77
  "src/lib/**/*.js",
@@ -426,7 +426,9 @@ function validateMatch(match, where) {
426
426
  try {
427
427
  // No `g` flag, ever: a sticky lastIndex makes repeated .test() calls
428
428
  // return alternating answers, which reads as an intermittent gate.
429
- void new RegExp(pattern);
429
+ // Compiling IS the assertion — an invalid pattern throws right here — so
430
+ // the result is deliberately unused.
431
+ const _compiled = new RegExp(pattern);
430
432
  } catch (error) {
431
433
  throw new GateConfigError(
432
434
  `${where}: \`match.pattern\` does not compile: ${error instanceof Error ? error.message : String(error)}`
@@ -1550,6 +1552,9 @@ function sleep(ms) {
1550
1552
  return new Promise(resolve => setTimeout(resolve, ms));
1551
1553
  }
1552
1554
 
1555
+ /** GitHub's remaining-quota response header, read from three call sites. */
1556
+ const RATELIMIT_REMAINING_HEADER = "x-ratelimit-remaining";
1557
+
1553
1558
  /**
1554
1559
  * How long to wait before retrying a throttled response, bounded.
1555
1560
  *
@@ -1563,7 +1568,7 @@ export function retryDelayMs(response, attempt, maxSeconds) {
1563
1568
  if (Number.isFinite(retryAfter) && retryAfter > 0) {
1564
1569
  return Math.min(retryAfter, maxSeconds) * 1000;
1565
1570
  }
1566
- const remaining = response.headers?.get?.("x-ratelimit-remaining");
1571
+ const remaining = response.headers?.get?.(RATELIMIT_REMAINING_HEADER);
1567
1572
  const reset = Number(response.headers?.get?.("x-ratelimit-reset"));
1568
1573
  if (remaining === "0" && Number.isFinite(reset)) {
1569
1574
  const seconds = Math.max(0, reset - Math.floor(Date.now() / 1000));
@@ -1613,7 +1618,7 @@ export async function apiGet(api, path, wait = sleep) {
1613
1618
  // 401/403 without rate-limit headers is an auth problem: retrying cannot
1614
1619
  // fix a token that is not allowed to read run history, so fail immediately
1615
1620
  // with a message that names the fix.
1616
- const remaining = response.headers?.get?.("x-ratelimit-remaining");
1621
+ const remaining = response.headers?.get?.(RATELIMIT_REMAINING_HEADER);
1617
1622
  const throttled =
1618
1623
  response.status === 429 || (response.status === 403 && remaining === "0");
1619
1624
  if ((response.status === 401 || response.status === 403) && !throttled) {
@@ -1782,7 +1787,7 @@ export async function apiWrite(api, method, path, payload, wait = sleep) {
1782
1787
  continue;
1783
1788
  }
1784
1789
  if (response.ok) return await response.json();
1785
- const remaining = response.headers?.get?.("x-ratelimit-remaining");
1790
+ const remaining = response.headers?.get?.(RATELIMIT_REMAINING_HEADER);
1786
1791
  const throttled =
1787
1792
  response.status === 429 || (response.status === 403 && remaining === "0");
1788
1793
  if ((response.status === 401 || response.status === 403) && !throttled) {
@@ -120,9 +120,7 @@ try {
120
120
 
121
121
  if (changed.length === 0) {
122
122
  console.log(
123
- "⚪ Mutation gate: no changed source files vs " +
124
- since +
125
- ". Nothing to mutate."
123
+ `⚪ Mutation gate: no changed source files vs ${since}. Nothing to mutate.`
126
124
  );
127
125
  process.exit(0);
128
126
  }