@codyswann/lisa 4.54.13 → 4.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (578) hide show
  1. package/all/copy-contents/.gitattributes +8 -0
  2. package/all/copy-overwrite/scripts/check-orphaned-branches.mjs +78 -9
  3. package/all/copy-overwrite/scripts/check-third-party-action-pins.mjs +17 -1
  4. package/all/copy-overwrite/scripts/lib/process-tree-runner.mjs +194 -7
  5. package/all/copy-overwrite/scripts/lisa-enforcement-fallback.sh +31 -0
  6. package/all/copy-overwrite/scripts/lisa-hooks/block-blind-automerge.sh +81 -2
  7. package/all/copy-overwrite/scripts/lisa-hooks/block-direct-issue-create.sh +293 -8
  8. package/all/copy-overwrite/scripts/lisa-hooks/block-instruction-file-edits.sh +21 -0
  9. package/all/copy-overwrite/scripts/lisa-hooks/block-managed-file-edits.sh +78 -15
  10. package/all/copy-overwrite/scripts/lisa-hooks/block-no-verify.sh +21 -0
  11. package/all/copy-overwrite/scripts/lisa-hooks/block-shell-json-parsing.sh +21 -0
  12. package/all/copy-overwrite/scripts/lisa-hooks/guard-dedupe.bash +340 -0
  13. package/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +29 -1
  14. package/all/copy-overwrite/scripts/lisa-hooks/worktree-binding-guard.mjs +183 -1
  15. package/all/copy-overwrite/scripts/lisa-hooks/worktree-binding-guard.sh +21 -0
  16. package/all/copy-overwrite/scripts/lisa-postinstall.mjs +21 -1
  17. package/all/copy-overwrite/scripts/lisa-run-gates.mjs +100 -5
  18. package/all/copy-overwrite/scripts/lisa-work-item.mjs +1085 -80
  19. package/all/create-only/.github/workflows/continuous-gates.yml +7 -0
  20. package/cdk/create-only/.github/workflows/ci.yml +7 -0
  21. package/cdk/create-only/.github/workflows/deploy.yml +21 -0
  22. package/dist/cli/apply.d.ts.map +1 -1
  23. package/dist/cli/apply.js +9 -1
  24. package/dist/cli/apply.js.map +1 -1
  25. package/dist/cli/doctor-apply-deletions.d.ts +61 -0
  26. package/dist/cli/doctor-apply-deletions.d.ts.map +1 -0
  27. package/dist/cli/doctor-apply-deletions.js +137 -0
  28. package/dist/cli/doctor-apply-deletions.js.map +1 -0
  29. package/dist/cli/doctor-cdk-preset-adoption.d.ts +15 -0
  30. package/dist/cli/doctor-cdk-preset-adoption.d.ts.map +1 -0
  31. package/dist/cli/doctor-cdk-preset-adoption.js +316 -0
  32. package/dist/cli/doctor-cdk-preset-adoption.js.map +1 -0
  33. package/dist/cli/doctor-config-shadowing.d.ts +22 -0
  34. package/dist/cli/doctor-config-shadowing.d.ts.map +1 -0
  35. package/dist/cli/doctor-config-shadowing.js +123 -0
  36. package/dist/cli/doctor-config-shadowing.js.map +1 -0
  37. package/dist/cli/doctor-rails-deploy-intent.d.ts +15 -0
  38. package/dist/cli/doctor-rails-deploy-intent.d.ts.map +1 -0
  39. package/dist/cli/doctor-rails-deploy-intent.js +197 -0
  40. package/dist/cli/doctor-rails-deploy-intent.js.map +1 -0
  41. package/dist/cli/doctor-seeded-artifacts.d.ts +12 -0
  42. package/dist/cli/doctor-seeded-artifacts.d.ts.map +1 -0
  43. package/dist/cli/doctor-seeded-artifacts.js +54 -0
  44. package/dist/cli/doctor-seeded-artifacts.js.map +1 -0
  45. package/dist/cli/doctor-stale-banner-scan.d.ts +44 -0
  46. package/dist/cli/doctor-stale-banner-scan.d.ts.map +1 -0
  47. package/dist/cli/doctor-stale-banner-scan.js +152 -0
  48. package/dist/cli/doctor-stale-banner-scan.js.map +1 -0
  49. package/dist/cli/doctor-stale-managed-banner.d.ts +19 -0
  50. package/dist/cli/doctor-stale-managed-banner.d.ts.map +1 -0
  51. package/dist/cli/doctor-stale-managed-banner.js +198 -0
  52. package/dist/cli/doctor-stale-managed-banner.js.map +1 -0
  53. package/dist/cli/doctor.d.ts.map +1 -1
  54. package/dist/cli/doctor.js +16 -0
  55. package/dist/cli/doctor.js.map +1 -1
  56. package/dist/cli/ui-detected-stacks.d.ts.map +1 -1
  57. package/dist/cli/ui-detected-stacks.js +3 -2
  58. package/dist/cli/ui-detected-stacks.js.map +1 -1
  59. package/dist/cli/update-check.d.ts.map +1 -1
  60. package/dist/cli/update-check.js +14 -0
  61. package/dist/cli/update-check.js.map +1 -1
  62. package/dist/cli/worktree-liveness.d.ts +5 -0
  63. package/dist/cli/worktree-liveness.d.ts.map +1 -1
  64. package/dist/cli/worktree-liveness.js +7 -6
  65. package/dist/cli/worktree-liveness.js.map +1 -1
  66. package/dist/cli/worktree-ownership.d.ts +13 -0
  67. package/dist/cli/worktree-ownership.d.ts.map +1 -1
  68. package/dist/cli/worktree-ownership.js +14 -0
  69. package/dist/cli/worktree-ownership.js.map +1 -1
  70. package/dist/configs/eslint/base.d.ts +34 -0
  71. package/dist/configs/eslint/base.d.ts.map +1 -1
  72. package/dist/configs/eslint/base.js +34 -0
  73. package/dist/configs/eslint/base.js.map +1 -1
  74. package/dist/core/apply-receipt.d.ts +65 -0
  75. package/dist/core/apply-receipt.d.ts.map +1 -1
  76. package/dist/core/apply-receipt.js +62 -2
  77. package/dist/core/apply-receipt.js.map +1 -1
  78. package/dist/core/cdk-preset-adoption.d.ts +136 -0
  79. package/dist/core/cdk-preset-adoption.d.ts.map +1 -0
  80. package/dist/core/cdk-preset-adoption.js +135 -0
  81. package/dist/core/cdk-preset-adoption.js.map +1 -0
  82. package/dist/core/config-shadowing.d.ts +36 -0
  83. package/dist/core/config-shadowing.d.ts.map +1 -1
  84. package/dist/core/config-shadowing.js +38 -0
  85. package/dist/core/config-shadowing.js.map +1 -1
  86. package/dist/core/config.d.ts +11 -0
  87. package/dist/core/config.d.ts.map +1 -1
  88. package/dist/core/config.js.map +1 -1
  89. package/dist/core/learnings-merge-driver.d.ts +10 -0
  90. package/dist/core/learnings-merge-driver.d.ts.map +1 -1
  91. package/dist/core/learnings-merge-driver.js +18 -0
  92. package/dist/core/learnings-merge-driver.js.map +1 -1
  93. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  94. package/dist/core/lisa-owned-hash-ledger.js +136 -0
  95. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  96. package/dist/core/lisa.d.ts.map +1 -1
  97. package/dist/core/lisa.js +9 -1
  98. package/dist/core/lisa.js.map +1 -1
  99. package/dist/core/nightly-e2e-guard-behavior-certificate.d.ts +5 -5
  100. package/dist/core/nightly-e2e-guard-behavior-certificate.js +7 -7
  101. package/dist/core/nightly-e2e-guard-behavior-certificate.js.map +1 -1
  102. package/dist/core/ownership-header.d.ts +74 -0
  103. package/dist/core/ownership-header.d.ts.map +1 -0
  104. package/dist/core/ownership-header.js +76 -0
  105. package/dist/core/ownership-header.js.map +1 -0
  106. package/dist/core/rails-deploy-production-intent.d.ts +36 -0
  107. package/dist/core/rails-deploy-production-intent.d.ts.map +1 -0
  108. package/dist/core/rails-deploy-production-intent.js +79 -0
  109. package/dist/core/rails-deploy-production-intent.js.map +1 -0
  110. package/dist/core/stale-managed-banner.d.ts +78 -0
  111. package/dist/core/stale-managed-banner.d.ts.map +1 -0
  112. package/dist/core/stale-managed-banner.js +118 -0
  113. package/dist/core/stale-managed-banner.js.map +1 -0
  114. package/dist/core/two-channel-delivery-scan.d.ts +33 -1
  115. package/dist/core/two-channel-delivery-scan.d.ts.map +1 -1
  116. package/dist/core/two-channel-delivery-scan.js +133 -6
  117. package/dist/core/two-channel-delivery-scan.js.map +1 -1
  118. package/dist/core/two-channel-delivery.d.ts +101 -4
  119. package/dist/core/two-channel-delivery.d.ts.map +1 -1
  120. package/dist/core/two-channel-delivery.js +121 -13
  121. package/dist/core/two-channel-delivery.js.map +1 -1
  122. package/dist/core/two-channel-staleness.d.ts +137 -0
  123. package/dist/core/two-channel-staleness.d.ts.map +1 -0
  124. package/dist/core/two-channel-staleness.js +268 -0
  125. package/dist/core/two-channel-staleness.js.map +1 -0
  126. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  127. package/dist/core/upstream-evidence-manifest.js +265 -121
  128. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  129. package/dist/core/workflow-deletion-ownership.d.ts +6 -24
  130. package/dist/core/workflow-deletion-ownership.d.ts.map +1 -1
  131. package/dist/core/workflow-deletion-ownership.js +22 -42
  132. package/dist/core/workflow-deletion-ownership.js.map +1 -1
  133. package/dist/detection/detectors/cdk.d.ts.map +1 -1
  134. package/dist/detection/detectors/cdk.js +2 -1
  135. package/dist/detection/detectors/cdk.js.map +1 -1
  136. package/dist/health/template-inspection.d.ts.map +1 -1
  137. package/dist/health/template-inspection.js +2 -1
  138. package/dist/health/template-inspection.js.map +1 -1
  139. package/dist/migrations/ensure-lisa-postinstall.d.ts +16 -0
  140. package/dist/migrations/ensure-lisa-postinstall.d.ts.map +1 -1
  141. package/dist/migrations/ensure-lisa-postinstall.js +16 -0
  142. package/dist/migrations/ensure-lisa-postinstall.js.map +1 -1
  143. package/dist/migrations/ensure-pinned-reusable-workflow-refs.d.ts +2 -2
  144. package/dist/migrations/ensure-pinned-reusable-workflow-refs.d.ts.map +1 -1
  145. package/dist/migrations/ensure-pinned-reusable-workflow-refs.js +98 -34
  146. package/dist/migrations/ensure-pinned-reusable-workflow-refs.js.map +1 -1
  147. package/dist/migrations/ensure-playwright-dedicated-caller.d.ts.map +1 -1
  148. package/dist/migrations/ensure-playwright-dedicated-caller.js +1 -0
  149. package/dist/migrations/ensure-playwright-dedicated-caller.js.map +1 -1
  150. package/dist/opencode/hooks-installer.d.ts.map +1 -1
  151. package/dist/opencode/hooks-installer.js +1 -0
  152. package/dist/opencode/hooks-installer.js.map +1 -1
  153. package/dist/opencode/plugin-templates/block-managed-file-edits.sh +78 -15
  154. package/dist/opencode/plugin-templates/block-no-verify.sh +21 -0
  155. package/dist/opencode/plugin-templates/guard-dedupe.bash +337 -0
  156. package/dist/opencode/plugin-templates/lisa-block-direct-issue-create.ts +242 -55
  157. package/dist/opencode/plugin-templates/parity-safety-net.sh +29 -1
  158. package/dist/strategies/package-lisa.d.ts.map +1 -1
  159. package/dist/strategies/package-lisa.js +2 -1
  160. package/dist/strategies/package-lisa.js.map +1 -1
  161. package/dist/utils/path-utils.d.ts +29 -0
  162. package/dist/utils/path-utils.d.ts.map +1 -1
  163. package/dist/utils/path-utils.js +41 -0
  164. package/dist/utils/path-utils.js.map +1 -1
  165. package/expo/create-only/.github/workflows/ci.yml +21 -0
  166. package/expo/create-only/.github/workflows/deploy.yml +28 -0
  167. package/expo/create-only/.github/workflows/maestro-e2e.yml +7 -0
  168. package/expo/create-only/.github/workflows/nightly-e2e-bypass-reaper.yml +124 -13
  169. package/expo/create-only/.github/workflows/nightly-e2e-health.yml +7 -0
  170. package/expo/create-only/.github/workflows/nightly-e2e-report.yml +7 -0
  171. package/expo/create-only/.github/workflows/nightly-e2e-tracking.yml +8 -0
  172. package/expo/create-only/.github/workflows/playwright-e2e.yml +7 -0
  173. package/harper-fabric/copy-overwrite/.github/workflows/ci.yml +7 -0
  174. package/harper-fabric/create-only/.github/workflows/deploy.yml +14 -0
  175. package/nestjs/create-only/.github/workflows/ci.yml +14 -0
  176. package/nestjs/create-only/.github/workflows/deploy.yml +28 -0
  177. package/package.json +10 -5
  178. package/phaser/copy-overwrite/.github/workflows/ci.yml +7 -0
  179. package/plugins/lisa/.claude-plugin/plugin.json +28 -1
  180. package/plugins/lisa/.codex-plugin/hooks.json +27 -0
  181. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  182. package/plugins/lisa/.codex-plugin/skills/lisa-drive-pr-to-merge/SKILL.md +130 -19
  183. package/plugins/lisa/.codex-plugin/skills/lisa-github-build-intake/SKILL.md +27 -4
  184. package/plugins/lisa/.codex-plugin/skills/lisa-github-prd-intake/SKILL.md +1 -1
  185. package/plugins/lisa/.codex-plugin/skills/lisa-github-validate-issue/SKILL.md +22 -0
  186. package/plugins/lisa/.codex-plugin/skills/lisa-github-verify/SKILL.md +14 -0
  187. package/plugins/lisa/.codex-plugin/skills/lisa-github-write-issue/SKILL.md +51 -0
  188. package/plugins/lisa/.codex-plugin/skills/lisa-intake/SKILL.md +1 -1
  189. package/plugins/lisa/.codex-plugin/skills/lisa-jira-build-intake/SKILL.md +27 -4
  190. package/plugins/lisa/.codex-plugin/skills/lisa-jira-validate-ticket/SKILL.md +22 -0
  191. package/plugins/lisa/.codex-plugin/skills/lisa-jira-verify/SKILL.md +14 -0
  192. package/plugins/lisa/.codex-plugin/skills/lisa-jira-write-ticket/SKILL.md +51 -0
  193. package/plugins/lisa/.codex-plugin/skills/lisa-linear-access/SKILL.md +55 -2
  194. package/plugins/lisa/.codex-plugin/skills/lisa-linear-build-intake/SKILL.md +27 -4
  195. package/plugins/lisa/.codex-plugin/skills/lisa-linear-prd-intake/SKILL.md +47 -30
  196. package/plugins/lisa/.codex-plugin/skills/lisa-linear-to-tracker/SKILL.md +3 -3
  197. package/plugins/lisa/.codex-plugin/skills/lisa-linear-validate-issue/SKILL.md +22 -0
  198. package/plugins/lisa/.codex-plugin/skills/lisa-linear-verify/SKILL.md +14 -0
  199. package/plugins/lisa/.codex-plugin/skills/lisa-linear-write-issue/SKILL.md +54 -3
  200. package/plugins/lisa/.codex-plugin/skills/lisa-prd-ticket-coverage/SKILL.md +2 -2
  201. package/plugins/lisa/.codex-plugin/skills/lisa-qa-clear/SKILL.md +19 -7
  202. package/plugins/lisa/.codex-plugin/skills/lisa-qa-fail/SKILL.md +28 -3
  203. package/plugins/lisa/.codex-plugin/skills/lisa-qa-queue/SKILL.md +28 -3
  204. package/plugins/lisa/.codex-plugin/skills/lisa-repair-intake/SKILL.md +94 -24
  205. package/plugins/lisa/.codex-plugin/skills/lisa-rework-triage/SKILL.md +32 -2
  206. package/plugins/lisa/.codex-plugin/skills/lisa-setup-linear/SKILL.md +4 -4
  207. package/plugins/lisa/.codex-plugin/skills/lisa-track/SKILL.md +1 -1
  208. package/plugins/lisa/.codex-plugin/skills/lisa-tracker-build-intake/SKILL.md +9 -0
  209. package/plugins/lisa/agents/linear-prd-intake.md +8 -8
  210. package/plugins/lisa/hooks/block-blind-automerge.sh +81 -2
  211. package/plugins/lisa/hooks/block-direct-issue-create.agy.sh +64 -16
  212. package/plugins/lisa/hooks/block-direct-issue-create.sh +293 -8
  213. package/plugins/lisa/hooks/block-instruction-file-edits.sh +21 -0
  214. package/plugins/lisa/hooks/block-managed-file-edits.sh +78 -15
  215. package/plugins/lisa/hooks/block-no-verify.sh +21 -0
  216. package/plugins/lisa/hooks/block-shell-json-parsing.sh +21 -0
  217. package/plugins/lisa/hooks/failure-signature-index.mjs +240 -5
  218. package/plugins/lisa/hooks/guard-dedupe.bash +337 -0
  219. package/plugins/lisa/hooks/inject-rules.sh +13 -5
  220. package/plugins/lisa/hooks/operational-hazards.mjs +736 -0
  221. package/plugins/lisa/hooks/operational-hazards.sh +28 -0
  222. package/plugins/lisa/hooks/parity-safety-net.sh +29 -1
  223. package/plugins/lisa/hooks/threshold-ratchet-compare.mjs +102 -21
  224. package/plugins/lisa/hooks/threshold-ratchet-families.mjs +130 -2
  225. package/plugins/lisa/hooks/threshold-ratchet.mjs +63 -13
  226. package/plugins/lisa/hooks/worktree-binding-guard.mjs +183 -1
  227. package/plugins/lisa/hooks/worktree-binding-guard.sh +21 -0
  228. package/plugins/lisa/rules/eager/00-rule-index.md +1 -0
  229. package/plugins/lisa/rules/eager/operational-hazards.md +50 -0
  230. package/plugins/lisa/rules/eager/tracked-work.md +1 -1
  231. package/plugins/lisa/rules/reference/config-resolution.md +1 -1
  232. package/plugins/lisa/rules/reference/leaf-only-lifecycle.md +1 -1
  233. package/plugins/lisa/rules/reference/operational-hazards.md +115 -0
  234. package/plugins/lisa/rules/reference/work-item-trailer-definition.md +129 -0
  235. package/plugins/lisa/scripts/intake-blocker-reprobe.mjs +407 -26
  236. package/plugins/lisa/scripts/qa-signal-lifecycle.mjs +507 -0
  237. package/plugins/lisa/skills/lisa-drive-pr-to-merge/SKILL.md +130 -19
  238. package/plugins/lisa/skills/lisa-github-build-intake/SKILL.md +27 -4
  239. package/plugins/lisa/skills/lisa-github-prd-intake/SKILL.md +1 -1
  240. package/plugins/lisa/skills/lisa-github-validate-issue/SKILL.md +22 -0
  241. package/plugins/lisa/skills/lisa-github-verify/SKILL.md +14 -0
  242. package/plugins/lisa/skills/lisa-github-write-issue/SKILL.md +51 -0
  243. package/plugins/lisa/skills/lisa-intake/SKILL.md +1 -1
  244. package/plugins/lisa/skills/lisa-jira-build-intake/SKILL.md +27 -4
  245. package/plugins/lisa/skills/lisa-jira-validate-ticket/SKILL.md +22 -0
  246. package/plugins/lisa/skills/lisa-jira-verify/SKILL.md +14 -0
  247. package/plugins/lisa/skills/lisa-jira-write-ticket/SKILL.md +51 -0
  248. package/plugins/lisa/skills/lisa-linear-access/SKILL.md +55 -2
  249. package/plugins/lisa/skills/lisa-linear-build-intake/SKILL.md +27 -4
  250. package/plugins/lisa/skills/lisa-linear-prd-intake/SKILL.md +48 -31
  251. package/plugins/lisa/skills/lisa-linear-to-tracker/SKILL.md +3 -3
  252. package/plugins/lisa/skills/lisa-linear-validate-issue/SKILL.md +22 -0
  253. package/plugins/lisa/skills/lisa-linear-verify/SKILL.md +14 -0
  254. package/plugins/lisa/skills/lisa-linear-write-issue/SKILL.md +54 -3
  255. package/plugins/lisa/skills/lisa-prd-ticket-coverage/SKILL.md +2 -2
  256. package/plugins/lisa/skills/lisa-qa-clear/SKILL.md +19 -7
  257. package/plugins/lisa/skills/lisa-qa-fail/SKILL.md +28 -3
  258. package/plugins/lisa/skills/lisa-qa-queue/SKILL.md +28 -3
  259. package/plugins/lisa/skills/lisa-repair-intake/SKILL.md +94 -24
  260. package/plugins/lisa/skills/lisa-rework-triage/SKILL.md +32 -2
  261. package/plugins/lisa/skills/lisa-setup-linear/SKILL.md +5 -5
  262. package/plugins/lisa/skills/lisa-track/SKILL.md +1 -1
  263. package/plugins/lisa/skills/lisa-tracker-build-intake/SKILL.md +9 -0
  264. package/plugins/lisa-agy/agents/linear-prd-intake.md +8 -8
  265. package/plugins/lisa-agy/hooks/block-blind-automerge.sh +81 -2
  266. package/plugins/lisa-agy/hooks/block-direct-issue-create.agy.sh +64 -16
  267. package/plugins/lisa-agy/hooks/block-direct-issue-create.sh +293 -8
  268. package/plugins/lisa-agy/hooks/block-instruction-file-edits.sh +21 -0
  269. package/plugins/lisa-agy/hooks/block-managed-file-edits.sh +78 -15
  270. package/plugins/lisa-agy/hooks/block-shell-json-parsing.sh +21 -0
  271. package/plugins/lisa-agy/hooks/guard-dedupe.bash +337 -0
  272. package/plugins/lisa-agy/hooks/parity-safety-net.sh +29 -1
  273. package/plugins/lisa-agy/hooks.json +1 -1
  274. package/plugins/lisa-agy/plugin.json +1 -1
  275. package/plugins/lisa-agy/scripts/intake-blocker-reprobe.mjs +407 -26
  276. package/plugins/lisa-agy/scripts/qa-signal-lifecycle.mjs +507 -0
  277. package/plugins/lisa-agy/skills/lisa-drive-pr-to-merge/SKILL.md +130 -19
  278. package/plugins/lisa-agy/skills/lisa-github-build-intake/SKILL.md +27 -4
  279. package/plugins/lisa-agy/skills/lisa-github-prd-intake/SKILL.md +1 -1
  280. package/plugins/lisa-agy/skills/lisa-github-validate-issue/SKILL.md +22 -0
  281. package/plugins/lisa-agy/skills/lisa-github-verify/SKILL.md +14 -0
  282. package/plugins/lisa-agy/skills/lisa-github-write-issue/SKILL.md +51 -0
  283. package/plugins/lisa-agy/skills/lisa-intake/SKILL.md +1 -1
  284. package/plugins/lisa-agy/skills/lisa-jira-build-intake/SKILL.md +27 -4
  285. package/plugins/lisa-agy/skills/lisa-jira-validate-ticket/SKILL.md +22 -0
  286. package/plugins/lisa-agy/skills/lisa-jira-verify/SKILL.md +14 -0
  287. package/plugins/lisa-agy/skills/lisa-jira-write-ticket/SKILL.md +51 -0
  288. package/plugins/lisa-agy/skills/lisa-linear-access/SKILL.md +55 -2
  289. package/plugins/lisa-agy/skills/lisa-linear-build-intake/SKILL.md +27 -4
  290. package/plugins/lisa-agy/skills/lisa-linear-prd-intake/SKILL.md +48 -31
  291. package/plugins/lisa-agy/skills/lisa-linear-to-tracker/SKILL.md +3 -3
  292. package/plugins/lisa-agy/skills/lisa-linear-validate-issue/SKILL.md +22 -0
  293. package/plugins/lisa-agy/skills/lisa-linear-verify/SKILL.md +14 -0
  294. package/plugins/lisa-agy/skills/lisa-linear-write-issue/SKILL.md +54 -3
  295. package/plugins/lisa-agy/skills/lisa-prd-ticket-coverage/SKILL.md +2 -2
  296. package/plugins/lisa-agy/skills/lisa-qa-clear/SKILL.md +19 -7
  297. package/plugins/lisa-agy/skills/lisa-qa-fail/SKILL.md +28 -3
  298. package/plugins/lisa-agy/skills/lisa-qa-queue/SKILL.md +28 -3
  299. package/plugins/lisa-agy/skills/lisa-repair-intake/SKILL.md +94 -24
  300. package/plugins/lisa-agy/skills/lisa-rework-triage/SKILL.md +32 -2
  301. package/plugins/lisa-agy/skills/lisa-setup-linear/SKILL.md +5 -5
  302. package/plugins/lisa-agy/skills/lisa-track/SKILL.md +1 -1
  303. package/plugins/lisa-agy/skills/lisa-tracker-build-intake/SKILL.md +9 -0
  304. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  305. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  306. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  307. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  308. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  309. package/plugins/lisa-copilot/.claude-plugin/plugin.json +19 -1
  310. package/plugins/lisa-copilot/agents/linear-prd-intake.agent.md +8 -8
  311. package/plugins/lisa-copilot/hooks/block-blind-automerge.sh +81 -2
  312. package/plugins/lisa-copilot/hooks/block-direct-issue-create.sh +293 -8
  313. package/plugins/lisa-copilot/hooks/block-instruction-file-edits.sh +21 -0
  314. package/plugins/lisa-copilot/hooks/block-managed-file-edits.sh +78 -15
  315. package/plugins/lisa-copilot/hooks/block-no-verify.sh +21 -0
  316. package/plugins/lisa-copilot/hooks/block-shell-json-parsing.sh +21 -0
  317. package/plugins/lisa-copilot/hooks/failure-signature-index.mjs +240 -5
  318. package/plugins/lisa-copilot/hooks/guard-dedupe.bash +337 -0
  319. package/plugins/lisa-copilot/hooks/inject-rules.sh +13 -5
  320. package/plugins/lisa-copilot/hooks/operational-hazards.mjs +736 -0
  321. package/plugins/lisa-copilot/hooks/operational-hazards.sh +28 -0
  322. package/plugins/lisa-copilot/hooks/parity-safety-net.sh +29 -1
  323. package/plugins/lisa-copilot/hooks/threshold-ratchet-compare.mjs +102 -21
  324. package/plugins/lisa-copilot/hooks/threshold-ratchet-families.mjs +130 -2
  325. package/plugins/lisa-copilot/hooks/threshold-ratchet.mjs +63 -13
  326. package/plugins/lisa-copilot/hooks/worktree-binding-guard.mjs +183 -1
  327. package/plugins/lisa-copilot/hooks/worktree-binding-guard.sh +21 -0
  328. package/plugins/lisa-copilot/rules/eager/00-rule-index.md +1 -0
  329. package/plugins/lisa-copilot/rules/eager/operational-hazards.md +50 -0
  330. package/plugins/lisa-copilot/rules/eager/tracked-work.md +1 -1
  331. package/plugins/lisa-copilot/rules/reference/config-resolution.md +1 -1
  332. package/plugins/lisa-copilot/rules/reference/leaf-only-lifecycle.md +1 -1
  333. package/plugins/lisa-copilot/rules/reference/operational-hazards.md +115 -0
  334. package/plugins/lisa-copilot/rules/reference/work-item-trailer-definition.md +129 -0
  335. package/plugins/lisa-copilot/scripts/intake-blocker-reprobe.mjs +407 -26
  336. package/plugins/lisa-copilot/scripts/qa-signal-lifecycle.mjs +507 -0
  337. package/plugins/lisa-copilot/skills/lisa-drive-pr-to-merge/SKILL.md +130 -19
  338. package/plugins/lisa-copilot/skills/lisa-github-build-intake/SKILL.md +27 -4
  339. package/plugins/lisa-copilot/skills/lisa-github-prd-intake/SKILL.md +1 -1
  340. package/plugins/lisa-copilot/skills/lisa-github-validate-issue/SKILL.md +22 -0
  341. package/plugins/lisa-copilot/skills/lisa-github-verify/SKILL.md +14 -0
  342. package/plugins/lisa-copilot/skills/lisa-github-write-issue/SKILL.md +51 -0
  343. package/plugins/lisa-copilot/skills/lisa-intake/SKILL.md +1 -1
  344. package/plugins/lisa-copilot/skills/lisa-jira-build-intake/SKILL.md +27 -4
  345. package/plugins/lisa-copilot/skills/lisa-jira-validate-ticket/SKILL.md +22 -0
  346. package/plugins/lisa-copilot/skills/lisa-jira-verify/SKILL.md +14 -0
  347. package/plugins/lisa-copilot/skills/lisa-jira-write-ticket/SKILL.md +51 -0
  348. package/plugins/lisa-copilot/skills/lisa-linear-access/SKILL.md +55 -2
  349. package/plugins/lisa-copilot/skills/lisa-linear-build-intake/SKILL.md +27 -4
  350. package/plugins/lisa-copilot/skills/lisa-linear-prd-intake/SKILL.md +48 -31
  351. package/plugins/lisa-copilot/skills/lisa-linear-to-tracker/SKILL.md +3 -3
  352. package/plugins/lisa-copilot/skills/lisa-linear-validate-issue/SKILL.md +22 -0
  353. package/plugins/lisa-copilot/skills/lisa-linear-verify/SKILL.md +14 -0
  354. package/plugins/lisa-copilot/skills/lisa-linear-write-issue/SKILL.md +54 -3
  355. package/plugins/lisa-copilot/skills/lisa-prd-ticket-coverage/SKILL.md +2 -2
  356. package/plugins/lisa-copilot/skills/lisa-qa-clear/SKILL.md +19 -7
  357. package/plugins/lisa-copilot/skills/lisa-qa-fail/SKILL.md +28 -3
  358. package/plugins/lisa-copilot/skills/lisa-qa-queue/SKILL.md +28 -3
  359. package/plugins/lisa-copilot/skills/lisa-repair-intake/SKILL.md +94 -24
  360. package/plugins/lisa-copilot/skills/lisa-rework-triage/SKILL.md +32 -2
  361. package/plugins/lisa-copilot/skills/lisa-setup-linear/SKILL.md +5 -5
  362. package/plugins/lisa-copilot/skills/lisa-track/SKILL.md +1 -1
  363. package/plugins/lisa-copilot/skills/lisa-tracker-build-intake/SKILL.md +9 -0
  364. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  365. package/plugins/lisa-cursor/agents/linear-prd-intake.md +8 -8
  366. package/plugins/lisa-cursor/hooks/block-blind-automerge.sh +81 -2
  367. package/plugins/lisa-cursor/hooks/block-direct-issue-create.sh +293 -8
  368. package/plugins/lisa-cursor/hooks/block-instruction-file-edits.sh +21 -0
  369. package/plugins/lisa-cursor/hooks/block-managed-file-edits.sh +78 -15
  370. package/plugins/lisa-cursor/hooks/block-no-verify.sh +21 -0
  371. package/plugins/lisa-cursor/hooks/block-shell-json-parsing.sh +21 -0
  372. package/plugins/lisa-cursor/hooks/failure-signature-index.mjs +240 -5
  373. package/plugins/lisa-cursor/hooks/guard-dedupe.bash +337 -0
  374. package/plugins/lisa-cursor/hooks/hooks.json +10 -0
  375. package/plugins/lisa-cursor/hooks/operational-hazards.mjs +736 -0
  376. package/plugins/lisa-cursor/hooks/operational-hazards.sh +28 -0
  377. package/plugins/lisa-cursor/hooks/parity-safety-net.sh +29 -1
  378. package/plugins/lisa-cursor/hooks/threshold-ratchet-compare.mjs +102 -21
  379. package/plugins/lisa-cursor/hooks/threshold-ratchet-families.mjs +130 -2
  380. package/plugins/lisa-cursor/hooks/threshold-ratchet.mjs +63 -13
  381. package/plugins/lisa-cursor/hooks/worktree-binding-guard.mjs +183 -1
  382. package/plugins/lisa-cursor/hooks/worktree-binding-guard.sh +21 -0
  383. package/plugins/lisa-cursor/rules/00-rule-index.mdc +1 -0
  384. package/plugins/lisa-cursor/rules/config-resolution-reference.mdc +1 -1
  385. package/plugins/lisa-cursor/rules/leaf-only-lifecycle-reference.mdc +1 -1
  386. package/plugins/lisa-cursor/rules/operational-hazards-reference.mdc +120 -0
  387. package/plugins/lisa-cursor/rules/operational-hazards.mdc +55 -0
  388. package/plugins/lisa-cursor/rules/tracked-work.mdc +1 -1
  389. package/plugins/lisa-cursor/rules/work-item-trailer-definition-reference.mdc +134 -0
  390. package/plugins/lisa-cursor/scripts/intake-blocker-reprobe.mjs +407 -26
  391. package/plugins/lisa-cursor/scripts/qa-signal-lifecycle.mjs +507 -0
  392. package/plugins/lisa-cursor/skills/lisa-drive-pr-to-merge/SKILL.md +130 -19
  393. package/plugins/lisa-cursor/skills/lisa-github-build-intake/SKILL.md +27 -4
  394. package/plugins/lisa-cursor/skills/lisa-github-prd-intake/SKILL.md +1 -1
  395. package/plugins/lisa-cursor/skills/lisa-github-validate-issue/SKILL.md +22 -0
  396. package/plugins/lisa-cursor/skills/lisa-github-verify/SKILL.md +14 -0
  397. package/plugins/lisa-cursor/skills/lisa-github-write-issue/SKILL.md +51 -0
  398. package/plugins/lisa-cursor/skills/lisa-intake/SKILL.md +1 -1
  399. package/plugins/lisa-cursor/skills/lisa-jira-build-intake/SKILL.md +27 -4
  400. package/plugins/lisa-cursor/skills/lisa-jira-validate-ticket/SKILL.md +22 -0
  401. package/plugins/lisa-cursor/skills/lisa-jira-verify/SKILL.md +14 -0
  402. package/plugins/lisa-cursor/skills/lisa-jira-write-ticket/SKILL.md +51 -0
  403. package/plugins/lisa-cursor/skills/lisa-linear-access/SKILL.md +55 -2
  404. package/plugins/lisa-cursor/skills/lisa-linear-build-intake/SKILL.md +27 -4
  405. package/plugins/lisa-cursor/skills/lisa-linear-prd-intake/SKILL.md +48 -31
  406. package/plugins/lisa-cursor/skills/lisa-linear-to-tracker/SKILL.md +3 -3
  407. package/plugins/lisa-cursor/skills/lisa-linear-validate-issue/SKILL.md +22 -0
  408. package/plugins/lisa-cursor/skills/lisa-linear-verify/SKILL.md +14 -0
  409. package/plugins/lisa-cursor/skills/lisa-linear-write-issue/SKILL.md +54 -3
  410. package/plugins/lisa-cursor/skills/lisa-prd-ticket-coverage/SKILL.md +2 -2
  411. package/plugins/lisa-cursor/skills/lisa-qa-clear/SKILL.md +19 -7
  412. package/plugins/lisa-cursor/skills/lisa-qa-fail/SKILL.md +28 -3
  413. package/plugins/lisa-cursor/skills/lisa-qa-queue/SKILL.md +28 -3
  414. package/plugins/lisa-cursor/skills/lisa-repair-intake/SKILL.md +94 -24
  415. package/plugins/lisa-cursor/skills/lisa-rework-triage/SKILL.md +32 -2
  416. package/plugins/lisa-cursor/skills/lisa-setup-linear/SKILL.md +5 -5
  417. package/plugins/lisa-cursor/skills/lisa-track/SKILL.md +1 -1
  418. package/plugins/lisa-cursor/skills/lisa-tracker-build-intake/SKILL.md +9 -0
  419. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  420. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  421. package/plugins/lisa-expo-agy/plugin.json +1 -1
  422. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  423. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  424. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  425. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  426. package/plugins/lisa-harper-fabric/hooks/inject-rules.sh +14 -3
  427. package/plugins/lisa-harper-fabric/rules/eager/harper-fabric.md +31 -0
  428. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  429. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  430. package/plugins/lisa-harper-fabric-copilot/hooks/inject-rules.sh +14 -3
  431. package/plugins/lisa-harper-fabric-copilot/rules/eager/harper-fabric.md +31 -0
  432. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  433. package/plugins/lisa-harper-fabric-cursor/rules/harper-fabric-reference.mdc +57 -0
  434. package/plugins/lisa-harper-fabric-cursor/rules/harper-fabric.mdc +32 -53
  435. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  436. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  437. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  438. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  439. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  440. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  441. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  442. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  443. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  444. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  445. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  446. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  447. package/plugins/lisa-phaser/hooks/inject-rules.sh +14 -3
  448. package/plugins/lisa-phaser/rules/eager/phaser.md +44 -0
  449. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  450. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  451. package/plugins/lisa-phaser-copilot/hooks/inject-rules.sh +14 -3
  452. package/plugins/lisa-phaser-copilot/rules/eager/phaser.md +44 -0
  453. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  454. package/plugins/lisa-phaser-cursor/rules/phaser-reference.mdc +189 -0
  455. package/plugins/lisa-phaser-cursor/rules/phaser.mdc +45 -185
  456. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  457. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  458. package/plugins/lisa-rails/hooks/inject-rules.sh +14 -4
  459. package/plugins/lisa-rails/rules/eager/rails-conventions.md +23 -0
  460. package/plugins/lisa-rails-agy/plugin.json +1 -1
  461. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  462. package/plugins/lisa-rails-copilot/hooks/inject-rules.sh +14 -4
  463. package/plugins/lisa-rails-copilot/rules/eager/rails-conventions.md +23 -0
  464. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  465. package/plugins/lisa-rails-cursor/rules/rails-conventions-reference.mdc +181 -0
  466. package/plugins/lisa-rails-cursor/rules/rails-conventions.mdc +24 -177
  467. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  468. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  469. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  470. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  471. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  472. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  473. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  474. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  475. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  476. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  477. package/plugins/materialized-artifacts.json +1 -0
  478. package/plugins/src/base/.claude-plugin/plugin.json +27 -0
  479. package/plugins/src/base/agents/linear-prd-intake.md +8 -8
  480. package/plugins/src/base/hooks/block-blind-automerge.sh +81 -2
  481. package/plugins/src/base/hooks/block-direct-issue-create.agy.sh +64 -16
  482. package/plugins/src/base/hooks/block-direct-issue-create.sh +293 -8
  483. package/plugins/src/base/hooks/block-instruction-file-edits.sh +21 -0
  484. package/plugins/src/base/hooks/block-managed-file-edits.sh +78 -15
  485. package/plugins/src/base/hooks/block-no-verify.sh +21 -0
  486. package/plugins/src/base/hooks/block-shell-json-parsing.sh +21 -0
  487. package/plugins/src/base/hooks/failure-signature-index.mjs +240 -5
  488. package/plugins/src/base/hooks/guard-dedupe.bash +337 -0
  489. package/plugins/src/base/hooks/inject-rules.sh +13 -5
  490. package/plugins/src/base/hooks/operational-hazards.mjs +736 -0
  491. package/plugins/src/base/hooks/operational-hazards.sh +28 -0
  492. package/plugins/src/base/hooks/parity-safety-net.sh +29 -1
  493. package/plugins/src/base/hooks/threshold-ratchet-compare.mjs +102 -21
  494. package/plugins/src/base/hooks/threshold-ratchet-families.mjs +130 -2
  495. package/plugins/src/base/hooks/threshold-ratchet.mjs +63 -13
  496. package/plugins/src/base/hooks/worktree-binding-guard.mjs +183 -1
  497. package/plugins/src/base/hooks/worktree-binding-guard.sh +21 -0
  498. package/plugins/src/base/rules/eager/00-rule-index.md +1 -0
  499. package/plugins/src/base/rules/eager/operational-hazards.md +50 -0
  500. package/plugins/src/base/rules/eager/tracked-work.md +1 -1
  501. package/plugins/src/base/rules/reference/config-resolution.md +1 -1
  502. package/plugins/src/base/rules/reference/leaf-only-lifecycle.md +1 -1
  503. package/plugins/src/base/rules/reference/operational-hazards.md +115 -0
  504. package/plugins/src/base/rules/reference/work-item-trailer-definition.md +129 -0
  505. package/plugins/src/base/scripts/intake-blocker-reprobe.mjs +407 -26
  506. package/plugins/src/base/scripts/qa-signal-lifecycle.mjs +507 -0
  507. package/plugins/src/base/skills/lisa-drive-pr-to-merge/SKILL.md +130 -19
  508. package/plugins/src/base/skills/lisa-github-build-intake/SKILL.md +27 -4
  509. package/plugins/src/base/skills/lisa-github-prd-intake/SKILL.md +1 -1
  510. package/plugins/src/base/skills/lisa-github-validate-issue/SKILL.md +22 -0
  511. package/plugins/src/base/skills/lisa-github-verify/SKILL.md +14 -0
  512. package/plugins/src/base/skills/lisa-github-write-issue/SKILL.md +51 -0
  513. package/plugins/src/base/skills/lisa-intake/SKILL.md +1 -1
  514. package/plugins/src/base/skills/lisa-jira-build-intake/SKILL.md +27 -4
  515. package/plugins/src/base/skills/lisa-jira-validate-ticket/SKILL.md +22 -0
  516. package/plugins/src/base/skills/lisa-jira-verify/SKILL.md +14 -0
  517. package/plugins/src/base/skills/lisa-jira-write-ticket/SKILL.md +51 -0
  518. package/plugins/src/base/skills/lisa-linear-access/SKILL.md +55 -2
  519. package/plugins/src/base/skills/lisa-linear-build-intake/SKILL.md +27 -4
  520. package/plugins/src/base/skills/lisa-linear-prd-intake/SKILL.md +48 -31
  521. package/plugins/src/base/skills/lisa-linear-to-tracker/SKILL.md +3 -3
  522. package/plugins/src/base/skills/lisa-linear-validate-issue/SKILL.md +22 -0
  523. package/plugins/src/base/skills/lisa-linear-verify/SKILL.md +14 -0
  524. package/plugins/src/base/skills/lisa-linear-write-issue/SKILL.md +54 -3
  525. package/plugins/src/base/skills/lisa-prd-ticket-coverage/SKILL.md +2 -2
  526. package/plugins/src/base/skills/lisa-qa-clear/SKILL.md +19 -7
  527. package/plugins/src/base/skills/lisa-qa-fail/SKILL.md +28 -3
  528. package/plugins/src/base/skills/lisa-qa-queue/SKILL.md +28 -3
  529. package/plugins/src/base/skills/lisa-repair-intake/SKILL.md +94 -24
  530. package/plugins/src/base/skills/lisa-rework-triage/SKILL.md +32 -2
  531. package/plugins/src/base/skills/lisa-setup-linear/SKILL.md +5 -5
  532. package/plugins/src/base/skills/lisa-track/SKILL.md +1 -1
  533. package/plugins/src/base/skills/lisa-tracker-build-intake/SKILL.md +9 -0
  534. package/plugins/src/harper-fabric/hooks/inject-rules.sh +14 -3
  535. package/plugins/src/harper-fabric/rules/eager/harper-fabric.md +31 -0
  536. package/plugins/src/phaser/hooks/inject-rules.sh +14 -3
  537. package/plugins/src/phaser/rules/eager/phaser.md +44 -0
  538. package/plugins/src/rails/hooks/inject-rules.sh +14 -4
  539. package/plugins/src/rails/rules/eager/rails-conventions.md +23 -0
  540. package/rails/copy-overwrite/scripts/check-threshold-ratchet.mjs +63 -13
  541. package/rails/copy-overwrite/scripts/threshold-ratchet-compare.mjs +102 -21
  542. package/rails/copy-overwrite/scripts/threshold-ratchet-families.mjs +130 -2
  543. package/rails/create-only/.github/workflows/ci.yml +8 -0
  544. package/rails/create-only/.github/workflows/deploy.yml +22 -0
  545. package/scripts/build-plugins.sh +2 -1
  546. package/scripts/check-engine-floor.mjs +361 -0
  547. package/scripts/check-guard-parity-notes.mjs +709 -0
  548. package/scripts/check-template-workflow-refs.mjs +9 -0
  549. package/scripts/check-third-party-action-pins.mjs +17 -1
  550. package/scripts/check-workflow-contract-assertions.mjs +480 -0
  551. package/scripts/copy-opencode-plugin-templates.mjs +1 -0
  552. package/scripts/deployed-guard-advice.mjs +636 -0
  553. package/scripts/generate-agy-plugin-artifacts.mjs +35 -10
  554. package/scripts/generate-export-surface.mjs +105 -19
  555. package/scripts/generate-two-channel-couplings.ts +143 -212
  556. package/scripts/lisa-enforcement-fallback.sh +31 -0
  557. package/scripts/measure-scripts-profile-gap.mts +361 -0
  558. package/scripts/mutation-performance-measure.mjs +21 -5
  559. package/scripts/reconcile-release-tags.mjs +65 -84
  560. package/scripts/two-channel-couplings.json +302 -70
  561. package/scripts/workflow-contract-assertion.sh +47 -0
  562. package/typescript/copy-overwrite/.lintstagedrc.json +1 -0
  563. package/typescript/copy-overwrite/eslint.ignore.config.json +1 -0
  564. package/typescript/copy-overwrite/scripts/check-nightly-e2e-health.mjs +527 -53
  565. package/typescript/copy-overwrite/scripts/check-skipped-required-checks.mjs +301 -13
  566. package/typescript/copy-overwrite/scripts/check-threshold-ratchet.mjs +63 -13
  567. package/typescript/copy-overwrite/scripts/threshold-ratchet-compare.mjs +102 -21
  568. package/typescript/copy-overwrite/scripts/threshold-ratchet-families.mjs +130 -2
  569. package/typescript/create-only/.github/workflows/ci.yml +7 -0
  570. /package/plugins/lisa-harper-fabric/rules/{harper-fabric.md → reference/harper-fabric.md} +0 -0
  571. /package/plugins/lisa-harper-fabric-copilot/rules/{harper-fabric.md → reference/harper-fabric.md} +0 -0
  572. /package/plugins/lisa-phaser/rules/{phaser.md → reference/phaser.md} +0 -0
  573. /package/plugins/lisa-phaser-copilot/rules/{phaser.md → reference/phaser.md} +0 -0
  574. /package/plugins/lisa-rails/rules/{rails-conventions.md → reference/rails-conventions.md} +0 -0
  575. /package/plugins/lisa-rails-copilot/rules/{rails-conventions.md → reference/rails-conventions.md} +0 -0
  576. /package/plugins/src/harper-fabric/rules/{harper-fabric.md → reference/harper-fabric.md} +0 -0
  577. /package/plugins/src/phaser/rules/{phaser.md → reference/phaser.md} +0 -0
  578. /package/plugins/src/rails/rules/{rails-conventions.md → reference/rails-conventions.md} +0 -0
@@ -0,0 +1,736 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * A project-owned surface for a SHORT-LIVED operational hazard, read at every
4
+ * session and subagent start, whose entries expire on their own.
5
+ *
6
+ * ## The defect this exists against
7
+ *
8
+ * A broadcast cannot reach a session that starts after it, and the sessions
9
+ * that missed it never knew there was something to miss. That is a detector
10
+ * whose only failure mode is staying quiet, and the population a fan-out
11
+ * structurally cannot reach is the same population most exposed: the lanes
12
+ * that just started are the ones standing up fresh environments and colliding
13
+ * on ports, saturating workers, and re-hitting a defect somebody diagnosed an
14
+ * hour ago (CodySwannGT/lisa#3681).
15
+ *
16
+ * Measured on one machine in one hour: one session started 18 minutes after a
17
+ * warning it needed, another 8 minutes after a different one, four more
18
+ * appeared and vanished entirely. Worse, the hole is unmeasurable rather than
19
+ * merely growing — two sessions enumerating peers at the same moment returned
20
+ * DIFFERENT rosters, so "I checked the list" carries an unknown gap and a
21
+ * fan-out audited against one session's view cannot be audited at all.
22
+ *
23
+ * The existing rule injector cannot take this content. It reads
24
+ * `CLAUDE_PLUGIN_ROOT/rules/eager` — the plugin root, not the project — so a
25
+ * project cannot register anything there without editing an installed plugin
26
+ * that the next apply overwrites; and it `cat`s every file unconditionally, so
27
+ * anything written there becomes permanent scripture. A hazard surface without
28
+ * an expiry IS the permanent-scripture problem, and projects correctly refuse
29
+ * to use one.
30
+ *
31
+ * ## Why the expiry is the whole mechanism, not a convenience
32
+ *
33
+ * The expiry is what separates a transient hazard from doctrine, and it is the
34
+ * only reason this surface can be trusted with transient content: nobody has
35
+ * to remember to remove anything. So `until` is REQUIRED by the writer, and a
36
+ * past `until` stops the entry being injected with no human action.
37
+ *
38
+ * The shape is deliberately the threshold-ratchet exemption's
39
+ * (CodySwannGT/lisa#3856) rather than a second scheme: record the condition in
40
+ * a form something can evaluate, and never let an unevaluable condition lapse
41
+ * quietly into the permissive answer. An entry naming no evaluable expiry —
42
+ * hand-written, or written before `until` existed — still applies, and is
43
+ * reported as `unchecked` every time it is read. An expiry that lapses into
44
+ * "no longer shown" is worse than no expiry.
45
+ *
46
+ * ## The inverse, because a state change needs one
47
+ *
48
+ * A hazard also ends before its expiry, and a declaration nothing can void is
49
+ * the defect CodySwannGT/lisa#3855 and #3852 closed twice. `--lift` appends a
50
+ * void record naming what resolved it. Matching is time-ordered: a lift voids
51
+ * only a declaration that PRE-DATES it, so a hazard re-declared after an
52
+ * earlier lift is not born discharged — the unsafe direction that must not be
53
+ * introduced.
54
+ *
55
+ * ## Two tiers, because they reach different audiences
56
+ *
57
+ * - the repository ledger, `.lisa/HAZARDS.jsonl`, committed — reaches every
58
+ * future reader of this project and merges by union;
59
+ * - the machine ledger, `${LISA_STATE_HOME:-~/.lisa}/operational-hazards.jsonl`
60
+ * — reaches sibling sessions in OTHER worktrees on this machine NOW, which
61
+ * a committed file cannot do until it is merged and pulled. That is the
62
+ * tier the fleet case above actually needs.
63
+ *
64
+ * A declaration is written to both. Reads are the union.
65
+ *
66
+ * ## Which half of the reporting gap this closes
67
+ *
68
+ * `--session-start` reaches every session and subagent that starts AFTER the
69
+ * declaration — the half a fan-out structurally cannot serve. `--hook` runs as
70
+ * a PostToolUse hook and announces, on the next tool result, a hazard declared
71
+ * or lifted SINCE this session started — which is the only thing that reaches
72
+ * a session already running. Surfaces with no PostToolUse runtime get the
73
+ * first half and `--list`.
74
+ * @module operational-hazards
75
+ */
76
+ import * as fs from "node:fs";
77
+ import * as os from "node:os";
78
+ import * as path from "node:path";
79
+ import { execFileSync } from "node:child_process";
80
+ import { fileURLToPath } from "node:url";
81
+
82
+ /** Repository-relative path of the committed, durable ledger. */
83
+ export const REPO_LEDGER = path.join(".lisa", "HAZARDS.jsonl");
84
+ /** Filename of the machine-local ledger inside the Lisa state home. */
85
+ export const MACHINE_LEDGER = "operational-hazards.jsonl";
86
+
87
+ /** An entry with an expiry still in the future. */
88
+ export const LIVE = "live";
89
+ /** An entry naming no evaluable expiry: it still applies, and says so. */
90
+ export const UNCHECKED = "unchecked";
91
+ /** An entry whose expiry has passed. Inert, with no human action. */
92
+ export const EXPIRED = "expired";
93
+ /** An entry voided early by a `--lift` recorded after it was declared. */
94
+ export const LIFTED = "lifted";
95
+
96
+ /** How a day-only `until` is completed into the instant it stops applying. */
97
+ const END_OF_DAY = "T23:59:59.999Z";
98
+ /** Matches a bare calendar day. Both quantifiers are bounded. */
99
+ const DAY_ONLY = /^\d{4}-\d{2}-\d{2}$/;
100
+
101
+ /**
102
+ * The machine-local state directory. Overridable so tests never touch `$HOME`.
103
+ * @returns {string} An absolute directory path.
104
+ */
105
+ export function stateHome() {
106
+ return process.env.LISA_STATE_HOME || path.join(os.homedir(), ".lisa");
107
+ }
108
+
109
+ /**
110
+ * The repository root, or the working directory when there is no repository.
111
+ * @returns {string} An absolute directory path.
112
+ */
113
+ export function repoRoot() {
114
+ try {
115
+ return execFileSync("git", ["rev-parse", "--show-toplevel"], {
116
+ encoding: "utf-8",
117
+ stdio: ["ignore", "pipe", "ignore"],
118
+ }).trim();
119
+ } catch {
120
+ return process.cwd();
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Both ledger paths, repository tier first.
126
+ * @param {string} root Repository root.
127
+ * @returns {readonly string[]} Absolute ledger paths.
128
+ */
129
+ export function ledgerPaths(root) {
130
+ return [path.join(root, REPO_LEDGER), path.join(stateHome(), MACHINE_LEDGER)];
131
+ }
132
+
133
+ /**
134
+ * A cheap identity for the ledger pair: size and mtime, no parsing.
135
+ *
136
+ * Two `stat` calls is the entire cost of the common case, and `--hook` fires
137
+ * on every tool call in every session, so anything expensive multiplies.
138
+ * @param {readonly string[]} paths Ledger paths.
139
+ * @returns {string} A fingerprint that changes whenever either file changes.
140
+ */
141
+ export function fingerprint(paths) {
142
+ return paths
143
+ .map(file => {
144
+ try {
145
+ const stat = fs.statSync(file);
146
+ return `${stat.size}:${stat.mtimeMs}`;
147
+ } catch {
148
+ return "-";
149
+ }
150
+ })
151
+ .join("|");
152
+ }
153
+
154
+ /**
155
+ * The instant an `until` value stops applying, or null when it names none.
156
+ *
157
+ * A bare `YYYY-MM-DD` is live THROUGH that day, which is what a person means
158
+ * when they write one. Anything unparseable returns null and is therefore
159
+ * treated as naming no condition — reported, never silently permissive.
160
+ * @param {unknown} until The recorded expiry.
161
+ * @returns {number | null} Epoch milliseconds, or null.
162
+ */
163
+ export function expiryMs(until) {
164
+ if (typeof until !== "string") return null;
165
+ const trimmed = until.trim();
166
+ if (trimmed === "") return null;
167
+ const completed = DAY_ONLY.test(trimmed)
168
+ ? `${trimmed}${END_OF_DAY}`
169
+ : trimmed;
170
+ const parsed = Date.parse(completed);
171
+ return Number.isNaN(parsed) ? null : parsed;
172
+ }
173
+
174
+ /**
175
+ * Parse one ledger file into declarations and lifts, reporting bad lines.
176
+ *
177
+ * A record carrying `lift: true` is a void record rather than a hazard, so the
178
+ * two travel in one append-only file and merge by the same union driver.
179
+ * @param {string} file Absolute ledger path.
180
+ * @returns {{ entries: object[], lifts: object[], malformed: string[] }} Parsed lines.
181
+ */
182
+ export function parseLedger(file) {
183
+ const entries = [];
184
+ const lifts = [];
185
+ const malformed = [];
186
+ let raw = "";
187
+ try {
188
+ raw = fs.readFileSync(file, "utf-8");
189
+ } catch {
190
+ return { entries, lifts, malformed };
191
+ }
192
+ for (const line of raw.split("\n")) {
193
+ if (line.trim() === "") continue;
194
+ const record = tryParse(line);
195
+ if (record === null) malformed.push(line);
196
+ else if (record.lift === true) lifts.push(record);
197
+ else entries.push(record);
198
+ }
199
+ return { entries, lifts, malformed };
200
+ }
201
+
202
+ /**
203
+ * Parse one ledger line, requiring the identity every reader keys on.
204
+ * @param {string} line A single ledger line.
205
+ * @returns {object | null} The record, or null when it is unusable.
206
+ */
207
+ function tryParse(line) {
208
+ try {
209
+ const record = JSON.parse(line);
210
+ const usable = typeof record?.id === "string" && record.id !== "";
211
+ return usable ? record : null;
212
+ } catch {
213
+ return null;
214
+ }
215
+ }
216
+
217
+ /**
218
+ * When a record was written, or zero when it says nothing evaluable.
219
+ * @param {object} record A declaration or a lift.
220
+ * @param {string} field The timestamp field to read.
221
+ * @returns {number} Epoch milliseconds, or zero.
222
+ */
223
+ export function stampMs(record, field) {
224
+ const parsed = Date.parse(String(record?.[field] ?? ""));
225
+ return Number.isNaN(parsed) ? 0 : parsed;
226
+ }
227
+
228
+ /**
229
+ * The union of both ledger tiers, de-duplicated by id.
230
+ *
231
+ * The NEWEST declaration for an id wins, not the first one read. Both tiers
232
+ * are append-only and a re-declaration is an append, so first-wins would pin
233
+ * an id to the oldest thing ever said about it — and a hazard re-declared
234
+ * after an earlier lift would be born discharged, which is exactly the unsafe
235
+ * direction CodySwannGT/lisa#3852 refuses.
236
+ * @param {string} root Repository root.
237
+ * @returns {{ entries: object[], lifts: object[], malformed: string[] }} Every record.
238
+ */
239
+ export function readLedgers(root) {
240
+ const byId = new Map();
241
+ const lifts = [];
242
+ const malformed = [];
243
+ for (const file of ledgerPaths(root)) {
244
+ const parsed = parseLedger(file);
245
+ for (const entry of parsed.entries) {
246
+ const held = byId.get(entry.id);
247
+ const newer =
248
+ held === undefined ||
249
+ stampMs(entry, "declaredAt") >= stampMs(held, "declaredAt");
250
+ if (newer) byId.set(entry.id, entry);
251
+ }
252
+ lifts.push(...parsed.lifts);
253
+ malformed.push(...parsed.malformed);
254
+ }
255
+ return { entries: [...byId.values()], lifts, malformed };
256
+ }
257
+
258
+ /**
259
+ * Whether a lift voids this declaration.
260
+ *
261
+ * Time-ordered on purpose: a lift discharges only a declaration that pre-dates
262
+ * it. Re-declaring a hazard after an earlier lift must not be born discharged
263
+ * — the sibling refusal CodySwannGT/lisa#3852 records for the human gate.
264
+ * @param {object} entry A hazard declaration.
265
+ * @param {readonly object[]} lifts Every void record.
266
+ * @returns {boolean} Whether the entry has been lifted.
267
+ */
268
+ export function isLifted(entry, lifts) {
269
+ const declared = stampMs(entry, "declaredAt");
270
+ return lifts.some(
271
+ lift => lift.id === entry.id && stampMs(lift, "liftedAt") >= declared
272
+ );
273
+ }
274
+
275
+ /**
276
+ * Classify one declaration at a moment in time.
277
+ * @param {object} entry A hazard declaration.
278
+ * @param {readonly object[]} lifts Every void record.
279
+ * @param {number} now Epoch milliseconds.
280
+ * @returns {string} One of LIVE, UNCHECKED, EXPIRED or LIFTED.
281
+ */
282
+ export function classify(entry, lifts, now) {
283
+ if (isLifted(entry, lifts)) return LIFTED;
284
+ const expiry = expiryMs(entry.until);
285
+ if (expiry === null) return UNCHECKED;
286
+ return expiry < now ? EXPIRED : LIVE;
287
+ }
288
+
289
+ /**
290
+ * Split the ledger union into the four states a reader cares about.
291
+ * @param {string} root Repository root.
292
+ * @param {number} now Epoch milliseconds.
293
+ * @returns {{ live: object[], unchecked: object[], expired: object[], lifted: object[], malformed: string[] }} Partitioned records.
294
+ */
295
+ export function partition(root, now) {
296
+ const { entries, lifts, malformed } = readLedgers(root);
297
+ const buckets = { live: [], unchecked: [], expired: [], lifted: [] };
298
+ for (const entry of entries) buckets[classify(entry, lifts, now)].push(entry);
299
+ return { ...buckets, malformed };
300
+ }
301
+
302
+ /**
303
+ * Render one declaration as the lines a reader can act on.
304
+ * @param {object} entry A hazard declaration.
305
+ * @param {string} state The classification from `classify`.
306
+ * @returns {string} A bullet block.
307
+ */
308
+ export function formatEntry(entry, state) {
309
+ const head =
310
+ state === UNCHECKED
311
+ ? `- ${entry.id} — NO EVALUABLE EXPIRY, so it still applies (${UNCHECKED})`
312
+ : `- ${entry.id} — live through ${entry.until}`;
313
+ return [
314
+ head,
315
+ ` hazard: ${entry.hazard ?? "(not recorded)"}`,
316
+ ` instead: ${entry.avoid ?? "(not recorded)"}`,
317
+ ` declared: ${entry.declaredAt ?? "(undated)"}`,
318
+ ].join("\n");
319
+ }
320
+
321
+ /**
322
+ * Render live and unchecked hazards as an injectable notice.
323
+ *
324
+ * Expired and lifted entries are absent by construction — that is the point of
325
+ * the surface, and the property the expiry test asserts.
326
+ * @param {readonly object[]} live Entries with a future expiry.
327
+ * @param {readonly object[]} unchecked Entries naming no evaluable expiry.
328
+ * @param {string} headline The opening line.
329
+ * @returns {string} Notice text, empty when there is nothing to say.
330
+ */
331
+ export function formatNotice(live, unchecked, headline) {
332
+ if (live.length + unchecked.length === 0) return "";
333
+ return [
334
+ headline,
335
+ "",
336
+ ...live.map(entry => formatEntry(entry, LIVE)),
337
+ ...unchecked.map(entry => formatEntry(entry, UNCHECKED)),
338
+ "",
339
+ "These are transient conditions on this machine or branch, not doctrine. Each one",
340
+ "stops being shown when its expiry passes, with nobody remembering to remove it.",
341
+ "If you resolve one, record that so every other session stops paying for it:",
342
+ ` node "$CLAUDE_PLUGIN_ROOT/hooks/operational-hazards.mjs" --lift <id> --because "<what resolved it>"`,
343
+ ].join("\n");
344
+ }
345
+
346
+ /**
347
+ * Where this session's delivery mark lives.
348
+ * @param {string} sessionId The harness-supplied session id.
349
+ * @returns {string} An absolute file path.
350
+ */
351
+ export function markPath(sessionId) {
352
+ return path.join(
353
+ stateHome(),
354
+ "hazard-marks",
355
+ `${sessionId.replace(/[^\w.-]/g, "_")}.json`
356
+ );
357
+ }
358
+
359
+ /**
360
+ * Write a JSON file, creating parents.
361
+ * @param {string} file Absolute path.
362
+ * @param {object} value Serialisable value.
363
+ * @returns {void}
364
+ */
365
+ export function writeJson(file, value) {
366
+ fs.mkdirSync(path.dirname(file), { recursive: true });
367
+ fs.writeFileSync(file, `${JSON.stringify(value)}\n`);
368
+ }
369
+
370
+ /**
371
+ * Emit a hook result on stdout, and the same text on stderr for humans.
372
+ * @param {string} event The hook event name.
373
+ * @param {string} notice Notice text.
374
+ * @returns {number} An exit code.
375
+ */
376
+ export function emit(event, notice) {
377
+ if (notice === "") return 0;
378
+ process.stderr.write(`${notice}\n`);
379
+ process.stdout.write(
380
+ `${JSON.stringify({
381
+ hookSpecificOutput: { hookEventName: event, additionalContext: notice },
382
+ })}\n`
383
+ );
384
+ return 0;
385
+ }
386
+
387
+ /**
388
+ * Read the hook payload's session id.
389
+ * @param {string} raw Raw stdin.
390
+ * @returns {string} The session id, or the empty string.
391
+ */
392
+ export function sessionIdOf(raw) {
393
+ try {
394
+ const payload = JSON.parse(raw);
395
+ return typeof payload?.session_id === "string" ? payload.session_id : "";
396
+ } catch {
397
+ return "";
398
+ }
399
+ }
400
+
401
+ /**
402
+ * SessionStart / SubagentStart: deliver every applying hazard and record what
403
+ * this session was born knowing.
404
+ *
405
+ * This is the half a fan-out structurally cannot serve — a session that starts
406
+ * 18 minutes after the warning reads it here.
407
+ * @param {string} raw Raw stdin.
408
+ * @param {string} root Repository root.
409
+ * @param {number} now Epoch milliseconds.
410
+ * @returns {number} An exit code.
411
+ */
412
+ export function runSessionStart(raw, root, now) {
413
+ const sessionId = sessionIdOf(raw);
414
+ if (sessionId === "") return 0;
415
+ const { live, unchecked } = partition(root, now);
416
+ const applying = [...live, ...unchecked];
417
+ writeJson(markPath(sessionId), {
418
+ delivered: applying.map(entry => entry.id),
419
+ fingerprint: fingerprint(ledgerPaths(root)),
420
+ });
421
+ return emit(
422
+ "SessionStart",
423
+ formatNotice(
424
+ live,
425
+ unchecked,
426
+ `Lisa: ${applying.length} short-lived operational hazard(s) apply to this project right now.`
427
+ )
428
+ );
429
+ }
430
+
431
+ /**
432
+ * PostToolUse: announce what changed AFTER this session started.
433
+ *
434
+ * Two directions, because both were measured. A hazard declared while this
435
+ * session runs is the collision it is about to cause; a LIFT of a hazard this
436
+ * session was told about is the hold it is still paying for after everyone
437
+ * else stopped — the provenance of CodySwannGT/lisa#3681 is a session sent a
438
+ * lift for a hold it had never received.
439
+ * @param {string} raw Raw stdin.
440
+ * @param {string} root Repository root.
441
+ * @param {number} now Epoch milliseconds.
442
+ * @returns {number} An exit code.
443
+ */
444
+ export function runHook(raw, root, now) {
445
+ const sessionId = sessionIdOf(raw);
446
+ if (sessionId === "") return 0;
447
+ const mark = readMark(markPath(sessionId));
448
+ if (mark === null) return 0;
449
+ const current = fingerprint(ledgerPaths(root));
450
+ if (current === mark.fingerprint) return 0;
451
+ const { live, unchecked } = partition(root, now);
452
+ const applying = [...live, ...unchecked];
453
+ const known = new Set(mark.delivered);
454
+ const applyingIds = new Set(applying.map(entry => entry.id));
455
+ const dropped = [...known].filter(id => !applyingIds.has(id));
456
+ writeJson(markPath(sessionId), {
457
+ delivered: applying.map(entry => entry.id),
458
+ fingerprint: current,
459
+ });
460
+ const fresh = applying.filter(entry => !known.has(entry.id));
461
+ return emit("PostToolUse", changeNotice(fresh, dropped));
462
+ }
463
+
464
+ /**
465
+ * Read a session mark, treating any unreadable mark as absent.
466
+ *
467
+ * A missing mark is deliberately silent: a session whose starting knowledge is
468
+ * unknown would otherwise be told every historical hazard, which degrades the
469
+ * mechanism into distrusting everything.
470
+ * @param {string} file Absolute mark path.
471
+ * @returns {{ delivered: string[], fingerprint: string } | null} The mark.
472
+ */
473
+ export function readMark(file) {
474
+ try {
475
+ const mark = JSON.parse(fs.readFileSync(file, "utf-8"));
476
+ return {
477
+ delivered: Array.isArray(mark?.delivered) ? mark.delivered : [],
478
+ fingerprint: String(mark?.fingerprint ?? ""),
479
+ };
480
+ } catch {
481
+ return null;
482
+ }
483
+ }
484
+
485
+ /**
486
+ * Render the mid-session change notice for a running session.
487
+ * @param {readonly object[]} fresh Hazards that began applying since the mark.
488
+ * @param {readonly string[]} dropped Ids that stopped applying since the mark.
489
+ * @returns {string} Notice text, empty when nothing changed.
490
+ */
491
+ export function changeNotice(fresh, dropped) {
492
+ const parts = [];
493
+ if (fresh.length > 0)
494
+ parts.push(
495
+ formatNotice(
496
+ fresh.filter(entry => expiryMs(entry.until) !== null),
497
+ fresh.filter(entry => expiryMs(entry.until) === null),
498
+ "Lisa: an operational hazard was declared AFTER this session started."
499
+ )
500
+ );
501
+ if (dropped.length > 0)
502
+ parts.push(
503
+ [
504
+ "Lisa: an operational hazard you were told about no longer applies —",
505
+ "it was lifted or its expiry passed. Stop working around it.",
506
+ "",
507
+ ...dropped.map(id => `- ${id}`),
508
+ ].join("\n")
509
+ );
510
+ return parts.join("\n\n");
511
+ }
512
+
513
+ /**
514
+ * Read a `--flag value` pair out of argv.
515
+ * @param {readonly string[]} argv Arguments.
516
+ * @param {string} flag The flag, including dashes.
517
+ * @returns {string} The value, or the empty string.
518
+ */
519
+ export function flagValue(argv, flag) {
520
+ const index = argv.indexOf(flag);
521
+ return index >= 0 && index + 1 < argv.length ? argv[index + 1] : "";
522
+ }
523
+
524
+ /**
525
+ * Build the record `--declare` appends, refusing anything a reader cannot use.
526
+ *
527
+ * `--until` is required and there is no default. A hazard surface whose entries
528
+ * do not expire becomes permanent scripture, which is the failure this exists
529
+ * against — so the writer cannot produce one, and the reader still tolerates a
530
+ * hand-written entry that names no expiry by reporting it rather than dropping
531
+ * it.
532
+ * @param {readonly string[]} argv Arguments.
533
+ * @param {string} now ISO-8601 timestamp.
534
+ * @returns {{ record?: object, error?: string }} The record, or why not.
535
+ */
536
+ export function buildDeclaration(argv, now) {
537
+ const id = flagValue(argv, "--declare");
538
+ const hazard = flagValue(argv, "--hazard");
539
+ const avoid = flagValue(argv, "--avoid");
540
+ const until = flagValue(argv, "--until");
541
+ if (id === "") return { error: "--declare needs an id" };
542
+ if (hazard === "")
543
+ return { error: "--hazard is required: say what will go wrong" };
544
+ if (avoid === "")
545
+ return {
546
+ error:
547
+ "--avoid is required: a hazard nobody can act on reaches them without reaching them",
548
+ };
549
+ if (until === "")
550
+ return {
551
+ error:
552
+ "--until is required: an entry that never expires is doctrine, and doctrine belongs in a rule",
553
+ };
554
+ if (expiryMs(until) === null)
555
+ return { error: `--until ${until} is not a date or ISO-8601 timestamp` };
556
+ return { record: { id, declaredAt: now, until, hazard, avoid } };
557
+ }
558
+
559
+ /**
560
+ * Append one record to both ledger tiers.
561
+ * @param {object} record The record to append.
562
+ * @param {string} root Repository root.
563
+ * @returns {void}
564
+ */
565
+ export function appendBothTiers(record, root) {
566
+ const line = `${JSON.stringify(record)}\n`;
567
+ for (const file of ledgerPaths(root)) {
568
+ fs.mkdirSync(path.dirname(file), { recursive: true });
569
+ fs.appendFileSync(file, line);
570
+ }
571
+ }
572
+
573
+ /**
574
+ * `--declare`: record a hazard in both tiers. One command, by design.
575
+ * @param {readonly string[]} argv Arguments.
576
+ * @param {string} root Repository root.
577
+ * @returns {number} An exit code.
578
+ */
579
+ export function runDeclare(argv, root) {
580
+ const built = buildDeclaration(argv, new Date().toISOString());
581
+ if (built.error !== undefined) {
582
+ process.stderr.write(`operational-hazards: ${built.error}\n`);
583
+ return 2;
584
+ }
585
+ appendBothTiers(built.record, root);
586
+ process.stdout.write(
587
+ `operational-hazards: ${built.record.id} applies through ${built.record.until}\n`
588
+ );
589
+ return 0;
590
+ }
591
+
592
+ /**
593
+ * `--lift`: void a hazard before its expiry. The executable inverse.
594
+ * @param {readonly string[]} argv Arguments.
595
+ * @param {string} root Repository root.
596
+ * @returns {number} An exit code.
597
+ */
598
+ export function runLift(argv, root) {
599
+ const id = flagValue(argv, "--lift");
600
+ const because = flagValue(argv, "--because");
601
+ if (because === "") {
602
+ process.stderr.write(
603
+ "operational-hazards: --because is required: say what resolved it\n"
604
+ );
605
+ return 2;
606
+ }
607
+ appendBothTiers(
608
+ { id, lift: true, liftedAt: new Date().toISOString(), because },
609
+ root
610
+ );
611
+ process.stdout.write(`operational-hazards: lifted ${id}\n`);
612
+ return 0;
613
+ }
614
+
615
+ /**
616
+ * `--list`: the surface for harnesses with no hook runtime, and for anyone
617
+ * asking what is currently in force.
618
+ * @param {string} root Repository root.
619
+ * @param {number} now Epoch milliseconds.
620
+ * @returns {number} An exit code.
621
+ */
622
+ export function runList(root, now) {
623
+ const { live, unchecked, expired, lifted } = partition(root, now);
624
+ const notice = formatNotice(
625
+ live,
626
+ unchecked,
627
+ `operational-hazards: ${live.length + unchecked.length} applying`
628
+ );
629
+ const tail = `operational-hazards: ${expired.length} expired, ${lifted.length} lifted (both inert)`;
630
+ process.stdout.write(notice === "" ? `${tail}\n` : `${notice}\n${tail}\n`);
631
+ return 0;
632
+ }
633
+
634
+ /**
635
+ * Every reason a ledger entry is unusable by a reader.
636
+ * @param {object} entry A hazard declaration.
637
+ * @returns {string[]} Problem descriptions.
638
+ */
639
+ export function entryProblems(entry) {
640
+ const problems = [];
641
+ if (typeof entry.hazard !== "string" || entry.hazard === "")
642
+ problems.push(`${entry.id}: no hazard recorded`);
643
+ if (Number.isNaN(Date.parse(String(entry.declaredAt ?? ""))))
644
+ problems.push(`${entry.id}: declaredAt is not an ISO-8601 timestamp`);
645
+ if (entry.until !== undefined && expiryMs(entry.until) === null)
646
+ problems.push(
647
+ `${entry.id}: until "${entry.until}" names no evaluable expiry, so this entry can never stop applying on its own`
648
+ );
649
+ return problems;
650
+ }
651
+
652
+ /**
653
+ * `--check`: refuse a ledger that would swallow a hazard, or hold one forever.
654
+ *
655
+ * The failure that matters is an `until` that LOOKS like an expiry and is not:
656
+ * every signal reads healthy while the entry becomes permanent. Expired and
657
+ * lifted entries are not failures — they are the mechanism working, and they
658
+ * are only reported.
659
+ * @param {string} root Repository root.
660
+ * @param {number} now Epoch milliseconds.
661
+ * @returns {number} An exit code.
662
+ */
663
+ export function runCheck(root, now) {
664
+ const { live, unchecked, expired, lifted, malformed } = partition(root, now);
665
+ const problems = malformed.map(
666
+ line => `unparseable ledger line: ${line.slice(0, 120)}`
667
+ );
668
+ for (const entry of [...live, ...unchecked, ...expired, ...lifted])
669
+ problems.push(...entryProblems(entry));
670
+ if (problems.length > 0) {
671
+ process.stderr.write(`operational-hazards: ${problems.join("\n ")}\n`);
672
+ return 1;
673
+ }
674
+ process.stdout.write(
675
+ `operational-hazards: ${live.length} live, ${unchecked.length} ${UNCHECKED}, ${expired.length} ${EXPIRED}, ${lifted.length} ${LIFTED}; ledger well-formed\n`
676
+ );
677
+ return 0;
678
+ }
679
+
680
+ /**
681
+ * Dispatch on the requested mode.
682
+ * @param {readonly string[]} argv Command-line arguments.
683
+ * @param {string} raw Piped stdin, when the mode needs it.
684
+ * @param {string} root Repository root.
685
+ * @param {number} now Epoch milliseconds.
686
+ * @returns {number} An exit code.
687
+ */
688
+ export function main(argv, raw, root, now = Date.now()) {
689
+ if (argv.includes("--session-start")) return runSessionStart(raw, root, now);
690
+ if (argv.includes("--hook")) return runHook(raw, root, now);
691
+ if (argv.includes("--declare")) return runDeclare(argv, root);
692
+ if (argv.includes("--lift")) return runLift(argv, root);
693
+ if (argv.includes("--list")) return runList(root, now);
694
+ return runCheck(root, now);
695
+ }
696
+
697
+ /**
698
+ * Whether this module is the process entry point.
699
+ *
700
+ * Both sides are realpath'd, because `import.meta.url` is the REAL path while
701
+ * `argv[1]` is whatever the caller typed — they disagree through a git worktree
702
+ * or any `/tmp` path on macOS, and for a CHECK that disagreement is a
703
+ * fail-OPEN. The one implementation lives at `scripts/lib/invoked-as-script.mjs`;
704
+ * this file cannot import it, being materialized into plugin payloads that have
705
+ * no `./lib/`, the same accommodation `withdrawn-rulings.mjs` makes.
706
+ * @param {string} moduleUrl The caller's own `import.meta.url`.
707
+ * @param {string | undefined} [argv1] Entry path; defaults to `process.argv[1]`.
708
+ * @returns {boolean} Whether the caller should run its CLI body.
709
+ */
710
+ export function invokedAsScript(moduleUrl, argv1 = process.argv[1]) {
711
+ if (!argv1) return false;
712
+ try {
713
+ return fs.realpathSync(argv1) === fs.realpathSync(fileURLToPath(moduleUrl));
714
+ } catch {
715
+ return false;
716
+ }
717
+ }
718
+
719
+ /**
720
+ * Read all of stdin.
721
+ * @returns {Promise<string>} The piped text.
722
+ */
723
+ export async function readStdin() {
724
+ if (process.stdin.isTTY) return "";
725
+ const chunks = [];
726
+ for await (const chunk of process.stdin) chunks.push(chunk);
727
+ return Buffer.concat(chunks).toString("utf-8");
728
+ }
729
+
730
+ if (invokedAsScript(import.meta.url)) {
731
+ const argv = process.argv.slice(2);
732
+ const needsStdin =
733
+ argv.includes("--hook") || argv.includes("--session-start");
734
+ const raw = needsStdin ? await readStdin() : "";
735
+ process.exitCode = main(argv, raw, repoRoot());
736
+ }