@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
@@ -116,6 +116,27 @@ set -euo pipefail
116
116
 
117
117
  input="$(cat)"
118
118
 
119
+ # Evaluate once per tool call when this guard is registered on both channels.
120
+ #
121
+ # Lisa reaches an agent through the repository dispatcher AND the plugin
122
+ # manifest, and where both are live the harness runs this guard twice for one
123
+ # tool call. `guard-dedupe.bash` short-circuits the second run ONLY when a
124
+ # byte-identical copy already ALLOWED this exact payload on this exact tool
125
+ # call; a differing vintage, a refusal, and a host with one channel all
126
+ # evaluate exactly as before. Nothing is de-registered by it
127
+ # (CodySwannGT/lisa#3814).
128
+ #
129
+ # Absent library means no dedupe, which is the pre-existing behaviour, so an
130
+ # older channel copy that predates it is unaffected.
131
+ lisa_guard_hook_dir="${BASH_SOURCE[0]%/*}"
132
+ lisa_guard_dedupe_lib="$lisa_guard_hook_dir/guard-dedupe.bash"
133
+ if [ -r "$lisa_guard_dedupe_lib" ]; then
134
+ # shellcheck source=guard-dedupe.bash
135
+ . "$lisa_guard_dedupe_lib"
136
+ trap 'lisa_guard_dedupe_record $?' EXIT
137
+ lisa_guard_dedupe block-direct-issue-create "$input"
138
+ fi
139
+
119
140
  # Probe both interpreters before use and announce a missing one rather than
120
141
  # swallowing it. Under `set -e` an absent jq aborts with 127, and Claude Code
121
142
  # treats any non-2 exit as a NON-BLOCKING hook error — so the guard would
@@ -332,10 +353,66 @@ EOF
332
353
  esac
333
354
  }
334
355
 
356
+ # The branch-specific half of the remediation, printed only for the branch that
357
+ # fired. Every refusal this guard issues has to name an inverse the author can
358
+ # actually perform — a refusal with none is a state change with no inverse, and
359
+ # on a file-contents refusal it made a path permanently unnameable to any
360
+ # command the guard did not recognise as a reader (CodySwannGT/lisa#3683).
361
+ #
362
+ # NEITHER PARAGRAPH IS AN ESCAPE HATCH, and that is deliberate. `3594` records
363
+ # that a PreToolUse guard whose escape the guarded agent can set is not a
364
+ # control, so nothing here is settable: the only inverses named are edits to
365
+ # the refused command itself, and performing either one leaves a genuine
366
+ # undeclared creation refused by the checks above. Re-quoting an unlexable
367
+ # creation hands it to the parsed path, which refuses it; naming a document to
368
+ # a reader files nothing, which is the whole point. The one true escape stays
369
+ # what it was — an ambient variable a human exports before the session, which a
370
+ # tool-call shell cannot reach because its exports do not survive into this
371
+ # hook's environment.
372
+ remedy_hint() {
373
+ case "${1:-}" in
374
+ unparseable)
375
+ cat <<'EOF'
376
+
377
+ THIS COMMAND DID NOT LEX, so it was judged by pattern rather than by parse. An
378
+ unbalanced quote is the cause, and an apostrophe in ordinary prose is the
379
+ measured one. The pattern cannot separate a sentence about filing from a real
380
+ filing hidden behind a trailing `#` comment, so it refuses both.
381
+
382
+ THE INVERSE IS EXECUTABLE: re-quote the command so it lexes, and run it again.
383
+ It is not a bypass — a re-quoted creation is handed to the full parser, which
384
+ refuses it unless it declares readiness the ordinary way.
385
+
386
+ A command whose every segment runs a known READER is not refused on this branch
387
+ at all, so `cat`, `grep`, `ls`, `cp`, `mv`, `git` and `echo` never reach it.
388
+ EOF
389
+ ;;
390
+ file)
391
+ cat <<'EOF'
392
+
393
+ THIS REFUSAL CAME FROM A FILE THIS COMMAND RUNS. A path is opened only when the
394
+ command puts it in a COMMAND position; a program that takes it as data — `cat`,
395
+ `grep`, `ls`, `cp`, `mv`, `git`, a test runner — never reaches this branch. Two
396
+ inverses, both executable:
397
+
398
+ - The file WRITES a payload locally and cannot transmit it (no HTTP client,
399
+ no CLI, no process spawn): it is read as data, not as a filing. Drop the
400
+ transmitting primitive, or slice the payload out of an existing file
401
+ instead of restating it here.
402
+ - The file produces no local artifact either — it is a DOCUMENT that quotes a
403
+ creation rather than a program that performs one. Name it to a reader
404
+ rather than to an interpreter; rewording its contents is not the remedy and
405
+ does not converge.
406
+ EOF
407
+ ;;
408
+ esac
409
+ }
410
+
335
411
  refuse() {
336
412
  local signature="$1"
337
413
  local roles="$2"
338
414
  local target="$3"
415
+ local remedy="${4:-}"
339
416
  if [ -n "$target" ]; then
340
417
  refuse_cross_repo "$signature" "$roles" "$target"
341
418
  fi
@@ -406,6 +483,8 @@ WHERE THE DECLARATION IS READ FROM: argv, the request payload — inline, in a
406
483
  this command runs. Moving the create into a file no longer moves it out of
407
484
  sight, so the declaration can live wherever the create does.
408
485
 
486
+ $(remedy_hint "$remedy")
487
+
409
488
  OPERATOR ESCAPE: a human can export \`LISA_ALLOW_DIRECT_ISSUE_CREATE=1\` in the
410
489
  environment before starting the session. It is deliberately not reachable by
411
490
  setting it inline on this command — an inline assignment is refused.
@@ -785,6 +864,84 @@ TRACKER_ENDPOINT = re.compile(
785
864
  re.IGNORECASE,
786
865
  )
787
866
 
867
+ # ---------------------------------------------------------------------------
868
+ # A PAYLOAD HELD AS DATA IS NOT A SUBMISSION
869
+ #
870
+ # The conjunction above — an endpoint AND a creation verb in one file — cannot
871
+ # tell a payload being WRITTEN AS TEST DATA from one about to be SUBMITTED.
872
+ # Measured (CodySwannGT/lisa#3943): a helper that assembles test fixtures, and
873
+ # holds a mutation body as a string constant it writes to a fixture file, was
874
+ # refused as "a tracker creation inside <path>". It submits nothing. The same
875
+ # helper rewritten to SLICE the payload out of an existing file was allowed —
876
+ # so the discriminator was the literal, not the behaviour.
877
+ #
878
+ # The behaviour that separates them is EGRESS. A file can only file an issue if
879
+ # it can hand its bytes to another host or another process. A file with no such
880
+ # primitive anywhere in it cannot submit whatever its constants spell, so the
881
+ # conjunction is reading data.
882
+ #
883
+ # BOTH HALVES ARE REQUIRED, and the second one is why this is not a hole. The
884
+ # exemption needs POSITIVE evidence that the file produces a local artifact
885
+ # (`LOCAL_WRITE`) as well as the ABSENCE of egress, so it is bounded to the
886
+ # measured population — a fixture writer — rather than granted to any file that
887
+ # happens to use a transport nobody enumerated. Egress is over-matched on
888
+ # purpose: every miss in `PAYLOAD_EGRESS` widens the exemption, so the list
889
+ # reaches for `.post(`-shaped calls and process spawning as well as named HTTP
890
+ # clients, and a file doing both is refused. That is the guard's usual
891
+ # direction of failure — a false refusal is reported, a false allow is silent.
892
+ #
893
+ # RESIDUAL, stated rather than hidden: a submitting file that ALSO writes a
894
+ # local artifact and reaches the network through a primitive no pattern here
895
+ # names is allowed. Nothing about `text_declares_readiness`, the nested shell
896
+ # scan, the unparseable arm, or the argv path is weakened — this reads only the
897
+ # coarse conjunction, which is the only arm that ever inferred a submission
898
+ # from contents alone.
899
+ PAYLOAD_EGRESS = re.compile(
900
+ # Clients and trackers, by name.
901
+ r"\bcurl\b|\bwget\b|\bhttpie\b|\bgh\b|\bjira\b|\bacli\b"
902
+ r"|requests\.|httpx|aiohttp|pycurl|urllib|urlopen|http\.client|\bsocket\b"
903
+ r"|https?connection|httpurlconnection|httpclient|httprequest|webclient"
904
+ r"|okhttp|net::http|restclient|faraday|httparty|open-uri|\blwp\b|http::tiny"
905
+ r"|curl_init|curl_exec|guzzle|fsockopen|file_get_contents"
906
+ r"|\bfetch\s*\(|axios|xmlhttprequest|node-fetch|undici|superagent"
907
+ r"|http\.post|http\.newrequest"
908
+ # Handing the payload to another process, which can carry it anywhere.
909
+ r"|subprocess|os\.system|popen|child_process|execsync|spawnsync"
910
+ r"|\bspawn\s*\(|\bexec\s*\(|\bsystem\s*\(|bun\.spawn|deno\.command"
911
+ # The shape a send takes in almost any language, whatever the client is
912
+ # called. Over-matching here costs an exemption, never a refusal.
913
+ r"|\.post\s*\(|\.put\s*\(|\.patch\s*\(|\.request\s*\(|\.send\s*\("
914
+ r"|\.execute\s*\(|\.mutate\s*\(|\.do\s*\(",
915
+ re.IGNORECASE,
916
+ )
917
+ # Producing a local artifact: the positive half of the exemption.
918
+ LOCAL_WRITE = re.compile(
919
+ r"\.write\s*\(|\.writelines\s*\(|write_text\s*\(|write_bytes\s*\("
920
+ r"|writefilesync|writefile\s*\(|createwritestream|outputstream"
921
+ r"|ioutil\.writefile|\btee\b"
922
+ # A shell redirect. Anchored on a separator so `=>` and `->` are not read
923
+ # as one, and required to be followed by something path-shaped.
924
+ r"|(?:^|[\s;&|(])>>?\s*[\"']?[\w./$~-]",
925
+ re.IGNORECASE | re.MULTILINE,
926
+ )
927
+
928
+
929
+ def payload_is_inert(text):
930
+ """Whether this file HOLDS a creation payload rather than submitting one.
931
+
932
+ Args:
933
+ text: The file's contents.
934
+
935
+ Returns:
936
+ True when the file writes a local artifact and carries no primitive
937
+ capable of transmitting anything.
938
+ """
939
+ return (
940
+ LOCAL_WRITE.search(text) is not None
941
+ and PAYLOAD_EGRESS.search(text) is None
942
+ )
943
+
944
+
788
945
  # ---------------------------------------------------------------------------
789
946
  # DECLARING BUILD-READY WHEN THE ROLE IS A STATE AND NOT A LABEL
790
947
  #
@@ -1817,11 +1974,23 @@ def is_assignment_word(token):
1817
1974
  # harm #3705 records, because it can block the verification of a fix while
1818
1975
  # saying nothing about why.
1819
1976
  #
1977
+ # RELOCATION AND ECHOING ARE READS TOO, and leaving them off is what turned a
1978
+ # false positive into a property of the repository (CodySwannGT/lisa#3683). A
1979
+ # file that QUOTES a creation was already reachable by `grep`, `cat` and `ls`
1980
+ # once this list existed — but not by `cp` or `mv`, so the file could be read
1981
+ # and never moved, by any agent and by CI, for as long as the literal stayed in
1982
+ # it. The measured case shows why "reword it" is not the inverse: the refusal
1983
+ # recurred AFTER the content had been reworded, on a different file quoting the
1984
+ # same command. None of these four executes an operand: `cp` and `mv` copy
1985
+ # bytes, `echo` and `printf` write them to stdout. They are also the vocabulary
1986
+ # `unparseable_reads_only` answers with when the text could not be lexed at all.
1987
+ #
1820
1988
  # RESIDUAL, stated rather than hidden: a reader NOT on this list is still
1821
1989
  # followed and can still over-refuse. The set of read-only tools is unbounded,
1822
1990
  # so this closes the measured population and not the class. Add names here as
1823
1991
  # they are measured; do not invert the default to close it by fiat, or the
1824
1992
  # executed-script reach that #3484 bought is lost.
1993
+ #
1825
1994
  # The copy/move/link family is measured, not inferred: `cp <a file that files>`
1826
1995
  # was refused as "an unparseable command that reads as a tracker creation
1827
1996
  # inside <path>" (CodySwannGT/lisa#3683, trip 5, reproduced in this guard's own
@@ -1837,11 +2006,12 @@ def is_assignment_word(token):
1837
2006
  # standing instruction to add on measurement rather than by sweep.
1838
2007
  READ_ONLY_PROGRAMS = {
1839
2008
  "awk", "bat", "cat", "cksum", "cmp", "column", "comm", "cp", "cut",
1840
- "diff", "du", "file", "fold", "git", "grep", "head", "hexdump",
1841
- "install", "jest", "jq", "less", "ln", "ls", "md5", "md5sum", "more",
1842
- "mv", "nl", "od", "pytest", "rg", "sed", "sha1sum", "sha256sum",
1843
- "shellcheck", "shfmt", "sort", "stat", "strings", "tail", "tee",
1844
- "uniq", "vitest", "wc", "xxd", "yamllint",
2009
+ "diff", "du", "echo", "file", "fold", "git", "grep", "head",
2010
+ "hexdump", "install", "jest", "jq", "less", "ln", "ls", "md5",
2011
+ "md5sum", "more", "mv", "nl", "od", "printf", "pytest", "rg",
2012
+ "sed", "sha1sum", "sha256sum", "shellcheck", "shfmt", "sort",
2013
+ "stat", "strings", "tail", "tee", "uniq", "vitest", "wc", "xxd",
2014
+ "yamllint",
1845
2015
  }
1846
2016
 
1847
2017
 
@@ -1882,6 +2052,67 @@ def executing_command(argv):
1882
2052
  return (None, None, [])
1883
2053
 
1884
2054
 
2055
+ # Operators that end a command in raw shell text. Used ONLY where `shlex` has
2056
+ # already refused the text, so a quote-aware split is not available — and
2057
+ # OVER-splitting is the safe direction here, because an extra segment can only
2058
+ # add a command word to check, never remove one.
2059
+ RAW_SEGMENT_SPLIT = re.compile(r"&&|\|\||[;|&\n]")
2060
+
2061
+
2062
+ def raw_command_words(text):
2063
+ """The program each segment of unlexable text puts in command position.
2064
+
2065
+ Answered on a whitespace split because the reason this path exists is that
2066
+ `shlex` refused the text. It still routes through `executing_command`, so a
2067
+ leading assignment and a wrapper — `nice`, `env`, `sudo`, `timeout` — are
2068
+ stepped over here exactly as they are on the parsed path. Reading the
2069
+ wrapper as the command is the bypass this guard's own comments record.
2070
+
2071
+ Args:
2072
+ text: The raw command string.
2073
+
2074
+ Returns:
2075
+ One entry per non-empty segment: the resolved program name, or None
2076
+ when the segment names none.
2077
+ """
2078
+ words = []
2079
+ for chunk in RAW_SEGMENT_SPLIT.split(text):
2080
+ tokens = chunk.split()
2081
+ if not tokens:
2082
+ continue
2083
+ words.append(executing_command(tokens)[1])
2084
+ return words
2085
+
2086
+
2087
+ def unparseable_reads_only(text):
2088
+ """Whether unlexable text only READS, and therefore files nothing.
2089
+
2090
+ `UNPARSEABLE_CREATION` matches raw text, so it cannot tell a filing from a
2091
+ sentence about one. `echo the guard's <creation> behaviour` fails to lex
2092
+ for the apostrophe alone, and was refused as a tracker creation — a command
2093
+ that files nothing, told that "this filing declares no readiness", with no
2094
+ printed remedy that applied to any part of it (CodySwannGT/lisa#3683).
2095
+
2096
+ The fallback is NOT removable and is not narrowed here. `<creation> #'` is
2097
+ a real filing that bash runs and `shlex` rejects, so "I could not parse it"
2098
+ must keep meaning refuse. What this asks instead is the COMMAND POSITION
2099
+ question the parsed path already asks and this arm skipped: only a command
2100
+ position can run anything, and a reader takes its operands as data.
2101
+
2102
+ Fails closed on everything else, which is the same asymmetry
2103
+ `READ_ONLY_PROGRAMS` documents: an unresolvable command word, an
2104
+ unrecognised program, or a text with no segments at all is refused.
2105
+
2106
+ Args:
2107
+ text: The raw command string that would not lex.
2108
+
2109
+ Returns:
2110
+ True when every segment's command word is a known reader.
2111
+ """
2112
+ words = raw_command_words(text)
2113
+ return bool(words) and all(word in READ_ONLY_PROGRAMS for word in words)
2114
+
2115
+
1885
2116
  def executed_operand(argv):
1886
2117
  """The path this command EXECUTES, or None.
1887
2118
 
@@ -2024,7 +2255,9 @@ def file_creation(text, depth):
2024
2255
  `node wrapper.mjs`, a Python client, or anything else that speaks HTTP
2025
2256
  directly — it needs a tracker endpoint AND a creation verb in the same
2026
2257
  file, which is what keeps a changelog that merely mentions `issueCreate`
2027
- from reading as a creation.
2258
+ from reading as a creation. It also needs the file to be capable of
2259
+ SENDING what it spells: a helper that writes the same payload into a test
2260
+ fixture submits nothing. See `payload_is_inert`.
2028
2261
 
2029
2262
  Args:
2030
2263
  text: The file's contents.
@@ -2044,6 +2277,9 @@ def file_creation(text, depth):
2044
2277
  GRAPHQL_CREATE.search(text)
2045
2278
  and TRACKER_ENDPOINT.search(text)
2046
2279
  and not text_declares_readiness(text)
2280
+ # A payload the file WRITES rather than SENDS is data. See
2281
+ # `payload_is_inert` for why the absence of egress alone is not enough.
2282
+ and not payload_is_inert(text)
2047
2283
  ):
2048
2284
  return "a tracker creation", [ready_role], None
2049
2285
  return None
@@ -2098,6 +2334,12 @@ def scan(text, depth, from_file=False):
2098
2334
  # reachable by declaring inline instead, which this change makes work.
2099
2335
  if text_declares_readiness(text):
2100
2336
  return None
2337
+ # The command-position arm of the same reasoning `executed_operand`
2338
+ # applies. A text every one of whose segments runs a known READER
2339
+ # cannot file anything, whatever its prose says — see
2340
+ # `unparseable_reads_only` for why this does not narrow the fallback.
2341
+ if unparseable_reads_only(text):
2342
+ return None
2101
2343
  if UNPARSEABLE_CREATION.search(text):
2102
2344
  return (
2103
2345
  "an unparseable command that reads as a tracker creation",
@@ -2202,6 +2444,30 @@ def scan(text, depth, from_file=False):
2202
2444
  # a human one retry and a false negative costs the entire control.
2203
2445
  inline_override = (OVERRIDE_NAME + "=") in command
2204
2446
 
2447
+
2448
+ def remedy_for(signature):
2449
+ """Which extra remediation paragraph this refusal earns.
2450
+
2451
+ A guard that prints every remedy it knows prints one that does not apply,
2452
+ and a remedy that cannot be performed is the failure this ticket is about
2453
+ (CodySwannGT/lisa#3683): the file-payload paragraph told an author to drop
2454
+ a transmitting primitive that a DOCUMENT does not have, and the
2455
+ unparseable arm printed declaration advice for a command that files
2456
+ nothing. Both are now keyed off which branch actually fired.
2457
+
2458
+ Args:
2459
+ signature: The refusal signature.
2460
+
2461
+ Returns:
2462
+ A key the shell half maps to a paragraph, or an empty string.
2463
+ """
2464
+ if signature.startswith("an unparseable command"):
2465
+ return "unparseable"
2466
+ if " inside " in signature:
2467
+ return "file"
2468
+ return ""
2469
+
2470
+
2205
2471
  found = scan(command, 0)
2206
2472
  if found is not None:
2207
2473
  signature, roles, target = found
@@ -2212,6 +2478,7 @@ if found is not None:
2212
2478
  print("signature=%s" % signature)
2213
2479
  print("roles=%s" % ", ".join(role for role in roles if role))
2214
2480
  print("target=%s" % (target or ""))
2481
+ print("remedy=%s" % remedy_for(signature))
2215
2482
  sys.exit(0)
2216
2483
 
2217
2484
  print("ALLOW")
@@ -2242,11 +2509,28 @@ if [ -n "$structured_call" ]; then
2242
2509
  exit 0
2243
2510
  fi
2244
2511
 
2512
+ # A PACKED label string counts. Exact equality against the flattened value
2513
+ # list reads `labels: ["status:ready"]` and nothing else, but the same
2514
+ # compliant filing spelled `labels: "status:ready,type:Bug"` — the shape
2515
+ # `gh issue create --label` takes, and the shape several MCP servers pass
2516
+ # through verbatim — carries no value equal to the role, so the guard
2517
+ # refused a filing that had declared exactly what it demanded. A false
2518
+ # positive in a guard costs more than a miss: it teaches the operator that
2519
+ # the guard is wrong, and the next refusal is argued with rather than
2520
+ # obeyed.
2521
+ #
2522
+ # Split on the DELIMITERS a packed list uses (comma, semicolon, newline) and
2523
+ # trimmed, never on a `contains` match. `contains` would accept a body that
2524
+ # merely mentions the role in prose — "do not mark this status:ready" — and
2525
+ # that is a fail-open on the one question this path exists to answer.
2245
2526
  structured_declaration="$(
2246
2527
  printf '%s' "$input" |
2247
2528
  jq -r --arg role "$ready_role" '
2248
2529
  [(.tool_input // {}) | .. | strings] as $values
2249
- | if ($values | index($role)) then "role"
2530
+ | ($values + ($values
2531
+ | map(splits("[,;\n]"))
2532
+ | map(sub("^\\s+"; "") | sub("\\s+$"; "")))) as $atoms
2533
+ | if ($atoms | index($role)) then "role"
2250
2534
  elif ($values | map(select(contains("[lisa-human-gate]"))) | length) > 0 then "gate"
2251
2535
  else "" end
2252
2536
  ' 2>/dev/null || printf 'UNREADABLE'
@@ -2299,7 +2583,8 @@ case "$verdict" in
2299
2583
  refuse \
2300
2584
  "$(verdict_field signature)" \
2301
2585
  "$(verdict_field roles)" \
2302
- "$(verdict_field target)"
2586
+ "$(verdict_field target)" \
2587
+ "$(verdict_field remedy)"
2303
2588
  ;;
2304
2589
  esac
2305
2590
 
@@ -34,6 +34,27 @@ set -euo pipefail
34
34
 
35
35
  input="$(cat)"
36
36
 
37
+ # Evaluate once per tool call when this guard is registered on both channels.
38
+ #
39
+ # Lisa reaches an agent through the repository dispatcher AND the plugin
40
+ # manifest, and where both are live the harness runs this guard twice for one
41
+ # tool call. `guard-dedupe.bash` short-circuits the second run ONLY when a
42
+ # byte-identical copy already ALLOWED this exact payload on this exact tool
43
+ # call; a differing vintage, a refusal, and a host with one channel all
44
+ # evaluate exactly as before. Nothing is de-registered by it
45
+ # (CodySwannGT/lisa#3814).
46
+ #
47
+ # Absent library means no dedupe, which is the pre-existing behaviour, so an
48
+ # older channel copy that predates it is unaffected.
49
+ lisa_guard_hook_dir="${BASH_SOURCE[0]%/*}"
50
+ lisa_guard_dedupe_lib="$lisa_guard_hook_dir/guard-dedupe.bash"
51
+ if [ -r "$lisa_guard_dedupe_lib" ]; then
52
+ # shellcheck source=guard-dedupe.bash
53
+ . "$lisa_guard_dedupe_lib"
54
+ trap 'lisa_guard_dedupe_record $?' EXIT
55
+ lisa_guard_dedupe block-instruction-file-edits "$input"
56
+ fi
57
+
37
58
  command -v jq >/dev/null 2>&1 || exit 0
38
59
 
39
60
  # The operator's override. Checked before anything else so it is always the
@@ -73,25 +73,57 @@
73
73
  # propagate any further either — a path a followed script merely NAMES is data
74
74
  # one file further out, not a third hop.
75
75
  #
76
- # ## Every agent surface reaches this guard
76
+ # ## Every agent surface reaches this guard, and Codex reaches it twice over
77
77
  #
78
- # There is no parity gap left to record. A note here used to record one for
79
- # three surfaces; every one of those ports now exists, and the note outlived
80
- # them. It is deleted rather than trimmed, because a stale gap note is
81
- # worse than none: it OVERSTATES the gap, and a reader who trusts it goes
82
- # looking for three missing ports, finds two present, and cannot tell which
83
- # third is real without redoing the measurement from scratch. AGENTS.md asks
84
- # for a gap to be documented instead of silently dropped — that only works
85
- # while the document is true.
78
+ # There is no parity gap left to record. A note here used to record one, and it
79
+ # was wrong by the time anyone read it through two wordings. The first named
80
+ # three surfaces; every one of those ports then shipped and the note stayed
81
+ # unedited. The second named Codex alone, and Codex had never been the gap
82
+ # either: the note and the check that read it were both looking in
83
+ # `src/codex/scripts/`, and `src/codex/hooks-installer.ts` says in its own
84
+ # opening remark that the linked-script layout that directory serves is RETIRED
85
+ # (CodySwannGT/lisa#3750).
86
+ #
87
+ # The note is deleted rather than trimmed, because a stale gap note is worse
88
+ # than none: it OVERSTATES the gap, and a reader who trusts it goes looking for
89
+ # three missing ports, finds two present, and cannot tell which third is real
90
+ # without redoing the measurement from scratch. AGENTS.md asks for a gap to be
91
+ # documented instead of silently dropped — that only works while the document
92
+ # is true.
93
+ #
94
+ # The deeper lesson is not "the note was stale". It is that a note asserting
95
+ # ABSENCE and a check deriving presence from ONE path shape agree with each
96
+ # other for free — the check read the same retired directory the note did, so
97
+ # it returned a clean tick on a claim that was false.
86
98
  #
87
99
  # Where each surface picks it up, so the next reader measures instead of
88
100
  # guessing: Claude and Copilot from `.claude-plugin/plugin.json`, Cursor from
89
- # `hooks/hooks.json`, Codex from `.codex-plugin/hooks.json` AND from the
90
- # enforcement-fallback dispatcher, Antigravity from `hooks.json` via the
91
- # `.agy.sh` adapter beside this file, and OpenCode from
92
- # `src/opencode/plugin-templates/lisa-block-managed-file-edits.ts`.
93
- # `tests/unit/hooks/managed-file-guard-parity-note.test.ts` fails if this
94
- # paragraph goes stale in either direction.
101
+ # `hooks/hooks.json`, Antigravity from `hooks.json` via the `.agy.sh` adapter
102
+ # beside this file, OpenCode from
103
+ # `src/opencode/plugin-templates/lisa-block-managed-file-edits.ts`, and Codex
104
+ # from TWO channels rather than one:
105
+ #
106
+ # `plugins/lisa/.codex-plugin/hooks.json` registers this guard directly, on
107
+ # the Codex plugin channel.
108
+ #
109
+ # `scripts/lisa-enforcement-fallback.sh` names this guard in its roster, and
110
+ # `src/codex/enforcement-fallback-installer.ts` registers that dispatcher on
111
+ # `PreToolUse` for `Bash|Edit|Write|apply_patch` — which is how Codex's
112
+ # `apply_patch` writes are reached at all.
113
+ #
114
+ # Both Codex channels are measured rather than inferred — see
115
+ # `tests/unit/codex/block-managed-file-edits-codex.test.ts`, which drives the
116
+ # registered command as a subprocess in a synthetic host project: a Codex
117
+ # `apply_patch` or `Bash` redirect at a copy-overwrite template exits 2; an
118
+ # ordinary edit to a host-owned file exits 0.
119
+ #
120
+ # Two checks hold this paragraph to the tree.
121
+ # `tests/unit/hooks/managed-file-guard-parity-note.test.ts` fails if it goes
122
+ # stale in either direction — a port that ships without the note being
123
+ # narrowed, and a port REMOVED without it being restored.
124
+ # `check:guard-parity-notes` refuses an absence claim written back in, and now
125
+ # resolves Codex from those two registration channels as well as from a path
126
+ # shape, so it can no longer agree with a false note for free.
95
127
  #
96
128
  # Exemptions (allowed):
97
129
  # - `LISA_ALLOW_MANAGED_FILE_WRITE` set — the operator's explicit override,
@@ -110,6 +142,27 @@ set -euo pipefail
110
142
 
111
143
  input="$(cat)"
112
144
 
145
+ # Evaluate once per tool call when this guard is registered on both channels.
146
+ #
147
+ # Lisa reaches an agent through the repository dispatcher AND the plugin
148
+ # manifest, and where both are live the harness runs this guard twice for one
149
+ # tool call. `guard-dedupe.bash` short-circuits the second run ONLY when a
150
+ # byte-identical copy already ALLOWED this exact payload on this exact tool
151
+ # call; a differing vintage, a refusal, and a host with one channel all
152
+ # evaluate exactly as before. Nothing is de-registered by it
153
+ # (CodySwannGT/lisa#3814).
154
+ #
155
+ # Absent library means no dedupe, which is the pre-existing behaviour, so an
156
+ # older channel copy that predates it is unaffected.
157
+ lisa_guard_hook_dir="${BASH_SOURCE[0]%/*}"
158
+ lisa_guard_dedupe_lib="$lisa_guard_hook_dir/guard-dedupe.bash"
159
+ if [ -r "$lisa_guard_dedupe_lib" ]; then
160
+ # shellcheck source=guard-dedupe.bash
161
+ . "$lisa_guard_dedupe_lib"
162
+ trap 'lisa_guard_dedupe_record $?' EXIT
163
+ lisa_guard_dedupe block-managed-file-edits "$input"
164
+ fi
165
+
113
166
  command -v jq >/dev/null 2>&1 || exit 0
114
167
 
115
168
  # The operator's override, checked first so it is always the cheapest way out.
@@ -550,6 +603,16 @@ SOURCE_BUILTINS = {"source", "."}
550
603
  FOLLOW_WRAPPERS = {
551
604
  "builtin": (frozenset(), 0),
552
605
  "command": (frozenset(), 0),
606
+ # `env bash edit.sh` runs `bash edit.sh`. Its absence here was the one
607
+ # wrapper on this list a caller reaches for without thinking about
608
+ # wrappers at all, because `env VAR=value <cmd>` is how a one-off
609
+ # environment override is spelled. Those `VAR=value` operands need no
610
+ # positional count: the walk below re-enters its assignment skip on the
611
+ # next iteration and steps over as many as are present.
612
+ "env": (
613
+ frozenset({"-C", "--chdir", "-P", "-S", "--split-string", "-u", "--unset"}),
614
+ 0,
615
+ ),
553
616
  "exec": (frozenset({"-a"}), 0),
554
617
  "nice": (frozenset({"-n", "--adjustment"}), 0),
555
618
  "nohup": (frozenset(), 0),
@@ -94,6 +94,27 @@ set -euo pipefail
94
94
 
95
95
  input="$(cat)"
96
96
 
97
+ # Evaluate once per tool call when this guard is registered on both channels.
98
+ #
99
+ # Lisa reaches an agent through the repository dispatcher AND the plugin
100
+ # manifest, and where both are live the harness runs this guard twice for one
101
+ # tool call. `guard-dedupe.bash` short-circuits the second run ONLY when a
102
+ # byte-identical copy already ALLOWED this exact payload on this exact tool
103
+ # call; a differing vintage, a refusal, and a host with one channel all
104
+ # evaluate exactly as before. Nothing is de-registered by it
105
+ # (CodySwannGT/lisa#3814).
106
+ #
107
+ # Absent library means no dedupe, which is the pre-existing behaviour, so an
108
+ # older channel copy that predates it is unaffected.
109
+ lisa_guard_hook_dir="${BASH_SOURCE[0]%/*}"
110
+ lisa_guard_dedupe_lib="$lisa_guard_hook_dir/guard-dedupe.bash"
111
+ if [ -r "$lisa_guard_dedupe_lib" ]; then
112
+ # shellcheck source=guard-dedupe.bash
113
+ . "$lisa_guard_dedupe_lib"
114
+ trap 'lisa_guard_dedupe_record $?' EXIT
115
+ lisa_guard_dedupe block-no-verify "$input"
116
+ fi
117
+
97
118
  # Both interpreters must be probed BEFORE they are used, and a missing one must
98
119
  # be announced rather than swallowed.
99
120
  #
@@ -27,6 +27,27 @@ set -euo pipefail
27
27
 
28
28
  input="$(cat)"
29
29
 
30
+ # Evaluate once per tool call when this guard is registered on both channels.
31
+ #
32
+ # Lisa reaches an agent through the repository dispatcher AND the plugin
33
+ # manifest, and where both are live the harness runs this guard twice for one
34
+ # tool call. `guard-dedupe.bash` short-circuits the second run ONLY when a
35
+ # byte-identical copy already ALLOWED this exact payload on this exact tool
36
+ # call; a differing vintage, a refusal, and a host with one channel all
37
+ # evaluate exactly as before. Nothing is de-registered by it
38
+ # (CodySwannGT/lisa#3814).
39
+ #
40
+ # Absent library means no dedupe, which is the pre-existing behaviour, so an
41
+ # older channel copy that predates it is unaffected.
42
+ lisa_guard_hook_dir="${BASH_SOURCE[0]%/*}"
43
+ lisa_guard_dedupe_lib="$lisa_guard_hook_dir/guard-dedupe.bash"
44
+ if [ -r "$lisa_guard_dedupe_lib" ]; then
45
+ # shellcheck source=guard-dedupe.bash
46
+ . "$lisa_guard_dedupe_lib"
47
+ trap 'lisa_guard_dedupe_record $?' EXIT
48
+ lisa_guard_dedupe block-shell-json-parsing "$input"
49
+ fi
50
+
30
51
  # Probe the interpreters before the first use. Under `set -euo pipefail` an
31
52
  # absent jq does not merely skip the parse — the assignment below dies with
32
53
  # 127, and 127 is not a refusal, so the guard vanishes and the command runs.