@codyswann/lisa 4.54.13 → 4.55.0

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 (578) hide show
  1. package/all/copy-contents/.gitattributes +8 -0
  2. package/all/copy-overwrite/scripts/check-orphaned-branches.mjs +78 -9
  3. package/all/copy-overwrite/scripts/check-third-party-action-pins.mjs +17 -1
  4. package/all/copy-overwrite/scripts/lib/process-tree-runner.mjs +194 -7
  5. package/all/copy-overwrite/scripts/lisa-enforcement-fallback.sh +31 -0
  6. package/all/copy-overwrite/scripts/lisa-hooks/block-blind-automerge.sh +81 -2
  7. package/all/copy-overwrite/scripts/lisa-hooks/block-direct-issue-create.sh +293 -8
  8. package/all/copy-overwrite/scripts/lisa-hooks/block-instruction-file-edits.sh +21 -0
  9. package/all/copy-overwrite/scripts/lisa-hooks/block-managed-file-edits.sh +78 -15
  10. package/all/copy-overwrite/scripts/lisa-hooks/block-no-verify.sh +21 -0
  11. package/all/copy-overwrite/scripts/lisa-hooks/block-shell-json-parsing.sh +21 -0
  12. package/all/copy-overwrite/scripts/lisa-hooks/guard-dedupe.bash +340 -0
  13. package/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +29 -1
  14. package/all/copy-overwrite/scripts/lisa-hooks/worktree-binding-guard.mjs +183 -1
  15. package/all/copy-overwrite/scripts/lisa-hooks/worktree-binding-guard.sh +21 -0
  16. package/all/copy-overwrite/scripts/lisa-postinstall.mjs +21 -1
  17. package/all/copy-overwrite/scripts/lisa-run-gates.mjs +100 -5
  18. package/all/copy-overwrite/scripts/lisa-work-item.mjs +1085 -80
  19. package/all/create-only/.github/workflows/continuous-gates.yml +7 -0
  20. package/cdk/create-only/.github/workflows/ci.yml +7 -0
  21. package/cdk/create-only/.github/workflows/deploy.yml +21 -0
  22. package/dist/cli/apply.d.ts.map +1 -1
  23. package/dist/cli/apply.js +9 -1
  24. package/dist/cli/apply.js.map +1 -1
  25. package/dist/cli/doctor-apply-deletions.d.ts +61 -0
  26. package/dist/cli/doctor-apply-deletions.d.ts.map +1 -0
  27. package/dist/cli/doctor-apply-deletions.js +137 -0
  28. package/dist/cli/doctor-apply-deletions.js.map +1 -0
  29. package/dist/cli/doctor-cdk-preset-adoption.d.ts +15 -0
  30. package/dist/cli/doctor-cdk-preset-adoption.d.ts.map +1 -0
  31. package/dist/cli/doctor-cdk-preset-adoption.js +316 -0
  32. package/dist/cli/doctor-cdk-preset-adoption.js.map +1 -0
  33. package/dist/cli/doctor-config-shadowing.d.ts +22 -0
  34. package/dist/cli/doctor-config-shadowing.d.ts.map +1 -0
  35. package/dist/cli/doctor-config-shadowing.js +123 -0
  36. package/dist/cli/doctor-config-shadowing.js.map +1 -0
  37. package/dist/cli/doctor-rails-deploy-intent.d.ts +15 -0
  38. package/dist/cli/doctor-rails-deploy-intent.d.ts.map +1 -0
  39. package/dist/cli/doctor-rails-deploy-intent.js +197 -0
  40. package/dist/cli/doctor-rails-deploy-intent.js.map +1 -0
  41. package/dist/cli/doctor-seeded-artifacts.d.ts +12 -0
  42. package/dist/cli/doctor-seeded-artifacts.d.ts.map +1 -0
  43. package/dist/cli/doctor-seeded-artifacts.js +54 -0
  44. package/dist/cli/doctor-seeded-artifacts.js.map +1 -0
  45. package/dist/cli/doctor-stale-banner-scan.d.ts +44 -0
  46. package/dist/cli/doctor-stale-banner-scan.d.ts.map +1 -0
  47. package/dist/cli/doctor-stale-banner-scan.js +152 -0
  48. package/dist/cli/doctor-stale-banner-scan.js.map +1 -0
  49. package/dist/cli/doctor-stale-managed-banner.d.ts +19 -0
  50. package/dist/cli/doctor-stale-managed-banner.d.ts.map +1 -0
  51. package/dist/cli/doctor-stale-managed-banner.js +198 -0
  52. package/dist/cli/doctor-stale-managed-banner.js.map +1 -0
  53. package/dist/cli/doctor.d.ts.map +1 -1
  54. package/dist/cli/doctor.js +16 -0
  55. package/dist/cli/doctor.js.map +1 -1
  56. package/dist/cli/ui-detected-stacks.d.ts.map +1 -1
  57. package/dist/cli/ui-detected-stacks.js +3 -2
  58. package/dist/cli/ui-detected-stacks.js.map +1 -1
  59. package/dist/cli/update-check.d.ts.map +1 -1
  60. package/dist/cli/update-check.js +14 -0
  61. package/dist/cli/update-check.js.map +1 -1
  62. package/dist/cli/worktree-liveness.d.ts +5 -0
  63. package/dist/cli/worktree-liveness.d.ts.map +1 -1
  64. package/dist/cli/worktree-liveness.js +7 -6
  65. package/dist/cli/worktree-liveness.js.map +1 -1
  66. package/dist/cli/worktree-ownership.d.ts +13 -0
  67. package/dist/cli/worktree-ownership.d.ts.map +1 -1
  68. package/dist/cli/worktree-ownership.js +14 -0
  69. package/dist/cli/worktree-ownership.js.map +1 -1
  70. package/dist/configs/eslint/base.d.ts +34 -0
  71. package/dist/configs/eslint/base.d.ts.map +1 -1
  72. package/dist/configs/eslint/base.js +34 -0
  73. package/dist/configs/eslint/base.js.map +1 -1
  74. package/dist/core/apply-receipt.d.ts +65 -0
  75. package/dist/core/apply-receipt.d.ts.map +1 -1
  76. package/dist/core/apply-receipt.js +62 -2
  77. package/dist/core/apply-receipt.js.map +1 -1
  78. package/dist/core/cdk-preset-adoption.d.ts +136 -0
  79. package/dist/core/cdk-preset-adoption.d.ts.map +1 -0
  80. package/dist/core/cdk-preset-adoption.js +135 -0
  81. package/dist/core/cdk-preset-adoption.js.map +1 -0
  82. package/dist/core/config-shadowing.d.ts +36 -0
  83. package/dist/core/config-shadowing.d.ts.map +1 -1
  84. package/dist/core/config-shadowing.js +38 -0
  85. package/dist/core/config-shadowing.js.map +1 -1
  86. package/dist/core/config.d.ts +11 -0
  87. package/dist/core/config.d.ts.map +1 -1
  88. package/dist/core/config.js.map +1 -1
  89. package/dist/core/learnings-merge-driver.d.ts +10 -0
  90. package/dist/core/learnings-merge-driver.d.ts.map +1 -1
  91. package/dist/core/learnings-merge-driver.js +18 -0
  92. package/dist/core/learnings-merge-driver.js.map +1 -1
  93. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  94. package/dist/core/lisa-owned-hash-ledger.js +136 -0
  95. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  96. package/dist/core/lisa.d.ts.map +1 -1
  97. package/dist/core/lisa.js +9 -1
  98. package/dist/core/lisa.js.map +1 -1
  99. package/dist/core/nightly-e2e-guard-behavior-certificate.d.ts +5 -5
  100. package/dist/core/nightly-e2e-guard-behavior-certificate.js +7 -7
  101. package/dist/core/nightly-e2e-guard-behavior-certificate.js.map +1 -1
  102. package/dist/core/ownership-header.d.ts +74 -0
  103. package/dist/core/ownership-header.d.ts.map +1 -0
  104. package/dist/core/ownership-header.js +76 -0
  105. package/dist/core/ownership-header.js.map +1 -0
  106. package/dist/core/rails-deploy-production-intent.d.ts +36 -0
  107. package/dist/core/rails-deploy-production-intent.d.ts.map +1 -0
  108. package/dist/core/rails-deploy-production-intent.js +79 -0
  109. package/dist/core/rails-deploy-production-intent.js.map +1 -0
  110. package/dist/core/stale-managed-banner.d.ts +78 -0
  111. package/dist/core/stale-managed-banner.d.ts.map +1 -0
  112. package/dist/core/stale-managed-banner.js +118 -0
  113. package/dist/core/stale-managed-banner.js.map +1 -0
  114. package/dist/core/two-channel-delivery-scan.d.ts +33 -1
  115. package/dist/core/two-channel-delivery-scan.d.ts.map +1 -1
  116. package/dist/core/two-channel-delivery-scan.js +133 -6
  117. package/dist/core/two-channel-delivery-scan.js.map +1 -1
  118. package/dist/core/two-channel-delivery.d.ts +101 -4
  119. package/dist/core/two-channel-delivery.d.ts.map +1 -1
  120. package/dist/core/two-channel-delivery.js +121 -13
  121. package/dist/core/two-channel-delivery.js.map +1 -1
  122. package/dist/core/two-channel-staleness.d.ts +137 -0
  123. package/dist/core/two-channel-staleness.d.ts.map +1 -0
  124. package/dist/core/two-channel-staleness.js +268 -0
  125. package/dist/core/two-channel-staleness.js.map +1 -0
  126. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  127. package/dist/core/upstream-evidence-manifest.js +265 -121
  128. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  129. package/dist/core/workflow-deletion-ownership.d.ts +6 -24
  130. package/dist/core/workflow-deletion-ownership.d.ts.map +1 -1
  131. package/dist/core/workflow-deletion-ownership.js +22 -42
  132. package/dist/core/workflow-deletion-ownership.js.map +1 -1
  133. package/dist/detection/detectors/cdk.d.ts.map +1 -1
  134. package/dist/detection/detectors/cdk.js +2 -1
  135. package/dist/detection/detectors/cdk.js.map +1 -1
  136. package/dist/health/template-inspection.d.ts.map +1 -1
  137. package/dist/health/template-inspection.js +2 -1
  138. package/dist/health/template-inspection.js.map +1 -1
  139. package/dist/migrations/ensure-lisa-postinstall.d.ts +16 -0
  140. package/dist/migrations/ensure-lisa-postinstall.d.ts.map +1 -1
  141. package/dist/migrations/ensure-lisa-postinstall.js +16 -0
  142. package/dist/migrations/ensure-lisa-postinstall.js.map +1 -1
  143. package/dist/migrations/ensure-pinned-reusable-workflow-refs.d.ts +2 -2
  144. package/dist/migrations/ensure-pinned-reusable-workflow-refs.d.ts.map +1 -1
  145. package/dist/migrations/ensure-pinned-reusable-workflow-refs.js +98 -34
  146. package/dist/migrations/ensure-pinned-reusable-workflow-refs.js.map +1 -1
  147. package/dist/migrations/ensure-playwright-dedicated-caller.d.ts.map +1 -1
  148. package/dist/migrations/ensure-playwright-dedicated-caller.js +1 -0
  149. package/dist/migrations/ensure-playwright-dedicated-caller.js.map +1 -1
  150. package/dist/opencode/hooks-installer.d.ts.map +1 -1
  151. package/dist/opencode/hooks-installer.js +1 -0
  152. package/dist/opencode/hooks-installer.js.map +1 -1
  153. package/dist/opencode/plugin-templates/block-managed-file-edits.sh +78 -15
  154. package/dist/opencode/plugin-templates/block-no-verify.sh +21 -0
  155. package/dist/opencode/plugin-templates/guard-dedupe.bash +337 -0
  156. package/dist/opencode/plugin-templates/lisa-block-direct-issue-create.ts +242 -55
  157. package/dist/opencode/plugin-templates/parity-safety-net.sh +29 -1
  158. package/dist/strategies/package-lisa.d.ts.map +1 -1
  159. package/dist/strategies/package-lisa.js +2 -1
  160. package/dist/strategies/package-lisa.js.map +1 -1
  161. package/dist/utils/path-utils.d.ts +29 -0
  162. package/dist/utils/path-utils.d.ts.map +1 -1
  163. package/dist/utils/path-utils.js +41 -0
  164. package/dist/utils/path-utils.js.map +1 -1
  165. package/expo/create-only/.github/workflows/ci.yml +21 -0
  166. package/expo/create-only/.github/workflows/deploy.yml +28 -0
  167. package/expo/create-only/.github/workflows/maestro-e2e.yml +7 -0
  168. package/expo/create-only/.github/workflows/nightly-e2e-bypass-reaper.yml +124 -13
  169. package/expo/create-only/.github/workflows/nightly-e2e-health.yml +7 -0
  170. package/expo/create-only/.github/workflows/nightly-e2e-report.yml +7 -0
  171. package/expo/create-only/.github/workflows/nightly-e2e-tracking.yml +8 -0
  172. package/expo/create-only/.github/workflows/playwright-e2e.yml +7 -0
  173. package/harper-fabric/copy-overwrite/.github/workflows/ci.yml +7 -0
  174. package/harper-fabric/create-only/.github/workflows/deploy.yml +14 -0
  175. package/nestjs/create-only/.github/workflows/ci.yml +14 -0
  176. package/nestjs/create-only/.github/workflows/deploy.yml +28 -0
  177. package/package.json +10 -5
  178. package/phaser/copy-overwrite/.github/workflows/ci.yml +7 -0
  179. package/plugins/lisa/.claude-plugin/plugin.json +28 -1
  180. package/plugins/lisa/.codex-plugin/hooks.json +27 -0
  181. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  182. package/plugins/lisa/.codex-plugin/skills/lisa-drive-pr-to-merge/SKILL.md +130 -19
  183. package/plugins/lisa/.codex-plugin/skills/lisa-github-build-intake/SKILL.md +27 -4
  184. package/plugins/lisa/.codex-plugin/skills/lisa-github-prd-intake/SKILL.md +1 -1
  185. package/plugins/lisa/.codex-plugin/skills/lisa-github-validate-issue/SKILL.md +22 -0
  186. package/plugins/lisa/.codex-plugin/skills/lisa-github-verify/SKILL.md +14 -0
  187. package/plugins/lisa/.codex-plugin/skills/lisa-github-write-issue/SKILL.md +51 -0
  188. package/plugins/lisa/.codex-plugin/skills/lisa-intake/SKILL.md +1 -1
  189. package/plugins/lisa/.codex-plugin/skills/lisa-jira-build-intake/SKILL.md +27 -4
  190. package/plugins/lisa/.codex-plugin/skills/lisa-jira-validate-ticket/SKILL.md +22 -0
  191. package/plugins/lisa/.codex-plugin/skills/lisa-jira-verify/SKILL.md +14 -0
  192. package/plugins/lisa/.codex-plugin/skills/lisa-jira-write-ticket/SKILL.md +51 -0
  193. package/plugins/lisa/.codex-plugin/skills/lisa-linear-access/SKILL.md +55 -2
  194. package/plugins/lisa/.codex-plugin/skills/lisa-linear-build-intake/SKILL.md +27 -4
  195. package/plugins/lisa/.codex-plugin/skills/lisa-linear-prd-intake/SKILL.md +47 -30
  196. package/plugins/lisa/.codex-plugin/skills/lisa-linear-to-tracker/SKILL.md +3 -3
  197. package/plugins/lisa/.codex-plugin/skills/lisa-linear-validate-issue/SKILL.md +22 -0
  198. package/plugins/lisa/.codex-plugin/skills/lisa-linear-verify/SKILL.md +14 -0
  199. package/plugins/lisa/.codex-plugin/skills/lisa-linear-write-issue/SKILL.md +54 -3
  200. package/plugins/lisa/.codex-plugin/skills/lisa-prd-ticket-coverage/SKILL.md +2 -2
  201. package/plugins/lisa/.codex-plugin/skills/lisa-qa-clear/SKILL.md +19 -7
  202. package/plugins/lisa/.codex-plugin/skills/lisa-qa-fail/SKILL.md +28 -3
  203. package/plugins/lisa/.codex-plugin/skills/lisa-qa-queue/SKILL.md +28 -3
  204. package/plugins/lisa/.codex-plugin/skills/lisa-repair-intake/SKILL.md +94 -24
  205. package/plugins/lisa/.codex-plugin/skills/lisa-rework-triage/SKILL.md +32 -2
  206. package/plugins/lisa/.codex-plugin/skills/lisa-setup-linear/SKILL.md +4 -4
  207. package/plugins/lisa/.codex-plugin/skills/lisa-track/SKILL.md +1 -1
  208. package/plugins/lisa/.codex-plugin/skills/lisa-tracker-build-intake/SKILL.md +9 -0
  209. package/plugins/lisa/agents/linear-prd-intake.md +8 -8
  210. package/plugins/lisa/hooks/block-blind-automerge.sh +81 -2
  211. package/plugins/lisa/hooks/block-direct-issue-create.agy.sh +64 -16
  212. package/plugins/lisa/hooks/block-direct-issue-create.sh +293 -8
  213. package/plugins/lisa/hooks/block-instruction-file-edits.sh +21 -0
  214. package/plugins/lisa/hooks/block-managed-file-edits.sh +78 -15
  215. package/plugins/lisa/hooks/block-no-verify.sh +21 -0
  216. package/plugins/lisa/hooks/block-shell-json-parsing.sh +21 -0
  217. package/plugins/lisa/hooks/failure-signature-index.mjs +240 -5
  218. package/plugins/lisa/hooks/guard-dedupe.bash +337 -0
  219. package/plugins/lisa/hooks/inject-rules.sh +13 -5
  220. package/plugins/lisa/hooks/operational-hazards.mjs +736 -0
  221. package/plugins/lisa/hooks/operational-hazards.sh +28 -0
  222. package/plugins/lisa/hooks/parity-safety-net.sh +29 -1
  223. package/plugins/lisa/hooks/threshold-ratchet-compare.mjs +102 -21
  224. package/plugins/lisa/hooks/threshold-ratchet-families.mjs +130 -2
  225. package/plugins/lisa/hooks/threshold-ratchet.mjs +63 -13
  226. package/plugins/lisa/hooks/worktree-binding-guard.mjs +183 -1
  227. package/plugins/lisa/hooks/worktree-binding-guard.sh +21 -0
  228. package/plugins/lisa/rules/eager/00-rule-index.md +1 -0
  229. package/plugins/lisa/rules/eager/operational-hazards.md +50 -0
  230. package/plugins/lisa/rules/eager/tracked-work.md +1 -1
  231. package/plugins/lisa/rules/reference/config-resolution.md +1 -1
  232. package/plugins/lisa/rules/reference/leaf-only-lifecycle.md +1 -1
  233. package/plugins/lisa/rules/reference/operational-hazards.md +115 -0
  234. package/plugins/lisa/rules/reference/work-item-trailer-definition.md +129 -0
  235. package/plugins/lisa/scripts/intake-blocker-reprobe.mjs +407 -26
  236. package/plugins/lisa/scripts/qa-signal-lifecycle.mjs +507 -0
  237. package/plugins/lisa/skills/lisa-drive-pr-to-merge/SKILL.md +130 -19
  238. package/plugins/lisa/skills/lisa-github-build-intake/SKILL.md +27 -4
  239. package/plugins/lisa/skills/lisa-github-prd-intake/SKILL.md +1 -1
  240. package/plugins/lisa/skills/lisa-github-validate-issue/SKILL.md +22 -0
  241. package/plugins/lisa/skills/lisa-github-verify/SKILL.md +14 -0
  242. package/plugins/lisa/skills/lisa-github-write-issue/SKILL.md +51 -0
  243. package/plugins/lisa/skills/lisa-intake/SKILL.md +1 -1
  244. package/plugins/lisa/skills/lisa-jira-build-intake/SKILL.md +27 -4
  245. package/plugins/lisa/skills/lisa-jira-validate-ticket/SKILL.md +22 -0
  246. package/plugins/lisa/skills/lisa-jira-verify/SKILL.md +14 -0
  247. package/plugins/lisa/skills/lisa-jira-write-ticket/SKILL.md +51 -0
  248. package/plugins/lisa/skills/lisa-linear-access/SKILL.md +55 -2
  249. package/plugins/lisa/skills/lisa-linear-build-intake/SKILL.md +27 -4
  250. package/plugins/lisa/skills/lisa-linear-prd-intake/SKILL.md +48 -31
  251. package/plugins/lisa/skills/lisa-linear-to-tracker/SKILL.md +3 -3
  252. package/plugins/lisa/skills/lisa-linear-validate-issue/SKILL.md +22 -0
  253. package/plugins/lisa/skills/lisa-linear-verify/SKILL.md +14 -0
  254. package/plugins/lisa/skills/lisa-linear-write-issue/SKILL.md +54 -3
  255. package/plugins/lisa/skills/lisa-prd-ticket-coverage/SKILL.md +2 -2
  256. package/plugins/lisa/skills/lisa-qa-clear/SKILL.md +19 -7
  257. package/plugins/lisa/skills/lisa-qa-fail/SKILL.md +28 -3
  258. package/plugins/lisa/skills/lisa-qa-queue/SKILL.md +28 -3
  259. package/plugins/lisa/skills/lisa-repair-intake/SKILL.md +94 -24
  260. package/plugins/lisa/skills/lisa-rework-triage/SKILL.md +32 -2
  261. package/plugins/lisa/skills/lisa-setup-linear/SKILL.md +5 -5
  262. package/plugins/lisa/skills/lisa-track/SKILL.md +1 -1
  263. package/plugins/lisa/skills/lisa-tracker-build-intake/SKILL.md +9 -0
  264. package/plugins/lisa-agy/agents/linear-prd-intake.md +8 -8
  265. package/plugins/lisa-agy/hooks/block-blind-automerge.sh +81 -2
  266. package/plugins/lisa-agy/hooks/block-direct-issue-create.agy.sh +64 -16
  267. package/plugins/lisa-agy/hooks/block-direct-issue-create.sh +293 -8
  268. package/plugins/lisa-agy/hooks/block-instruction-file-edits.sh +21 -0
  269. package/plugins/lisa-agy/hooks/block-managed-file-edits.sh +78 -15
  270. package/plugins/lisa-agy/hooks/block-shell-json-parsing.sh +21 -0
  271. package/plugins/lisa-agy/hooks/guard-dedupe.bash +337 -0
  272. package/plugins/lisa-agy/hooks/parity-safety-net.sh +29 -1
  273. package/plugins/lisa-agy/hooks.json +1 -1
  274. package/plugins/lisa-agy/plugin.json +1 -1
  275. package/plugins/lisa-agy/scripts/intake-blocker-reprobe.mjs +407 -26
  276. package/plugins/lisa-agy/scripts/qa-signal-lifecycle.mjs +507 -0
  277. package/plugins/lisa-agy/skills/lisa-drive-pr-to-merge/SKILL.md +130 -19
  278. package/plugins/lisa-agy/skills/lisa-github-build-intake/SKILL.md +27 -4
  279. package/plugins/lisa-agy/skills/lisa-github-prd-intake/SKILL.md +1 -1
  280. package/plugins/lisa-agy/skills/lisa-github-validate-issue/SKILL.md +22 -0
  281. package/plugins/lisa-agy/skills/lisa-github-verify/SKILL.md +14 -0
  282. package/plugins/lisa-agy/skills/lisa-github-write-issue/SKILL.md +51 -0
  283. package/plugins/lisa-agy/skills/lisa-intake/SKILL.md +1 -1
  284. package/plugins/lisa-agy/skills/lisa-jira-build-intake/SKILL.md +27 -4
  285. package/plugins/lisa-agy/skills/lisa-jira-validate-ticket/SKILL.md +22 -0
  286. package/plugins/lisa-agy/skills/lisa-jira-verify/SKILL.md +14 -0
  287. package/plugins/lisa-agy/skills/lisa-jira-write-ticket/SKILL.md +51 -0
  288. package/plugins/lisa-agy/skills/lisa-linear-access/SKILL.md +55 -2
  289. package/plugins/lisa-agy/skills/lisa-linear-build-intake/SKILL.md +27 -4
  290. package/plugins/lisa-agy/skills/lisa-linear-prd-intake/SKILL.md +48 -31
  291. package/plugins/lisa-agy/skills/lisa-linear-to-tracker/SKILL.md +3 -3
  292. package/plugins/lisa-agy/skills/lisa-linear-validate-issue/SKILL.md +22 -0
  293. package/plugins/lisa-agy/skills/lisa-linear-verify/SKILL.md +14 -0
  294. package/plugins/lisa-agy/skills/lisa-linear-write-issue/SKILL.md +54 -3
  295. package/plugins/lisa-agy/skills/lisa-prd-ticket-coverage/SKILL.md +2 -2
  296. package/plugins/lisa-agy/skills/lisa-qa-clear/SKILL.md +19 -7
  297. package/plugins/lisa-agy/skills/lisa-qa-fail/SKILL.md +28 -3
  298. package/plugins/lisa-agy/skills/lisa-qa-queue/SKILL.md +28 -3
  299. package/plugins/lisa-agy/skills/lisa-repair-intake/SKILL.md +94 -24
  300. package/plugins/lisa-agy/skills/lisa-rework-triage/SKILL.md +32 -2
  301. package/plugins/lisa-agy/skills/lisa-setup-linear/SKILL.md +5 -5
  302. package/plugins/lisa-agy/skills/lisa-track/SKILL.md +1 -1
  303. package/plugins/lisa-agy/skills/lisa-tracker-build-intake/SKILL.md +9 -0
  304. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  305. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  306. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  307. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  308. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  309. package/plugins/lisa-copilot/.claude-plugin/plugin.json +19 -1
  310. package/plugins/lisa-copilot/agents/linear-prd-intake.agent.md +8 -8
  311. package/plugins/lisa-copilot/hooks/block-blind-automerge.sh +81 -2
  312. package/plugins/lisa-copilot/hooks/block-direct-issue-create.sh +293 -8
  313. package/plugins/lisa-copilot/hooks/block-instruction-file-edits.sh +21 -0
  314. package/plugins/lisa-copilot/hooks/block-managed-file-edits.sh +78 -15
  315. package/plugins/lisa-copilot/hooks/block-no-verify.sh +21 -0
  316. package/plugins/lisa-copilot/hooks/block-shell-json-parsing.sh +21 -0
  317. package/plugins/lisa-copilot/hooks/failure-signature-index.mjs +240 -5
  318. package/plugins/lisa-copilot/hooks/guard-dedupe.bash +337 -0
  319. package/plugins/lisa-copilot/hooks/inject-rules.sh +13 -5
  320. package/plugins/lisa-copilot/hooks/operational-hazards.mjs +736 -0
  321. package/plugins/lisa-copilot/hooks/operational-hazards.sh +28 -0
  322. package/plugins/lisa-copilot/hooks/parity-safety-net.sh +29 -1
  323. package/plugins/lisa-copilot/hooks/threshold-ratchet-compare.mjs +102 -21
  324. package/plugins/lisa-copilot/hooks/threshold-ratchet-families.mjs +130 -2
  325. package/plugins/lisa-copilot/hooks/threshold-ratchet.mjs +63 -13
  326. package/plugins/lisa-copilot/hooks/worktree-binding-guard.mjs +183 -1
  327. package/plugins/lisa-copilot/hooks/worktree-binding-guard.sh +21 -0
  328. package/plugins/lisa-copilot/rules/eager/00-rule-index.md +1 -0
  329. package/plugins/lisa-copilot/rules/eager/operational-hazards.md +50 -0
  330. package/plugins/lisa-copilot/rules/eager/tracked-work.md +1 -1
  331. package/plugins/lisa-copilot/rules/reference/config-resolution.md +1 -1
  332. package/plugins/lisa-copilot/rules/reference/leaf-only-lifecycle.md +1 -1
  333. package/plugins/lisa-copilot/rules/reference/operational-hazards.md +115 -0
  334. package/plugins/lisa-copilot/rules/reference/work-item-trailer-definition.md +129 -0
  335. package/plugins/lisa-copilot/scripts/intake-blocker-reprobe.mjs +407 -26
  336. package/plugins/lisa-copilot/scripts/qa-signal-lifecycle.mjs +507 -0
  337. package/plugins/lisa-copilot/skills/lisa-drive-pr-to-merge/SKILL.md +130 -19
  338. package/plugins/lisa-copilot/skills/lisa-github-build-intake/SKILL.md +27 -4
  339. package/plugins/lisa-copilot/skills/lisa-github-prd-intake/SKILL.md +1 -1
  340. package/plugins/lisa-copilot/skills/lisa-github-validate-issue/SKILL.md +22 -0
  341. package/plugins/lisa-copilot/skills/lisa-github-verify/SKILL.md +14 -0
  342. package/plugins/lisa-copilot/skills/lisa-github-write-issue/SKILL.md +51 -0
  343. package/plugins/lisa-copilot/skills/lisa-intake/SKILL.md +1 -1
  344. package/plugins/lisa-copilot/skills/lisa-jira-build-intake/SKILL.md +27 -4
  345. package/plugins/lisa-copilot/skills/lisa-jira-validate-ticket/SKILL.md +22 -0
  346. package/plugins/lisa-copilot/skills/lisa-jira-verify/SKILL.md +14 -0
  347. package/plugins/lisa-copilot/skills/lisa-jira-write-ticket/SKILL.md +51 -0
  348. package/plugins/lisa-copilot/skills/lisa-linear-access/SKILL.md +55 -2
  349. package/plugins/lisa-copilot/skills/lisa-linear-build-intake/SKILL.md +27 -4
  350. package/plugins/lisa-copilot/skills/lisa-linear-prd-intake/SKILL.md +48 -31
  351. package/plugins/lisa-copilot/skills/lisa-linear-to-tracker/SKILL.md +3 -3
  352. package/plugins/lisa-copilot/skills/lisa-linear-validate-issue/SKILL.md +22 -0
  353. package/plugins/lisa-copilot/skills/lisa-linear-verify/SKILL.md +14 -0
  354. package/plugins/lisa-copilot/skills/lisa-linear-write-issue/SKILL.md +54 -3
  355. package/plugins/lisa-copilot/skills/lisa-prd-ticket-coverage/SKILL.md +2 -2
  356. package/plugins/lisa-copilot/skills/lisa-qa-clear/SKILL.md +19 -7
  357. package/plugins/lisa-copilot/skills/lisa-qa-fail/SKILL.md +28 -3
  358. package/plugins/lisa-copilot/skills/lisa-qa-queue/SKILL.md +28 -3
  359. package/plugins/lisa-copilot/skills/lisa-repair-intake/SKILL.md +94 -24
  360. package/plugins/lisa-copilot/skills/lisa-rework-triage/SKILL.md +32 -2
  361. package/plugins/lisa-copilot/skills/lisa-setup-linear/SKILL.md +5 -5
  362. package/plugins/lisa-copilot/skills/lisa-track/SKILL.md +1 -1
  363. package/plugins/lisa-copilot/skills/lisa-tracker-build-intake/SKILL.md +9 -0
  364. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  365. package/plugins/lisa-cursor/agents/linear-prd-intake.md +8 -8
  366. package/plugins/lisa-cursor/hooks/block-blind-automerge.sh +81 -2
  367. package/plugins/lisa-cursor/hooks/block-direct-issue-create.sh +293 -8
  368. package/plugins/lisa-cursor/hooks/block-instruction-file-edits.sh +21 -0
  369. package/plugins/lisa-cursor/hooks/block-managed-file-edits.sh +78 -15
  370. package/plugins/lisa-cursor/hooks/block-no-verify.sh +21 -0
  371. package/plugins/lisa-cursor/hooks/block-shell-json-parsing.sh +21 -0
  372. package/plugins/lisa-cursor/hooks/failure-signature-index.mjs +240 -5
  373. package/plugins/lisa-cursor/hooks/guard-dedupe.bash +337 -0
  374. package/plugins/lisa-cursor/hooks/hooks.json +10 -0
  375. package/plugins/lisa-cursor/hooks/operational-hazards.mjs +736 -0
  376. package/plugins/lisa-cursor/hooks/operational-hazards.sh +28 -0
  377. package/plugins/lisa-cursor/hooks/parity-safety-net.sh +29 -1
  378. package/plugins/lisa-cursor/hooks/threshold-ratchet-compare.mjs +102 -21
  379. package/plugins/lisa-cursor/hooks/threshold-ratchet-families.mjs +130 -2
  380. package/plugins/lisa-cursor/hooks/threshold-ratchet.mjs +63 -13
  381. package/plugins/lisa-cursor/hooks/worktree-binding-guard.mjs +183 -1
  382. package/plugins/lisa-cursor/hooks/worktree-binding-guard.sh +21 -0
  383. package/plugins/lisa-cursor/rules/00-rule-index.mdc +1 -0
  384. package/plugins/lisa-cursor/rules/config-resolution-reference.mdc +1 -1
  385. package/plugins/lisa-cursor/rules/leaf-only-lifecycle-reference.mdc +1 -1
  386. package/plugins/lisa-cursor/rules/operational-hazards-reference.mdc +120 -0
  387. package/plugins/lisa-cursor/rules/operational-hazards.mdc +55 -0
  388. package/plugins/lisa-cursor/rules/tracked-work.mdc +1 -1
  389. package/plugins/lisa-cursor/rules/work-item-trailer-definition-reference.mdc +134 -0
  390. package/plugins/lisa-cursor/scripts/intake-blocker-reprobe.mjs +407 -26
  391. package/plugins/lisa-cursor/scripts/qa-signal-lifecycle.mjs +507 -0
  392. package/plugins/lisa-cursor/skills/lisa-drive-pr-to-merge/SKILL.md +130 -19
  393. package/plugins/lisa-cursor/skills/lisa-github-build-intake/SKILL.md +27 -4
  394. package/plugins/lisa-cursor/skills/lisa-github-prd-intake/SKILL.md +1 -1
  395. package/plugins/lisa-cursor/skills/lisa-github-validate-issue/SKILL.md +22 -0
  396. package/plugins/lisa-cursor/skills/lisa-github-verify/SKILL.md +14 -0
  397. package/plugins/lisa-cursor/skills/lisa-github-write-issue/SKILL.md +51 -0
  398. package/plugins/lisa-cursor/skills/lisa-intake/SKILL.md +1 -1
  399. package/plugins/lisa-cursor/skills/lisa-jira-build-intake/SKILL.md +27 -4
  400. package/plugins/lisa-cursor/skills/lisa-jira-validate-ticket/SKILL.md +22 -0
  401. package/plugins/lisa-cursor/skills/lisa-jira-verify/SKILL.md +14 -0
  402. package/plugins/lisa-cursor/skills/lisa-jira-write-ticket/SKILL.md +51 -0
  403. package/plugins/lisa-cursor/skills/lisa-linear-access/SKILL.md +55 -2
  404. package/plugins/lisa-cursor/skills/lisa-linear-build-intake/SKILL.md +27 -4
  405. package/plugins/lisa-cursor/skills/lisa-linear-prd-intake/SKILL.md +48 -31
  406. package/plugins/lisa-cursor/skills/lisa-linear-to-tracker/SKILL.md +3 -3
  407. package/plugins/lisa-cursor/skills/lisa-linear-validate-issue/SKILL.md +22 -0
  408. package/plugins/lisa-cursor/skills/lisa-linear-verify/SKILL.md +14 -0
  409. package/plugins/lisa-cursor/skills/lisa-linear-write-issue/SKILL.md +54 -3
  410. package/plugins/lisa-cursor/skills/lisa-prd-ticket-coverage/SKILL.md +2 -2
  411. package/plugins/lisa-cursor/skills/lisa-qa-clear/SKILL.md +19 -7
  412. package/plugins/lisa-cursor/skills/lisa-qa-fail/SKILL.md +28 -3
  413. package/plugins/lisa-cursor/skills/lisa-qa-queue/SKILL.md +28 -3
  414. package/plugins/lisa-cursor/skills/lisa-repair-intake/SKILL.md +94 -24
  415. package/plugins/lisa-cursor/skills/lisa-rework-triage/SKILL.md +32 -2
  416. package/plugins/lisa-cursor/skills/lisa-setup-linear/SKILL.md +5 -5
  417. package/plugins/lisa-cursor/skills/lisa-track/SKILL.md +1 -1
  418. package/plugins/lisa-cursor/skills/lisa-tracker-build-intake/SKILL.md +9 -0
  419. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  420. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  421. package/plugins/lisa-expo-agy/plugin.json +1 -1
  422. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  423. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  424. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  425. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  426. package/plugins/lisa-harper-fabric/hooks/inject-rules.sh +14 -3
  427. package/plugins/lisa-harper-fabric/rules/eager/harper-fabric.md +31 -0
  428. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  429. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  430. package/plugins/lisa-harper-fabric-copilot/hooks/inject-rules.sh +14 -3
  431. package/plugins/lisa-harper-fabric-copilot/rules/eager/harper-fabric.md +31 -0
  432. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  433. package/plugins/lisa-harper-fabric-cursor/rules/harper-fabric-reference.mdc +57 -0
  434. package/plugins/lisa-harper-fabric-cursor/rules/harper-fabric.mdc +32 -53
  435. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  436. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  437. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  438. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  439. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  440. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  441. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  442. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  443. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  444. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  445. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  446. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  447. package/plugins/lisa-phaser/hooks/inject-rules.sh +14 -3
  448. package/plugins/lisa-phaser/rules/eager/phaser.md +44 -0
  449. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  450. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  451. package/plugins/lisa-phaser-copilot/hooks/inject-rules.sh +14 -3
  452. package/plugins/lisa-phaser-copilot/rules/eager/phaser.md +44 -0
  453. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  454. package/plugins/lisa-phaser-cursor/rules/phaser-reference.mdc +189 -0
  455. package/plugins/lisa-phaser-cursor/rules/phaser.mdc +45 -185
  456. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  457. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  458. package/plugins/lisa-rails/hooks/inject-rules.sh +14 -4
  459. package/plugins/lisa-rails/rules/eager/rails-conventions.md +23 -0
  460. package/plugins/lisa-rails-agy/plugin.json +1 -1
  461. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  462. package/plugins/lisa-rails-copilot/hooks/inject-rules.sh +14 -4
  463. package/plugins/lisa-rails-copilot/rules/eager/rails-conventions.md +23 -0
  464. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  465. package/plugins/lisa-rails-cursor/rules/rails-conventions-reference.mdc +181 -0
  466. package/plugins/lisa-rails-cursor/rules/rails-conventions.mdc +24 -177
  467. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  468. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  469. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  470. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  471. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  472. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  473. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  474. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  475. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  476. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  477. package/plugins/materialized-artifacts.json +1 -0
  478. package/plugins/src/base/.claude-plugin/plugin.json +27 -0
  479. package/plugins/src/base/agents/linear-prd-intake.md +8 -8
  480. package/plugins/src/base/hooks/block-blind-automerge.sh +81 -2
  481. package/plugins/src/base/hooks/block-direct-issue-create.agy.sh +64 -16
  482. package/plugins/src/base/hooks/block-direct-issue-create.sh +293 -8
  483. package/plugins/src/base/hooks/block-instruction-file-edits.sh +21 -0
  484. package/plugins/src/base/hooks/block-managed-file-edits.sh +78 -15
  485. package/plugins/src/base/hooks/block-no-verify.sh +21 -0
  486. package/plugins/src/base/hooks/block-shell-json-parsing.sh +21 -0
  487. package/plugins/src/base/hooks/failure-signature-index.mjs +240 -5
  488. package/plugins/src/base/hooks/guard-dedupe.bash +337 -0
  489. package/plugins/src/base/hooks/inject-rules.sh +13 -5
  490. package/plugins/src/base/hooks/operational-hazards.mjs +736 -0
  491. package/plugins/src/base/hooks/operational-hazards.sh +28 -0
  492. package/plugins/src/base/hooks/parity-safety-net.sh +29 -1
  493. package/plugins/src/base/hooks/threshold-ratchet-compare.mjs +102 -21
  494. package/plugins/src/base/hooks/threshold-ratchet-families.mjs +130 -2
  495. package/plugins/src/base/hooks/threshold-ratchet.mjs +63 -13
  496. package/plugins/src/base/hooks/worktree-binding-guard.mjs +183 -1
  497. package/plugins/src/base/hooks/worktree-binding-guard.sh +21 -0
  498. package/plugins/src/base/rules/eager/00-rule-index.md +1 -0
  499. package/plugins/src/base/rules/eager/operational-hazards.md +50 -0
  500. package/plugins/src/base/rules/eager/tracked-work.md +1 -1
  501. package/plugins/src/base/rules/reference/config-resolution.md +1 -1
  502. package/plugins/src/base/rules/reference/leaf-only-lifecycle.md +1 -1
  503. package/plugins/src/base/rules/reference/operational-hazards.md +115 -0
  504. package/plugins/src/base/rules/reference/work-item-trailer-definition.md +129 -0
  505. package/plugins/src/base/scripts/intake-blocker-reprobe.mjs +407 -26
  506. package/plugins/src/base/scripts/qa-signal-lifecycle.mjs +507 -0
  507. package/plugins/src/base/skills/lisa-drive-pr-to-merge/SKILL.md +130 -19
  508. package/plugins/src/base/skills/lisa-github-build-intake/SKILL.md +27 -4
  509. package/plugins/src/base/skills/lisa-github-prd-intake/SKILL.md +1 -1
  510. package/plugins/src/base/skills/lisa-github-validate-issue/SKILL.md +22 -0
  511. package/plugins/src/base/skills/lisa-github-verify/SKILL.md +14 -0
  512. package/plugins/src/base/skills/lisa-github-write-issue/SKILL.md +51 -0
  513. package/plugins/src/base/skills/lisa-intake/SKILL.md +1 -1
  514. package/plugins/src/base/skills/lisa-jira-build-intake/SKILL.md +27 -4
  515. package/plugins/src/base/skills/lisa-jira-validate-ticket/SKILL.md +22 -0
  516. package/plugins/src/base/skills/lisa-jira-verify/SKILL.md +14 -0
  517. package/plugins/src/base/skills/lisa-jira-write-ticket/SKILL.md +51 -0
  518. package/plugins/src/base/skills/lisa-linear-access/SKILL.md +55 -2
  519. package/plugins/src/base/skills/lisa-linear-build-intake/SKILL.md +27 -4
  520. package/plugins/src/base/skills/lisa-linear-prd-intake/SKILL.md +48 -31
  521. package/plugins/src/base/skills/lisa-linear-to-tracker/SKILL.md +3 -3
  522. package/plugins/src/base/skills/lisa-linear-validate-issue/SKILL.md +22 -0
  523. package/plugins/src/base/skills/lisa-linear-verify/SKILL.md +14 -0
  524. package/plugins/src/base/skills/lisa-linear-write-issue/SKILL.md +54 -3
  525. package/plugins/src/base/skills/lisa-prd-ticket-coverage/SKILL.md +2 -2
  526. package/plugins/src/base/skills/lisa-qa-clear/SKILL.md +19 -7
  527. package/plugins/src/base/skills/lisa-qa-fail/SKILL.md +28 -3
  528. package/plugins/src/base/skills/lisa-qa-queue/SKILL.md +28 -3
  529. package/plugins/src/base/skills/lisa-repair-intake/SKILL.md +94 -24
  530. package/plugins/src/base/skills/lisa-rework-triage/SKILL.md +32 -2
  531. package/plugins/src/base/skills/lisa-setup-linear/SKILL.md +5 -5
  532. package/plugins/src/base/skills/lisa-track/SKILL.md +1 -1
  533. package/plugins/src/base/skills/lisa-tracker-build-intake/SKILL.md +9 -0
  534. package/plugins/src/harper-fabric/hooks/inject-rules.sh +14 -3
  535. package/plugins/src/harper-fabric/rules/eager/harper-fabric.md +31 -0
  536. package/plugins/src/phaser/hooks/inject-rules.sh +14 -3
  537. package/plugins/src/phaser/rules/eager/phaser.md +44 -0
  538. package/plugins/src/rails/hooks/inject-rules.sh +14 -4
  539. package/plugins/src/rails/rules/eager/rails-conventions.md +23 -0
  540. package/rails/copy-overwrite/scripts/check-threshold-ratchet.mjs +63 -13
  541. package/rails/copy-overwrite/scripts/threshold-ratchet-compare.mjs +102 -21
  542. package/rails/copy-overwrite/scripts/threshold-ratchet-families.mjs +130 -2
  543. package/rails/create-only/.github/workflows/ci.yml +8 -0
  544. package/rails/create-only/.github/workflows/deploy.yml +22 -0
  545. package/scripts/build-plugins.sh +2 -1
  546. package/scripts/check-engine-floor.mjs +361 -0
  547. package/scripts/check-guard-parity-notes.mjs +709 -0
  548. package/scripts/check-template-workflow-refs.mjs +9 -0
  549. package/scripts/check-third-party-action-pins.mjs +17 -1
  550. package/scripts/check-workflow-contract-assertions.mjs +480 -0
  551. package/scripts/copy-opencode-plugin-templates.mjs +1 -0
  552. package/scripts/deployed-guard-advice.mjs +636 -0
  553. package/scripts/generate-agy-plugin-artifacts.mjs +35 -10
  554. package/scripts/generate-export-surface.mjs +105 -19
  555. package/scripts/generate-two-channel-couplings.ts +143 -212
  556. package/scripts/lisa-enforcement-fallback.sh +31 -0
  557. package/scripts/measure-scripts-profile-gap.mts +361 -0
  558. package/scripts/mutation-performance-measure.mjs +21 -5
  559. package/scripts/reconcile-release-tags.mjs +65 -84
  560. package/scripts/two-channel-couplings.json +302 -70
  561. package/scripts/workflow-contract-assertion.sh +47 -0
  562. package/typescript/copy-overwrite/.lintstagedrc.json +1 -0
  563. package/typescript/copy-overwrite/eslint.ignore.config.json +1 -0
  564. package/typescript/copy-overwrite/scripts/check-nightly-e2e-health.mjs +527 -53
  565. package/typescript/copy-overwrite/scripts/check-skipped-required-checks.mjs +301 -13
  566. package/typescript/copy-overwrite/scripts/check-threshold-ratchet.mjs +63 -13
  567. package/typescript/copy-overwrite/scripts/threshold-ratchet-compare.mjs +102 -21
  568. package/typescript/copy-overwrite/scripts/threshold-ratchet-families.mjs +130 -2
  569. package/typescript/create-only/.github/workflows/ci.yml +7 -0
  570. /package/plugins/lisa-harper-fabric/rules/{harper-fabric.md → reference/harper-fabric.md} +0 -0
  571. /package/plugins/lisa-harper-fabric-copilot/rules/{harper-fabric.md → reference/harper-fabric.md} +0 -0
  572. /package/plugins/lisa-phaser/rules/{phaser.md → reference/phaser.md} +0 -0
  573. /package/plugins/lisa-phaser-copilot/rules/{phaser.md → reference/phaser.md} +0 -0
  574. /package/plugins/lisa-rails/rules/{rails-conventions.md → reference/rails-conventions.md} +0 -0
  575. /package/plugins/lisa-rails-copilot/rules/{rails-conventions.md → reference/rails-conventions.md} +0 -0
  576. /package/plugins/src/harper-fabric/rules/{harper-fabric.md → reference/harper-fabric.md} +0 -0
  577. /package/plugins/src/phaser/rules/{phaser.md → reference/phaser.md} +0 -0
  578. /package/plugins/src/rails/rules/{rails-conventions.md → reference/rails-conventions.md} +0 -0
@@ -345,6 +345,8 @@ export const VIOLATIONS = Object.freeze({
345
345
  reviewWaived: "review_evidence_waived",
346
346
  reviewUnsatisfied: "review_evidence_unsatisfied",
347
347
  reviewCarried: "review_evidence_carried_unreviewed",
348
+ reviewObjected: "review_evidence_objected",
349
+ reviewObjectionUnread: "review_evidence_objection_unread",
348
350
  });
349
351
 
350
352
  /**
@@ -395,10 +397,14 @@ export const REVIEW_DESCRIPTION_DEFAULTS = Object.freeze({
395
397
  * what any change here can achieve: **the description is not a function of
396
398
  * whether a review happened.** `proof` is matched whole-string, so a
397
399
  * reviewed-and-objected pull request still carrying `Review rate limited` can
398
- * never reach it. Settling the question needs a second input — `reviews` /
399
- * `reviewThreads`which this script does not read and deliberately does not
400
- * fetch: that would make the claim checkable at the cost of turning a
401
- * description classifier into a network client (CodySwannGT/lisa#3827).
400
+ * never reach it. Settling the question needs a second input — the review
401
+ * OBJECTSand that input now exists, in the one place it can exist without
402
+ * turning this classifier into a network client (CodySwannGT/lisa#3706, #3827):
403
+ * {@link readReviewObjection} fetches `pulls/{pr}/reviews` from the ALREADY
404
+ * network-bound {@link inspectVacuity} arm, beside the check, carried-batch and
405
+ * waive-rate reads it already makes, and {@link readObjectionAtHead} judges the
406
+ * result purely. Everything in THIS classifier still reads only a description,
407
+ * and still may not conclude from `rate limited` that nothing ran.
402
408
  *
403
409
  * So the phrase stays in `no_work` — denying credit on it remains correct and
404
410
  * remains the safe direction — and only the WORDING changes. For these phrases
@@ -482,6 +488,12 @@ export const NEVER_BLOCKING = Object.freeze([
482
488
  // constituents' own gates already went red, on branches no ruleset watches.
483
489
  // What was missing was that the batch rendered green anyway.
484
490
  VIOLATIONS.reviewCarried,
491
+ // "The objection probe could not read the reviews" is the ABSENCE of a
492
+ // reading, not a finding about the code. It is reported — and it caps the
493
+ // rendered verdict, see `reviewGateVerdict` — but reddening a build because
494
+ // one API call failed would punish an author for a network, which is the
495
+ // "gate that gets deleted" shape this file names three times already.
496
+ VIOLATIONS.reviewObjectionUnread,
485
497
  ]);
486
498
 
487
499
  /**
@@ -521,6 +533,7 @@ export const NEVER_BLOCKING = Object.freeze([
521
533
  export const REPORT_ONLY = Object.freeze([
522
534
  VIOLATIONS.reviewWaived,
523
535
  VIOLATIONS.reviewCarried,
536
+ VIOLATIONS.reviewObjectionUnread,
524
537
  ]);
525
538
 
526
539
  /**
@@ -534,6 +547,15 @@ export const REPORT_ONLY = Object.freeze([
534
547
  */
535
548
  export const REVIEW_GATE_BLOCKING = Object.freeze([
536
549
  VIOLATIONS.reviewUnsatisfied,
550
+ // A review that RAN AND OBJECTED is the one condition no waiver covers, and
551
+ // this file has said so in prose since #3221 while having no way to observe
552
+ // it: the objection lives in a review OBJECT, and until #3706 nothing here
553
+ // read one. MEASURED on this repository, 60 most recently updated merged
554
+ // pull requests: 2 carried a standing `CHANGES_REQUESTED` at the merged head
555
+ // and the gate published `REVIEWED — CodeRabbit reported "Review completed"`.
556
+ // Blocking on it is the same policy `reviewUnsatisfied` already carries, and
557
+ // it is armed by the same `--require-review-evidence` flag.
558
+ VIOLATIONS.reviewObjected,
537
559
  ]);
538
560
 
539
561
  /**
@@ -588,6 +610,36 @@ const REQUIRE_REVIEW_EVIDENCE_FLAG = "--require-review-evidence";
588
610
  */
589
611
  const WAIVE_RATE_SAMPLE_FLAG = "--waive-rate-sample";
590
612
 
613
+ /**
614
+ * How many waivers in the sample escalate a waiver from `neutral` to `failure`.
615
+ *
616
+ * ## Unset by default, and that is the whole safety property
617
+ *
618
+ * CodySwannGT/lisa#3706 asks a question this script cannot answer for a
619
+ * repository: "should N waivers in a row stop being tolerated?" The waiver
620
+ * itself is the repository owner's ruling on CodySwannGT/lisa#3221 — a pull
621
+ * request author cannot fix a vendor entitlement — and that ruling stands until
622
+ * an owner replaces it. What was missing was not the judgement but the LEVER:
623
+ * the rate was already computed, printed, and acted on by nothing.
624
+ *
625
+ * So this ships the lever and leaves it down. With the flag absent, every
626
+ * waiver renders exactly as it did before, and no workflow in this tree passes
627
+ * it. Passing `--waive-rate-escalate=N` is a deliberate act with a number
628
+ * attached, and the number is measurable first: the same run prints the rate it
629
+ * would be compared against.
630
+ *
631
+ * ## It escalates the RENDERING, not the exit code
632
+ *
633
+ * {@link VIOLATIONS.reviewWaived} stays in {@link NEVER_BLOCKING} and in
634
+ * {@link REPORT_ONLY} whatever this is set to. Those two arrays ARE the #3221
635
+ * ruling, and a flag that reached through them would repeal an owner's decision
636
+ * from a command line. What escalation changes is the conclusion published on
637
+ * the check run — the one surface a merge decision reads — from `neutral` to
638
+ * `failure`. Whether that red can block anything is a separate, deliberate,
639
+ * human act: promoting the context in the live ruleset.
640
+ */
641
+ const WAIVE_RATE_ESCALATE_FLAG = "--waive-rate-escalate";
642
+
591
643
  /**
592
644
  * The one call the waive-rate sample makes.
593
645
  *
@@ -2082,6 +2134,166 @@ export function evaluateReviewGate(declaration, checks, options = {}) {
2082
2134
  return { violations, states, conditions, descriptions, checked };
2083
2135
  }
2084
2136
 
2137
+ /**
2138
+ * The review-OBJECT state that means a reviewer objected.
2139
+ *
2140
+ * A review object, never a status description. The two are different data and
2141
+ * the distinction is the entire content of CodySwannGT/lisa#3706: measured on
2142
+ * #3762, `CodeRabbit` reported `success` with the description
2143
+ * `Review rate limited` on a pull request it HAD reviewed, posting
2144
+ * `CHANGES_REQUESTED` that found a real defect. The same string, on a different
2145
+ * pull request the same week, meant nobody had read anything. A classifier
2146
+ * reading the string collapses those two, and it collapses them PERMISSIVELY.
2147
+ */
2148
+ export const REVIEW_OBJECTION_STATE = "CHANGES_REQUESTED";
2149
+
2150
+ /**
2151
+ * Whether an objection STANDS at this head, from the review objects.
2152
+ *
2153
+ * ## Filtered to the head, in both directions
2154
+ *
2155
+ * A review is counted only when its `commit_id` is this head. Leaving the
2156
+ * filter out breaks the gate in whichever direction the data happens to point:
2157
+ * a stale `CHANGES_REQUESTED` from three pushes ago would block work a newer
2158
+ * head already fixed (CodySwannGT/lisa#3720), and that is a false RED, which
2159
+ * this file argues repeatedly is the expensive kind to introduce while chasing
2160
+ * a false green. A review with no `commit_id` is dropped for the same reason —
2161
+ * unknown is not "at this head".
2162
+ *
2163
+ * MEASURED on the 60 most recently updated merged pull requests in this
2164
+ * repository: 4 carried a `CHANGES_REQUESTED` from the reviewer, and only 2 of
2165
+ * those were at the merged head. An unfiltered read would have reddened the
2166
+ * other two for objections their authors had already addressed.
2167
+ *
2168
+ * ## Reported, never inferred
2169
+ *
2170
+ * This says only what the review objects say. It does NOT consult the status
2171
+ * description, and nothing about a waiver, a rate limit or an entitlement
2172
+ * reaches it — that is the point of having a second input at all.
2173
+ * @param {ReadonlyArray<{state?: string, commitSha?: string, author?: string}>} reviews - Review objects
2174
+ * @param {string|undefined} headSha - The head commit under inspection
2175
+ * @returns {{objected: boolean, reviewers: string[], staleDropped: number}} What stands at head
2176
+ */
2177
+ export function readObjectionAtHead(reviews, headSha) {
2178
+ const all = reviews ?? [];
2179
+ const atHead =
2180
+ headSha === undefined || headSha === ""
2181
+ ? []
2182
+ : all.filter(review => review?.commitSha === headSha);
2183
+ const objections = atHead.filter(
2184
+ review =>
2185
+ String(review?.state ?? "").toUpperCase() === REVIEW_OBJECTION_STATE
2186
+ );
2187
+ return {
2188
+ objected: objections.length > 0,
2189
+ reviewers: [
2190
+ ...new Set(objections.map(review => String(review?.author ?? "unknown"))),
2191
+ ].sort((left, right) => left.localeCompare(right)),
2192
+ staleDropped: all.length - atHead.length,
2193
+ };
2194
+ }
2195
+
2196
+ /**
2197
+ * Reads the review objects on one pull request.
2198
+ *
2199
+ * REST rather than the rollup, for the reason the description read is REST:
2200
+ * `gh pr checks` and `gh pr view --json statusCheckRollup` cannot see a review
2201
+ * object at all, so neither can say whether an objection stands. The endpoint
2202
+ * carries `commit_id`, which is what makes the staleness filter possible.
2203
+ * @param {string|number} pr - The pull request
2204
+ * @param {string} [repo] - `OWNER/NAME`; defaults to the current repository
2205
+ * @returns {Array<{state: string, commitSha: string, author: string}>} Review objects
2206
+ * @throws {Error} When the slug cannot be resolved or `gh` cannot answer
2207
+ */
2208
+ export function fetchPullRequestReviews(pr, repo) {
2209
+ const slug = resolveRepoSlug(repo);
2210
+ if (slug === undefined) {
2211
+ throw new Error(
2212
+ `check-skipped-required-checks: cannot read the reviews on PR ${pr} without an OWNER/NAME. Pass \`--repo=OWNER/NAME\`, or set GITHUB_REPOSITORY.`
2213
+ );
2214
+ }
2215
+ return ghApiPaginatedArray(
2216
+ `repos/${slug}/pulls/${pr}/reviews?per_page=100`,
2217
+ "[.[] | {state: .state, commitSha: .commit_id, author: .user.login}]"
2218
+ );
2219
+ }
2220
+
2221
+ /**
2222
+ * Reads and judges whether a review objection stands at this head.
2223
+ *
2224
+ * FAILING TO READ IS NOT PASSING, the same way {@link readCarriedReview} means
2225
+ * it: an unreadable reviews endpoint reports UNREAD, which caps the rendered
2226
+ * verdict below `REVIEWED` rather than reporting a clean one nothing looked at.
2227
+ * It does not redden the build — see {@link NEVER_BLOCKING} — because the
2228
+ * failure is the gate's own, not the author's.
2229
+ * @param {string|number} pr - The pull request under inspection
2230
+ * @param {string|undefined} repo - `OWNER/NAME`; defaults to the current repository
2231
+ * @param {string|undefined} headSha - The head commit under inspection
2232
+ * @param {{fetchReviews?: Function}} [options] - Injection seam
2233
+ * @returns {{violations: object[], objected: boolean, reviewers: string[], staleDropped: number, unread?: string}} Findings and the standing
2234
+ */
2235
+ export function readReviewObjection(pr, repo, headSha, options = {}) {
2236
+ const read = options.fetchReviews ?? fetchPullRequestReviews;
2237
+ let reviews;
2238
+ try {
2239
+ reviews = read(pr, repo);
2240
+ } catch (error) {
2241
+ const why = error instanceof Error ? error.message : String(error);
2242
+ return {
2243
+ violations: [
2244
+ {
2245
+ kind: VIOLATIONS.reviewObjectionUnread,
2246
+ token: "objection",
2247
+ contexts: [],
2248
+ message: `The review objects on this pull request could not be read (${why}), so this run cannot say whether a reviewer OBJECTED at ${headSha ?? "the head commit"}. A status description cannot answer it: the same phrase has been observed on a pull request nothing reviewed and on one a reviewer objected to. Refusing to report a clean review nothing looked at.`,
2249
+ },
2250
+ ],
2251
+ objected: false,
2252
+ reviewers: [],
2253
+ staleDropped: 0,
2254
+ unread: why,
2255
+ };
2256
+ }
2257
+ const standing = readObjectionAtHead(reviews, headSha);
2258
+ if (!standing.objected) return { ...standing, violations: [] };
2259
+ return {
2260
+ ...standing,
2261
+ violations: [
2262
+ {
2263
+ kind: VIOLATIONS.reviewObjected,
2264
+ condition: REVIEW_GATE_CONDITIONS.objected,
2265
+ token: "objection",
2266
+ contexts: [],
2267
+ message: `A review RAN AND OBJECTED at ${headSha ?? "this head"}: ${standing.reviewers.join(", ")} submitted ${REVIEW_OBJECTION_STATE}. READ THE OBJECTION. This is the one condition no waiver covers, and it is invisible in the check's conclusion and in its description alike — both have been observed reading exactly as they do on a pull request nobody reviewed.${citeHeadSha(headSha)}`,
2268
+ },
2269
+ ],
2270
+ };
2271
+ }
2272
+
2273
+ /**
2274
+ * Whether a sampled waive rate has reached the escalation threshold.
2275
+ *
2276
+ * Three refusals, and each one is a direction an escalation could go wrong:
2277
+ *
2278
+ * - **No threshold** — nobody asked. The shipped default, and the reason this
2279
+ * change moves no merge.
2280
+ * - **No rate** — the sample failed. {@link sampleWaiveRate} is best-effort by
2281
+ * construction, so an unreachable API must cost a sentence, never a red. An
2282
+ * escalation on an unmeasured rate would be a verdict from a measurement that
2283
+ * never happened, which is the family of defect this whole file is about.
2284
+ * - **Nothing sampled** — a zero denominator. `0 >= 0` is true and would
2285
+ * escalate every waiver on an empty sample.
2286
+ * @param {{waived?: number, sampled?: number}|undefined} rate - The sampled tally
2287
+ * @param {number|undefined} threshold - Waivers at or above which to escalate
2288
+ * @returns {boolean} Whether the waiver escalates
2289
+ */
2290
+ export function waiverEscalates(rate, threshold) {
2291
+ if (!Number.isInteger(threshold) || threshold <= 0) return false;
2292
+ if (rate === undefined) return false;
2293
+ if (!Number.isInteger(rate.sampled) || rate.sampled <= 0) return false;
2294
+ return Number(rate.waived) >= threshold;
2295
+ }
2296
+
2085
2297
  /**
2086
2298
  * How many carried pull requests this will account for before it gives up.
2087
2299
  *
@@ -2244,6 +2456,21 @@ function waiveRateSuffix(rate) {
2244
2456
  : ` · waived on ${rate.waived} of the last ${rate.sampled} merged pull requests`;
2245
2457
  }
2246
2458
 
2459
+ /**
2460
+ * The unread-objection sentence appended to a verdict title, or nothing.
2461
+ *
2462
+ * Only when the probe FAILED. A probe that ran and found nothing standing is a
2463
+ * reading, and a title that announced it on every clean pull request would be
2464
+ * the noise that gets a sentence skimmed past on the one run it mattered.
2465
+ * @param {{unread?: string}|undefined} objection - The objection standing
2466
+ * @returns {string} The sentence, or an empty string
2467
+ */
2468
+ function objectionSuffix(objection) {
2469
+ return objection?.unread === undefined
2470
+ ? ""
2471
+ : " · whether a reviewer OBJECTED at this head was NOT read";
2472
+ }
2473
+
2247
2474
  /**
2248
2475
  * The carried-batch sentence appended to a verdict title, or nothing.
2249
2476
  *
@@ -2289,10 +2516,18 @@ function carriedSuffix(carried) {
2289
2516
  * out: every state above is read from THIS pull request's head, which is the
2290
2517
  * one commit a batch integration pull request does not speak for.
2291
2518
  *
2292
- * @param {{states?: Record<string, string>, conditions?: Record<string, string>, descriptions?: Record<string, string>, refusal?: {kind: string}|null, waiveRate?: {waived: number, sampled: number}, carried?: {unreviewed?: readonly string[], reviewed?: number, unread?: string}}} reading -
2519
+ * AND AN OBJECTION OUTRANKS ALL OF IT (#3706). Every state above is read from a
2520
+ * vendor's status DESCRIPTION, and the description has been measured saying the
2521
+ * opposite of the truth in both directions on the same string: `Review rate
2522
+ * limited` on a pull request nothing read, and `Review rate limited` on one the
2523
+ * reviewer read and objected to. `reading.objection` is the second input that
2524
+ * settles it, and it comes from review OBJECTS.
2525
+ *
2526
+ * @param {{states?: Record<string, string>, conditions?: Record<string, string>, descriptions?: Record<string, string>, refusal?: {kind: string}|null, waiveRate?: {waived: number, sampled: number}, carried?: {unreviewed?: readonly string[], reviewed?: number, unread?: string}, objection?: {objected?: boolean, reviewers?: readonly string[], unread?: string}, escalate?: number}} reading -
2293
2527
  * The gate's per-check states and conditions, the descriptions they were read
2294
- * from, any refusal, an optional sampled waive rate, and the tally for the
2295
- * pull requests this one CARRIES
2528
+ * from, any refusal, an optional sampled waive rate, the tally for the pull
2529
+ * requests this one CARRIES, whether a review objection stands at this head,
2530
+ * and any waive-rate escalation threshold this repository set
2296
2531
  * @returns {{verdict: string, conclusion: string, title: string}} What to publish
2297
2532
  */
2298
2533
  /**
@@ -2323,7 +2558,27 @@ export function reviewGateVerdict(reading = {}) {
2323
2558
  const descriptions = reading.descriptions ?? {};
2324
2559
  const names = Object.keys(states);
2325
2560
  const carried = carriedSuffix(reading.carried);
2326
- const suffix = `${carried}${waiveRateSuffix(reading.waiveRate)}`;
2561
+ const suffix = `${carried}${objectionSuffix(reading.objection)}${waiveRateSuffix(reading.waiveRate)}`;
2562
+
2563
+ // FIRST, AND ABOVE EVERY STATE READ FROM A DESCRIPTION (#3706). An objection
2564
+ // is the one thing here read from review OBJECTS rather than from a vendor
2565
+ // sentence, and it is the only reading that establishes a review HAPPENED and
2566
+ // said no. Every branch below it is settled by a description, and the
2567
+ // description has now been measured saying the opposite of the truth in both
2568
+ // directions on the same string. So an objection outranks a waiver — which
2569
+ // would otherwise merge a pull request a reviewer objected to, the permissive
2570
+ // collapse this ticket is about — and it outranks `undetermined`, because a
2571
+ // gate that stopped waiting has observed nothing while this has observed
2572
+ // something.
2573
+ if (reading.objection?.objected === true) {
2574
+ return {
2575
+ verdict: REVIEW_GATE_STATES.unsatisfied,
2576
+ conclusion: REVIEW_VERDICT_CONCLUSIONS.unsatisfied,
2577
+ title: fitTitle(
2578
+ `OBJECTED — a review RAN and requested changes at this head: ${reading.objection.reviewers.join(", ")} submitted ${REVIEW_OBJECTION_STATE}. This is NOT a waiver case and NOT an unreviewed one — READ THE OBJECTION${suffix}`
2579
+ ),
2580
+ };
2581
+ }
2327
2582
 
2328
2583
  if (reading.refusal || names.length === 0) {
2329
2584
  return {
@@ -2399,6 +2654,11 @@ export function reviewGateVerdict(reading = {}) {
2399
2654
  name => states[name] === REVIEW_GATE_STATES.waived
2400
2655
  );
2401
2656
  if (waived.length > 0) {
2657
+ // The lever from #3706, and it is DOWN unless a repository set it. See
2658
+ // `WAIVE_RATE_ESCALATE_FLAG`: the waiver's severity is the owner's ruling on
2659
+ // #3221 and stays report-only in the exit code either way; what a threshold
2660
+ // changes is the conclusion published where a merge decision reads it.
2661
+ const escalated = waiverEscalates(reading.waiveRate, reading.escalate);
2402
2662
  // AND THE TITLE IS WHERE THE CLAIM IS READ (#3706). "This pull request is
2403
2663
  // UNREVIEWED" is the sentence an operator quotes into a merge disclosure,
2404
2664
  // and it was printed unconditionally — measured false on 7 of the last 34
@@ -2406,17 +2666,18 @@ export function reviewGateVerdict(reading = {}) {
2406
2666
  // head. Severity does not move: both leads publish `neutral`, and the
2407
2667
  // waiver's rationale is untouched. Only the claim changes, and only when
2408
2668
  // EVERY waived check saw review activity, so one genuinely silent check
2409
- // alongside a reviewed one still leads with UNREVIEWED.
2669
+ // alongside a reviewed one still leads with UNREVIEWED. Orthogonal to the
2670
+ // escalation above: the rate decides the LEAD, this decides the CLAIM.
2410
2671
  const allWithReview = waived.every(
2411
2672
  name => conditions[name] === REVIEW_GATE_CONDITIONS.waivedWithReview
2412
2673
  );
2413
2674
  return {
2414
2675
  verdict: REVIEW_GATE_STATES.waived,
2415
- conclusion: REVIEW_VERDICT_CONCLUSIONS.waived,
2676
+ conclusion: escalated
2677
+ ? REVIEW_VERDICT_CONCLUSIONS.unsatisfied
2678
+ : REVIEW_VERDICT_CONCLUSIONS.waived,
2416
2679
  title: fitTitle(
2417
- allWithReview
2418
- ? `WAIVED — the review check could not review, and merging is a decision taken on that basis; but this pull request DOES carry review activity that raised no objection, so do NOT record it as unreviewed: ${waived.map(quote).join("; ")}${suffix}`
2419
- : `WAIVED — this pull request is UNREVIEWED and merging it is a decision taken on that basis: ${waived.map(quote).join("; ")}${suffix}`
2680
+ `${escalated ? "WAIVED, AND THE WAIVER RATE HAS PASSED THE THRESHOLD THIS REPOSITORY SET" : "WAIVED"} — ${allWithReview ? "the review check could not review, and merging is a decision taken on that basis; but this pull request DOES carry review activity that raised no objection, so do NOT record it as unreviewed" : "this pull request is UNREVIEWED and merging it is a decision taken on that basis"}: ${waived.map(quote).join("; ")}${suffix}`
2420
2681
  ),
2421
2682
  };
2422
2683
  }
@@ -2443,6 +2704,22 @@ export function reviewGateVerdict(reading = {}) {
2443
2704
  };
2444
2705
  }
2445
2706
 
2707
+ // A review ran and nothing here read whether it OBJECTED. Capped at `waived`
2708
+ // for the reason the carried branch is: `REVIEWED` would report a clean
2709
+ // review on the strength of a probe that failed, and the two states this file
2710
+ // exists to keep apart are "a review said nothing was wrong" and "nothing
2711
+ // established that". It blocks nothing — the failed read is the gate's, not
2712
+ // the author's — and it stops looking like a pass.
2713
+ if (reading.objection?.unread !== undefined) {
2714
+ return {
2715
+ verdict: REVIEW_GATE_STATES.waived,
2716
+ conclusion: REVIEW_VERDICT_CONCLUSIONS.waived,
2717
+ title: fitTitle(
2718
+ `REVIEWED, OBJECTION STANDING UNKNOWN — the review ran, and whether it requested changes at this head could not be read: ${names.map(quote).join("; ")}${suffix}`
2719
+ ),
2720
+ };
2721
+ }
2722
+
2446
2723
  return {
2447
2724
  verdict: REVIEW_GATE_STATES.satisfied,
2448
2725
  conclusion: REVIEW_VERDICT_CONCLUSIONS.satisfied,
@@ -3615,6 +3892,13 @@ export function inspectVacuity(argv, declaration, options = {}) {
3615
3892
  const carried = requireReviewEvidence
3616
3893
  ? readCarriedReview(declaration, pr, repo, options)
3617
3894
  : undefined;
3895
+ // #3706. Everything above is settled by a vendor's status DESCRIPTION, and
3896
+ // the description does not answer the question the gate is asking. This is
3897
+ // the second input — the review OBJECTS — and it is armed by the same flag as
3898
+ // the carried arm, so an ordinary run pays no read it did not ask for.
3899
+ const objection = requireReviewEvidence
3900
+ ? readReviewObjection(pr, repo, read.headSha, options)
3901
+ : undefined;
3618
3902
  // Sampled ONLY when this pull request is itself waived. The rate answers
3619
3903
  // "is this the exception or the rule?", a question that only arises once a
3620
3904
  // waiver is on the table, and every other run keeps the sampler's network
@@ -3639,6 +3923,7 @@ export function inspectVacuity(argv, declaration, options = {}) {
3639
3923
  ...absent.violations,
3640
3924
  ...gate.violations,
3641
3925
  ...(carried?.violations ?? []),
3926
+ ...(objection?.violations ?? []),
3642
3927
  ],
3643
3928
  absent: absent.absent,
3644
3929
  gateStates: gate.states,
@@ -3646,12 +3931,15 @@ export function inspectVacuity(argv, declaration, options = {}) {
3646
3931
  gateDescriptions: gate.descriptions,
3647
3932
  waiveRate,
3648
3933
  carried,
3934
+ objection,
3649
3935
  verdict: reviewGateVerdict({
3650
3936
  states: gate.states,
3651
3937
  conditions: gate.conditions,
3652
3938
  descriptions: gate.descriptions,
3653
3939
  waiveRate,
3654
3940
  carried,
3941
+ objection,
3942
+ escalate: readIntegerFlag(argv, WAIVE_RATE_ESCALATE_FLAG, undefined),
3655
3943
  }),
3656
3944
  settled: read.settled,
3657
3945
  refusal: null,
@@ -22,10 +22,20 @@
22
22
  * every entry carries a written decision.
23
23
  *
24
24
  * Human override: `.lisa.config.json` → `thresholdRatchet.allow` entries
25
- * ({ file, key, reason }). Honored ONLY from the baseline side (HEAD /
25
+ * ({ file, key, reason, until }). Honored ONLY from the baseline side (HEAD /
26
26
  * merge-base), never from the change under review — an agent cannot grant
27
27
  * itself an exception in the same change that weakens a gate. `key: "*"`
28
- * allows every key in the file.
28
+ * allows every key in the file, and reports as file-wide so it is not mistaken
29
+ * for a key-scoped one.
30
+ *
31
+ * Exemptions END (#3856). `until` is a `YYYY-MM-DD` day the entry is live
32
+ * through; past it the entry stops exempting and the weakening it permitted is
33
+ * refused again, naming the entry, its scope, its reason and the remedy. An
34
+ * entry naming no evaluable condition still exempts — that keeps a project
35
+ * mid-migration off a red wall — but is reported on every run until somebody
36
+ * gives it a condition or deletes it. Modelled on `_thresholdsDivergence` in
37
+ * `stryker.conf.json`, the sibling exemption that already stops exempting when
38
+ * it goes stale; see `threshold-ratchet-families.mjs` `ALLOW_STATE`.
29
39
  *
30
40
  * One exception, and only one: a PROMOTION between deploy-chain branches
31
41
  * (`--base` + `--head`, both named in `deploy.branches`, head upstream of
@@ -49,6 +59,7 @@ import {
49
59
  import {
50
60
  applyAllowList,
51
61
  compareFile,
62
+ describeAllowList,
52
63
  formatReport,
53
64
  } from "./threshold-ratchet-compare.mjs";
54
65
 
@@ -390,6 +401,13 @@ function run(mode, baseRef, onlyFiles, headRef) {
390
401
  );
391
402
  }
392
403
 
404
+ const allow = resolveAllowList(
405
+ root,
406
+ plan.baselineRef,
407
+ mode,
408
+ baseRef,
409
+ headRef
410
+ );
393
411
  const findings = watched.flatMap(f =>
394
412
  compareFile(
395
413
  f,
@@ -397,28 +415,60 @@ function run(mode, baseRef, onlyFiles, headRef) {
397
415
  plan.readCurrent(f)
398
416
  )
399
417
  );
400
- if (findings.length === 0) return 0;
418
+ // The allow-list inventory is printed whether or not the change produced
419
+ // findings. An exemption that has outlived its condition is dead weight the
420
+ // moment the condition passes, and reporting it only when something else has
421
+ // already failed is how a list nobody reads stays a list nobody reads.
422
+ if (findings.length === 0) {
423
+ reportAllowList(allow.entries, []);
424
+ return 0;
425
+ }
426
+ return reportFindings(findings, allow, mode);
427
+ }
401
428
 
402
- const allow = resolveAllowList(
403
- root,
404
- plan.baselineRef,
405
- mode,
406
- baseRef,
407
- headRef
408
- );
429
+ /**
430
+ * Print the allow entries a human has to act on, minus the ones a refusal is
431
+ * about to name in full.
432
+ * @param {object[]} entries The resolved allow list
433
+ * @param {Array<{ message: string }>} expired Refusals already being printed
434
+ * @returns {void}
435
+ */
436
+ function reportAllowList(entries, expired) {
437
+ for (const line of describeAllowList(entries)) {
438
+ if (expired.some(item => item.message.startsWith(line))) continue;
439
+ process.stdout.write(`threshold-ratchet: ${line}\n`);
440
+ }
441
+ }
442
+
443
+ /**
444
+ * Print the verdict for a change that produced findings.
445
+ * @param {object[]} findings Findings from every watched file
446
+ * @param {{ entries: object[], promotion: boolean, note: string | null }} allow
447
+ * The resolved allow list and its provenance
448
+ * @param {"hook"|"staged"|"base"} mode Comparison mode
449
+ * @returns {number} Process exit code (2 for hook mode, 1 otherwise; 0 clean)
450
+ */
451
+ function reportFindings(findings, allow, mode) {
409
452
  const split = partitionCarriedEntries(findings, allow.promotion);
410
- const { blocked, allowed } = applyAllowList(split.rest, allow.entries);
453
+ const { blocked, allowed, expired } = applyAllowList(
454
+ split.rest,
455
+ allow.entries
456
+ );
411
457
  if (allow.note) process.stdout.write(`${allow.note}\n`);
458
+ reportAllowList(allow.entries, expired);
412
459
  for (const finding of split.carried) {
413
460
  process.stdout.write(
414
461
  `threshold-ratchet: carried forward by this promotion, approved upstream — ${finding.message}\n`
415
462
  );
416
463
  }
417
- for (const finding of allowed) {
464
+ for (const item of allowed) {
418
465
  process.stdout.write(
419
- `threshold-ratchet: allowed by .lisa.config.json exception — ${finding.message}\n`
466
+ `threshold-ratchet: ${item.message} (.lisa.config.json exception)\n`
420
467
  );
421
468
  }
469
+ for (const item of expired) {
470
+ process.stderr.write(`threshold-ratchet: refused — ${item.message}\n`);
471
+ }
422
472
  if (blocked.length === 0) return 0;
423
473
  process.stderr.write(`${formatReport(blocked)}\n`);
424
474
  return mode === "hook" ? 2 : 1;