@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
@@ -20,6 +20,30 @@
20
20
  * covered. Two checks for one condition, drifting apart, each making the other
21
21
  * look redundant — worse than the wasted diagnosis. Hence `guard` on an entry.
22
22
  *
23
+ * ## A row may only assert what its evidence supports
24
+ *
25
+ * One symptom is routinely produced by two OPPOSITE causes, and a row that
26
+ * names one of them is then confidently wrong half the time. Measured
27
+ * (CodySwannGT/lisa#3812): a conflict in a path `.gitattributes` maps to a
28
+ * custom merge driver was attributed to an UNREGISTERED driver while the
29
+ * driver had in fact run and printed its own diagnosis three lines earlier,
30
+ * sending the reader to verify registration and away from the resolution
31
+ * already on their screen. `exit 1` from a merge driver carries "declined" and
32
+ * "never ran" alike, so an exit code cannot separate them either.
33
+ *
34
+ * Two fields exist for that, and both are about evidence rather than prose:
35
+ *
36
+ * - `excludes` withholds a row when the output carries the counter-evidence
37
+ * that refutes it. `counterSample` is its proof: text that matches
38
+ * `signature` and is nonetheless excluded, so `--check` refuses a
39
+ * discriminator that does not discriminate.
40
+ * - `determination` measures the checkout at match time and picks the arm
41
+ * the measurement supports — including an explicit `unknown` arm. The
42
+ * third arm is not a formality: a row that says "not registered" when it
43
+ * could not determine registration is the fail-open shape this exists
44
+ * against, and absence of driver output is NOT proof a driver did not run
45
+ * (a killed command loses its streams, and only a bounded tail is read).
46
+ *
23
47
  * ## Why an index and not more prose
24
48
  *
25
49
  * The natural fix — "search harder", or "record cross-cutting hazards
@@ -47,8 +71,8 @@
47
71
  *
48
72
  * ## Why it cannot rot into a decoration
49
73
  *
50
- * `--check` refuses three ways, and each one is a way this could otherwise
51
- * report all-clear while indexing nothing:
74
+ * `--check` refuses five ways, and each one is a way this could otherwise
75
+ * report all-clear while indexing nothing, or while indexing the wrong cause:
52
76
  *
53
77
  * - an entry whose cited record no longer contains its anchor — the pointer
54
78
  * rotted, which is exactly what happened to the reference that named
@@ -57,7 +81,12 @@
57
81
  * regex typo silently disables an entry, the same way a malformed ERE
58
82
  * silently disabled a project safety rule in `parity-safety-net.sh`;
59
83
  * - an index that resolves ZERO entries — an empty routing table is a broken
60
- * one, never a clean one.
84
+ * one, never a clean one;
85
+ * - an entry whose `excludes` has no `counterSample` proving it excludes
86
+ * something the row would otherwise have claimed, or whose `counterSample`
87
+ * is not in fact excluded — a discriminator nothing tests is a decoration;
88
+ * - an entry whose `determination` is missing an arm or measures no key, so
89
+ * it would have to answer a question it never asked.
61
90
  *
62
91
  * ## What deliberately does NOT belong here
63
92
  *
@@ -115,6 +144,28 @@ const REQUIRED_TEXT_FIELDS = Object.freeze([
115
144
  * explanation already lives. Never moved here; only pointed at.
116
145
  * @property {{file: string, anchor: string, name: string}} [guard] The control
117
146
  * that already covers this, when one exists, so nobody builds a second.
147
+ * @property {string} [excludes] `RegExp` source of counter-evidence. The row
148
+ * stays silent when the output contains this, however well `signature` fits.
149
+ * @property {string} [counterSample] Verbatim output that matches `signature`
150
+ * and is excluded anyway — the proof that `excludes` discriminates.
151
+ * @property {Determination} [determination] A measurement of this checkout
152
+ * that chooses which cause the evidence actually supports.
153
+ */
154
+
155
+ /**
156
+ * A question about the checkout, asked at match time, whose answer selects a
157
+ * cause instead of asserting one.
158
+ *
159
+ * Every arm is mandatory, `unknown` included. An unanswerable probe is a real
160
+ * outcome and must read as one; collapsing it into `present` or `absent`
161
+ * reintroduces the confident wrong cause the whole field exists against.
162
+ * @typedef {object} Determination
163
+ * @property {string} question What is being measured, in one line.
164
+ * @property {readonly string[]} gitConfigKeys `git config --get` keys to read.
165
+ * @property {string} present Cause when every key resolves to a value.
166
+ * @property {string} absent Cause when every key is unset.
167
+ * @property {string} unknown Cause when the probe could not answer, which
168
+ * includes a mixed reading — say so, never guess.
118
169
  */
119
170
 
120
171
  /**
@@ -173,6 +224,21 @@ function compile(entry) {
173
224
  }
174
225
  }
175
226
 
227
+ /**
228
+ * Compile an entry's counter-evidence pattern.
229
+ * @param {SignatureEntry} entry The row.
230
+ * @returns {RegExp|null|undefined} The pattern, `undefined` when the row
231
+ * declares none, and `null` when it declares one that does not compile.
232
+ */
233
+ function compileExcludes(entry) {
234
+ if (typeof entry?.excludes !== "string") return undefined;
235
+ try {
236
+ return new RegExp(entry.excludes);
237
+ } catch {
238
+ return null;
239
+ }
240
+ }
241
+
176
242
  /**
177
243
  * The 1-based line an anchor sits on.
178
244
  * @param {string} root Repository root.
@@ -187,6 +253,87 @@ export function anchorLine(root, record) {
187
253
  return found === -1 ? null : found + 1;
188
254
  }
189
255
 
256
+ /**
257
+ * Every complaint an entry's counter-evidence earns.
258
+ *
259
+ * A discriminator is only worth having if it can be shown to discriminate, so
260
+ * `counterSample` is mandatory alongside `excludes` and must be output the row
261
+ * WOULD have claimed without it. Without that proof the field is indistinguish-
262
+ * able from a regex that never matches, which silently restores the wrong
263
+ * attribution it was added to prevent (CodySwannGT/lisa#3812).
264
+ * @param {SignatureEntry} entry The row.
265
+ * @param {string} label The row's id, for the message.
266
+ * @param {RegExp|null} pattern The row's compiled `signature`.
267
+ * @returns {string[]} Human-readable problems, empty when the row is sound.
268
+ */
269
+ function excludeProblems(entry, label, pattern) {
270
+ const excludes = compileExcludes(entry);
271
+ if (excludes === undefined) {
272
+ return typeof entry?.counterSample === "string"
273
+ ? [`${label}: has a "counterSample" but no "excludes" to exclude it`]
274
+ : [];
275
+ }
276
+ if (excludes === null) {
277
+ return [`${label}: "excludes" is not a valid regular expression`];
278
+ }
279
+ const sampleProblem = excludes.test(String(entry.sample ?? ""))
280
+ ? [
281
+ `${label}: "excludes" excludes its own "sample", so this row can never fire`,
282
+ ]
283
+ : [];
284
+ const counter = entry?.counterSample;
285
+ if (typeof counter !== "string" || counter.length === 0) {
286
+ return [
287
+ ...sampleProblem,
288
+ `${label}: has "excludes" but no "counterSample", so nothing proves it discriminates`,
289
+ ];
290
+ }
291
+ const matchesSignature =
292
+ pattern !== null && pattern.test(counter)
293
+ ? []
294
+ : [
295
+ `${label}: "counterSample" does not match "signature", so it never tested the exclusion`,
296
+ ];
297
+ const isExcluded = excludes.test(counter)
298
+ ? []
299
+ : [
300
+ `${label}: "counterSample" is not excluded by "excludes", so the discriminator does not discriminate`,
301
+ ];
302
+ return [...sampleProblem, ...matchesSignature, ...isExcluded];
303
+ }
304
+
305
+ /**
306
+ * Every complaint an entry's determination earns.
307
+ *
308
+ * The `unknown` arm is required exactly as hard as the other two. A row that
309
+ * can only say "present" or "absent" has to answer even when it could not
310
+ * measure, and answering anyway is the defect (CodySwannGT/lisa#3812).
311
+ * @param {SignatureEntry} entry The row.
312
+ * @param {string} label The row's id, for the message.
313
+ * @returns {string[]} Human-readable problems, empty when the row is sound.
314
+ */
315
+ function determinationProblems(entry, label) {
316
+ const determination = entry?.determination;
317
+ if (determination === undefined) return [];
318
+ const missingArms = ["question", "present", "absent", "unknown"]
319
+ .filter(
320
+ arm =>
321
+ typeof determination[arm] !== "string" ||
322
+ determination[arm].length === 0
323
+ )
324
+ .map(arm => `${label}: determination "${arm}" is missing or empty`);
325
+ const keys = determination.gitConfigKeys;
326
+ const keyProblems =
327
+ Array.isArray(keys) &&
328
+ keys.length > 0 &&
329
+ keys.every(key => typeof key === "string" && key.length > 0)
330
+ ? []
331
+ : [
332
+ `${label}: determination has no "gitConfigKeys", so it measures nothing`,
333
+ ];
334
+ return [...missingArms, ...keyProblems];
335
+ }
336
+
190
337
  /**
191
338
  * Every complaint one entry earns.
192
339
  * @param {SignatureEntry} entry The row.
@@ -223,7 +370,14 @@ function entryProblems(entry, root) {
223
370
  `${label}: guard ${entry.guard.file} no longer contains "${entry.guard.anchor}" — the pointer rotted`,
224
371
  ]
225
372
  : [];
226
- return [...missing, ...patternProblems, ...recordProblems, ...guardProblems];
373
+ return [
374
+ ...missing,
375
+ ...patternProblems,
376
+ ...recordProblems,
377
+ ...guardProblems,
378
+ ...excludeProblems(entry, label, pattern),
379
+ ...determinationProblems(entry, label),
380
+ ];
227
381
  }
228
382
 
229
383
  /**
@@ -266,17 +420,92 @@ export function matchEntries(text, entries) {
266
420
  return entries
267
421
  .filter(entry => {
268
422
  const pattern = compile(entry);
269
- return pattern !== null && pattern.test(scanned);
423
+ if (pattern === null || !pattern.test(scanned)) return false;
424
+ const excludes = compileExcludes(entry);
425
+ // A discriminator that will not compile leaves the row unable to tell
426
+ // its two causes apart, so the row says nothing. Staying quiet costs a
427
+ // notice; firing anyway restores the confident wrong cause. `--check`
428
+ // is what stops this state from lasting.
429
+ if (excludes === null) return false;
430
+ return excludes === undefined || !excludes.test(scanned);
270
431
  })
271
432
  .slice(0, MAX_REPORTED);
272
433
  }
273
434
 
435
+ /**
436
+ * Read the `git config` keys a determination names, in this checkout.
437
+ *
438
+ * Three outcomes, never two. `git config --get` exits 1 for a key that is
439
+ * simply unset, which is a real answer; ANY other failure — no git, no
440
+ * repository, a malformed key — is not an answer and must not be read as one.
441
+ * A mixed reading is `unknown` too: some registered and some not cannot say
442
+ * which applies to the path in front of the reader.
443
+ *
444
+ * `GIT_*` is stripped from the child environment. Those variables name the
445
+ * repository a PARENT process was operating on — a git hook exports `GIT_DIR`
446
+ * and `GIT_INDEX_FILE` to everything it runs — so inheriting them answers about
447
+ * some other checkout while the notice says "in this checkout".
448
+ * @param {string} root Directory to ask from.
449
+ * @param {readonly string[]} keys Config keys to read.
450
+ * @returns {{verdict: "present"|"absent"|"unknown", readings: string[]}} The
451
+ * answer, and what was read per key so the reader can check the working.
452
+ */
453
+ export function probeGitConfig(root, keys) {
454
+ const env = Object.fromEntries(
455
+ Object.entries(process.env).filter(([name]) => !name.startsWith("GIT_"))
456
+ );
457
+ const readings = keys.map(key => {
458
+ try {
459
+ const value = execFileSync("git", ["config", "--get", key], {
460
+ cwd: root,
461
+ env,
462
+ encoding: "utf8",
463
+ stdio: ["ignore", "pipe", "ignore"],
464
+ });
465
+ return { key, state: value.trim().length > 0 ? "present" : "absent" };
466
+ } catch (cause) {
467
+ return { key, state: cause?.status === 1 ? "absent" : "unknown" };
468
+ }
469
+ });
470
+ const states = new Set(readings.map(reading => reading.state));
471
+ return {
472
+ verdict: states.size === 1 ? [...states][0] : "unknown",
473
+ readings: readings.map(reading => `${reading.key}=${reading.state}`),
474
+ };
475
+ }
476
+
477
+ /**
478
+ * The cause a row is entitled to state, given what this checkout measures.
479
+ * @param {SignatureEntry} entry The matched row.
480
+ * @param {string} root Repository root.
481
+ * @returns {{measured: string|null, therefore: string|null}} The reading and
482
+ * the arm it selects, both null for a row that measures nothing.
483
+ */
484
+ export function resolveDetermination(entry, root) {
485
+ const determination = entry?.determination;
486
+ if (determination === undefined) return { measured: null, therefore: null };
487
+ const keys = Array.isArray(determination.gitConfigKeys)
488
+ ? determination.gitConfigKeys
489
+ : [];
490
+ const { verdict, readings } = probeGitConfig(root, keys);
491
+ return {
492
+ measured: `${determination.question} — ${verdict.toUpperCase()} (${readings.join(", ")})`,
493
+ therefore: determination[verdict] ?? determination.unknown,
494
+ };
495
+ }
496
+
274
497
  /**
275
498
  * The notice a matched row prints.
276
499
  *
277
500
  * Worded to stop a rediscovery rather than to start one: it names the cause
278
501
  * outright, cites the record with a line number so it is one click away, and
279
502
  * says explicitly that the diagnosis is already done.
503
+ *
504
+ * A row carrying a `determination` also prints what was measured HERE and the
505
+ * arm that reading selects — including an arm that says the question was not
506
+ * answered. Showing the reading rather than only its conclusion is what lets a
507
+ * reader catch the notice being wrong, which is the failure that produced this
508
+ * (CodySwannGT/lisa#3812).
280
509
  * @param {SignatureEntry} entry The matched row.
281
510
  * @param {string} root Repository root.
282
511
  * @returns {string} An operator-readable block.
@@ -292,10 +521,16 @@ export function formatNotice(entry, root) {
292
521
  : [
293
522
  ` ALREADY GUARDED by ${entry.guard.name} (${entry.guard.file}) — do NOT build a second control for this.`,
294
523
  ];
524
+ const { measured, therefore } = resolveDetermination(entry, root);
525
+ const determined =
526
+ measured === null
527
+ ? []
528
+ : [` Measured: ${measured}`, ` Therefore: ${therefore}`];
295
529
  return [
296
530
  ` ${entry.id}`,
297
531
  ` Symptom: ${entry.symptom}`,
298
532
  ` Cause: ${entry.cause}`,
533
+ ...determined,
299
534
  ` This is already written down, next to the cause:`,
300
535
  ...records,
301
536
  ...guard,
@@ -0,0 +1,337 @@
1
+ # shellcheck shell=bash
2
+ # Evaluate a dual-channel guard once per tool call, without de-registering it.
3
+ #
4
+ # Sourced — never executed — by every guard that Lisa registers on BOTH of its
5
+ # enforcement channels: the repository dispatcher
6
+ # (`scripts/lisa-enforcement-fallback.sh`, which sweeps the guard roster in one
7
+ # process) and the plugin manifest, which registers the same guards
8
+ # individually. On a machine where both are live the harness runs each guard
9
+ # twice for one tool call. The verdicts agree — the guards are idempotent — so
10
+ # the defect is cost, not correctness (CodySwannGT/lisa#3814).
11
+ #
12
+ # WHY THIS IS NOT "DROP ONE REGISTRATION".
13
+ #
14
+ # The obvious fix is to de-register the fallback where the plugin covers the
15
+ # same ground. That fix cannot be made safely from here, and the reason is
16
+ # written into the dispatcher itself: plugin-hook LIVENESS is not observable
17
+ # from disk. `installed_plugins.json` answers "was this plugin ever installed",
18
+ # which is project-blind, enablement-blind and session-blind — hooks load at
19
+ # session start, the record is rewritten on any install. A stand-down keyed on
20
+ # that record switched enforcement off in sessions where the plugin hooks were
21
+ # not loaded, and did it silently. So the dispatcher runs unconditionally and
22
+ # always will.
23
+ #
24
+ # What IS observable is EXECUTION. A guard that has already run in this session
25
+ # from a different directory is proof — not inference — that a second channel is
26
+ # live right now. This file turns that observation into a per-call memo, so the
27
+ # second copy of an identical guard replays a verdict the first one produced
28
+ # instead of computing it again. Nothing is de-registered, no matcher narrows,
29
+ # and a host with the plugin absent takes the single-channel path where the memo
30
+ # can never hit.
31
+ #
32
+ # WHAT THE MEMO IS ALLOWED TO SHORT-CIRCUIT, and the three conditions that make
33
+ # it sound. Every one of them is a REFUSAL to dedupe when it cannot be proven:
34
+ #
35
+ # 1. BYTE-IDENTICAL CODE. The digest covers the guard script and this library
36
+ # verbatim. Two channels at different vintages therefore compute different
37
+ # keys and both run — which is required, because the agent is meant to see
38
+ # the UNION of two vintages' verdicts, and a tightening on either channel
39
+ # must take effect at once. Deduplicating across vintages would let the
40
+ # older, weaker copy answer for the newer one.
41
+ # 2. IDENTICAL PAYLOAD, SAME TOOL CALL. The digest covers the payload and a
42
+ # per-call discriminator — the transcript's size, which grows with every
43
+ # turn. Without the discriminator a command repeated later in the session
44
+ # would hit a memo written before the repository changed underneath it, and
45
+ # a guard that classifies against the working tree would be answering with
46
+ # a stale verdict. No discriminator available means no memo.
47
+ # 3. ALLOW VERDICTS ONLY. A refusal is never memoised and never replayed. That
48
+ # is deliberate: refusals are the rare path, so deduplicating them saves
49
+ # nothing measurable, and it removes the whole class of hazard the ticket
50
+ # names — a replayed refusal whose producing channel died mid-run, or a
51
+ # refusal reaching the agent without the message that explains it. On a
52
+ # refusal every channel evaluates and speaks exactly as it does today.
53
+ #
54
+ # COST ON THE PATH THAT GETS NO BENEFIT. A host with one channel must not pay
55
+ # for a mechanism it cannot use. The channel check is builtins only — one small
56
+ # file read, no subshell, no fork — and returns before the digest is computed
57
+ # unless a second channel has actually been seen. The digest, and the one `stat`
58
+ # that feeds it the per-call discriminator, are reached only where a second
59
+ # channel is live, which is exactly where they are repaid.
60
+
61
+ # Key this guard would memoise under, once computed. Empty means "record
62
+ # nothing on exit".
63
+ lisa_guard_memo_key=""
64
+
65
+ # File that memo is written to.
66
+ lisa_guard_memo_file=""
67
+
68
+ # Base directory for memo state, resolved at most once per process.
69
+ lisa_guard_memo_base=""
70
+
71
+ # Whether a key was computed and a memo may be recorded on exit.
72
+ lisa_guard_memo_ready=0
73
+
74
+ # Digest tool for the memo key, resolved once. Empty disables the memo.
75
+ lisa_guard_memo_digest_tool=""
76
+
77
+ # Whether a directory is a real directory this user owns, and not a symlink —
78
+ # builtins only, so this costs no process.
79
+ #
80
+ # `-O` is the load-bearing test: a memo planted by another user would suppress a
81
+ # refusal, so a directory this user does not own is never used. The dispatcher
82
+ # validates the whole parent chain before exporting `LISA_GUARD_MEMO_DIR`; this
83
+ # re-checks the leaf rather than trusting the environment variable, because an
84
+ # exported path is an input like any other.
85
+ #
86
+ # $1 - candidate directory
87
+ lisa_guard_memo_dir_usable() {
88
+ [ -n "${1:-}" ] || return 1
89
+ [ -d "$1" ] || return 1
90
+ [ ! -L "$1" ] || return 1
91
+ [ -O "$1" ] || return 1
92
+ }
93
+
94
+ # Establish the memo base directory.
95
+ #
96
+ # Preferred source is `LISA_GUARD_MEMO_DIR`, exported by the dispatcher, which
97
+ # has already resolved `TMPDIR` physically and walked every parent for ownership
98
+ # and mode. Guards it spawns therefore pay nothing for that walk. A guard
99
+ # reached by the plugin channel has no such parent, so it creates the directory
100
+ # itself under `TMPDIR` — private by umask, and used only when this user owns
101
+ # it. If neither works the memo is unavailable and every guard evaluates, which
102
+ # is today's behaviour.
103
+ lisa_guard_memo_resolve_base() {
104
+ [ -z "$lisa_guard_memo_base" ] || return 0
105
+
106
+ local candidate="${LISA_GUARD_MEMO_DIR:-}"
107
+ if lisa_guard_memo_dir_usable "$candidate"; then
108
+ lisa_guard_memo_base="$candidate"
109
+ return 0
110
+ fi
111
+
112
+ local uid="${LISA_GUARD_MEMO_UID:-}"
113
+ if [ -z "$uid" ]; then
114
+ uid="$(id -u 2>/dev/null || printf 'unknown')"
115
+ fi
116
+ candidate="${TMPDIR:-/tmp}"
117
+ candidate="${candidate%/}/lisa-guard-memo-$uid"
118
+ if [ ! -e "$candidate" ] && [ ! -L "$candidate" ]; then
119
+ (umask 077 && mkdir -p "$candidate") 2>/dev/null || true
120
+ fi
121
+ if lisa_guard_memo_dir_usable "$candidate"; then
122
+ lisa_guard_memo_base="$candidate"
123
+ return 0
124
+ fi
125
+ return 1
126
+ }
127
+
128
+ # Resolve a SHA-256 tool, or report that there is none.
129
+ #
130
+ # Two spellings because the guards run on macOS, on Linux CI images, and in
131
+ # containers that carry neither. A missing digest tool disables dedupe rather
132
+ # than degrading it to something weaker: a cheap checksum could collide, and a
133
+ # collision here means a DIFFERENT payload reads as already-allowed, which is
134
+ # lost enforcement rather than a slow guard.
135
+ lisa_guard_memo_resolve_digest() {
136
+ [ -z "$lisa_guard_memo_digest_tool" ] || return 0
137
+ if command -v shasum >/dev/null 2>&1; then
138
+ lisa_guard_memo_digest_tool="shasum"
139
+ elif command -v sha256sum >/dev/null 2>&1; then
140
+ lisa_guard_memo_digest_tool="sha256sum"
141
+ else
142
+ return 1
143
+ fi
144
+ return 0
145
+ }
146
+
147
+ # Remove memo state older than a day.
148
+ #
149
+ # Files first, then the emptied session directories, both with `find -delete`.
150
+ # Never a recursive forced delete: this runs unattended inside every guard, and
151
+ # the blast radius of a mis-resolved base directory has to stay bounded to
152
+ # entries this scheme itself created.
153
+ lisa_guard_memo_sweep() {
154
+ find "$lisa_guard_memo_base" -mindepth 2 -maxdepth 2 -type f -mmin +1440 \
155
+ -delete 2>/dev/null || true
156
+ find "$lisa_guard_memo_base" -mindepth 1 -maxdepth 1 -type d -empty \
157
+ -mmin +1440 -delete 2>/dev/null || true
158
+ }
159
+
160
+ # Decide whether this guard has already been evaluated for this tool call, and
161
+ # exit 0 in place of re-evaluating it when it has.
162
+ #
163
+ # Returns 0 for "carry on and evaluate". Never returns non-zero, so a caller
164
+ # running under `set -e` is unaffected by it.
165
+ #
166
+ # $1 - guard name, used only to name the memo file
167
+ # $2 - the raw tool payload this guard was handed on stdin
168
+ lisa_guard_dedupe() {
169
+ local guard_name="${1:-}"
170
+ local payload="${2:-}"
171
+ [ -n "$guard_name" ] || return 0
172
+ [ -n "$payload" ] || return 0
173
+ [ -z "${LISA_GUARD_DEDUPE_DISABLE:-}" ] || return 0
174
+
175
+ # The session scopes every piece of state below. No session id means the
176
+ # payload came from a surface this cannot reason about — an adapter's
177
+ # synthesised envelope, a test harness — and the answer there is to evaluate.
178
+ local session_id=""
179
+ local session_pattern="\"session_id\"[[:space:]]*:[[:space:]]*\"([^\"]+)\""
180
+ if [[ "$payload" =~ $session_pattern ]]; then
181
+ session_id="${BASH_REMATCH[1]}"
182
+ fi
183
+ [ -n "$session_id" ] || return 0
184
+ # It is used as a path component, so anything that could escape the state
185
+ # directory disqualifies it outright.
186
+ case "$session_id" in *[!A-Za-z0-9._-]* | .*) return 0 ;; esac
187
+
188
+ lisa_guard_memo_resolve_base || return 0
189
+
190
+ local session_dir="$lisa_guard_memo_base/$session_id"
191
+ local fresh_session=0
192
+ if [ ! -d "$session_dir" ]; then
193
+ if (umask 077 && mkdir "$session_dir") 2>/dev/null; then
194
+ fresh_session=1
195
+ fi
196
+ fi
197
+ [ -d "$session_dir" ] || return 0
198
+ [ ! -L "$session_dir" ] || return 0
199
+
200
+ # Swept once, by the process that created the session directory, so a
201
+ # long-lived machine does not accumulate memo state. Off the hot path by
202
+ # construction: every later call in the session finds the directory present.
203
+ if [ "$fresh_session" -eq 1 ]; then
204
+ lisa_guard_memo_sweep
205
+ fi
206
+
207
+ # The directory this copy of the guard was served from. Two registrations of
208
+ # one guard are always two different directories — a repository tree or an
209
+ # applied `scripts/lisa-hooks/` on one side, a plugin cache on the other — so
210
+ # the set of directories seen this session IS the set of live channels.
211
+ local self_script="${BASH_SOURCE[1]:-$0}"
212
+ local self_dir="."
213
+ case "$self_script" in */*) self_dir="${self_script%/*}" ;; esac
214
+
215
+ local channels_file="$session_dir/channels"
216
+ local seen_self=0
217
+ local other_channel=0
218
+ local line=""
219
+ if [ -f "$channels_file" ]; then
220
+ while IFS= read -r line || [ -n "$line" ]; do
221
+ [ -n "$line" ] || continue
222
+ if [ "$line" = "$self_dir" ]; then
223
+ seen_self=1
224
+ else
225
+ other_channel=1
226
+ fi
227
+ done <"$channels_file"
228
+ fi
229
+ if [ "$seen_self" -eq 0 ]; then
230
+ printf '%s\n' "$self_dir" >>"$channels_file" 2>/dev/null || true
231
+ fi
232
+
233
+ # Single channel: the memo can never hit, so nothing below is worth its cost.
234
+ # This is the branch a host with no plugin takes on every call, and it is the
235
+ # reason the fork lives below this line rather than above it.
236
+ [ "$other_channel" -eq 1 ] || return 0
237
+
238
+ # The per-call discriminator. The transcript grows with every turn, so its
239
+ # size separates two identical commands issued at different points in one
240
+ # session — which matters because a guard classifies against a working tree
241
+ # that may have changed between them. Both channels read the same transcript
242
+ # within one tool call, and anything appended between them costs a memo miss,
243
+ # never a stale hit.
244
+ local transcript=""
245
+ local transcript_pattern="\"transcript_path\"[[:space:]]*:[[:space:]]*\"([^\"]+)\""
246
+ if [[ "$payload" =~ $transcript_pattern ]]; then
247
+ transcript="${BASH_REMATCH[1]}"
248
+ fi
249
+ [ -n "$transcript" ] || return 0
250
+ [ -f "$transcript" ] || return 0
251
+
252
+ lisa_guard_memo_resolve_digest || return 0
253
+
254
+ # The transcript's size, captured and VALIDATED before it can reach the
255
+ # digest, because a `stat` that rejects the other platform's spelling can
256
+ # still write to stdout on its way out. GNU `stat -f '%z' FILE` reads `-f` as
257
+ # `--file-system` and the format string as a FILE that does not exist: it
258
+ # exits non-zero AND prints a filesystem status block for the real operand,
259
+ # whose `Free` and `Available` counts move under any disk activity. Appended
260
+ # straight into the digest, that made the two channels of ONE tool call
261
+ # compute different keys, so the memo could only ever miss — the whole
262
+ # mechanism switched off on every Linux host, while the three "must not
263
+ # dedupe" controls stayed green because they assert a miss.
264
+ #
265
+ # Only an all-digits answer is accepted, and no size means NO MEMO rather
266
+ # than a memo with nothing separating one call from the next: that is
267
+ # condition 2 above, which the `A || B` spelling had left unenforced on a
268
+ # host carrying neither `stat`.
269
+ local transcript_size=""
270
+ transcript_size="$(stat -c '%s' "$transcript" 2>/dev/null)" ||
271
+ transcript_size=""
272
+ case "$transcript_size" in
273
+ "" | *[!0-9]*)
274
+ transcript_size="$(stat -f '%z' "$transcript" 2>/dev/null)" ||
275
+ transcript_size=""
276
+ ;;
277
+ esac
278
+ case "$transcript_size" in "" | *[!0-9]*) return 0 ;; esac
279
+
280
+ # One command substitution, covering everything the verdict depends on: the
281
+ # guard's own bytes, this library's bytes, the payload, and which call it is.
282
+ local key=""
283
+ key="$(
284
+ {
285
+ cat "$self_script" "${BASH_SOURCE[0]}" 2>/dev/null
286
+ printf '\n%s\n' "$payload"
287
+ printf '%s\n' "$transcript_size"
288
+ } | if [ "$lisa_guard_memo_digest_tool" = "shasum" ]; then
289
+ shasum -a 256 2>/dev/null
290
+ else
291
+ sha256sum 2>/dev/null
292
+ fi
293
+ )" || key=""
294
+ key="${key%% *}"
295
+ case "$key" in "" | *[!0-9a-f]*) return 0 ;; esac
296
+
297
+ # One memo file per guard per session, rewritten as the call moves on, so the
298
+ # state directory stays a fixed size however long a session runs. A saturated
299
+ # temp directory is its own outage on this fleet, and a memo scheme that grew
300
+ # a file per tool call would be one.
301
+ local memo_file="$session_dir/$guard_name.allow"
302
+ local recorded=""
303
+ if [ -f "$memo_file" ] && [ ! -L "$memo_file" ]; then
304
+ IFS= read -r recorded <"$memo_file" 2>/dev/null || recorded=""
305
+ if [ "$recorded" = "$key" ]; then
306
+ # A byte-identical copy of this guard already allowed this exact payload
307
+ # on this exact tool call. The evaluation being skipped could only have
308
+ # produced the verdict that was already delivered.
309
+ exit 0
310
+ fi
311
+ fi
312
+
313
+ lisa_guard_memo_key="$key"
314
+ lisa_guard_memo_file="$memo_file"
315
+ lisa_guard_memo_ready=1
316
+ return 0
317
+ }
318
+
319
+ # Record an ALLOW verdict for the other channel to replay. Installed as the
320
+ # guard's EXIT trap, so it covers every path the guard can leave by.
321
+ #
322
+ # Any status other than 0 writes nothing and, when a memo from an earlier call
323
+ # is still sitting there, removes it: a guard that refused must never leave
324
+ # behind something that reads as permission.
325
+ #
326
+ # $1 - the exiting status
327
+ lisa_guard_dedupe_record() {
328
+ [ "${lisa_guard_memo_ready:-0}" -eq 1 ] || return 0
329
+ [ -n "$lisa_guard_memo_file" ] || return 0
330
+ if [ "${1:-1}" -eq 0 ]; then
331
+ printf '%s\n' "$lisa_guard_memo_key" >"$lisa_guard_memo_file" 2>/dev/null ||
332
+ true
333
+ else
334
+ rm -f "$lisa_guard_memo_file" 2>/dev/null || true
335
+ fi
336
+ return 0
337
+ }
@@ -19,11 +19,19 @@ fi
19
19
  ROOT="${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}}"
20
20
  RULES_DIR="$ROOT/rules/eager"
21
21
 
22
- # Backward compatibility: if the eager subdir is absent (older Lisa install),
23
- # fall back to the flat rules/ directory so a partial upgrade still ships rules.
24
- if [ ! -d "$RULES_DIR" ]; then
25
- RULES_DIR="$ROOT/rules"
26
- fi
22
+ # There is deliberately NO fallback to a flat rules/ directory (#3993).
23
+ #
24
+ # It existed to cover an "older Lisa install", but a plugin's rules and this
25
+ # script ship in the same directory and install as one unit, so that skew cannot
26
+ # arise. What the branch actually did was absorb plugins that never adopted the
27
+ # split: `lisa-phaser` shipped a flat 11,007-byte rules/phaser.md on 2026-06-11,
28
+ # two weeks AFTER the split commit a820527 (2026-05-28), and the fallback
29
+ # injected the whole body at every SessionStart and SubagentStart without any
30
+ # surface reporting it. A quiet success is indistinguishable from a tree that was
31
+ # never split. With the branch gone, an unsplit plugin injects nothing and gets
32
+ # noticed. (The Codex mirror injector keeps its fallback: `.codex/lisa-rules/` is
33
+ # written separately from the script that reads it, so vintage skew IS possible
34
+ # there.)
27
35
 
28
36
  # Bail silently if no rules directory at all
29
37
  [ -d "$RULES_DIR" ] || exit 0