@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
@@ -26,4 +26,12 @@
26
26
  # failure this ledger exists to make impossible.
27
27
  .lisa/WITHDRAWN.jsonl merge=union
28
28
 
29
+ # Short-lived operational hazards, appended by any session that declares
30
+ # one (CodySwannGT/lisa#3681). Same append-only JSONL shape as the
31
+ # tombstones above and the same built-in union driver — a default line
32
+ # merge would drop a hazard, and a hazard nobody is told about is the
33
+ # collision this ledger exists to prevent. Entries carry an expiry and
34
+ # stop applying on their own, so the file drains without an editor.
35
+ .lisa/HAZARDS.jsonl merge=union
36
+
29
37
  # END: AI GUARDRAILS
@@ -86,6 +86,11 @@
86
86
  import { spawnSync } from "node:child_process";
87
87
 
88
88
  import { invokedAsScript } from "./lib/invoked-as-script.mjs";
89
+ // Downstream both files land flat in one `scripts/` directory, so this
90
+ // resolves there exactly as it does here. Imported rather than reimplemented:
91
+ // a second lane parser is a second answer to `whose is this?`, and two
92
+ // answers is the state this report exists to end.
93
+ import { parseLaneId, workItemLines } from "./lisa-work-item.mjs";
89
94
 
90
95
  /** Branch name prefixes that are never work-in-flight. */
91
96
  const IGNORED_PREFIXES = Object.freeze(["backup/", "gh-readonly-queue/"]);
@@ -254,9 +259,23 @@ export function branchesAhead(remote = "origin", base = "main") {
254
259
  * The work-item reference a branch is bound to, and how strongly.
255
260
  *
256
261
  * The trailer is asked first because it is the same binding the commit gate
257
- * enforces. It is routinely absent the branch that motivated this check
258
- * carried no trailer at all — so the branch name is the fallback, reported as
259
- * an inference rather than as a fact.
262
+ * enforces and it is read the way the gate reads it, through the exported
263
+ * `workItemLines`, over each commit's whole message.
264
+ *
265
+ * This used to ask git: `--format=%(trailers:key=Work-Item,valueonly)`. That is
266
+ * the obvious tool and it is the wrong one. Git's parser sees only a message's
267
+ * FINAL contiguous block of `Key: value` lines, and this fleet's convention
268
+ * puts `🤖 Generated with Claude Code` boilerplate below the trailer, which
269
+ * ends the block. Measured over non-merge commits that name an item: on a
270
+ * single multi-agent branch git found none on 47 of 51; over full
271
+ * default-branch history it missed 477 of 1,730. So the claim this docstring
272
+ * used to make — "it is routinely absent" — was mostly a property of the
273
+ * reader, not of the branches, and every one of those branches was ruled on
274
+ * from a branch-name INFERENCE while its canonical binding sat in the message
275
+ * unread. See the `work-item-trailer-definition` rule (#3747).
276
+ *
277
+ * The branch name remains the fallback for a branch that genuinely carries no
278
+ * trailer, reported as an inference rather than as a fact.
260
279
  * @param {string} branch Short branch name.
261
280
  * @param {string} base Default branch name.
262
281
  * @param {string} remote Remote name.
@@ -264,15 +283,12 @@ export function branchesAhead(remote = "origin", base = "main") {
264
283
  * @returns {{ref: string, refSource: string} | undefined} The reference.
265
284
  */
266
285
  export function workItemRef(branch, base, remote = "origin", exec = run) {
267
- const trailers = exec("git", [
286
+ const messages = exec("git", [
268
287
  "log",
269
- "--format=%(trailers:key=Work-Item,valueonly,separator=%x2C)",
288
+ "--format=%B",
270
289
  `${remote}/${base}..${remote}/${branch}`,
271
290
  ]);
272
- const fromTrailer = trailers
273
- ?.split(/[\n,]/u)
274
- .map(value => value.trim())
275
- .find(value => value.length > 0);
291
+ const [fromTrailer] = workItemLines(messages ?? "");
276
292
  if (fromTrailer) {
277
293
  const digits = REF_IN_TRAILER.exec(fromTrailer);
278
294
  if (digits) return { ref: digits[1], refSource: REF_SOURCE.TRAILER };
@@ -455,6 +471,38 @@ export function divergences(branches, base, remote = "origin", exec = run) {
455
471
  return counts;
456
472
  }
457
473
 
474
+ /**
475
+ * The lane that produced each branch's tip commit.
476
+ *
477
+ * Every session in this fleet pushes under ONE git identity, so `author.login`
478
+ * is a constant here and answers nothing: it is the same name on a branch a
479
+ * live session is still working and on one whose session ended days ago. The
480
+ * lane stamp is the only thing in the record that differs between them, which
481
+ * makes it the only thing this report can route on.
482
+ *
483
+ * A branch with no stamp is reported as unrouted rather than omitted. Silence
484
+ * about attribution reads as "nobody made this", and the sweep that motivated
485
+ * the stamp already reached two wrong conclusions that way.
486
+ * @param {ReadonlyArray<string>} branches Branch names, without the remote.
487
+ * @param {string} remote Remote the branches live on.
488
+ * @param {(command: string, args: string[]) => (string|undefined)} [exec] Runner.
489
+ * @returns {Map<string, string>} Lane id by branch, absent where none is stamped.
490
+ */
491
+ export function branchLanes(branches, remote = "origin", exec = run) {
492
+ const lanes = new Map();
493
+ for (const branch of branches) {
494
+ const message = exec("git", [
495
+ "log",
496
+ "-1",
497
+ "--format=%B",
498
+ `${remote}/${branch}`,
499
+ ]);
500
+ const lane = message === undefined ? undefined : parseLaneId(message);
501
+ if (lane !== undefined) lanes.set(branch, lane);
502
+ }
503
+ return lanes;
504
+ }
505
+
458
506
  /**
459
507
  * Branches carrying commits that no pull request has ever covered, each with
460
508
  * the tracker's verdict on whether the WORK survived.
@@ -467,12 +515,14 @@ export function divergences(branches, base, remote = "origin", exec = run) {
467
515
  * @param {Set<string>} inputs.submitted Branch names that have a pull request.
468
516
  * @param {Map<string, WorkItem>} [inputs.items] Work-item states by branch.
469
517
  * @param {Map<string, Divergence>} [inputs.divergence] Two-sided counts by branch.
518
+ * @param {Map<string, string>} [inputs.lanes] Lane id by branch.
470
519
  * @returns {ReadonlyArray<Candidate>} The candidates, each carrying a verdict.
471
520
  */
472
521
  export function orphanedBranches({
473
522
  ahead,
474
523
  divergence = new Map(),
475
524
  items = new Map(),
525
+ lanes = new Map(),
476
526
  submitted,
477
527
  }) {
478
528
  return ahead
@@ -488,6 +538,7 @@ export function orphanedBranches({
488
538
  baseLeads: sides ? sides.baseAhead > 0 : undefined,
489
539
  branch: row.branch,
490
540
  divergence: sides,
541
+ lane: lanes.get(row.branch),
491
542
  ref: item?.ref,
492
543
  refSource: item?.refSource,
493
544
  route: item?.route,
@@ -516,6 +567,7 @@ export function main(argv = process.argv.slice(2), probe = {}) {
516
567
  const {
517
568
  collectAhead = branchesAhead,
518
569
  collectDivergence = divergences,
570
+ collectLanes = branchLanes,
519
571
  collectSubmitted = branchesWithPullRequests,
520
572
  collectWorkItems = workItemStates,
521
573
  log = console.log,
@@ -553,6 +605,7 @@ export function main(argv = process.argv.slice(2), probe = {}) {
553
605
  ahead,
554
606
  divergence: collectDivergence(candidates, base, remote),
555
607
  items: collectWorkItems(candidates, base, remote),
608
+ lanes: collectLanes(candidates, remote),
556
609
  submitted,
557
610
  });
558
611
  log(
@@ -624,9 +677,25 @@ function renderRow(row, base) {
624
677
  lines.push(
625
678
  ` ${row.ahead} commit(s) ahead of ${base}${direction(row, base)}`
626
679
  );
680
+ lines.push(` ${routing(row)}`);
627
681
  return lines;
628
682
  }
629
683
 
684
+ /**
685
+ * Who this branch can be handed back to.
686
+ *
687
+ * The lane is a one-way digest of an id the agent runtime supplied: it names
688
+ * no session, no URL and no person, and a live session recognises its own work
689
+ * by deriving the same token rather than by being told whose it is.
690
+ * @param {Candidate} row A row from `orphanedBranches`.
691
+ * @returns {string} The routing clause.
692
+ */
693
+ function routing(row) {
694
+ if (row.lane === undefined)
695
+ return "no lane on the tip commit, so this cannot be routed to a session — it predates the stamp, or was made outside a Lisa hook";
696
+ return `produced by ${row.lane} — a session confirms this is its own with \`node scripts/lisa-work-item.mjs lane\``;
697
+ }
698
+
630
699
  /**
631
700
  * The conflict-direction clause for one candidate.
632
701
  *
@@ -160,8 +160,24 @@ const NO_VERSION = "missing-version-comment";
160
160
  * `uses: <owner>/<repo>[/<subpath>]@<ref>` with anything after it captured, so
161
161
  * a trailing `# v1.2.3` can be read. Local (`./path`) and container
162
162
  * (`docker://`) references have no owner and never match.
163
+ *
164
+ * The value may be QUOTED, and that is not a stylistic detail here. YAML
165
+ * accepts `uses: "owner/repo@ref"` and `uses: 'owner/repo@ref'` as the same
166
+ * scalar GitHub Actions resolves identically, but a value class that excluded
167
+ * quotes could not begin matching at one — so a quoted reference did not fail
168
+ * the scan, it left it entirely, and the gate reported clean over an entry it
169
+ * had never inspected. A pin checker that silently skips a spelling is a
170
+ * vacuous pass on every reference written that way, which is the failure this
171
+ * gate exists to close rather than to reproduce.
172
+ *
173
+ * The closing quote is matched by back-reference so `"a'` cannot read as a
174
+ * quoted value, and it is consumed by the match so the trailing `# v1.2.3` is
175
+ * still found by the slice in `findActionRefs`. An unquoted value binds the
176
+ * group to the empty string, where the back-reference matches empty and the
177
+ * pattern behaves exactly as it did before.
163
178
  */
164
- const USES_RE = /^[ \t]*(?:-[ \t]*)?uses:[ \t]*(?<value>[^\s"'#]+)/;
179
+ const USES_RE =
180
+ /^[ \t]*(?:-[ \t]*)?uses:[ \t]*(?<quote>["']?)(?<value>[^\s"'#]+)\k<quote>/;
165
181
 
166
182
  /** A well-formed action owner. Anchored, so matching is linear. */
167
183
  const OWNER_RE = /^[A-Za-z0-9][A-Za-z0-9-]*$/;
@@ -11,6 +11,20 @@
11
11
  * This small asynchronous supervisor is invoked through a synchronous parent:
12
12
  * it owns the process-group id, applies the deadline, terminates the group, and
13
13
  * only then lets the parent continue.
14
+ *
15
+ * ## Interrupting a run
16
+ *
17
+ * The deadline was never the only way a run ends early. An operator — or an
18
+ * agent told to free a contended machine — kills the gate runner, and because
19
+ * this supervisor holds the ONLY handle to the detached group it started,
20
+ * killing its parent used to leave it reparented to pid 1 with the whole tree
21
+ * still executing. Measured: the kill reported success, `pgrep` for the runner
22
+ * returned nothing, and eleven processes carried on (CodySwannGT/lisa#3829).
23
+ *
24
+ * So the supervisor watches for the run it serves going away, tears the tree
25
+ * down through the same single reap the deadline uses, and SAYS SO. A silent
26
+ * teardown would be the same defect wearing a different face: the operator who
27
+ * asked for the run to stop cannot tell a stop from a survival either way.
14
28
  * @module scripts/lib/process-tree-runner
15
29
  */
16
30
 
@@ -22,6 +36,16 @@ const KILL_GRACE_MS = 750;
22
36
  const REAP_POLL_MS = 25;
23
37
  const WINDOWS_TIMEOUT_EXIT_CODE = 255;
24
38
  const TERMINATING_SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
39
+
40
+ /**
41
+ * How often the supervisor asks whether the run it serves is still there.
42
+ *
43
+ * Four times a second. This is a liveness poll, not a budget: the cost of
44
+ * asking is one `kill(pid, 0)` per watched pid, and the cost of asking too
45
+ * rarely is measured in whole test suites that keep running for a machine
46
+ * nobody is watching any more.
47
+ */
48
+ const ORPHAN_POLL_MS = 250;
25
49
  const DEFAULT_REAP_CONTROLS = Object.freeze({
26
50
  kill: killTree,
27
51
  exists: treeExists,
@@ -41,17 +65,126 @@ export function timeoutVerdictForPlatform(platform) {
41
65
  : { code: null, signal: "SIGKILL" };
42
66
  }
43
67
 
68
+ /**
69
+ * Whether a pid is worth watching for disappearance.
70
+ *
71
+ * `1` is excluded on purpose rather than by accident. It is both the pid an
72
+ * orphan is reparented ONTO and a process that never exits, so watching it
73
+ * would arm a condition that can never fire while looking exactly like a
74
+ * watch that is working.
75
+ * @param {unknown} value A candidate pid.
76
+ * @returns {boolean} Whether it names a process that can meaningfully die.
77
+ */
78
+ export function isWatchablePid(value) {
79
+ return Number.isInteger(value) && value > 1;
80
+ }
81
+
82
+ /**
83
+ * Parse the pids this supervisor was asked to outlive nobody but.
84
+ * @param {readonly string[]} argv Raw arguments.
85
+ * @returns {number[]} Watchable pids, in the order given, without duplicates.
86
+ */
87
+ export function parseWatchPids(argv) {
88
+ const prefix = "--watch-pid=";
89
+ const pids = argv
90
+ .filter(value => value.startsWith(prefix))
91
+ .map(value => Number(value.slice(prefix.length)))
92
+ .filter(pid => isWatchablePid(pid));
93
+ return [...new Set(pids)];
94
+ }
95
+
44
96
  function parseArguments(argv) {
45
97
  const timeoutArg = argv.find(value => value.startsWith("--timeout-ms="));
46
98
  const separator = argv.indexOf("--");
47
99
  const timeoutMs = Number(timeoutArg?.slice("--timeout-ms=".length));
100
+ const flags = separator >= 0 ? argv.slice(0, separator) : argv;
48
101
  const command = separator >= 0 ? argv.slice(separator + 1).join(" ") : "";
49
102
  if (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || !command) {
50
103
  throw new Error(
51
- "usage: process-tree-runner.mjs --timeout-ms=<positive-ms> -- <command>"
104
+ "usage: process-tree-runner.mjs --timeout-ms=<positive-ms> " +
105
+ "[--watch-pid=<pid>]... -- <command>"
52
106
  );
53
107
  }
54
- return { command, timeoutMs };
108
+ return { command, timeoutMs, watchPids: parseWatchPids(flags) };
109
+ }
110
+
111
+ /**
112
+ * Whether a pid no longer names a live process.
113
+ *
114
+ * Fail-CLOSED, matching every other liveness question in this file: only the
115
+ * kernel's `ESRCH` proves absence. An `EPERM` — a pid that outlived the run and
116
+ * was reused by another user's process — means the question was refused, not
117
+ * answered, and refusing to answer must never be read as "the run is over".
118
+ * That direction matters here more than anywhere else in the module: a wrong
119
+ * "gone" tears down a gate run that was still executing correctly.
120
+ * @param {number} pid The process to ask about.
121
+ * @param {typeof process.kill} probe Injectable process probe.
122
+ * @returns {boolean} Whether the process is provably absent.
123
+ */
124
+ export function pidIsGone(pid, probe = process.kill) {
125
+ try {
126
+ probe(pid, 0);
127
+ return false;
128
+ } catch (error) {
129
+ return isAbsentProcessError(error);
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Why this supervisor should stop, if it should.
135
+ *
136
+ * Two separable questions, deliberately answered in one place so the report
137
+ * can name WHICH one fired:
138
+ *
139
+ * 1. **Orphaning.** The process that started this supervisor is gone, so the
140
+ * verdict this run would produce has no one left to receive it. Detected by
141
+ * comparing the parent pid captured at start against the live one — a
142
+ * reparented process reads `1` (or a subreaper) instead of its launcher.
143
+ * 2. **A watched pid died.** The caller named a process further up the chain —
144
+ * a git hook, say — whose death means the run is moot even though this
145
+ * supervisor's own parent is still alive.
146
+ *
147
+ * Watch-pid absence is proved with `pidIsGone`, which fails closed; the
148
+ * orphan check needs no such care because a changed parent pid is a fact the
149
+ * kernel reports directly and cannot be refused.
150
+ * @param {object} options Inputs.
151
+ * @param {number} options.launchParentPid Parent pid captured at start.
152
+ * @param {number} options.currentParentPid Parent pid right now.
153
+ * @param {readonly number[]} [options.watchPids] Pids to outlive nobody but.
154
+ * @param {(pid: number) => boolean} [options.gone] Injectable absence probe.
155
+ * @returns {string|null} An operator-readable reason, or null to keep running.
156
+ */
157
+ export function interruptionReason({
158
+ launchParentPid,
159
+ currentParentPid,
160
+ watchPids = [],
161
+ gone = pidIsGone,
162
+ }) {
163
+ if (isWatchablePid(launchParentPid) && currentParentPid !== launchParentPid) {
164
+ return `the run that started it (pid ${launchParentPid}) exited`;
165
+ }
166
+ const dead = watchPids.find(pid => gone(pid));
167
+ if (dead !== undefined) return `the run it serves (pid ${dead}) exited`;
168
+ return null;
169
+ }
170
+
171
+ /**
172
+ * The line an interrupted supervisor leaves behind.
173
+ *
174
+ * A silent teardown reintroduces the defect in a different form: the operator
175
+ * who asked for the run to stop gets the same silence whether it stopped or
176
+ * kept running. So this states three things — that the run was INTERRUPTED
177
+ * rather than completed, why, and what was terminated — and it goes to stderr,
178
+ * which the gate runner inherits.
179
+ * @param {string} reason Why the supervisor stopped.
180
+ * @param {number} pid The process-group leader that was reaped.
181
+ * @returns {string} One line, for stderr.
182
+ */
183
+ export function interruptionReport(reason, pid) {
184
+ return (
185
+ `🛑 gate command INTERRUPTED — ${reason}; terminated its process tree ` +
186
+ `(group ${pid}) and every descendant. Nothing was proved by it.`
187
+ );
55
188
  }
56
189
 
57
190
  /** Whether a failed POSIX process-group operation proved the group is absent. */
@@ -265,7 +398,32 @@ export async function reapTree(pid, controls = DEFAULT_REAP_CONTROLS) {
265
398
  throw new Error(`gate process tree ${pid} survived SIGKILL`);
266
399
  }
267
400
 
268
- export function supervise(command, timeoutMs, reap = reapTree) {
401
+ /**
402
+ * Run one command as a supervised process tree.
403
+ *
404
+ * The supervisor answers to three ways a run can end early — its deadline, a
405
+ * terminating signal, and now the run it serves going away — and all three
406
+ * share one reap, because a tree reaped twice is a tree whose second kill
407
+ * lands on a recycled pid.
408
+ * @param {string} command Shell source to supervise.
409
+ * @param {number} timeoutMs Deadline for the whole tree.
410
+ * @param {typeof reapTree} [reap] Injectable group reaper.
411
+ * @param {object} [options] Interrupt-watch seams, injectable for tests.
412
+ * @param {readonly number[]} [options.watchPids] Pids whose death ends this run.
413
+ * @param {typeof interruptionReason} [options.detect] Interrupt predicate.
414
+ * @param {number} [options.pollMs] How often to ask.
415
+ * @param {(line: string) => void} [options.report] Where the report goes.
416
+ * @param {number} [options.launchParentPid] Parent pid captured at start.
417
+ * @returns {Promise<{code: number|null, signal: string|null}>} The verdict.
418
+ */
419
+ export function supervise(command, timeoutMs, reap = reapTree, options = {}) {
420
+ const {
421
+ watchPids = [],
422
+ detect = interruptionReason,
423
+ pollMs = ORPHAN_POLL_MS,
424
+ report = line => process.stderr.write(`${line}\n`),
425
+ launchParentPid = process.ppid,
426
+ } = options;
269
427
  return new Promise((resolve, reject) => {
270
428
  const shell =
271
429
  process.platform === "win32"
@@ -336,6 +494,7 @@ export function supervise(command, timeoutMs, reap = reapTree) {
336
494
  };
337
495
  const cleanup = () => {
338
496
  clearDeadline();
497
+ clearInterval(interruptWatch);
339
498
  clearSignalHandlers();
340
499
  };
341
500
  // Every exit path shares one reap. A signal that arrives while the direct
@@ -376,17 +535,43 @@ export function supervise(command, timeoutMs, reap = reapTree) {
376
535
  );
377
536
  };
378
537
 
379
- const relaySignal = signal => {
380
- if (terminating) return;
538
+ // Both early-stop paths below end the same way: reap the tree ONCE, say
539
+ // what was terminated, then take the signal-shaped exit. `stopWith` exists
540
+ // so the reporting cannot drift between them — a signal that tears the
541
+ // tree down silently and a watch that reports is the same defect twice.
542
+ const stopWith = (reason, signal) => {
543
+ if (terminating || settled) return;
381
544
  terminating = true;
382
545
  settling = true;
383
546
  clearDeadline();
547
+ clearInterval(interruptWatch);
384
548
  reapOnce().then(() => {
385
549
  cleanup();
550
+ report(interruptionReport(reason, pid));
386
551
  // Restore the signal-shaped exit after the detached tree is gone.
387
552
  process.kill(process.pid, signal);
388
553
  }, failReap);
389
554
  };
555
+ const relaySignal = signal => {
556
+ stopWith(`it received ${signal}`, signal);
557
+ };
558
+ // The wiring this module always had the means for and nothing invoked.
559
+ // Without it, killing the gate runner leaves this supervisor reparented to
560
+ // pid 1 with its whole detached tree still executing — measured, and the
561
+ // reason the tree survives is precisely that this supervisor holds the
562
+ // only handle to it. Nothing else on the machine knows the group id.
563
+ const interruptWatch = setInterval(() => {
564
+ if (terminating || settling || settled) return;
565
+ const reason = detect({
566
+ launchParentPid,
567
+ currentParentPid: process.ppid,
568
+ watchPids,
569
+ });
570
+ // SIGTERM rather than the signal nobody sent: this supervisor was not
571
+ // signalled, it outlived its purpose, and SIGTERM is the vocabulary the
572
+ // gate runner already reads as "terminated, therefore no verdict".
573
+ if (reason !== null) stopWith(reason, "SIGTERM");
574
+ }, pollMs);
390
575
  const deadline = setTimeout(() => {
391
576
  timedOut = true;
392
577
  terminating = true;
@@ -432,8 +617,10 @@ export function supervise(command, timeoutMs, reap = reapTree) {
432
617
  }
433
618
 
434
619
  async function main() {
435
- const { command, timeoutMs } = parseArguments(process.argv.slice(2));
436
- const result = await supervise(command, timeoutMs);
620
+ const { command, timeoutMs, watchPids } = parseArguments(
621
+ process.argv.slice(2)
622
+ );
623
+ const result = await supervise(command, timeoutMs, reapTree, { watchPids });
437
624
  if (result.signal) {
438
625
  process.kill(process.pid, result.signal);
439
626
  } else {
@@ -832,6 +832,37 @@ if [ "$notice_due" -eq 1 ]; then
832
832
  fi
833
833
  fi
834
834
 
835
+ # ---------------------------------------------------------------------------
836
+ # One evaluation per guard per tool call
837
+ #
838
+ # The guards below are also registered individually by the plugin manifest, so
839
+ # on a machine where both channels are live each one runs twice for a single
840
+ # tool call. `guard-dedupe.bash`, sourced by each guard, lets the second copy
841
+ # replay the first copy's ALLOW instead of recomputing it — and only when the
842
+ # two copies are byte-identical, on the identical payload, within the identical
843
+ # tool call. Nothing above is de-registered for it, because plugin-hook liveness
844
+ # is not observable from disk and standing down on a disk guess is how this
845
+ # dispatcher once switched enforcement off silently (CodySwannGT/lisa#3814).
846
+ #
847
+ # What is handed down here is only the state directory. `$notice_temp_base` has
848
+ # already been resolved physically and had every parent checked for ownership
849
+ # and mode, so the eight guards this loop spawns inherit that work instead of
850
+ # repeating it eight times. A guard reached by the plugin channel has no such
851
+ # parent and resolves its own; a guard that finds this variable unusable does
852
+ # the same. Exported only when the chain was trusted, so an untrusted TMPDIR
853
+ # hands down nothing rather than handing down a bad path.
854
+ if [ -n "$notice_temp_base" ] && [ "$notice_state_trusted" -eq 1 ]; then
855
+ guard_memo_dir="$notice_temp_base/lisa-guard-memo-$notice_uid"
856
+ if [ ! -e "$guard_memo_dir" ] && [ ! -L "$guard_memo_dir" ]; then
857
+ (umask 077 && mkdir "$guard_memo_dir") 2>/dev/null || true
858
+ fi
859
+ if [ -d "$guard_memo_dir" ] && [ ! -L "$guard_memo_dir" ] && \
860
+ [ -O "$guard_memo_dir" ]; then
861
+ export LISA_GUARD_MEMO_DIR="$guard_memo_dir"
862
+ export LISA_GUARD_MEMO_UID="$notice_uid"
863
+ fi
864
+ fi
865
+
835
866
  # ---------------------------------------------------------------------------
836
867
  # Dispatch
837
868
  status=0
@@ -165,6 +165,27 @@ set -euo pipefail
165
165
 
166
166
  input="$(cat)"
167
167
 
168
+ # Evaluate once per tool call when this guard is registered on both channels.
169
+ #
170
+ # Lisa reaches an agent through the repository dispatcher AND the plugin
171
+ # manifest, and where both are live the harness runs this guard twice for one
172
+ # tool call. `guard-dedupe.bash` short-circuits the second run ONLY when a
173
+ # byte-identical copy already ALLOWED this exact payload on this exact tool
174
+ # call; a differing vintage, a refusal, and a host with one channel all
175
+ # evaluate exactly as before. Nothing is de-registered by it
176
+ # (CodySwannGT/lisa#3814).
177
+ #
178
+ # Absent library means no dedupe, which is the pre-existing behaviour, so an
179
+ # older channel copy that predates it is unaffected.
180
+ lisa_guard_hook_dir="${BASH_SOURCE[0]%/*}"
181
+ lisa_guard_dedupe_lib="$lisa_guard_hook_dir/guard-dedupe.bash"
182
+ if [ -r "$lisa_guard_dedupe_lib" ]; then
183
+ # shellcheck source=guard-dedupe.bash
184
+ . "$lisa_guard_dedupe_lib"
185
+ trap 'lisa_guard_dedupe_record $?' EXIT
186
+ lisa_guard_dedupe block-blind-automerge "$input"
187
+ fi
188
+
168
189
  # Both interpreters are probed BEFORE use, and a missing one is ANNOUNCED
169
190
  # rather than swallowed. Under `set -e` an absent jq would abort with 127, and
170
191
  # Claude Code treats any non-2 exit as a non-blocking hook error — so the guard
@@ -247,9 +268,41 @@ PULL_REQUEST_ID_PATTERN = re.compile(
247
268
  NODE_ID_PATTERN = re.compile(r"^[A-Za-z0-9_=-]+$")
248
269
 
249
270
  # The probe that resolves a node id to the same fields `gh pr view` returns.
271
+ #
272
+ # `statusCheckRollup` is in here because the RE-TARGET path needs it and this
273
+ # is the only probe a GraphQL `updatePullRequest` can use. Without it
274
+ # `failing_check_names` read an absent key, found no failing check, and the
275
+ # guard announced the re-target as the sanctioned green-PR batching case —
276
+ # for a pull request that might be failing every check it has. The porcelain
277
+ # path asked the right question and the API path could not, so the identical
278
+ # act was guarded through `gh pr edit --base` and waved through as GraphQL:
279
+ # a bypass that needs no permission, only a different spelling.
280
+ #
281
+ # The nodes are reshaped by `--jq` below into the flat list `gh pr view
282
+ # --json statusCheckRollup` returns, so one reader serves both probes.
250
283
  NODE_QUERY = (
251
284
  "query($id:ID!){node(id:$id){... on PullRequest"
252
- "{number url reviewDecision state baseRefName}}}"
285
+ "{number url reviewDecision state baseRefName "
286
+ "commits(last:1){nodes{commit{statusCheckRollup{contexts(first:100){nodes{"
287
+ "__typename ... on CheckRun{name conclusion} "
288
+ "... on StatusContext{context state}"
289
+ "}}}}}}}}}"
290
+ )
291
+
292
+ # Reshape the envelope into the porcelain's shape: unwrap `.data.node`, hoist
293
+ # the last commit's rollup contexts to a top-level `statusCheckRollup` list,
294
+ # and drop the `commits` scaffolding that carried it.
295
+ # `has("commits")` rather than an unconditional default, and the distinction is
296
+ # the point: a node that CARRIES the scaffolding and no contexts is a PR with no
297
+ # checks, which is genuinely not failing and gets an empty list. A node without
298
+ # the scaffolding is a probe that never asked, and it must leave the key absent
299
+ # so the re-target path can tell the two apart instead of reading both as green.
300
+ NODE_JQ = (
301
+ "if .data.node == null then null else .data.node as $n "
302
+ "| if ($n | has(\"commits\")) then ($n | del(.commits)) "
303
+ "+ {statusCheckRollup: "
304
+ "((($n.commits.nodes[0].commit.statusCheckRollup.contexts.nodes)) // [])} "
305
+ "else $n end end"
253
306
  )
254
307
 
255
308
  # What a probe spec asks for. `view` and `node` differ only in how the PR is
@@ -728,7 +781,7 @@ def probe_args(kind, subject, repo_args, fields):
728
781
  "gh", "api", "graphql",
729
782
  "-f", "query=" + NODE_QUERY,
730
783
  "-f", "id=" + subject,
731
- "--jq", ".data.node",
784
+ "--jq", NODE_JQ,
732
785
  ]
733
786
  args = ["gh", "pr", "view"]
734
787
  if subject is not None:
@@ -969,6 +1022,20 @@ Do one of these instead:
969
1022
  - leave the PR on its covered base and merge it there.
970
1023
  """
971
1024
 
1025
+ RETARGET_CHECKS_UNREADABLE = """Blocked: re-targeting %s onto "%s" cannot be judged, because this guard could
1026
+ not read the pull request's checks.
1027
+
1028
+ That ref has ZERO required status checks, so the one thing standing between the
1029
+ move and a permission-free gate bypass is whether the PR is currently failing —
1030
+ and the probe returned no `statusCheckRollup` at all. An absent rollup is not an
1031
+ empty one. Reporting the move as the sanctioned green-PR batching case here
1032
+ would be a verdict about a question that was never asked.
1033
+
1034
+ Re-target through `gh pr edit --base` instead, which reads the checks, or fix
1035
+ the probe. Do not report the PR as batched until something has actually looked.
1036
+ """
1037
+
1038
+
972
1039
  UNCOVERED_RETARGET_NOTICE = (
973
1040
  "block-blind-automerge: %s is being re-targeted onto \"%s\", a ref with ZERO "
974
1041
  "required status checks; its gates will run but cannot block a merge\n"
@@ -1039,6 +1106,18 @@ for act, kind, subject, repo_args, target_ref in acts:
1039
1106
  # nothing can block it", which holds whatever base it came from.
1040
1107
  if not base_is_uncovered(payload, repo_args, target_ref):
1041
1108
  continue
1109
+ # Fails CLOSED, and only here. Everywhere else in this file an
1110
+ # unanswerable question degrades and continues, because the guard being
1111
+ # unable to run must not become the reason a command is refused. This
1112
+ # branch is different: the base has already been confirmed to have zero
1113
+ # required checks, so an empty `failing` list is the whole basis for
1114
+ # allowing the move, and an ABSENT rollup produces the identical empty
1115
+ # list. Passing on it would be a measurement that could not fail.
1116
+ if not isinstance(payload.get("statusCheckRollup"), list):
1117
+ sys.stderr.write(
1118
+ RETARGET_CHECKS_UNREADABLE % (pr_name(payload), target_ref)
1119
+ )
1120
+ sys.exit(1)
1042
1121
  failing = failing_check_names(payload)
1043
1122
  if not failing:
1044
1123
  # A green PR moving onto a stack base is the sanctioned batching