@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
@@ -71,7 +71,7 @@ function declarativeText(body) {
71
71
  }
72
72
 
73
73
  /**
74
- * Whether an HTML comment on this line carries the marker.
74
+ * Whether an HTML comment on this line carries the given marker.
75
75
  *
76
76
  * The second declaration form, and it is not a concession: an HTML comment is
77
77
  * invisible when the body is rendered, so nobody writes one to TALK about the
@@ -83,15 +83,16 @@ function declarativeText(body) {
83
83
  * `[^\n]*?` runs around a literal is the ambiguous shape this module avoids
84
84
  * elsewhere, and an indexOf walk is both linear and easier to be sure about.
85
85
  * @param {string} line - One line of the body
86
+ * @param {string} marker - The literal marker to look for
86
87
  * @returns {boolean} True when a comment on this line contains the marker
87
88
  */
88
- function commentCarriesMarker(line) {
89
+ function commentCarriesMarker(line, marker) {
89
90
  const segments = line.split("<!--");
90
91
  for (let index = 1; index < segments.length; index += 1) {
91
92
  const close = segments[index].indexOf("-->");
92
93
  const inside =
93
94
  close === -1 ? segments[index] : segments[index].slice(0, close);
94
- if (inside.includes(HUMAN_GATE_MARKER)) return true;
95
+ if (inside.includes(marker)) return true;
95
96
  }
96
97
  return false;
97
98
  }
@@ -106,13 +107,14 @@ function commentCarriesMarker(line) {
106
107
  * `--body` string and a shell script — where a declaration legitimately sits
107
108
  * after other text on its line.
108
109
  * @param {string} line - One line of the body
109
- * @returns {boolean} True when the line declares a hold
110
+ * @param {string} [marker] - The declaration marker to test for
111
+ * @returns {boolean} True when the line declares with that marker
110
112
  */
111
- function declaresOnLine(line) {
112
- if (line.replace(LEADING_DECORATION, "").startsWith(HUMAN_GATE_MARKER)) {
113
+ function declaresOnLine(line, marker = HUMAN_GATE_MARKER) {
114
+ if (line.replace(LEADING_DECORATION, "").startsWith(marker)) {
113
115
  return true;
114
116
  }
115
- return commentCarriesMarker(line);
117
+ return commentCarriesMarker(line, marker);
116
118
  }
117
119
 
118
120
  /**
@@ -174,6 +176,185 @@ export function bodyDeclaresHold(body) {
174
176
  return humanGateMentions(body).declared > 0;
175
177
  }
176
178
 
179
+ /**
180
+ * Marker a comment carries to DISCHARGE a hold.
181
+ *
182
+ * ## Why a comment and not the body
183
+ *
184
+ * The hold lives in the description, and the only description write this plugin
185
+ * has is a whole-body replacement — re-read everything, change one line, write
186
+ * it all back. On an item carrying acceptance criteria, a validation journey
187
+ * and a managed usage section, "delete one HTML comment" is therefore "rewrite
188
+ * the record and hope nothing was dropped". A release procedure whose only
189
+ * implementation risks destroying what it releases is not a release procedure:
190
+ * the rational move is always to leave the hold, so holds only accumulate
191
+ * (CodySwannGT/lisa#3852).
192
+ *
193
+ * A comment is append-only. It cannot drop a section it never held, and the
194
+ * same reader that reads the hold reads the discharge.
195
+ *
196
+ * ## Why it is not a superstring of the hold marker
197
+ *
198
+ * `HUMAN_GATE_MARKER` closes with `]`, so `[lisa-human-gate-release]` does not
199
+ * contain it. That is load-bearing rather than incidental: a release record
200
+ * that matched the hold test would declare a fresh hold every time one was
201
+ * lifted, which is the same trap `NORMALIZATION_HOLD_NOTE_MARKER` documents.
202
+ */
203
+ export const HUMAN_GATE_RELEASE_MARKER = "[lisa-human-gate-release]";
204
+
205
+ /** Structured key a hold and its release both name, so they pair up. */
206
+ const REASON_KEY = "reason=";
207
+
208
+ /**
209
+ * The comparable form of a hold reason.
210
+ *
211
+ * Case and internal whitespace drift between the hand that writes the hold and
212
+ * the hand that writes the release; the identity of the question does not.
213
+ * @param {unknown} value - A raw reason string
214
+ * @returns {string} Its comparable form, empty when there is none
215
+ */
216
+ function normalizeReason(value) {
217
+ return String(value ?? "")
218
+ .replace(/\s+/gu, " ")
219
+ .trim()
220
+ .toLowerCase();
221
+ }
222
+
223
+ /**
224
+ * The reason a declaring line names, or the empty key when it names none.
225
+ *
226
+ * A keyless declaration is not a defect to be rejected — markers in the wild
227
+ * carry no `reason=` at all, which is why the hold test is not keyed on one.
228
+ * The empty string is a real key here: a keyless hold is discharged by a
229
+ * keyless release and by nothing else, so the two surfaces stay symmetric
230
+ * whether or not a reason was ever written.
231
+ * @param {string} line - A line already known to declare
232
+ * @param {string} marker - The declaration marker it carries
233
+ * @returns {string} The normalized reason key
234
+ */
235
+ function reasonOnLine(line, marker) {
236
+ const at = line.indexOf(marker);
237
+ if (at === -1) return "";
238
+ const tail = line.slice(at + marker.length).split("-->")[0];
239
+ const key = tail.indexOf(REASON_KEY);
240
+ if (key === -1) return "";
241
+ return normalizeReason(tail.slice(key + REASON_KEY.length));
242
+ }
243
+
244
+ /**
245
+ * The reason keys of every hold a body DECLARES, deduplicated.
246
+ *
247
+ * Reuses the precision rule rather than restating it, so a body that merely
248
+ * discusses the marker yields no holds here for the same reason it reads as
249
+ * unheld in {@link bodyDeclaresHold}.
250
+ * @param {unknown} body - The item body
251
+ * @returns {string[]} One key per declared hold
252
+ */
253
+ export function humanGateHolds(body) {
254
+ const keys = declarativeText(body)
255
+ .split("\n")
256
+ .filter(line => declaresOnLine(line))
257
+ .map(line => reasonOnLine(line, HUMAN_GATE_MARKER));
258
+ return [...new Set(keys)];
259
+ }
260
+
261
+ /**
262
+ * The reason keys every recorded release discharges, deduplicated.
263
+ *
264
+ * Accepts comment bodies as strings or as `{ body }` objects, because the three
265
+ * vendor readers return both shapes and a release that a vendor's payload shape
266
+ * could hide would be a release path that exists on one tracker.
267
+ * @param {unknown} comments - Comment bodies, any order
268
+ * @returns {string[]} One key per recorded release
269
+ */
270
+ export function humanGateReleases(comments) {
271
+ const bodies = (Array.isArray(comments) ? comments : []).map(entry =>
272
+ typeof entry === "string" ? entry : (entry?.body ?? "")
273
+ );
274
+ const keys = bodies.flatMap(body =>
275
+ declarativeText(body)
276
+ .split("\n")
277
+ .filter(line => declaresOnLine(line, HUMAN_GATE_RELEASE_MARKER))
278
+ .map(line => reasonOnLine(line, HUMAN_GATE_RELEASE_MARKER))
279
+ );
280
+ return [...new Set(keys)];
281
+ }
282
+
283
+ /**
284
+ * Whether every hold on this item has a release recorded against it.
285
+ *
286
+ * Matching is per-reason on purpose. A blanket "any release clears everything"
287
+ * rule would make a hold declared AFTER a release be born discharged, which is
288
+ * the unsafe direction — the one this fix must not introduce while correcting
289
+ * the safe one. An item with no declared body hold is discharged only by a
290
+ * keyless release, which is the form that pairs with a label-only hold.
291
+ * @param {{ body?: unknown, comments?: unknown }} input - The item's surfaces
292
+ * @returns {boolean} True when nothing is left outstanding
293
+ */
294
+ export function humanGateDischarged(input = {}) {
295
+ const released = new Set(humanGateReleases(input.comments));
296
+ const holds = humanGateHolds(input.body);
297
+ if (holds.length === 0) return released.has("");
298
+ return holds.every(reason => released.has(reason));
299
+ }
300
+
301
+ /**
302
+ * The full held-ness verdict for an item, on every surface that carries a hold.
303
+ *
304
+ * Split out of {@link isHumanGated} because the release path needs to know more
305
+ * than yes-or-no: whether a hold existed at all, which reasons are still
306
+ * outstanding, and whether the durable label needs removing. A caller that only
307
+ * wants the boolean still gets exactly the boolean.
308
+ *
309
+ * The label is treated as a MIRROR of a declared body hold, never as a second
310
+ * independent hold, because that is how it is written: the vendor writers stamp
311
+ * both surfaces for one hold, and `planHumanGateReconciliation` adds the label
312
+ * to an item already carrying the marker. Counting it separately would leave
313
+ * every mirrored hold permanently outstanding under a keyed release — the exact
314
+ * defect this function exists to end, one layer down. Only when the body
315
+ * declares nothing does the label stand as a hold in its own right, and then it
316
+ * is a keyless one.
317
+ *
318
+ * Fails CLOSED on an unreadable discharge: with no comments supplied there are
319
+ * no releases, so a held item stays held. Holding a gate that may be stale
320
+ * beats releasing one that is not, and every caller that never passes comments
321
+ * keeps its current behaviour by construction.
322
+ * @param {{ labels?: unknown, body?: unknown, comments?: unknown, humanNeededLabel?: unknown }} input - Candidate surfaces
323
+ * @returns {{ held: boolean, reason: string, declared: number, outstanding: string[], released: string[], labelPresent: boolean }} Held-ness verdict
324
+ */
325
+ export function humanGateVerdict(input = {}) {
326
+ const configured = String(
327
+ input.humanNeededLabel ?? DEFAULT_HUMAN_NEEDED_LABEL
328
+ )
329
+ .trim()
330
+ .toLowerCase();
331
+ const labelPresent =
332
+ configured.length > 0 && normalizeLabels(input.labels).includes(configured);
333
+ const released = humanGateReleases(input.comments);
334
+ const releasedSet = new Set(released);
335
+ const holds = humanGateHolds(input.body);
336
+ const outstanding = holds.filter(reason => !releasedSet.has(reason));
337
+ const base = {
338
+ declared: holds.length,
339
+ released,
340
+ labelPresent,
341
+ };
342
+
343
+ if (outstanding.length > 0) {
344
+ return { held: true, reason: "hold-outstanding", outstanding, ...base };
345
+ }
346
+ if (holds.length > 0) {
347
+ return { held: false, reason: "hold-released", outstanding: [], ...base };
348
+ }
349
+ if (labelPresent) {
350
+ return releasedSet.has("")
351
+ ? { held: false, reason: "hold-released", outstanding: [], ...base }
352
+ : { held: true, reason: "hold-outstanding", outstanding: [""], ...base };
353
+ }
354
+
355
+ return { held: false, reason: "no-hold", outstanding: [], ...base };
356
+ }
357
+
177
358
  /**
178
359
  * Whether an item is held for a person, on either surface.
179
360
  *
@@ -185,23 +366,17 @@ export function bodyDeclaresHold(body) {
185
366
  * A path that read labels alone judged such an item unheld and promoted it —
186
367
  * which is #3805, and is why this is a shared function rather than a shared
187
368
  * convention.
188
- * @param {{ labels?: unknown, body?: unknown, humanNeededLabel?: unknown }} input - Candidate surfaces
369
+ *
370
+ * It is also the only place a hold ENDS, which is why the release lives behind
371
+ * the same call rather than beside it: a reader that could see the hold but not
372
+ * its discharge is how the gate became one-way in the first place. Pass the
373
+ * item's comments and a recorded release is honoured; omit them and the item
374
+ * stays held.
375
+ * @param {{ labels?: unknown, body?: unknown, comments?: unknown, humanNeededLabel?: unknown }} input - Candidate surfaces
189
376
  * @returns {boolean} True when a person is holding this item
190
377
  */
191
378
  export function isHumanGated(input) {
192
- const configured = String(
193
- input.humanNeededLabel ?? DEFAULT_HUMAN_NEEDED_LABEL
194
- )
195
- .trim()
196
- .toLowerCase();
197
- if (
198
- configured.length > 0 &&
199
- normalizeLabels(input.labels).includes(configured)
200
- ) {
201
- return true;
202
- }
203
-
204
- return bodyDeclaresHold(input.body);
379
+ return humanGateVerdict(input).held;
205
380
  }
206
381
 
207
382
  /**
@@ -247,6 +422,7 @@ function judgeProbe(probe) {
247
422
  * laneType?: unknown
248
423
  * labels?: unknown
249
424
  * body?: unknown
425
+ * comments?: unknown
250
426
  * humanNeededLabel?: unknown
251
427
  * statedBlocker?: unknown
252
428
  * probe?: { discharged?: unknown, evidence?: unknown } | null
@@ -359,7 +535,7 @@ export const HUMAN_GATE_NOTE_MARKER = "<!-- [lisa-human-gate-reconciled] -->";
359
535
  * a parser keyed on the structured field would miss those while appearing to
360
536
  * work on every item that happens to have one — reproducing this defect one
361
537
  * layer down.
362
- * @param {{ labels?: unknown, body?: unknown, humanNeededLabel?: unknown }} input - Candidate surfaces
538
+ * @param {{ labels?: unknown, body?: unknown, comments?: unknown, humanNeededLabel?: unknown }} input - Candidate surfaces
363
539
  * @returns {{ claimable: boolean, reason: string, humanGated: boolean }} Claim verdict
364
540
  */
365
541
  export function classifyReadyCandidate(input = {}) {
@@ -387,6 +563,7 @@ export function classifyReadyCandidate(input = {}) {
387
563
  * @param {{
388
564
  * labels?: unknown
389
565
  * body?: unknown
566
+ * comments?: unknown
390
567
  * humanNeededLabel?: unknown
391
568
  * readyLabel?: unknown
392
569
  * alreadyNotified?: unknown
@@ -429,6 +606,192 @@ export function planHumanGateReconciliation(input = {}) {
429
606
  };
430
607
  }
431
608
 
609
+ /**
610
+ * Marker on the note the release path leaves, so a later cycle recognises its
611
+ * own work instead of commenting again.
612
+ *
613
+ * A superstring of neither declaration marker, for the reason
614
+ * {@link HUMAN_GATE_RELEASE_MARKER} gives: a note ABOUT a release must not read
615
+ * as a release, or the note announcing one item's discharge would discharge
616
+ * every item it names.
617
+ */
618
+ export const HUMAN_GATE_RELEASE_NOTE_MARKER =
619
+ "<!-- [lisa-human-gate-released] -->";
620
+
621
+ /**
622
+ * The one sentence every hold note gives an operator to END the hold.
623
+ *
624
+ * One constant, two notes, because the instruction IS the release path as far
625
+ * as the person reading it is concerned. Two copies of it drift, and a drifted
626
+ * instruction sends someone to a mechanism that no longer exists — which is
627
+ * worse than the prose it replaced, since that at least described something
628
+ * real. It names a comment because the alternative it replaces — "remove the
629
+ * hold note from the description" — asks for a whole-body rewrite the tooling
630
+ * has no safe way to perform.
631
+ */
632
+ const RESUME_INSTRUCTION = [
633
+ "- To resume it: leave a comment on this item that starts with ",
634
+ HUMAN_GATE_RELEASE_MARKER,
635
+ " and repeats the reason the hold in the description names. The next sweep ",
636
+ "puts it back in the queue on its own. Nothing else has to change — do not ",
637
+ "edit the description, and do not move it by hand.",
638
+ ].join("");
639
+
640
+ /**
641
+ * Plan the state repair for an item whose hold has been discharged.
642
+ *
643
+ * The exact inverse of {@link planHumanGateReconciliation}, and deliberately
644
+ * the same shape: that one takes the ready role away and adds the marker label,
645
+ * this one takes the marker label away and puts the ready role back. A state
646
+ * change with no inverse is the defect class this pair exists to close
647
+ * (`state-changes-without-inverses`), and an inverse that is merely *possible*
648
+ * is not one — it has to sit on a path something actually runs, which is why
649
+ * this is a planner the intake sweeps call rather than an instruction to an
650
+ * operator.
651
+ *
652
+ * Two refusals are load-bearing:
653
+ *
654
+ * - **A still-held item plans nothing.** Every outstanding reason holds the
655
+ * whole item, so an item whose second hold has not been answered is not
656
+ * half-released.
657
+ * - **An item that was never held plans nothing.** Without that, this would be
658
+ * a path that adds the build-ready role to arbitrary items — a promotion
659
+ * mechanism wearing a release mechanism's name.
660
+ *
661
+ * The ready role is restored only when the item carries none of the configured
662
+ * lifecycle labels, mirroring {@link planLabelNormalization}. An item that was
663
+ * held and has since reached a terminal lane must not be dragged back to the
664
+ * queue by its own release.
665
+ *
666
+ * Idempotent by state rather than by memory: an item already unmarked and
667
+ * already back in the queue needs nothing, so asking twice yields no second
668
+ * mutation.
669
+ * @param {{
670
+ * labels?: unknown
671
+ * body?: unknown
672
+ * comments?: unknown
673
+ * humanNeededLabel?: unknown
674
+ * readyLabel?: unknown
675
+ * lifecycleLabels?: unknown
676
+ * alreadyNotified?: unknown
677
+ * }} input - Candidate surfaces plus the configured lane vocabulary
678
+ * @returns {{ released: boolean, reason: string, discharged: string[], actions: { removeHumanNeededLabel: boolean, addReadyLabel: string | null, comment: boolean } }} Release plan
679
+ */
680
+ export function planHumanGateRelease(input = {}) {
681
+ const idle = Object.freeze({
682
+ removeHumanNeededLabel: false,
683
+ addReadyLabel: null,
684
+ comment: false,
685
+ });
686
+ const verdict = humanGateVerdict(input);
687
+ if (verdict.held) {
688
+ return {
689
+ released: false,
690
+ reason: "hold-outstanding",
691
+ discharged: [],
692
+ actions: idle,
693
+ };
694
+ }
695
+ if (verdict.declared === 0 && !verdict.labelPresent) {
696
+ return {
697
+ released: false,
698
+ reason: "no-hold",
699
+ discharged: [],
700
+ actions: idle,
701
+ };
702
+ }
703
+
704
+ const needed = String(input.humanNeededLabel ?? DEFAULT_HUMAN_NEEDED_LABEL)
705
+ .trim()
706
+ .toLowerCase();
707
+ // The marker label is excluded from the lane test on both sides. It is one of
708
+ // the configured lifecycle labels AND it is the label this plan removes, so
709
+ // counting it would let the hold's own marker prove the item is already in a
710
+ // lane and veto the restoration — the release would clear the flag and leave
711
+ // the item in no queue at all, which is the original defect with one fewer
712
+ // symptom.
713
+ const labels = normalizeLabels(input.labels).filter(name => name !== needed);
714
+ const lifecycle = normalizeLabels(input.lifecycleLabels).filter(
715
+ name => name !== needed
716
+ );
717
+ const ready = trimmedString(input.readyLabel);
718
+ const inLifecycle = lifecycle.some(name => labels.includes(name));
719
+ const addReadyLabel =
720
+ ready.length > 0 && !inLifecycle && !labels.includes(ready.toLowerCase())
721
+ ? ready
722
+ : null;
723
+ const actions = {
724
+ removeHumanNeededLabel: verdict.labelPresent,
725
+ addReadyLabel,
726
+ comment:
727
+ input.alreadyNotified !== true &&
728
+ (verdict.labelPresent || addReadyLabel !== null),
729
+ };
730
+ const changed =
731
+ actions.removeHumanNeededLabel || addReadyLabel !== null || actions.comment;
732
+
733
+ return {
734
+ released: true,
735
+ reason: changed ? "release" : "already-released",
736
+ discharged: verdict.released,
737
+ actions,
738
+ };
739
+ }
740
+
741
+ /**
742
+ * Render the discharge record a person or a skill leaves to END a hold.
743
+ *
744
+ * Generated rather than retyped, because the release names the hold it ends by
745
+ * its reason and a mistyped reason discharges nothing while looking like it
746
+ * did. The reason is echoed verbatim from the hold, so the pairing is visible
747
+ * to a reader as well as to the matcher.
748
+ * @param {{ reason?: unknown, decidedBy?: unknown, decision?: unknown }} discharge - What was decided, and by whom
749
+ * @returns {string} Comment body carrying the release marker
750
+ */
751
+ export function formatHumanGateRelease(discharge = {}) {
752
+ const reason = trimmedString(discharge.reason);
753
+ const declaration =
754
+ reason.length > 0
755
+ ? `${HUMAN_GATE_RELEASE_MARKER} ${REASON_KEY}${reason}`
756
+ : HUMAN_GATE_RELEASE_MARKER;
757
+ const lines = [declaration, "", "**The hold on this item is lifted.**", ""];
758
+ lines.push(
759
+ `- What was being asked: ${reason.length > 0 ? reason : "(the hold named no reason)"}`
760
+ );
761
+ const decision = trimmedString(discharge.decision);
762
+ if (decision.length > 0) lines.push(`- What was decided: ${decision}`);
763
+ const decidedBy = trimmedString(discharge.decidedBy);
764
+ if (decidedBy.length > 0) lines.push(`- Decided by: ${decidedBy}`);
765
+ lines.push(
766
+ "- What happens next: this item goes back into the queue that agents build from."
767
+ );
768
+
769
+ return lines.join("\n");
770
+ }
771
+
772
+ /**
773
+ * Render the notice for an item this sweep released.
774
+ *
775
+ * Distinct from {@link formatHumanGateNote} for the same reason
776
+ * {@link formatNormalizationHoldNote} is: the two report opposite events, and
777
+ * reusing one to report the other tells an operator the wrong thing happened.
778
+ * @returns {string} Comment body, carrying the marker that keeps a re-run quiet
779
+ */
780
+ export function formatHumanGateReleaseNote() {
781
+ return [
782
+ "**Back in the queue: the hold was answered**",
783
+ "",
784
+ "- What was found: this item was being held for a person, and the " +
785
+ "decision it was waiting for has since been recorded on it.",
786
+ "- What changed: the hold flag has been taken off and the item has been " +
787
+ "put back into the queue that agents build from.",
788
+ "- Nothing was rewritten: the description is exactly as it was, hold note " +
789
+ "and all, so the history of why it was held stays readable.",
790
+ "",
791
+ HUMAN_GATE_RELEASE_NOTE_MARKER,
792
+ ].join("\n");
793
+ }
794
+
432
795
  /**
433
796
  * Marker on the note the normalization sweep leaves, so a later cycle
434
797
  * recognises its own work instead of commenting again.
@@ -467,6 +830,7 @@ export const NORMALIZATION_HOLD_NOTE_MARKER =
467
830
  * @param {{
468
831
  * labels?: unknown
469
832
  * body?: unknown
833
+ * comments?: unknown
470
834
  * humanNeededLabel?: unknown
471
835
  * lifecycleLabels?: unknown
472
836
  * readyLabel?: unknown
@@ -529,8 +893,7 @@ export function formatNormalizationHoldNote() {
529
893
  "it was not put into the queue that agents work from.",
530
894
  "- What changed: nothing at all. It was left exactly as it is, and " +
531
895
  "nothing will pick it up on its own.",
532
- "- To resume it: remove the hold note from the description, then put it " +
533
- "into the build queue.",
896
+ RESUME_INSTRUCTION,
534
897
  "",
535
898
  NORMALIZATION_HOLD_NOTE_MARKER,
536
899
  ].join("\n");
@@ -553,8 +916,7 @@ export function formatHumanGateNote() {
553
916
  "been picked up and built anyway.",
554
917
  "- What changed: it has been taken out of that queue and flagged as " +
555
918
  "needing a person, so nothing will build it automatically.",
556
- "- To resume it: remove the hold note from the description and put it " +
557
- "back in the build queue.",
919
+ RESUME_INSTRUCTION,
558
920
  "",
559
921
  HUMAN_GATE_NOTE_MARKER,
560
922
  ].join("\n");
@@ -596,3 +958,22 @@ export function summarizeHumanGateHolds(held = []) {
596
958
  if (names.length === 0) return "Held for a person: none.";
597
959
  return `Held for a person (${String(names.length)}): ${names.join(", ")}.`;
598
960
  }
961
+
962
+ /**
963
+ * Render the cycle-summary line naming what was RELEASED.
964
+ *
965
+ * The counterpart of {@link summarizeHumanGateHolds}, printed even when it is
966
+ * zero. Without it a release path that has stopped working is indistinguishable
967
+ * from a cycle where nothing needed releasing — which is exactly how the
968
+ * missing inverse went unnoticed for as long as it did, and a fix that cannot
969
+ * be observed to fire is a fix nobody can tell has regressed.
970
+ * @param {readonly unknown[]} released - Item references released this cycle
971
+ * @returns {string} One summary line
972
+ */
973
+ export function summarizeHumanGateReleases(released = []) {
974
+ const names = (Array.isArray(released) ? released : [])
975
+ .map(item => trimmedString(item))
976
+ .filter(name => name.length > 0);
977
+ if (names.length === 0) return "Released back to the queue: none.";
978
+ return `Released back to the queue (${String(names.length)}): ${names.join(", ")}.`;
979
+ }