@codyswann/lisa 4.71.7 → 4.71.9

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 (105) hide show
  1. package/README.md +7 -0
  2. package/all/copy-overwrite/scripts/check-threshold-ratchet.mjs +571 -0
  3. package/all/copy-overwrite/scripts/check-workflow-load-failures.mjs +31 -9
  4. package/all/copy-overwrite/scripts/lib/automation-provenance-contract.mjs +19 -0
  5. package/all/copy-overwrite/scripts/lib/automation-provenance-files.mjs +49 -0
  6. package/all/copy-overwrite/scripts/lib/automation-provenance-local.mjs +54 -38
  7. package/all/copy-overwrite/scripts/lib/npm-update-allocate.mjs +8 -2
  8. package/all/copy-overwrite/scripts/lib/npm-update-bun.mjs +189 -0
  9. package/all/copy-overwrite/scripts/lib/npm-update-cancel-origin.mjs +7 -3
  10. package/all/copy-overwrite/scripts/lib/npm-update-cancellation-proof.mjs +6 -1
  11. package/all/copy-overwrite/scripts/lib/npm-update-contract.mjs +26 -5
  12. package/all/copy-overwrite/scripts/lib/npm-update-gate.mjs +8 -5
  13. package/all/copy-overwrite/scripts/lib/npm-update-github.mjs +2 -2
  14. package/all/copy-overwrite/scripts/lib/npm-update-helper-inventory.mjs +3 -4
  15. package/all/copy-overwrite/scripts/lib/npm-update-hook-installation.mjs +68 -1
  16. package/all/copy-overwrite/scripts/lib/npm-update-hosted-gate.mjs +13 -80
  17. package/all/copy-overwrite/scripts/lib/npm-update-leaf-contract.mjs +6 -5
  18. package/all/copy-overwrite/scripts/lib/npm-update-prepare.mjs +35 -7
  19. package/all/copy-overwrite/scripts/lib/npm-update-publish.mjs +1 -1
  20. package/all/copy-overwrite/scripts/npm-updater-helper-graph.json +43 -16
  21. package/all/copy-overwrite/scripts/threshold-ratchet-compare.mjs +521 -0
  22. package/all/copy-overwrite/scripts/threshold-ratchet-families.mjs +546 -0
  23. package/dist/configs/repo-scan.js +1 -1
  24. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  25. package/dist/core/lisa-owned-hash-ledger.js +60 -0
  26. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  27. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  28. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  29. package/dist/core/upstream-evidence-manifest.js +46 -32
  30. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  31. package/dist/strategies/merge.d.ts +1 -1
  32. package/dist/strategies/merge.d.ts.map +1 -1
  33. package/dist/strategies/merge.js +22 -2
  34. package/dist/strategies/merge.js.map +1 -1
  35. package/package.json +4 -4
  36. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  38. package/plugins/lisa/hooks/threshold-ratchet-compare.mjs +3 -3
  39. package/plugins/lisa/hooks/threshold-ratchet.mjs +1 -1
  40. package/plugins/lisa-agy/plugin.json +1 -1
  41. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  43. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  44. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-copilot/hooks/threshold-ratchet-compare.mjs +3 -3
  48. package/plugins/lisa-copilot/hooks/threshold-ratchet.mjs +1 -1
  49. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-cursor/hooks/threshold-ratchet-compare.mjs +3 -3
  51. package/plugins/lisa-cursor/hooks/threshold-ratchet.mjs +1 -1
  52. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  54. package/plugins/lisa-expo-agy/plugin.json +1 -1
  55. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  59. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  60. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  64. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  65. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  67. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  69. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  70. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  71. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  72. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  73. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  74. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  75. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  76. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  77. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  78. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  79. package/plugins/lisa-rails-agy/plugin.json +1 -1
  80. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  81. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  82. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  83. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  84. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  85. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  86. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  87. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  88. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  89. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  90. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  91. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  92. package/plugins/materialized-artifacts.json +3 -0
  93. package/plugins/src/base/hooks/threshold-ratchet-compare.mjs +3 -3
  94. package/plugins/src/base/hooks/threshold-ratchet.mjs +1 -1
  95. package/rails/copy-overwrite/scripts/check-threshold-ratchet.mjs +1 -1
  96. package/rails/copy-overwrite/scripts/threshold-ratchet-compare.mjs +3 -3
  97. package/scripts/build-plugins.sh +2 -1
  98. package/scripts/generate-scratch-supervisor-profile.mjs +1 -1
  99. package/scripts/install-claude-plugins.sh +8 -4
  100. package/scripts/two-channel-couplings.json +9 -7
  101. package/typescript/copy-overwrite/scripts/check-threshold-ratchet.mjs +1 -1
  102. package/typescript/copy-overwrite/scripts/threshold-ratchet-compare.mjs +3 -3
  103. /package/{rails → all}/copy-contents/scripts/lisa-mutation.sh +0 -0
  104. /package/{rails → all}/copy-overwrite/scripts/lisa-clean-git-env.sh +0 -0
  105. /package/{rails → all}/copy-overwrite/scripts/lisa-scratch-run.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.71.7",
3
+ "version": "4.71.9",
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.71.7",
3
+ "version": "4.71.9",
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.71.7",
3
+ "version": "4.71.9",
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,4 +1,5 @@
1
1
  [
2
+ "all/copy-overwrite/scripts/check-threshold-ratchet.mjs",
2
3
  "all/copy-overwrite/scripts/lib/bounded-spawn.mjs",
3
4
  "all/copy-overwrite/scripts/lib/invoked-as-script.mjs",
4
5
  "all/copy-overwrite/scripts/lisa-enforcement-fallback.sh",
@@ -14,6 +15,8 @@
14
15
  "all/copy-overwrite/scripts/lisa-hooks/sonar-secrets.sh",
15
16
  "all/copy-overwrite/scripts/lisa-hooks/worktree-binding-guard.mjs",
16
17
  "all/copy-overwrite/scripts/lisa-hooks/worktree-binding-guard.sh",
18
+ "all/copy-overwrite/scripts/threshold-ratchet-compare.mjs",
19
+ "all/copy-overwrite/scripts/threshold-ratchet-families.mjs",
17
20
  "expo/copy-overwrite/scripts/lib/bounded-spawn.mjs",
18
21
  "expo/copy-overwrite/scripts/lib/invoked-as-script.mjs",
19
22
  "plugins/lisa/hooks/block-blind-automerge.sh",
@@ -296,8 +296,8 @@ function compareLighthouse(relPath, base, current) {
296
296
  current?.ci?.assert?.assertions != null) &&
297
297
  (previous == null ||
298
298
  (typeof previous === "number" && Number.isFinite(previous))) &&
299
- typeof added === "number" &&
300
- Number.isFinite(added)
299
+ (added == null || (typeof added === "number" && Number.isFinite(added))) &&
300
+ !(previous == null && added == null)
301
301
  ) {
302
302
  // This checker reads top-level settings even beside canonical assertions.
303
303
  // Keep its comparison separate so a new setting cannot replace a canonical
@@ -307,7 +307,7 @@ function compareLighthouse(relPath, base, current) {
307
307
  ...compareConstraints(
308
308
  relPath,
309
309
  new Map([[key, { value: previous ?? 100, direction: "max" }]]),
310
- new Map([[key, { value: added, direction: "max" }]])
310
+ new Map([[key, { value: added ?? 100, direction: "max" }]])
311
311
  )
312
312
  );
313
313
  }
@@ -517,7 +517,7 @@ function main() {
517
517
  process.stderr.write(
518
518
  "usage: threshold-ratchet.mjs --hook | --staged | --base <ref> [--head <ref>]\n"
519
519
  );
520
- return 0;
520
+ return 2;
521
521
  }
522
522
 
523
523
  /**
@@ -520,7 +520,7 @@ function main() {
520
520
  process.stderr.write(
521
521
  "usage: threshold-ratchet.mjs --hook | --staged | --base <ref> [--head <ref>]\n"
522
522
  );
523
- return 0;
523
+ return 2;
524
524
  }
525
525
 
526
526
  /**
@@ -299,8 +299,8 @@ function compareLighthouse(relPath, base, current) {
299
299
  current?.ci?.assert?.assertions != null) &&
300
300
  (previous == null ||
301
301
  (typeof previous === "number" && Number.isFinite(previous))) &&
302
- typeof added === "number" &&
303
- Number.isFinite(added)
302
+ (added == null || (typeof added === "number" && Number.isFinite(added))) &&
303
+ !(previous == null && added == null)
304
304
  ) {
305
305
  // This checker reads top-level settings even beside canonical assertions.
306
306
  // Keep its comparison separate so a new setting cannot replace a canonical
@@ -310,7 +310,7 @@ function compareLighthouse(relPath, base, current) {
310
310
  ...compareConstraints(
311
311
  relPath,
312
312
  new Map([[key, { value: previous ?? 100, direction: "max" }]]),
313
- new Map([[key, { value: added, direction: "max" }]])
313
+ new Map([[key, { value: added ?? 100, direction: "max" }]])
314
314
  )
315
315
  );
316
316
  }
@@ -138,11 +138,12 @@ build_plugin base lisa
138
138
  # copies here so they can never drift; a unit test asserts byte-equality.
139
139
  if [ -f "$SRC_DIR/base/hooks/threshold-ratchet.mjs" ]; then
140
140
  for ratchet_scripts_dir in \
141
+ "$ROOT_DIR/all/copy-overwrite/scripts" \
141
142
  "$ROOT_DIR/typescript/copy-overwrite/scripts" \
142
143
  "$ROOT_DIR/rails/copy-overwrite/scripts"; do
143
144
  mkdir -p "$ratchet_scripts_dir"
144
145
  # The entry point takes the template check-* naming; its relative imports
145
- # (threshold-ratchet-*.mjs) keep their canonical names in both trees.
146
+ # (threshold-ratchet-*.mjs) keep their canonical names in all three trees.
146
147
  materialize "$SRC_DIR/base/hooks/threshold-ratchet.mjs" \
147
148
  "$ratchet_scripts_dir/check-threshold-ratchet.mjs"
148
149
  for ratchet_module in threshold-ratchet-families threshold-ratchet-compare; do
@@ -63,7 +63,7 @@ if (invokedAsScript(import.meta.url)) {
63
63
  const rendered = renderScratchProfile(
64
64
  current,
65
65
  readFileSync(
66
- path.join(root, "rails/copy-overwrite/scripts/lisa-scratch-run.sh")
66
+ path.join(root, "all/copy-overwrite/scripts/lisa-scratch-run.sh")
67
67
  )
68
68
  );
69
69
  if (process.argv.includes("--check")) {
@@ -580,16 +580,20 @@ done
580
580
 
581
581
  # Retire third-party plugins Lisa no longer curates. The base lisa plugin
582
582
  # bundles the Sentry MCP server (plugins/src/base/.mcp.json) for every agent
583
- # runtime, so the upstream sentry plugin registered the same server twice in
584
- # each Claude session (issue #1955). Likewise, the Lisa-native
583
+ # runtime. The official Claude plugin remains disabled by default to avoid
584
+ # duplicate registration (#1955), but an explicit host choice enables its own
585
+ # maintained skills and must survive sync (#4318). Likewise, the Lisa-native
585
586
  # parity-safety-net.sh hook screens every Bash command, so the upstream
586
587
  # safety-net plugin double-screened each command; its material guards were
587
- # absorbed into the Lisa hook (issue #1960). The merge settings templates flip
588
- # both enabledPlugins entries to false; this removes the installs themselves.
588
+ # absorbed into the Lisa hook (issue #1960). Remove unchosen retired installs.
589
589
  # Version-gated like the other one-time heals so same-version runs don't spawn
590
590
  # the CLI.
591
591
  if [ "$FORCE_PLUGIN_SYNC" = "true" ]; then
592
592
  for retired_plugin in "sentry@claude-plugins-official" "safety-net@cc-marketplace"; do
593
+ if [ "$retired_plugin" = "sentry@claude-plugins-official" ] &&
594
+ node -e 'try { const s = JSON.parse(require("node:fs").readFileSync(process.argv[1], "utf8")); process.exit(s.enabledPlugins?.["sentry@claude-plugins-official"] === true ? 0 : 1); } catch { process.exit(1); }' "$SETTINGS_FILE"; then
595
+ continue
596
+ fi
593
597
  claude plugin uninstall "$retired_plugin" --scope project </dev/null >/dev/null 2>&1 || true
594
598
  done
595
599
  fi
@@ -105,7 +105,7 @@
105
105
  "workflows": 25,
106
106
  "steps": 798,
107
107
  "couplings": 35,
108
- "inventory": 164
108
+ "inventory": 166
109
109
  },
110
110
  "counts": {
111
111
  "package-backed": 10,
@@ -400,10 +400,11 @@
400
400
  "decision": "warranted",
401
401
  "reason": "Verdict-bearing required gate: it compares committed thresholds against measured coverage and posts a red check. A copy predating a threshold-semantics change enforces the old comparison and its red is believed — the precise harm CodySwannGT/lisa#3477 measured on the work-item gate. The artifact exports no contract version yet, so closing this needs the artifact half authored first; recorded as an open obligation rather than exempted.",
402
402
  "lanes": [
403
+ "all/copy-overwrite",
403
404
  "rails/copy-overwrite",
404
405
  "typescript/copy-overwrite"
405
406
  ],
406
- "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. No existence test guards the read, which is ALL this scan examined of the step's control flow: it did not read anything that could absorb the failure. An absent path most likely fails the job loudly — the better of the two failures, because it is visible — but that is inferred from the missing guard, NOT MEASURED. The step's text does carry `else-branch`, `exit-zero`, `exit-nonzero`, `error-annotation` — located in the step, deliberately NOT related to this read, and enough to make \"it skips silently\" a claim to check rather than assume. STALENESS (a separate dimension from the delivery verdict above): `no-probe`. Neither an existence test nor a version probe bears on this read, so nothing here distinguishes a current artifact from an absent or a superseded one. Tokens found: `workflow-requests-version`. `workflow-requests-version` is workflow-scoped — it does not say THIS step asks."
407
+ "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 (all/copy-overwrite, 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. No existence test guards the read, which is ALL this scan examined of the step's control flow: it did not read anything that could absorb the failure. An absent path most likely fails the job loudly — the better of the two failures, because it is visible — but that is inferred from the missing guard, NOT MEASURED. The step's text does carry `else-branch`, `exit-zero`, `exit-nonzero`, `error-annotation` — located in the step, deliberately NOT related to this read, and enough to make \"it skips silently\" a claim to check rather than assume. STALENESS (a separate dimension from the delivery verdict above): `no-probe`. Neither an existence test nor a version probe bears on this read, so nothing here distinguishes a current artifact from an absent or a superseded one. Tokens found: `workflow-requests-version`. `workflow-requests-version` is workflow-scoped — it does not say THIS step asks."
407
408
  },
408
409
  {
409
410
  "key": "quality-rails.yml::scripts/lisa-mutation.sh",
@@ -425,9 +426,9 @@
425
426
  "decision": "not-needed",
426
427
  "reason": "A runner, not a judge. The gate's verdict is mutant's exit code, and what it runs is read from the project's own `mutation.gate.yml` and `.mutant.yml`; this script's own logic decides whether the gate is enabled and what ref to diff against, both of which come from configuration it does not own. A superseded copy runs the wrong subjects loudly rather than returning a wrong verdict quietly.",
427
428
  "lanes": [
428
- "rails/copy-contents"
429
+ "all/copy-contents"
429
430
  ],
430
- "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 sits behind an existence test, which is ALL this scan examined of the step's control flow: it did not read the test's other branch, any fallback, any explicit `exit`, or any verification of the step's output. Whether an absent path skips quietly, falls back, or fails loudly is therefore NOT MEASURED — read the step before concluding it proves nothing. The shape worth ruling out is the silent one, where a skip posts no context and an absent required context is not a red one. The step's text does carry `exit-zero`, `exit-nonzero`, `error-annotation` — located in the step, deliberately NOT related to this read, and enough to make \"it skips silently\" a claim to check rather than assume. STALENESS (a separate dimension from the delivery verdict above): `existence-only`. An existence test and no version probe: this coupling can see that the artifact is MISSING and cannot see that it is OLD. A present copy of any vintage runs, and its verdict is believed. Tokens found: `workflow-requests-version`. `workflow-requests-version` is workflow-scoped — it does not say THIS step asks."
431
+ "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 (all/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 sits behind an existence test, which is ALL this scan examined of the step's control flow: it did not read the test's other branch, any fallback, any explicit `exit`, or any verification of the step's output. Whether an absent path skips quietly, falls back, or fails loudly is therefore NOT MEASURED — read the step before concluding it proves nothing. The shape worth ruling out is the silent one, where a skip posts no context and an absent required context is not a red one. The step's text does carry `exit-zero`, `exit-nonzero`, `error-annotation` — located in the step, deliberately NOT related to this read, and enough to make \"it skips silently\" a claim to check rather than assume. STALENESS (a separate dimension from the delivery verdict above): `existence-only`. An existence test and no version probe: this coupling can see that the artifact is MISSING and cannot see that it is OLD. A present copy of any vintage runs, and its verdict is believed. Tokens found: `workflow-requests-version`. `workflow-requests-version` is workflow-scoped — it does not say THIS step asks."
431
432
  },
432
433
  {
433
434
  "key": "quality-rails.yml::scripts/lisa-scratch-run.sh",
@@ -449,9 +450,9 @@
449
450
  "decision": "not-needed",
450
451
  "reason": "A supervisor that hosts a verdict rather than computing one: it owns run-scoped scratch and cleanup around whatever it wraps. Nothing it does can turn a passing suite red or a failing one green — a superseded copy misplaces or fails to reap a scratch directory, which surfaces as its own visible failure rather than as a believed verdict.",
451
452
  "lanes": [
452
- "rails/copy-overwrite"
453
+ "all/copy-overwrite"
453
454
  ],
454
- "detail": "`quality-rails.yml` is delivered at `@main`, so this step is live in every consumer on their next run. It reads `scripts/lisa-scratch-run.sh` from the CALLER's tree with no package-relative candidate, and Lisa delivers that path on the `lisa apply` channel (rails/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 sits behind an existence test, which is ALL this scan examined of the step's control flow: it did not read the test's other branch, any fallback, any explicit `exit`, or any verification of the step's output. Whether an absent path skips quietly, falls back, or fails loudly is therefore NOT MEASURED — read the step before concluding it proves nothing. The shape worth ruling out is the silent one, where a skip posts no context and an absent required context is not a red one. The step's text does carry `exit-zero`, `exit-nonzero`, `error-annotation` — located in the step, deliberately NOT related to this read, and enough to make \"it skips silently\" a claim to check rather than assume. STALENESS (a separate dimension from the delivery verdict above): `existence-only`. An existence test and no version probe: this coupling can see that the artifact is MISSING and cannot see that it is OLD. A present copy of any vintage runs, and its verdict is believed. Tokens found: `workflow-requests-version`. `workflow-requests-version` is workflow-scoped — it does not say THIS step asks."
455
+ "detail": "`quality-rails.yml` is delivered at `@main`, so this step is live in every consumer on their next run. It reads `scripts/lisa-scratch-run.sh` 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 sits behind an existence test, which is ALL this scan examined of the step's control flow: it did not read the test's other branch, any fallback, any explicit `exit`, or any verification of the step's output. Whether an absent path skips quietly, falls back, or fails loudly is therefore NOT MEASURED — read the step before concluding it proves nothing. The shape worth ruling out is the silent one, where a skip posts no context and an absent required context is not a red one. The step's text does carry `exit-zero`, `exit-nonzero`, `error-annotation` — located in the step, deliberately NOT related to this read, and enough to make \"it skips silently\" a claim to check rather than assume. STALENESS (a separate dimension from the delivery verdict above): `existence-only`. An existence test and no version probe: this coupling can see that the artifact is MISSING and cannot see that it is OLD. A present copy of any vintage runs, and its verdict is believed. Tokens found: `workflow-requests-version`. `workflow-requests-version` is workflow-scoped — it does not say THIS step asks."
455
456
  },
456
457
  {
457
458
  "key": "quality-rails.yml::scripts/lisa-work-item.mjs",
@@ -576,10 +577,11 @@
576
577
  "decision": "warranted",
577
578
  "reason": "Verdict-bearing required gate, the same artifact and the same exposure as the `quality-rails.yml` entry: a copy predating a threshold-semantics change enforces the old comparison and its red is believed. Closing it once closes both couplings, since they read the same script down two stack lanes. The artifact exports no contract version yet; recorded as an open obligation rather than exempted.",
578
579
  "lanes": [
580
+ "all/copy-overwrite",
579
581
  "rails/copy-overwrite",
580
582
  "typescript/copy-overwrite"
581
583
  ],
582
- "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. No existence test guards the read, which is ALL this scan examined of the step's control flow: it did not read anything that could absorb the failure. An absent path most likely fails the job loudly — the better of the two failures, because it is visible — but that is inferred from the missing guard, NOT MEASURED. The step's text does carry `else-branch`, `exit-zero`, `exit-nonzero`, `error-annotation` — located in the step, deliberately NOT related to this read, and enough to make \"it skips silently\" a claim to check rather than assume. STALENESS (a separate dimension from the delivery verdict above): `no-probe`. Neither an existence test nor a version probe bears on this read, so nothing here distinguishes a current artifact from an absent or a superseded one. Tokens found: `workflow-requests-version`. `workflow-requests-version` is workflow-scoped — it does not say THIS step asks."
584
+ "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 (all/copy-overwrite, 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. No existence test guards the read, which is ALL this scan examined of the step's control flow: it did not read anything that could absorb the failure. An absent path most likely fails the job loudly — the better of the two failures, because it is visible — but that is inferred from the missing guard, NOT MEASURED. The step's text does carry `else-branch`, `exit-zero`, `exit-nonzero`, `error-annotation` — located in the step, deliberately NOT related to this read, and enough to make \"it skips silently\" a claim to check rather than assume. STALENESS (a separate dimension from the delivery verdict above): `no-probe`. Neither an existence test nor a version probe bears on this read, so nothing here distinguishes a current artifact from an absent or a superseded one. Tokens found: `workflow-requests-version`. `workflow-requests-version` is workflow-scoped — it does not say THIS step asks."
583
585
  },
584
586
  {
585
587
  "key": "quality.yml::scripts/check-verification-coverage.mjs",
@@ -520,7 +520,7 @@ function main() {
520
520
  process.stderr.write(
521
521
  "usage: threshold-ratchet.mjs --hook | --staged | --base <ref> [--head <ref>]\n"
522
522
  );
523
- return 0;
523
+ return 2;
524
524
  }
525
525
 
526
526
  /**
@@ -299,8 +299,8 @@ function compareLighthouse(relPath, base, current) {
299
299
  current?.ci?.assert?.assertions != null) &&
300
300
  (previous == null ||
301
301
  (typeof previous === "number" && Number.isFinite(previous))) &&
302
- typeof added === "number" &&
303
- Number.isFinite(added)
302
+ (added == null || (typeof added === "number" && Number.isFinite(added))) &&
303
+ !(previous == null && added == null)
304
304
  ) {
305
305
  // This checker reads top-level settings even beside canonical assertions.
306
306
  // Keep its comparison separate so a new setting cannot replace a canonical
@@ -310,7 +310,7 @@ function compareLighthouse(relPath, base, current) {
310
310
  ...compareConstraints(
311
311
  relPath,
312
312
  new Map([[key, { value: previous ?? 100, direction: "max" }]]),
313
- new Map([[key, { value: added, direction: "max" }]])
313
+ new Map([[key, { value: added ?? 100, direction: "max" }]])
314
314
  )
315
315
  );
316
316
  }