@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
@@ -0,0 +1,340 @@
1
+ # This file is managed by Lisa and IS replaced on each `lisa` run.
2
+ # Do not edit directly — durable changes belong upstream in Lisa.
3
+
4
+ # shellcheck shell=bash
5
+ # Evaluate a dual-channel guard once per tool call, without de-registering it.
6
+ #
7
+ # Sourced — never executed — by every guard that Lisa registers on BOTH of its
8
+ # enforcement channels: the repository dispatcher
9
+ # (`scripts/lisa-enforcement-fallback.sh`, which sweeps the guard roster in one
10
+ # process) and the plugin manifest, which registers the same guards
11
+ # individually. On a machine where both are live the harness runs each guard
12
+ # twice for one tool call. The verdicts agree — the guards are idempotent — so
13
+ # the defect is cost, not correctness (CodySwannGT/lisa#3814).
14
+ #
15
+ # WHY THIS IS NOT "DROP ONE REGISTRATION".
16
+ #
17
+ # The obvious fix is to de-register the fallback where the plugin covers the
18
+ # same ground. That fix cannot be made safely from here, and the reason is
19
+ # written into the dispatcher itself: plugin-hook LIVENESS is not observable
20
+ # from disk. `installed_plugins.json` answers "was this plugin ever installed",
21
+ # which is project-blind, enablement-blind and session-blind — hooks load at
22
+ # session start, the record is rewritten on any install. A stand-down keyed on
23
+ # that record switched enforcement off in sessions where the plugin hooks were
24
+ # not loaded, and did it silently. So the dispatcher runs unconditionally and
25
+ # always will.
26
+ #
27
+ # What IS observable is EXECUTION. A guard that has already run in this session
28
+ # from a different directory is proof — not inference — that a second channel is
29
+ # live right now. This file turns that observation into a per-call memo, so the
30
+ # second copy of an identical guard replays a verdict the first one produced
31
+ # instead of computing it again. Nothing is de-registered, no matcher narrows,
32
+ # and a host with the plugin absent takes the single-channel path where the memo
33
+ # can never hit.
34
+ #
35
+ # WHAT THE MEMO IS ALLOWED TO SHORT-CIRCUIT, and the three conditions that make
36
+ # it sound. Every one of them is a REFUSAL to dedupe when it cannot be proven:
37
+ #
38
+ # 1. BYTE-IDENTICAL CODE. The digest covers the guard script and this library
39
+ # verbatim. Two channels at different vintages therefore compute different
40
+ # keys and both run — which is required, because the agent is meant to see
41
+ # the UNION of two vintages' verdicts, and a tightening on either channel
42
+ # must take effect at once. Deduplicating across vintages would let the
43
+ # older, weaker copy answer for the newer one.
44
+ # 2. IDENTICAL PAYLOAD, SAME TOOL CALL. The digest covers the payload and a
45
+ # per-call discriminator — the transcript's size, which grows with every
46
+ # turn. Without the discriminator a command repeated later in the session
47
+ # would hit a memo written before the repository changed underneath it, and
48
+ # a guard that classifies against the working tree would be answering with
49
+ # a stale verdict. No discriminator available means no memo.
50
+ # 3. ALLOW VERDICTS ONLY. A refusal is never memoised and never replayed. That
51
+ # is deliberate: refusals are the rare path, so deduplicating them saves
52
+ # nothing measurable, and it removes the whole class of hazard the ticket
53
+ # names — a replayed refusal whose producing channel died mid-run, or a
54
+ # refusal reaching the agent without the message that explains it. On a
55
+ # refusal every channel evaluates and speaks exactly as it does today.
56
+ #
57
+ # COST ON THE PATH THAT GETS NO BENEFIT. A host with one channel must not pay
58
+ # for a mechanism it cannot use. The channel check is builtins only — one small
59
+ # file read, no subshell, no fork — and returns before the digest is computed
60
+ # unless a second channel has actually been seen. The digest, and the one `stat`
61
+ # that feeds it the per-call discriminator, are reached only where a second
62
+ # channel is live, which is exactly where they are repaid.
63
+
64
+ # Key this guard would memoise under, once computed. Empty means "record
65
+ # nothing on exit".
66
+ lisa_guard_memo_key=""
67
+
68
+ # File that memo is written to.
69
+ lisa_guard_memo_file=""
70
+
71
+ # Base directory for memo state, resolved at most once per process.
72
+ lisa_guard_memo_base=""
73
+
74
+ # Whether a key was computed and a memo may be recorded on exit.
75
+ lisa_guard_memo_ready=0
76
+
77
+ # Digest tool for the memo key, resolved once. Empty disables the memo.
78
+ lisa_guard_memo_digest_tool=""
79
+
80
+ # Whether a directory is a real directory this user owns, and not a symlink —
81
+ # builtins only, so this costs no process.
82
+ #
83
+ # `-O` is the load-bearing test: a memo planted by another user would suppress a
84
+ # refusal, so a directory this user does not own is never used. The dispatcher
85
+ # validates the whole parent chain before exporting `LISA_GUARD_MEMO_DIR`; this
86
+ # re-checks the leaf rather than trusting the environment variable, because an
87
+ # exported path is an input like any other.
88
+ #
89
+ # $1 - candidate directory
90
+ lisa_guard_memo_dir_usable() {
91
+ [ -n "${1:-}" ] || return 1
92
+ [ -d "$1" ] || return 1
93
+ [ ! -L "$1" ] || return 1
94
+ [ -O "$1" ] || return 1
95
+ }
96
+
97
+ # Establish the memo base directory.
98
+ #
99
+ # Preferred source is `LISA_GUARD_MEMO_DIR`, exported by the dispatcher, which
100
+ # has already resolved `TMPDIR` physically and walked every parent for ownership
101
+ # and mode. Guards it spawns therefore pay nothing for that walk. A guard
102
+ # reached by the plugin channel has no such parent, so it creates the directory
103
+ # itself under `TMPDIR` — private by umask, and used only when this user owns
104
+ # it. If neither works the memo is unavailable and every guard evaluates, which
105
+ # is today's behaviour.
106
+ lisa_guard_memo_resolve_base() {
107
+ [ -z "$lisa_guard_memo_base" ] || return 0
108
+
109
+ local candidate="${LISA_GUARD_MEMO_DIR:-}"
110
+ if lisa_guard_memo_dir_usable "$candidate"; then
111
+ lisa_guard_memo_base="$candidate"
112
+ return 0
113
+ fi
114
+
115
+ local uid="${LISA_GUARD_MEMO_UID:-}"
116
+ if [ -z "$uid" ]; then
117
+ uid="$(id -u 2>/dev/null || printf 'unknown')"
118
+ fi
119
+ candidate="${TMPDIR:-/tmp}"
120
+ candidate="${candidate%/}/lisa-guard-memo-$uid"
121
+ if [ ! -e "$candidate" ] && [ ! -L "$candidate" ]; then
122
+ (umask 077 && mkdir -p "$candidate") 2>/dev/null || true
123
+ fi
124
+ if lisa_guard_memo_dir_usable "$candidate"; then
125
+ lisa_guard_memo_base="$candidate"
126
+ return 0
127
+ fi
128
+ return 1
129
+ }
130
+
131
+ # Resolve a SHA-256 tool, or report that there is none.
132
+ #
133
+ # Two spellings because the guards run on macOS, on Linux CI images, and in
134
+ # containers that carry neither. A missing digest tool disables dedupe rather
135
+ # than degrading it to something weaker: a cheap checksum could collide, and a
136
+ # collision here means a DIFFERENT payload reads as already-allowed, which is
137
+ # lost enforcement rather than a slow guard.
138
+ lisa_guard_memo_resolve_digest() {
139
+ [ -z "$lisa_guard_memo_digest_tool" ] || return 0
140
+ if command -v shasum >/dev/null 2>&1; then
141
+ lisa_guard_memo_digest_tool="shasum"
142
+ elif command -v sha256sum >/dev/null 2>&1; then
143
+ lisa_guard_memo_digest_tool="sha256sum"
144
+ else
145
+ return 1
146
+ fi
147
+ return 0
148
+ }
149
+
150
+ # Remove memo state older than a day.
151
+ #
152
+ # Files first, then the emptied session directories, both with `find -delete`.
153
+ # Never a recursive forced delete: this runs unattended inside every guard, and
154
+ # the blast radius of a mis-resolved base directory has to stay bounded to
155
+ # entries this scheme itself created.
156
+ lisa_guard_memo_sweep() {
157
+ find "$lisa_guard_memo_base" -mindepth 2 -maxdepth 2 -type f -mmin +1440 \
158
+ -delete 2>/dev/null || true
159
+ find "$lisa_guard_memo_base" -mindepth 1 -maxdepth 1 -type d -empty \
160
+ -mmin +1440 -delete 2>/dev/null || true
161
+ }
162
+
163
+ # Decide whether this guard has already been evaluated for this tool call, and
164
+ # exit 0 in place of re-evaluating it when it has.
165
+ #
166
+ # Returns 0 for "carry on and evaluate". Never returns non-zero, so a caller
167
+ # running under `set -e` is unaffected by it.
168
+ #
169
+ # $1 - guard name, used only to name the memo file
170
+ # $2 - the raw tool payload this guard was handed on stdin
171
+ lisa_guard_dedupe() {
172
+ local guard_name="${1:-}"
173
+ local payload="${2:-}"
174
+ [ -n "$guard_name" ] || return 0
175
+ [ -n "$payload" ] || return 0
176
+ [ -z "${LISA_GUARD_DEDUPE_DISABLE:-}" ] || return 0
177
+
178
+ # The session scopes every piece of state below. No session id means the
179
+ # payload came from a surface this cannot reason about — an adapter's
180
+ # synthesised envelope, a test harness — and the answer there is to evaluate.
181
+ local session_id=""
182
+ local session_pattern="\"session_id\"[[:space:]]*:[[:space:]]*\"([^\"]+)\""
183
+ if [[ "$payload" =~ $session_pattern ]]; then
184
+ session_id="${BASH_REMATCH[1]}"
185
+ fi
186
+ [ -n "$session_id" ] || return 0
187
+ # It is used as a path component, so anything that could escape the state
188
+ # directory disqualifies it outright.
189
+ case "$session_id" in *[!A-Za-z0-9._-]* | .*) return 0 ;; esac
190
+
191
+ lisa_guard_memo_resolve_base || return 0
192
+
193
+ local session_dir="$lisa_guard_memo_base/$session_id"
194
+ local fresh_session=0
195
+ if [ ! -d "$session_dir" ]; then
196
+ if (umask 077 && mkdir "$session_dir") 2>/dev/null; then
197
+ fresh_session=1
198
+ fi
199
+ fi
200
+ [ -d "$session_dir" ] || return 0
201
+ [ ! -L "$session_dir" ] || return 0
202
+
203
+ # Swept once, by the process that created the session directory, so a
204
+ # long-lived machine does not accumulate memo state. Off the hot path by
205
+ # construction: every later call in the session finds the directory present.
206
+ if [ "$fresh_session" -eq 1 ]; then
207
+ lisa_guard_memo_sweep
208
+ fi
209
+
210
+ # The directory this copy of the guard was served from. Two registrations of
211
+ # one guard are always two different directories — a repository tree or an
212
+ # applied `scripts/lisa-hooks/` on one side, a plugin cache on the other — so
213
+ # the set of directories seen this session IS the set of live channels.
214
+ local self_script="${BASH_SOURCE[1]:-$0}"
215
+ local self_dir="."
216
+ case "$self_script" in */*) self_dir="${self_script%/*}" ;; esac
217
+
218
+ local channels_file="$session_dir/channels"
219
+ local seen_self=0
220
+ local other_channel=0
221
+ local line=""
222
+ if [ -f "$channels_file" ]; then
223
+ while IFS= read -r line || [ -n "$line" ]; do
224
+ [ -n "$line" ] || continue
225
+ if [ "$line" = "$self_dir" ]; then
226
+ seen_self=1
227
+ else
228
+ other_channel=1
229
+ fi
230
+ done <"$channels_file"
231
+ fi
232
+ if [ "$seen_self" -eq 0 ]; then
233
+ printf '%s\n' "$self_dir" >>"$channels_file" 2>/dev/null || true
234
+ fi
235
+
236
+ # Single channel: the memo can never hit, so nothing below is worth its cost.
237
+ # This is the branch a host with no plugin takes on every call, and it is the
238
+ # reason the fork lives below this line rather than above it.
239
+ [ "$other_channel" -eq 1 ] || return 0
240
+
241
+ # The per-call discriminator. The transcript grows with every turn, so its
242
+ # size separates two identical commands issued at different points in one
243
+ # session — which matters because a guard classifies against a working tree
244
+ # that may have changed between them. Both channels read the same transcript
245
+ # within one tool call, and anything appended between them costs a memo miss,
246
+ # never a stale hit.
247
+ local transcript=""
248
+ local transcript_pattern="\"transcript_path\"[[:space:]]*:[[:space:]]*\"([^\"]+)\""
249
+ if [[ "$payload" =~ $transcript_pattern ]]; then
250
+ transcript="${BASH_REMATCH[1]}"
251
+ fi
252
+ [ -n "$transcript" ] || return 0
253
+ [ -f "$transcript" ] || return 0
254
+
255
+ lisa_guard_memo_resolve_digest || return 0
256
+
257
+ # The transcript's size, captured and VALIDATED before it can reach the
258
+ # digest, because a `stat` that rejects the other platform's spelling can
259
+ # still write to stdout on its way out. GNU `stat -f '%z' FILE` reads `-f` as
260
+ # `--file-system` and the format string as a FILE that does not exist: it
261
+ # exits non-zero AND prints a filesystem status block for the real operand,
262
+ # whose `Free` and `Available` counts move under any disk activity. Appended
263
+ # straight into the digest, that made the two channels of ONE tool call
264
+ # compute different keys, so the memo could only ever miss — the whole
265
+ # mechanism switched off on every Linux host, while the three "must not
266
+ # dedupe" controls stayed green because they assert a miss.
267
+ #
268
+ # Only an all-digits answer is accepted, and no size means NO MEMO rather
269
+ # than a memo with nothing separating one call from the next: that is
270
+ # condition 2 above, which the `A || B` spelling had left unenforced on a
271
+ # host carrying neither `stat`.
272
+ local transcript_size=""
273
+ transcript_size="$(stat -c '%s' "$transcript" 2>/dev/null)" ||
274
+ transcript_size=""
275
+ case "$transcript_size" in
276
+ "" | *[!0-9]*)
277
+ transcript_size="$(stat -f '%z' "$transcript" 2>/dev/null)" ||
278
+ transcript_size=""
279
+ ;;
280
+ esac
281
+ case "$transcript_size" in "" | *[!0-9]*) return 0 ;; esac
282
+
283
+ # One command substitution, covering everything the verdict depends on: the
284
+ # guard's own bytes, this library's bytes, the payload, and which call it is.
285
+ local key=""
286
+ key="$(
287
+ {
288
+ cat "$self_script" "${BASH_SOURCE[0]}" 2>/dev/null
289
+ printf '\n%s\n' "$payload"
290
+ printf '%s\n' "$transcript_size"
291
+ } | if [ "$lisa_guard_memo_digest_tool" = "shasum" ]; then
292
+ shasum -a 256 2>/dev/null
293
+ else
294
+ sha256sum 2>/dev/null
295
+ fi
296
+ )" || key=""
297
+ key="${key%% *}"
298
+ case "$key" in "" | *[!0-9a-f]*) return 0 ;; esac
299
+
300
+ # One memo file per guard per session, rewritten as the call moves on, so the
301
+ # state directory stays a fixed size however long a session runs. A saturated
302
+ # temp directory is its own outage on this fleet, and a memo scheme that grew
303
+ # a file per tool call would be one.
304
+ local memo_file="$session_dir/$guard_name.allow"
305
+ local recorded=""
306
+ if [ -f "$memo_file" ] && [ ! -L "$memo_file" ]; then
307
+ IFS= read -r recorded <"$memo_file" 2>/dev/null || recorded=""
308
+ if [ "$recorded" = "$key" ]; then
309
+ # A byte-identical copy of this guard already allowed this exact payload
310
+ # on this exact tool call. The evaluation being skipped could only have
311
+ # produced the verdict that was already delivered.
312
+ exit 0
313
+ fi
314
+ fi
315
+
316
+ lisa_guard_memo_key="$key"
317
+ lisa_guard_memo_file="$memo_file"
318
+ lisa_guard_memo_ready=1
319
+ return 0
320
+ }
321
+
322
+ # Record an ALLOW verdict for the other channel to replay. Installed as the
323
+ # guard's EXIT trap, so it covers every path the guard can leave by.
324
+ #
325
+ # Any status other than 0 writes nothing and, when a memo from an earlier call
326
+ # is still sitting there, removes it: a guard that refused must never leave
327
+ # behind something that reads as permission.
328
+ #
329
+ # $1 - the exiting status
330
+ lisa_guard_dedupe_record() {
331
+ [ "${lisa_guard_memo_ready:-0}" -eq 1 ] || return 0
332
+ [ -n "$lisa_guard_memo_file" ] || return 0
333
+ if [ "${1:-1}" -eq 0 ]; then
334
+ printf '%s\n' "$lisa_guard_memo_key" >"$lisa_guard_memo_file" 2>/dev/null ||
335
+ true
336
+ else
337
+ rm -f "$lisa_guard_memo_file" 2>/dev/null || true
338
+ fi
339
+ return 0
340
+ }
@@ -121,6 +121,27 @@ trap 'printf "%s\n" "Blocked by safety-net: hook failed while parsing its input;
121
121
 
122
122
  input="$(cat)"
123
123
 
124
+ # Evaluate once per tool call when this guard is registered on both channels.
125
+ #
126
+ # Lisa reaches an agent through the repository dispatcher AND the plugin
127
+ # manifest, and where both are live the harness runs this guard twice for one
128
+ # tool call. `guard-dedupe.bash` short-circuits the second run ONLY when a
129
+ # byte-identical copy already ALLOWED this exact payload on this exact tool
130
+ # call; a differing vintage, a refusal, and a host with one channel all
131
+ # evaluate exactly as before. Nothing is de-registered by it
132
+ # (CodySwannGT/lisa#3814).
133
+ #
134
+ # Absent library means no dedupe, which is the pre-existing behaviour, so an
135
+ # older channel copy that predates it is unaffected.
136
+ lisa_guard_hook_dir="${BASH_SOURCE[0]%/*}"
137
+ lisa_guard_dedupe_lib="$lisa_guard_hook_dir/guard-dedupe.bash"
138
+ if [ -r "$lisa_guard_dedupe_lib" ]; then
139
+ # shellcheck source=guard-dedupe.bash
140
+ . "$lisa_guard_dedupe_lib"
141
+ trap 'lisa_guard_dedupe_record $?' EXIT
142
+ lisa_guard_dedupe parity-safety-net "$input"
143
+ fi
144
+
124
145
  tool_name="$(printf '%s' "$input" | jq -r '.tool_name // empty')"
125
146
  if [ "$tool_name" != "Bash" ]; then
126
147
  exit 0
@@ -247,12 +268,19 @@ later with `git apply "$patch"`.
247
268
 
248
269
  If the work is NOT yours -- a concurrent agent dirtied this worktree and your
249
270
  judgement is "none of this is mine" -- that same capture is how you return the
250
- tree to HEAD:
271
+ tree to HEAD. This block is self-contained; paste it as it stands:
251
272
 
273
+ patch="$(mktemp "${TMPDIR:-/tmp}/lisa-preserve-XXXXXX")"
252
274
  git reset
253
275
  git diff --binary HEAD > "$patch"
254
276
  git apply -R "$patch"
255
277
 
278
+ It names the file itself rather than inheriting one from the block above,
279
+ because the two blocks are alternatives and only one of them gets run. A remedy
280
+ printed at the moment an agent is blocked is followed literally; one that
281
+ depends on a variable the reader never set fails on its first line, and the
282
+ guard that printed it stops being believed.
283
+
256
284
  `git reset` clears staged and unmerged entries first, so the capture sees the
257
285
  whole tree as one diff; reversing it leaves the tree at HEAD with "$patch" as
258
286
  the record of what you discarded. Untracked files survive, exactly as they
@@ -405,6 +405,11 @@ function applyCd(current, target) {
405
405
  * disagreeing. The scripted form is the one neither covers, and it is the only
406
406
  * cell this arm fills.
407
407
  *
408
+ * That reason is a dependency on a runtime Lisa does not ship, and it can
409
+ * expire without anything here failing. It is pinned at
410
+ * {@link VERIFIED_RUNTIME_VERSION} and reported when the runtime moves off it
411
+ * — read that constant before widening this arm.
412
+ *
408
413
  * ## A relative script path is resolved against the cwd the command will have
409
414
  *
410
415
  * A leading `cd` establishes the directory this guard resolves relative tokens
@@ -514,11 +519,28 @@ function readState(sessionId) {
514
519
  }
515
520
  }
516
521
 
522
+ /**
523
+ * Write the session's binding state, MERGING over whatever is already there.
524
+ *
525
+ * Every caller below hands in all three lifecycle fields explicitly, so the
526
+ * merge changes nothing for them — `claimedRoot: null` still clears a claim.
527
+ * What it buys is that a field one writer owns is not silently dropped by
528
+ * another writer that had no opinion about it: `runtimeNoticed` is written by
529
+ * {@link noticeRuntimeDrift} and read by nobody else, and a clobbering write
530
+ * would turn its "reported once" into "reported again after the next
531
+ * acknowledgement", which is the noise the notice is designed not to be.
532
+ * @param sessionId - The session whose state file this is
533
+ * @param state - Fields to write over the recorded state
534
+ * @returns Whether the write succeeded
535
+ */
517
536
  function writeState(sessionId, state) {
518
537
  const file = stateFile(sessionId);
519
538
  try {
520
539
  mkdirSync(dirname(file), { recursive: true });
521
- writeFileSync(file, `${JSON.stringify(state, null, 2)}\n`);
540
+ writeFileSync(
541
+ file,
542
+ `${JSON.stringify({ ...readState(sessionId), ...state }, null, 2)}\n`
543
+ );
522
544
  return true;
523
545
  } catch (error) {
524
546
  say(`could not record the binding (${error.message}); NOT enforcing`);
@@ -698,6 +720,162 @@ function displaced(bound, observed) {
698
720
  ]);
699
721
  }
700
722
 
723
+ /**
724
+ * The runtime version the inline-redirect gap was last verified at.
725
+ *
726
+ * ## What this constant is a pin ON
727
+ *
728
+ * This guard deliberately does NOT refuse an INLINE redirect into another
729
+ * worktree — `cd <other> && git …`, `git -C <other> …`. The reason is good and
730
+ * is written out at {@link scriptReachingForeignTree}: the runtime's own
731
+ * worktree isolation already refuses them, and duplicating a control is how two
732
+ * controls drift into disagreeing about the same command.
733
+ *
734
+ * That reason is a DEPENDENCY on behaviour Lisa does not ship. It was verified
735
+ * refusing at this version, with the refusal wording "this command names git in
736
+ * a form too complex to verify that it stays inside the worktree" and
737
+ * "redirects git to the shared checkout via -C".
738
+ *
739
+ * ## Why a pin rather than a test
740
+ *
741
+ * The runtime's isolation is not reachable from a unit test: it adjudicates
742
+ * inside a live worktree-isolated session, and there is no entry point that
743
+ * takes one hook envelope and returns a verdict the way this guard does. So the
744
+ * dependency cannot be turned red by writing a test, and a claim that cannot go
745
+ * red goes stale silently instead (CodySwannGT/lisa#3944).
746
+ *
747
+ * What CAN be measured is whether the runtime is still the one the claim was
748
+ * checked against. That does not prove the behaviour changed — only that nobody
749
+ * has checked since, which is the honest thing to report and the whole of what
750
+ * this pin does.
751
+ *
752
+ * ## Moving it
753
+ *
754
+ * Re-run both inline forms in a worktree-isolated session. Still refused: move
755
+ * this pin, and say where. Permitted: those two cells are UNCOVERED, and #3944
756
+ * owns the decision about which of them Lisa takes on.
757
+ */
758
+ const VERIFIED_RUNTIME_VERSION = "2.1.261";
759
+
760
+ /** The ticket that owns the inline-redirect dependency and its expiry. */
761
+ const RUNTIME_ASSUMPTION_TICKET = "CodySwannGT/lisa#3944";
762
+
763
+ /**
764
+ * This session's runtime version, or null when this is not that runtime.
765
+ *
766
+ * Three answers, and the difference between the last two is the point:
767
+ * - `null` — not Claude Code. Codex, Cursor and Copilot ship this guard too,
768
+ * and the assumption above is about a control none of them has. A notice that
769
+ * fires on every session of every harness is noise, and noise gets switched
770
+ * off.
771
+ * - `""` — Claude Code, which will not say which version. Read as a DIVERGENCE
772
+ * rather than as a match: the assumption is unconfirmed either way, and
773
+ * treating an unreadable version as "still 2.1.261" is exactly the silent
774
+ * expiry #3944 was filed about.
775
+ * - a version — take it at its word.
776
+ *
777
+ * Both env vars are read because either can be the one a future release keeps.
778
+ * `AI_AGENT` carries `claude-code_2-1-261_agent`; `CLAUDE_CODE_EXECPATH` ends
779
+ * in the version directory the running build was launched from.
780
+ * @param env - Environment to read
781
+ * @returns The version, `""` when unreadable, or null for another runtime
782
+ */
783
+ const isDigit = ch => ch >= "0" && ch <= "9";
784
+
785
+ /** @param value - Segment to test @returns Whether it is all digits */
786
+ const allDigits = value => value.length > 0 && [...value].every(isDigit);
787
+
788
+ /** @param value - Segment to read @returns Its trailing digit run, else `""` */
789
+ const trailingDigits = value => {
790
+ let start = value.length;
791
+ while (start > 0 && isDigit(value[start - 1])) start -= 1;
792
+ return value.slice(start);
793
+ };
794
+
795
+ function runtimeVersion(env) {
796
+ if (!env.CLAUDECODE) return null;
797
+ const tagged = /claude-code[_-](\d+)[-.](\d+)[-.](\d+)/u.exec(
798
+ env.AI_AGENT ?? ""
799
+ );
800
+ if (tagged) return `${tagged[1]}.${tagged[2]}.${tagged[3]}`;
801
+ // Read the version off the final path segment by scanning characters rather
802
+ // than by regex: an unanchored `\d+\.\d+\.\d+` before `$` backtracks
803
+ // super-linearly on a long execpath, and the version Claude Code launches
804
+ // from is always the trailing directory name.
805
+ const segments = (env.CLAUDE_CODE_EXECPATH ?? "").split("/").filter(Boolean);
806
+ const trailing = segments.length ? segments[segments.length - 1] : "";
807
+ const parts = trailing.split(".");
808
+ if (parts.length < 3) return "";
809
+ const [major, minor, patch] = parts.slice(-3);
810
+ const majorDigits = trailingDigits(major);
811
+ if (!majorDigits || !allDigits(minor) || !allDigits(patch)) return "";
812
+ return `${majorDigits}.${minor}.${patch}`;
813
+ }
814
+
815
+ /**
816
+ * The notice text for a runtime the inline-redirect claim was not checked at.
817
+ * @param version - The observed runtime version, `""` when unreadable
818
+ * @returns The lines to report
819
+ */
820
+ function driftNotice(version) {
821
+ return [
822
+ "worktree-binding-guard: the inline-redirect gap is UNVERIFIED here.",
823
+ "",
824
+ "This guard does not refuse `cd <other-worktree> && git …` or",
825
+ "`git -C <other-worktree> …`. Those two cells are covered by the runtime's",
826
+ `own worktree isolation, verified refusing at Claude Code ${VERIFIED_RUNTIME_VERSION}.`,
827
+ version
828
+ ? `This session is running ${version}, so that check does not cover it.`
829
+ : "This session's runtime version could not be read, so nothing covers it.",
830
+ "",
831
+ "This does NOT say the runtime stopped refusing them — only that nobody has",
832
+ "checked since. Run both forms in a worktree-isolated session: still",
833
+ "refused, move VERIFIED_RUNTIME_VERSION in worktree-binding-guard.mjs;",
834
+ `permitted, the two cells are uncovered and ${RUNTIME_ASSUMPTION_TICKET}`,
835
+ "owns what Lisa does about it.",
836
+ ];
837
+ }
838
+
839
+ /**
840
+ * Report, once per session, that the runtime moved off the verified version.
841
+ *
842
+ * ## Why SessionStart and not the guarded call
843
+ *
844
+ * SessionStart is the guard's first evaluation of a session, it fires exactly
845
+ * once per start, and its `additionalContext` is the one channel here that the
846
+ * agent actually reads — the `say()` lines elsewhere in this file are for a
847
+ * human reading a transcript. A notice attached to a guarded tool call would
848
+ * either repeat on every Bash command or need a suppression rule of its own.
849
+ *
850
+ * ## Why it is recorded rather than counted
851
+ *
852
+ * A resume fires SessionStart again for the same session id. The recorded
853
+ * version is what makes the second one silent, and recording the VERSION rather
854
+ * than a flag means a runtime that moves twice inside one resumed session is
855
+ * still reported the second time.
856
+ *
857
+ * Never refuses. The dependency is a reporting gap, not a live displacement,
858
+ * and blocking a session over a version number would be a wall where an
859
+ * observation belongs.
860
+ * @param sessionId - The session being started
861
+ * @param env - Environment to read the runtime version from
862
+ * @returns Nothing; every path through this allows
863
+ */
864
+ function noticeRuntimeDrift(sessionId, env) {
865
+ const version = runtimeVersion(env);
866
+ if (version === null || version === VERIFIED_RUNTIME_VERSION) return;
867
+ if (readState(sessionId)?.runtimeNoticed === version) return;
868
+ writeState(sessionId, { runtimeNoticed: version });
869
+ process.stdout.write(
870
+ `${JSON.stringify({
871
+ hookSpecificOutput: {
872
+ hookEventName: "SessionStart",
873
+ additionalContext: driftNotice(version).join("\n"),
874
+ },
875
+ })}\n`
876
+ );
877
+ }
878
+
701
879
  function evaluate(payload) {
702
880
  const sessionId = payload.session_id;
703
881
  const cwd = payload.cwd;
@@ -710,6 +888,10 @@ function evaluate(payload) {
710
888
 
711
889
  if (payload.hook_event_name === "SessionStart") {
712
890
  recordBaseline(sessionId, observed);
891
+ // After the baseline, never before: `recordBaseline` writes a fresh state
892
+ // for a session it has not seen, and the notice's record has to go on top
893
+ // of that write rather than under it.
894
+ noticeRuntimeDrift(sessionId, process.env);
713
895
  return 0;
714
896
  }
715
897
  if (payload.tool_name === "EnterWorktree") {
@@ -39,6 +39,27 @@ set -uo pipefail
39
39
 
40
40
  input="$(cat)"
41
41
 
42
+ # Evaluate once per tool call when this guard is registered on both channels.
43
+ #
44
+ # Lisa reaches an agent through the repository dispatcher AND the plugin
45
+ # manifest, and where both are live the harness runs this guard twice for one
46
+ # tool call. `guard-dedupe.bash` short-circuits the second run ONLY when a
47
+ # byte-identical copy already ALLOWED this exact payload on this exact tool
48
+ # call; a differing vintage, a refusal, and a host with one channel all
49
+ # evaluate exactly as before. Nothing is de-registered by it
50
+ # (CodySwannGT/lisa#3814).
51
+ #
52
+ # Absent library means no dedupe, which is the pre-existing behaviour, so an
53
+ # older channel copy that predates it is unaffected.
54
+ lisa_guard_hook_dir="${BASH_SOURCE[0]%/*}"
55
+ lisa_guard_dedupe_lib="$lisa_guard_hook_dir/guard-dedupe.bash"
56
+ if [ -r "$lisa_guard_dedupe_lib" ]; then
57
+ # shellcheck source=guard-dedupe.bash
58
+ . "$lisa_guard_dedupe_lib"
59
+ trap 'lisa_guard_dedupe_record $?' EXIT
60
+ lisa_guard_dedupe worktree-binding-guard "$input"
61
+ fi
62
+
42
63
  if ! command -v node >/dev/null 2>&1; then
43
64
  printf 'worktree-binding-guard: node not found; worktree-binding enforcement is NOT active\n' >&2
44
65
  exit 0
@@ -173,7 +173,27 @@ export function runPostinstall(cwd = process.cwd(), env = process.env) {
173
173
  // package manager finish. Unknown throws still escape because converting a
174
174
  // broken postinstall module into an apparent apply failure would hide the
175
175
  // defect that needs fixing.
176
- if (!isChildTimeout(error)) throw error;
176
+ //
177
+ // The MARKER is written for an unknown throw too, before it escapes, and
178
+ // that is not the same decision as the exit code. The invocation this
179
+ // module is chained into ends in `|| true`, deliberately — a non-zero
180
+ // postinstall aborts `bun install` outright and strands the project with
181
+ // no way to install the fix. So the escaping throw reaches nothing that
182
+ // records it: the stack trace scrolls past once and every later instrument
183
+ // reads a project whose templates simply were not applied. `lisa doctor`
184
+ // asks the marker, and the marker was the only durable half. Writing it
185
+ // here keeps the loud-and-durable contract this module's header states,
186
+ // without trading it for an install that cannot complete.
187
+ if (!isChildTimeout(error)) {
188
+ recordFailure(cwd, {
189
+ status: null,
190
+ output:
191
+ error instanceof Error
192
+ ? (error.stack ?? error.message)
193
+ : String(error),
194
+ });
195
+ throw error;
196
+ }
177
197
  child = {
178
198
  error: Object.assign(
179
199
  new Error(