@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
@@ -39,6 +39,14 @@
39
39
  * - a ratification matching no live coupling, because a permission left
40
40
  * behind after its subject is gone is inherited for free by the next path
41
41
  * that happens to match;
42
+ * - a ledgered coupling with no recorded STALENESS decision (#3687) — the
43
+ * one field a new coupling cannot be added without, because its detection
44
+ * is derived and therefore free while the decision costs a sentence;
45
+ * - a staleness decision the tree refuses to support, in either direction:
46
+ * a claimed handshake nothing shows, or an exemption on a coupling that
47
+ * already has one;
48
+ * - a staleness decision matching no live coupling, for the same reason a
49
+ * stale ratification fails;
42
50
  * - `--check` against a stale ledger;
43
51
  * - and any run that measured nothing.
44
52
  *
@@ -82,6 +90,13 @@ import {
82
90
  isReusable,
83
91
  scanWorkflow,
84
92
  } from "../src/core/two-channel-delivery-scan.js";
93
+ import {
94
+ artifactStalenessSignals,
95
+ STALENESS_DECISIONS,
96
+ STALENESS_DETECTIONS,
97
+ type StalenessRecord,
98
+ type StalenessSignal,
99
+ } from "../src/core/two-channel-staleness.js";
85
100
 
86
101
  /** Where the fast channel's bodies live in this repository. */
87
102
  const WORKFLOWS_DIR = path.join(".github", "workflows");
@@ -228,52 +243,21 @@ export function buildDeliveryInventory(
228
243
  );
229
244
  }
230
245
 
231
- /**
232
- * The only part of a report these two functions read.
233
- *
234
- * Narrower than the full report on purpose: they need a key and a verdict per
235
- * entry and nothing else, and a parameter that asked for the whole measurement
236
- * would force every caller — including a test — to build one it does not use.
237
- */
238
- interface CouplingKeySource {
239
- readonly entries: readonly {
240
- readonly key: string;
241
- readonly verdict: string;
242
- }[];
243
- }
244
-
245
- /** One coupling's answer to "can this notice a STALE artifact?" (#3687). */
246
- export interface StalenessClassification {
247
- /**
248
- * What the coupling CAN detect today — an observation, not a decision.
249
- *
250
- * `version-handshake` asks the artifact for a contract version and compares
251
- * it. `capability-probe` asks for one subcommand or flag and infers age from
252
- * whether it answers, which sees staleness at exactly one point and is blind
253
- * above that floor — the CodySwannGT/lisa#3477 defect. `existence-only`
254
- * cannot see age at all.
255
- */
256
- readonly detects: "version-handshake" | "capability-probe" | "existence-only";
257
- /**
258
- * Why the current level is acceptable. Required unless a handshake exists.
259
- *
260
- * This is the DECISION half, kept separate from `detects` on purpose: a
261
- * coupling that cannot see age and is fine that way (an advisory read, an
262
- * artifact carrying no verdict-bearing logic) is a different fact from one
263
- * that cannot see age and should. Collapsing them into a single
264
- * "not-needed" token would lose exactly the distinction the sweep exists to
265
- * record, and would let an unexamined default wear the same word as a
266
- * considered exemption.
267
- */
268
- readonly reason?: string;
269
- }
270
-
271
246
  /** The ledger's hand-authored half plus its derived half. */
272
247
  interface Ledger {
273
248
  readonly ratified: Readonly<Record<string, string>>;
274
- readonly staleness: Readonly<Record<string, StalenessClassification>>;
249
+ /**
250
+ * The staleness decision recorded for each ledgered coupling.
251
+ *
252
+ * Hand-authored, and required. The DETECTION beside each coupling below is
253
+ * derived from the tree on every run; this is the half a person has to
254
+ * answer, which is what stops entry 24 from inheriting "nobody looked".
255
+ */
256
+ readonly staleness: Readonly<Record<string, StalenessRecord>>;
275
257
  readonly inspected: TwoChannelReport["inspected"];
276
258
  readonly counts: TwoChannelReport["counts"];
259
+ readonly detectionCounts: TwoChannelReport["detectionCounts"];
260
+ readonly decisionCounts: TwoChannelReport["decisionCounts"];
277
261
  readonly couplings: readonly {
278
262
  readonly key: string;
279
263
  readonly workflow: string;
@@ -282,172 +266,47 @@ interface Ledger {
282
266
  readonly verdict: string;
283
267
  readonly remedy: string;
284
268
  readonly guarded: boolean;
269
+ readonly handling: readonly string[];
270
+ readonly detection: string;
271
+ readonly stalenessSignals: readonly string[];
272
+ readonly decision: string | null;
273
+ readonly reason: string | null;
285
274
  readonly lanes: readonly string[];
286
275
  readonly detail: string;
287
276
  }[];
288
277
  }
289
278
 
290
279
  /**
291
- * Read the ratifications already recorded, so regenerating never drops them.
280
+ * The ledger as it currently stands, or an empty one when there is none.
292
281
  * @param ledgerPath - Absolute path to the ledger
293
- * @returns The ratification reasons, keyed `<workflow>::<path>`
282
+ * @returns Whatever the file holds, unvalidated
294
283
  */
295
- function readRatified(ledgerPath: string): Readonly<Record<string, string>> {
284
+ function readLedger(ledgerPath: string): Partial<Ledger> {
296
285
  if (!existsSync(ledgerPath)) return {};
297
- const parsed = JSON.parse(readFileSync(ledgerPath, "utf8")) as
298
- | Partial<Ledger>
299
- | undefined;
300
- return parsed?.ratified ?? {};
301
- }
302
-
303
- /**
304
- * Read the staleness classifications already recorded (#3687).
305
- *
306
- * Exactly the shape `readRatified` uses, and for the same reason: the ledger is
307
- * a DERIVED artifact regenerated in-commit, so a hand-authored field survives
308
- * only if the generator reads the previous version before rewriting it. This is
309
- * the established local precedent for persisting a human decision inside a
310
- * generated file, which is why the classification lives here rather than in a
311
- * new sidecar nobody would remember to update.
312
- * @param ledgerPath - Absolute path to the ledger
313
- * @returns The classifications, keyed `<workflow>::<path>`
314
- */
315
- function readStaleness(
316
- ledgerPath: string
317
- ): Readonly<Record<string, StalenessClassification>> {
318
- if (!existsSync(ledgerPath)) return {};
319
- const parsed = JSON.parse(readFileSync(ledgerPath, "utf8")) as
320
- | Partial<Ledger>
321
- | undefined;
322
- return parsed?.staleness ?? {};
323
- }
324
-
325
- /**
326
- * The verdict for a coupling the package channel already covers.
327
- *
328
- * Those are omitted from the ledger, so every question this file asks about a
329
- * live coupling has to exclude them — named once so the three sites cannot
330
- * disagree about which verdict that is.
331
- */
332
- const PACKAGE_BACKED = "package-backed";
333
-
334
- /** The `detects` value meaning the coupling asks for a contract version. */
335
- const VERSION_HANDSHAKE = "version-handshake";
336
-
337
- /** The `detects` value meaning it infers age from one subcommand or flag. */
338
- const CAPABILITY_PROBE = "capability-probe";
339
-
340
- /** The `detects` value meaning it can see presence and nothing else. */
341
- const EXISTENCE_ONLY = "existence-only";
342
-
343
- /** The `detects` values a classification may carry. */
344
- const DETECTS = new Set([VERSION_HANDSHAKE, CAPABILITY_PROBE, EXISTENCE_ONLY]);
345
-
346
- /**
347
- * The floor a stated reason must clear to be a reason rather than a label.
348
- *
349
- * A floor, not a proof — no check tells a real argument from filler. What it
350
- * buys is that "n/a", "ok" and an empty string cannot stand in for the decision
351
- * this field exists to record.
352
- */
353
- const MIN_REASON_CHARACTERS = 40;
354
-
355
- /**
356
- * Every problem with the staleness half, as operator-readable paragraphs.
357
- *
358
- * THREE refusals, and the first is the one that makes this a standing property
359
- * rather than a one-time sweep: a coupling with no classification fails, so
360
- * entry 25 cannot silently inherit "nobody looked". A sweep decays the moment
361
- * somebody adds an entry; a required field does not.
362
- * @param report - The measurement
363
- * @param staleness - The recorded classifications
364
- * @returns Problem paragraphs, empty when the staleness half is sound
365
- */
366
- export function stalenessProblems(
367
- report: CouplingKeySource,
368
- staleness: Readonly<Record<string, StalenessClassification>>
369
- ): readonly string[] {
370
- const live = new Set(
371
- report.entries
372
- .filter(entry => entry.verdict !== PACKAGE_BACKED)
373
- .map(entry => entry.key)
286
+ return (
287
+ (JSON.parse(readFileSync(ledgerPath, "utf8")) as Partial<Ledger> | null) ??
288
+ {}
374
289
  );
375
- const missing = [...live]
376
- .filter(key => staleness[key] === undefined)
377
- .map(
378
- key =>
379
- `\nUNCLASSIFIED COUPLING ${key}\n No staleness classification. Every ` +
380
- `coupling must record whether it can detect a STALE artifact, not only ` +
381
- `an absent one — absence skips and posts nothing, while present-and-old ` +
382
- `posts a verdict from superseded logic that every reader believes. Add ` +
383
- `a "staleness" entry with "detects" (version-handshake | ` +
384
- `capability-probe | existence-only) and, unless it is a handshake, a ` +
385
- `"reason" saying why that is acceptable here.`
386
- );
387
- const orphaned = Object.keys(staleness)
388
- .filter(key => !live.has(key))
389
- .map(
390
- key =>
391
- `\nSTALE CLASSIFICATION ${key}\n Nothing reads this path any more, so ` +
392
- `the classification is an unexamined judgement the next matching path ` +
393
- `would inherit. Delete it from ${LEDGER_PATH}.`
394
- );
395
- const malformed = Object.entries(staleness)
396
- .filter(([key]) => live.has(key))
397
- .flatMap(([key, value]) => describeMalformed(key, value));
398
- return [...missing, ...orphaned, ...malformed];
399
290
  }
400
291
 
401
292
  /**
402
- * Why one recorded classification is not usable, if it is not.
403
- * @param key - The coupling key
404
- * @param value - The recorded classification
405
- * @returns Problem paragraphs for this entry
293
+ * Read the ratifications already recorded, so regenerating never drops them.
294
+ * @param ledgerPath - Absolute path to the ledger
295
+ * @returns The ratification reasons, keyed `<workflow>::<path>`
406
296
  */
407
- function describeMalformed(
408
- key: string,
409
- value: StalenessClassification
410
- ): readonly string[] {
411
- if (!DETECTS.has(value?.detects)) {
412
- return [
413
- `\nMALFORMED CLASSIFICATION ${key}\n "detects" is ` +
414
- `${JSON.stringify(value?.detects)}; expected one of ` +
415
- `${[...DETECTS].join(", ")}.`,
416
- ];
417
- }
418
- if (value.detects === VERSION_HANDSHAKE) return [];
419
- const reason = typeof value.reason === "string" ? value.reason.trim() : "";
420
- return reason.length >= MIN_REASON_CHARACTERS
421
- ? []
422
- : [
423
- `\nUNREASONED CLASSIFICATION ${key}\n "detects" is ` +
424
- `"${value.detects}", which cannot see how old the artifact is, and no ` +
425
- `"reason" of at least ${MIN_REASON_CHARACTERS} characters says why ` +
426
- `that is acceptable here. A recorded decision and an unexamined ` +
427
- `default must not look the same.`,
428
- ];
297
+ function readRatified(ledgerPath: string): Readonly<Record<string, string>> {
298
+ return readLedger(ledgerPath).ratified ?? {};
429
299
  }
430
300
 
431
301
  /**
432
- * The one-line fleet coverage sentence (#3687).
433
- * @param report - The measurement
434
- * @param staleness - The recorded classifications
435
- * @returns A sentence naming how much of the fleet can see age
302
+ * Read the staleness decisions already recorded.
303
+ * @param ledgerPath - Absolute path to the ledger
304
+ * @returns The decisions, keyed `<workflow>::<path>`
436
305
  */
437
- export function stalenessCoverage(
438
- report: CouplingKeySource,
439
- staleness: Readonly<Record<string, StalenessClassification>>
440
- ): string {
441
- const live = report.entries.filter(entry => entry.verdict !== PACKAGE_BACKED);
442
- const tally = (kind: string): number =>
443
- live.filter(entry => staleness[entry.key]?.detects === kind).length;
444
- const handshake = tally(VERSION_HANDSHAKE);
445
- return (
446
- ` staleness detection: ${handshake} of ${live.length} coupling(s) can see ` +
447
- `how old the artifact is (${VERSION_HANDSHAKE}); ${tally(CAPABILITY_PROBE)} ` +
448
- `detect it at one floor only (${CAPABILITY_PROBE}); ${tally(EXISTENCE_ONLY)} ` +
449
- `detect absence only (${EXISTENCE_ONLY}).`
450
- );
306
+ function readClassified(
307
+ ledgerPath: string
308
+ ): Readonly<Record<string, StalenessRecord>> {
309
+ return readLedger(ledgerPath).staleness ?? {};
451
310
  }
452
311
 
453
312
  /**
@@ -464,15 +323,17 @@ export function stalenessCoverage(
464
323
  function toLedger(
465
324
  report: TwoChannelReport,
466
325
  ratified: Readonly<Record<string, string>>,
467
- staleness: Readonly<Record<string, StalenessClassification>>
326
+ classified: Readonly<Record<string, StalenessRecord>>
468
327
  ): Ledger {
469
328
  return {
470
329
  ratified,
471
- staleness,
330
+ staleness: classified,
472
331
  inspected: report.inspected,
473
332
  counts: report.counts,
333
+ detectionCounts: report.detectionCounts,
334
+ decisionCounts: report.decisionCounts,
474
335
  couplings: report.entries
475
- .filter(entry => entry.verdict !== PACKAGE_BACKED)
336
+ .filter(entry => entry.verdict !== "package-backed")
476
337
  .map(entry => ({
477
338
  key: entry.key,
478
339
  workflow: entry.workflow,
@@ -481,6 +342,11 @@ function toLedger(
481
342
  verdict: entry.verdict,
482
343
  remedy: entry.remedy,
483
344
  guarded: entry.guarded,
345
+ handling: entry.handling,
346
+ detection: entry.detection,
347
+ stalenessSignals: entry.staleness,
348
+ decision: entry.decision,
349
+ reason: entry.reason,
484
350
  lanes: entry.lanes,
485
351
  detail: entry.detail,
486
352
  })),
@@ -495,12 +361,27 @@ function toLedger(
495
361
  export function measure(root: string): {
496
362
  readonly report: TwoChannelReport;
497
363
  readonly ratified: Readonly<Record<string, string>>;
498
- readonly staleness: Readonly<Record<string, StalenessClassification>>;
364
+ readonly classified: Readonly<Record<string, StalenessRecord>>;
499
365
  } {
500
366
  const workflowsDir = path.join(root, WORKFLOWS_DIR);
501
367
  const inventory = buildDeliveryInventory(root);
502
368
  const lanesFor = (callerPath: string): readonly string[] =>
503
369
  inventory.get(callerPath) ?? [];
370
+ const artifactSignalsFor = (
371
+ callerPath: string
372
+ ): readonly StalenessSignal[] | null => {
373
+ const lanes = lanesFor(callerPath);
374
+ // No lane ships this path, so there is no artifact to ask. Returning [] —
375
+ // "it carries no version tokens" — would be the same bytes as a measured
376
+ // negative, which is the exact collapse this ledger exists to refuse.
377
+ if (lanes.length === 0) return null;
378
+ const signals = lanes.flatMap(lane =>
379
+ artifactStalenessSignals(
380
+ readFileSync(path.join(root, lane, callerPath), "utf8")
381
+ )
382
+ );
383
+ return [...new Set(signals)];
384
+ };
504
385
  const names = existsSync(workflowsDir)
505
386
  ? readdirSync(workflowsDir)
506
387
  .filter(name => name.endsWith(".yml") || name.endsWith(".yaml"))
@@ -519,6 +400,7 @@ export function measure(root: string): {
519
400
  workflow: entry.name,
520
401
  text: entry.text,
521
402
  lanesFor,
403
+ artifactSignalsFor,
522
404
  }),
523
405
  }));
524
406
  const couplings: readonly CouplingInput[] = scanned.flatMap(
@@ -534,9 +416,10 @@ export function measure(root: string): {
534
416
  inventory: inventory.size,
535
417
  },
536
418
  ratified: readRatified(path.join(root, LEDGER_PATH)),
419
+ classified: readClassified(path.join(root, LEDGER_PATH)),
537
420
  }),
538
421
  ratified: readRatified(path.join(root, LEDGER_PATH)),
539
- staleness: readStaleness(path.join(root, LEDGER_PATH)),
422
+ classified: readClassified(path.join(root, LEDGER_PATH)),
540
423
  };
541
424
  }
542
425
 
@@ -545,10 +428,7 @@ export function measure(root: string): {
545
428
  * @param report - The measurement
546
429
  * @returns The report text
547
430
  */
548
- function humanReport(
549
- report: TwoChannelReport,
550
- staleness: Readonly<Record<string, StalenessClassification>>
551
- ): string {
431
+ function humanReport(report: TwoChannelReport): string {
552
432
  const counts = report.inspected;
553
433
  const header =
554
434
  `two-channel delivery: inspected ${counts.workflows} reusable workflow(s), ` +
@@ -568,12 +448,64 @@ function humanReport(
568
448
  `inherit. Delete it from ${LEDGER_PATH}.`
569
449
  )
570
450
  .join("");
571
- // The coverage line answers the question the registry could not answer at
572
- // all before this: "how many of our couplings can detect staleness?" — in one
573
- // line, without opening 24 workflows.
574
- const coverage = stalenessCoverage(report, staleness);
575
- const unclassified = stalenessProblems(report, staleness).join("");
576
- return `${header}\n${verdicts}\n${coverage}${findings}${stale}${unclassified}`;
451
+ return `${header}\n${verdicts}\n${stalenessReport(report)}${findings}${stale}`;
452
+ }
453
+
454
+ /**
455
+ * The staleness half of the report: the fleet tally and anything unanswered.
456
+ *
457
+ * Every line carries its denominator. "10 can detect staleness" is a number
458
+ * somebody can believe without knowing whether it is out of 12 or out of 700,
459
+ * and a coverage figure nobody can size is the shape of report this repository
460
+ * has been burned by before.
461
+ * @param report - The measurement
462
+ * @returns The staleness section
463
+ */
464
+ function stalenessReport(report: TwoChannelReport): string {
465
+ const total = report.classifiable;
466
+ const handshakes = report.detectionCounts["version-handshake"];
467
+ const probes = report.detectionCounts["capability-probe"];
468
+ const blind =
469
+ report.detectionCounts["existence-only"] +
470
+ report.detectionCounts["no-probe"];
471
+ const unchecked = report.detectionCounts.unchecked;
472
+ const headline =
473
+ `staleness detection: ${handshakes}/${total} coupling(s) can detect a stale ` +
474
+ `artifact (version handshake); ${probes}/${total} detect it at one capability ` +
475
+ `floor only; ${blind}/${total} detect absence at most and never age; ` +
476
+ `${unchecked}/${total} could not be checked (nothing is delivered at the path, ` +
477
+ `so there is no artifact to ask).`;
478
+ const detections = STALENESS_DETECTIONS.map(
479
+ detection => ` ${detection}: ${report.detectionCounts[detection]}`
480
+ ).join("\n");
481
+ const decisions = STALENESS_DECISIONS.map(
482
+ decision => ` decision ${decision}: ${report.decisionCounts[decision]}`
483
+ ).join("\n");
484
+ const unclassified = report.unclassified
485
+ .map(
486
+ key =>
487
+ `\nUNCLASSIFIED ${key}\n No staleness decision is recorded for this ` +
488
+ `coupling. Its detection is derived on every run and costs nobody ` +
489
+ `anything; the decision is the half a person has to make. Add an entry ` +
490
+ `under "staleness" in ${LEDGER_PATH} with a decision of ` +
491
+ `${STALENESS_DECISIONS.join(" | ")} and, unless it is \`handshake\`, a reason.`
492
+ )
493
+ .join("");
494
+ const contradictions = report.contradictions
495
+ .map(sentence => `\nCONTRADICTED DECISION ${sentence}`)
496
+ .join("");
497
+ const malformed = report.malformed
498
+ .map(sentence => `\nMALFORMED DECISION ${sentence}`)
499
+ .join("");
500
+ const staleDecisions = report.staleClassifications
501
+ .map(
502
+ key =>
503
+ `\nSTALE STALENESS DECISION ${key}\n Nothing reads this path any more, ` +
504
+ `so the decision is an unexamined exemption the next matching path would ` +
505
+ `inherit. Delete it from ${LEDGER_PATH}.`
506
+ )
507
+ .join("");
508
+ return `${headline}\n${detections}\n${decisions}${unclassified}${contradictions}${malformed}${staleDecisions}`;
577
509
  }
578
510
 
579
511
  /**
@@ -587,9 +519,9 @@ export function main(argv: readonly string[]): number {
587
519
  process.stderr.write(`error: --root does not exist: ${options.root}\n`);
588
520
  return EXIT_OPERATIONAL;
589
521
  }
590
- const { report, ratified, staleness } = measure(options.root);
522
+ const { report, ratified, classified } = measure(options.root);
591
523
  process.stdout.write(
592
- `${options.json ? JSON.stringify(report, null, 2) : humanReport(report, staleness)}\n`
524
+ `${options.json ? JSON.stringify(report, null, 2) : humanReport(report)}\n`
593
525
  );
594
526
  if (!report.measured) {
595
527
  process.stderr.write(
@@ -600,7 +532,7 @@ export function main(argv: readonly string[]): number {
600
532
  return EXIT_OPERATIONAL;
601
533
  }
602
534
  const ledgerPath = path.join(options.root, LEDGER_PATH);
603
- const rendered = `${JSON.stringify(toLedger(report, ratified, staleness), null, 2)}\n`;
535
+ const rendered = `${JSON.stringify(toLedger(report, ratified, classified), null, 2)}\n`;
604
536
  const current = existsSync(ledgerPath)
605
537
  ? readFileSync(ledgerPath, "utf8")
606
538
  : "";
@@ -616,14 +548,13 @@ export function main(argv: readonly string[]): number {
616
548
  mkdirSync(path.dirname(ledgerPath), { recursive: true });
617
549
  writeFileSync(ledgerPath, rendered);
618
550
  }
619
- // The staleness half fails in BOTH modes, exactly as findings do. A gate you
620
- // can regenerate your way past is not a gate: regenerating records a coupling,
621
- // it does not classify one, and `toLedger` carries the map through untouched
622
- // rather than inventing a default for a new entry (#3687).
623
551
  const failed =
624
552
  report.findings.length > 0 ||
625
553
  report.staleRatifications.length > 0 ||
626
- stalenessProblems(report, staleness).length > 0 ||
554
+ report.unclassified.length > 0 ||
555
+ report.contradictions.length > 0 ||
556
+ report.malformed.length > 0 ||
557
+ report.staleClassifications.length > 0 ||
627
558
  ledgerStale;
628
559
  return failed ? 1 : 0;
629
560
  }
@@ -829,6 +829,37 @@ if [ "$notice_due" -eq 1 ]; then
829
829
  fi
830
830
  fi
831
831
 
832
+ # ---------------------------------------------------------------------------
833
+ # One evaluation per guard per tool call
834
+ #
835
+ # The guards below are also registered individually by the plugin manifest, so
836
+ # on a machine where both channels are live each one runs twice for a single
837
+ # tool call. `guard-dedupe.bash`, sourced by each guard, lets the second copy
838
+ # replay the first copy's ALLOW instead of recomputing it — and only when the
839
+ # two copies are byte-identical, on the identical payload, within the identical
840
+ # tool call. Nothing above is de-registered for it, because plugin-hook liveness
841
+ # is not observable from disk and standing down on a disk guess is how this
842
+ # dispatcher once switched enforcement off silently (CodySwannGT/lisa#3814).
843
+ #
844
+ # What is handed down here is only the state directory. `$notice_temp_base` has
845
+ # already been resolved physically and had every parent checked for ownership
846
+ # and mode, so the eight guards this loop spawns inherit that work instead of
847
+ # repeating it eight times. A guard reached by the plugin channel has no such
848
+ # parent and resolves its own; a guard that finds this variable unusable does
849
+ # the same. Exported only when the chain was trusted, so an untrusted TMPDIR
850
+ # hands down nothing rather than handing down a bad path.
851
+ if [ -n "$notice_temp_base" ] && [ "$notice_state_trusted" -eq 1 ]; then
852
+ guard_memo_dir="$notice_temp_base/lisa-guard-memo-$notice_uid"
853
+ if [ ! -e "$guard_memo_dir" ] && [ ! -L "$guard_memo_dir" ]; then
854
+ (umask 077 && mkdir "$guard_memo_dir") 2>/dev/null || true
855
+ fi
856
+ if [ -d "$guard_memo_dir" ] && [ ! -L "$guard_memo_dir" ] && \
857
+ [ -O "$guard_memo_dir" ]; then
858
+ export LISA_GUARD_MEMO_DIR="$guard_memo_dir"
859
+ export LISA_GUARD_MEMO_UID="$notice_uid"
860
+ fi
861
+ fi
862
+
832
863
  # ---------------------------------------------------------------------------
833
864
  # Dispatch
834
865
  status=0