@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,709 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * check-guard-parity-notes — refuse a guard's parity note that claims a port is
4
+ * missing when that port is present in the tree (CodySwannGT/lisa#3908).
5
+ *
6
+ * ## The defect
7
+ *
8
+ * `block-managed-file-edits.sh` carried a note headed "Parity gap, recorded
9
+ * rather than silently dropped" saying the guard had "no Antigravity, Codex or
10
+ * OpenCode port". Two of those three shipped afterwards; the note did not
11
+ * change, because nothing read it. Its neighbour on the very next line — the
12
+ * machine-checked `lisa-guard-capabilities:` declaration — stayed honest over
13
+ * the same period, and the only difference between them is that something
14
+ * reads one of them.
15
+ *
16
+ * A stale gap note is worse than no note, and the reason is the note's own
17
+ * stated purpose: it exists so a reader treats the gap as KNOWN and stops
18
+ * investigating. An overstated one sends that reader looking for three missing
19
+ * ports, they find two present, and they have no way to tell which third is
20
+ * real without redoing the measurement the note was written to spare them.
21
+ *
22
+ * ## What this refuses, and what it deliberately does not
23
+ *
24
+ * A CLAIM is refused when the surface it names has a port. Nothing else about
25
+ * a note is this check's business:
26
+ *
27
+ * refused the note says a surface has no port and the port exists. The
28
+ * note overstates the gap; the tree is the authority.
29
+ * accepted the note says a surface has no port and none exists. A genuine
30
+ * gap stays recordable — that is the whole point of the note, and
31
+ * a check that demanded its deletion would delete the record
32
+ * along with the drift.
33
+ * unread a guard with no claim at all. Absence of a note is not a
34
+ * finding: most guards have complete parity and say nothing.
35
+ *
36
+ * ## Surface presence is DERIVED, never declared
37
+ *
38
+ * The one thing that must not be hand-maintained here is the answer, because a
39
+ * second hand-written enumeration of which ports exist would go stale exactly
40
+ * the way the first one did. So presence is resolved from the files: a guard
41
+ * `<id>` has an Antigravity port when `plugins/src/base/hooks/<id>.agy.sh`
42
+ * exists, an OpenCode port when `src/opencode/plugin-templates/lisa-<id>.ts`
43
+ * does, and so on per `SURFACES`. Add a surface to Lisa and the entry added
44
+ * here is a path shape, not a list of which guards have it.
45
+ *
46
+ * ## A path shape alone measures the wrong thing on Codex
47
+ *
48
+ * A file named for the guard is sufficient evidence that a surface carries it;
49
+ * it is not NECESSARY. Codex is the surface where the difference is real, and
50
+ * the first version of this file got Codex wrong for exactly that reason
51
+ * (CodySwannGT/lisa#3750).
52
+ *
53
+ * `src/codex/scripts/<id>.sh` is the retired channel. `src/codex/hooks-installer.ts`
54
+ * says so in its own opening remark — the project overlay moved to
55
+ * plugin-bundled hooks plus one repository-owned dispatcher, and that module
56
+ * "remains for migration coverage of the retired linked script/rule layout".
57
+ * Two channels deliver a guard to Codex today, and neither puts a file in that
58
+ * directory:
59
+ *
60
+ * - `scripts/lisa-enforcement-fallback.sh`, whose roster names the guards it
61
+ * dispatches. `src/codex/enforcement-fallback-installer.ts` registers it on
62
+ * `PreToolUse` for `Bash|Edit|Write|apply_patch`.
63
+ * - `plugins/lisa/.codex-plugin/hooks.json`, the Codex plugin manifest, whose
64
+ * hook commands name the guards Codex runs directly.
65
+ *
66
+ * So a `registries` entry is read the same way a `ports` entry is — from the
67
+ * file, never from a declaration — but it answers "does this file REGISTER the
68
+ * guard" rather than "is there a file named for it". Both readers are
69
+ * STRUCTURAL: the roster reader parses the `for guard in ... ; do` list and the
70
+ * manifest reader parses JSON. Neither scans for the guard's name in prose,
71
+ * because the dispatcher's own header lists guard names in a sentence and a
72
+ * substring sweep would count that sentence as registration.
73
+ *
74
+ * ## The claim grammar
75
+ *
76
+ * Bounded on purpose. Inside one comment block, between a negation (`no`,
77
+ * `lacks`, `missing`, `without`) and the word `port`/`ports`, every word must
78
+ * be a surface name or an ordinary connective. Anything else ends the
79
+ * candidate. So this is a claim:
80
+ *
81
+ * This guard has no Antigravity, Codex or OpenCode port
82
+ *
83
+ * and a sentence that merely mentions a surface and, separately, the word
84
+ * "port" is not. Widening the grammar to catch every phrasing would make the
85
+ * check's own reach unreviewable; narrowing it is why a finding it does report
86
+ * can be trusted without reading the source. What the grammar cannot see is
87
+ * declared in `BLIND_SPOTS` and printed on every run.
88
+ *
89
+ * Determinism: Node built-ins only, no network, no clock, no `Math.random`.
90
+ * The scanned root is a parameter so the suite can point it at a fixture tree
91
+ * holding a known offender.
92
+ *
93
+ * CLI:
94
+ * node scripts/check-guard-parity-notes.mjs [--json] [root]
95
+ *
96
+ * Exit codes (mirroring the sibling check-* scripts):
97
+ * 0 — guard sources were read and no claim contradicts the tree.
98
+ * 1 — >=1 finding.
99
+ * 2 — operational error: unknown flag, or ZERO guard sources read.
100
+ *
101
+ * @module scripts/check-guard-parity-notes
102
+ */
103
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
104
+ import path from "node:path";
105
+ import process from "node:process";
106
+
107
+ import { invokedAsScript } from "./lib/invoked-as-script.mjs";
108
+
109
+ /**
110
+ * One refused claim.
111
+ * @typedef {object} ParityFinding
112
+ * @property {string} file - Repo-relative path of the note.
113
+ * @property {number} line - One-based line the claim starts on.
114
+ * @property {string} guard - Guard id the note belongs to.
115
+ * @property {string} surface - Surface the note says has no port.
116
+ * @property {string} evidence - Repo-relative path that contradicts the claim.
117
+ * @property {string} claim - The claim as written.
118
+ */
119
+
120
+ /**
121
+ * A completed sweep.
122
+ * @typedef {object} ParityReport
123
+ * @property {number} files - Guard sources read.
124
+ * @property {number} claims - Parity claims recognised.
125
+ * @property {ParityFinding[]} findings - Claims the tree contradicts.
126
+ */
127
+
128
+ /**
129
+ * The coding-agent surfaces Lisa supports, and where a port for each lives.
130
+ *
131
+ * `aliases` are the single words a note may use for the surface; `ports` are
132
+ * the candidate paths, any one of which existing means the surface carries the
133
+ * guard. Both are matched case-insensitively on the alias side and exactly on
134
+ * the path side.
135
+ *
136
+ * `registries` are files that REGISTER a guard without being named for it —
137
+ * see the module remark above. Each names a `reader` from
138
+ * {@link REGISTRY_READERS}, which returns the guard ids that file registers.
139
+ */
140
+ export const SURFACES = Object.freeze([
141
+ Object.freeze({
142
+ id: "claude",
143
+ aliases: Object.freeze(["claude"]),
144
+ ports: Object.freeze([
145
+ "plugins/src/base/hooks/{id}.sh",
146
+ "plugins/src/base/hooks/{id}.mjs",
147
+ ]),
148
+ }),
149
+ Object.freeze({
150
+ id: "codex",
151
+ aliases: Object.freeze(["codex"]),
152
+ ports: Object.freeze([
153
+ "src/codex/scripts/{id}.sh",
154
+ "src/codex/scripts/{id}.mjs",
155
+ ]),
156
+ registries: Object.freeze([
157
+ Object.freeze({
158
+ path: "scripts/lisa-enforcement-fallback.sh",
159
+ reader: "dispatcher-roster",
160
+ }),
161
+ Object.freeze({
162
+ path: "plugins/lisa/.codex-plugin/hooks.json",
163
+ reader: "codex-hooks-manifest",
164
+ }),
165
+ ]),
166
+ }),
167
+ Object.freeze({
168
+ id: "cursor",
169
+ aliases: Object.freeze(["cursor"]),
170
+ ports: Object.freeze([
171
+ "plugins/lisa-cursor/hooks/{id}.sh",
172
+ "plugins/lisa-cursor/hooks/{id}.mjs",
173
+ ]),
174
+ }),
175
+ Object.freeze({
176
+ id: "opencode",
177
+ aliases: Object.freeze(["opencode"]),
178
+ ports: Object.freeze(["src/opencode/plugin-templates/lisa-{id}.ts"]),
179
+ }),
180
+ Object.freeze({
181
+ id: "antigravity",
182
+ aliases: Object.freeze(["antigravity", "agy"]),
183
+ ports: Object.freeze(["plugins/src/base/hooks/{id}.agy.sh"]),
184
+ }),
185
+ Object.freeze({
186
+ id: "copilot",
187
+ aliases: Object.freeze(["copilot"]),
188
+ ports: Object.freeze([
189
+ "plugins/lisa-copilot/hooks/{id}.sh",
190
+ "plugins/lisa-copilot/hooks/{id}.mjs",
191
+ ]),
192
+ }),
193
+ ]);
194
+
195
+ /**
196
+ * Directories holding guard SOURCES.
197
+ *
198
+ * Generated copies — the per-plugin `hooks/` roots and the shipped host guard
199
+ * directory — are excluded on purpose. They inherit their note verbatim from a
200
+ * source read here, so scanning them would report the same drift five more
201
+ * times and make the finding count meaningless.
202
+ */
203
+ export const SCANNED_DIRS = Object.freeze([
204
+ "plugins/src/base/hooks",
205
+ "src/codex/scripts",
206
+ "src/opencode/plugin-templates",
207
+ ]);
208
+
209
+ /** What this sweep does not look at, printed on every run. */
210
+ export const BLIND_SPOTS = Object.freeze([
211
+ "generated copies of a guard — they inherit the source note scanned here",
212
+ "gaps stated outside the claim grammar (see the module remarks)",
213
+ "understated gaps: a note that omits a genuinely missing surface passes",
214
+ "whether a present port file is actually registered for its agent",
215
+ "a delivery channel absent from SURFACES — presence is only as complete as the ports and registries named there",
216
+ ]);
217
+
218
+ /** File extensions a guard source may use. */
219
+ const GUARD_EXTENSIONS = Object.freeze([".sh", ".mjs", ".ts", ".py"]);
220
+
221
+ /** Runs of whitespace, for splitting a roster or a command line. */
222
+ const WHITESPACE = /\s+/u;
223
+
224
+ /** The shape every guard id has; anything else is not one. */
225
+ const GUARD_ID_SHAPE = /^[a-z0-9][a-z0-9-]*$/u;
226
+
227
+ /** Shell words in the dispatcher roster that are syntax, not guard names. */
228
+ const ROSTER_KEYWORDS = new Set(["", "\\", "do", "for", "guard", "in"]);
229
+
230
+ /** Words that open a claim. */
231
+ const NEGATIONS = new Set(["no", "lacks", "lack", "missing", "without"]);
232
+
233
+ /** Words that close a claim. */
234
+ const CLAIM_TARGETS = new Set(["port", "ports"]);
235
+
236
+ /**
237
+ * Words permitted between the negation and the target.
238
+ *
239
+ * Connectives and the nouns a parity sentence uses about itself. Anything
240
+ * outside this set ends the candidate, which is what keeps the grammar from
241
+ * spanning an unrelated clause.
242
+ */
243
+ const FILLER = new Set([
244
+ "a",
245
+ "an",
246
+ "and",
247
+ "any",
248
+ "code",
249
+ "dedicated",
250
+ "guard",
251
+ "has",
252
+ "have",
253
+ "hook",
254
+ "hooks",
255
+ "its",
256
+ "native",
257
+ "neither",
258
+ "no",
259
+ "nor",
260
+ "of",
261
+ "or",
262
+ "own",
263
+ "separate",
264
+ "still",
265
+ "the",
266
+ "this",
267
+ "yet",
268
+ ]);
269
+
270
+ /** Characters that end a candidate claim outright. */
271
+ const TERMINATORS = new Set([".", ";", ":", "—", "–", "(", ")", "`", '"']);
272
+
273
+ /** Words and terminators, in order, with their offsets. */
274
+ const TOKEN_PATTERN = /[A-Za-z][A-Za-z-]*|[.;:—–()`"]/gu;
275
+
276
+ /** Surface id for each alias a note may write. */
277
+ const SURFACE_BY_ALIAS = new Map(
278
+ SURFACES.flatMap(surface => surface.aliases.map(alias => [alias, surface.id]))
279
+ );
280
+
281
+ /**
282
+ * The guard a source file belongs to.
283
+ *
284
+ * Every surface spells the same guard differently — an Antigravity sibling
285
+ * carries `.agy` before its extension, an OpenCode template carries a `lisa-`
286
+ * prefix — so the id is what lets one note be checked against every surface.
287
+ * @param {string} relative - Repo-relative path, forward-slashed.
288
+ * @returns {string | undefined} The guard id, or nothing for a non-guard file.
289
+ */
290
+ export function guardIdFor(relative) {
291
+ const base = path.posix.basename(relative);
292
+ const extension = GUARD_EXTENSIONS.find(candidate =>
293
+ base.endsWith(candidate)
294
+ );
295
+ if (!extension) return undefined;
296
+ const stem = base.slice(0, -extension.length);
297
+ const undotted = stem.endsWith(".agy") ? stem.slice(0, -".agy".length) : stem;
298
+ if (path.posix.dirname(relative) !== "src/opencode/plugin-templates") {
299
+ return undotted;
300
+ }
301
+ return undotted.startsWith("lisa-")
302
+ ? undotted.slice("lisa-".length)
303
+ : undefined;
304
+ }
305
+
306
+ /**
307
+ * The port file that proves a surface carries a guard.
308
+ * @param {string} repoRoot - Absolute path of the tree being scanned.
309
+ * @param {string} guardId - Guard id from {@link guardIdFor}.
310
+ * @param {string} surfaceId - Surface id from {@link SURFACES}.
311
+ * @returns {string | undefined} Repo-relative path, or nothing if absent.
312
+ */
313
+ export function portFor(repoRoot, guardId, surfaceId) {
314
+ const surface = SURFACES.find(candidate => candidate.id === surfaceId);
315
+ if (!surface) return undefined;
316
+ return surface.ports
317
+ .map(template => template.replace("{id}", guardId))
318
+ .find(relative => existsSync(path.join(repoRoot, relative)));
319
+ }
320
+
321
+ /**
322
+ * Guard ids the enforcement dispatcher runs.
323
+ *
324
+ * Read from the `for guard in ... ; do` roster and from nowhere else. The
325
+ * dispatcher's header also lists guard names in an English sentence, so a
326
+ * substring sweep of this file would report every guard it MENTIONS as one it
327
+ * RUNS — the sentinel-versus-talk-about-the-sentinel mistake, and the reason
328
+ * this reader is a parse rather than a search.
329
+ * @param {string} source - Full contents of the dispatcher.
330
+ * @returns {Set<string>} Guard ids in the roster; empty when there is none.
331
+ */
332
+ export function dispatcherRoster(source) {
333
+ const names = new Set();
334
+ const lines = source.split("\n");
335
+ let index = lines.findIndex(line =>
336
+ line.trimStart().startsWith("for guard in")
337
+ );
338
+ if (index === -1) return names;
339
+ let roster = "";
340
+ let open = true;
341
+ while (open && index < lines.length) {
342
+ const line = lines[index];
343
+ roster += ` ${line}`;
344
+ open = !line.includes("; do");
345
+ index += 1;
346
+ }
347
+ // Still open at end of file means the roster was never terminated, which is
348
+ // a malformed dispatcher rather than an empty one. Report nothing: a partial
349
+ // parse that answered anyway would claim registration it had not read.
350
+ if (open) return new Set();
351
+ for (const raw of roster.split(WHITESPACE)) {
352
+ const word = raw.endsWith(";") ? raw.slice(0, -1) : raw;
353
+ if (ROSTER_KEYWORDS.has(word) || !GUARD_ID_SHAPE.test(word)) continue;
354
+ names.add(word);
355
+ }
356
+ return names;
357
+ }
358
+
359
+ /**
360
+ * Guard ids the Codex plugin manifest registers.
361
+ *
362
+ * Structural: the JSON is parsed and every hook `command` is reduced to the
363
+ * basename of the file it runs. A command carrying arguments (`… --hook`)
364
+ * still resolves to its script, and anything unparseable registers nothing.
365
+ * @param {string} source - Full contents of the manifest.
366
+ * @returns {Set<string>} Guard ids the manifest registers.
367
+ */
368
+ export function codexHooksManifest(source) {
369
+ const names = new Set();
370
+ let parsed;
371
+ try {
372
+ parsed = JSON.parse(source);
373
+ } catch {
374
+ // probe-direction: fail-closed — an unreadable manifest registers nothing,
375
+ // so a genuine gap note keeps passing rather than being refused on the
376
+ // strength of a file this reader could not read.
377
+ return names;
378
+ }
379
+ const events = parsed?.hooks;
380
+ if (events === null || typeof events !== "object") return names;
381
+ for (const entries of Object.values(events)) {
382
+ if (!Array.isArray(entries)) continue;
383
+ for (const entry of entries) {
384
+ for (const hook of Array.isArray(entry?.hooks) ? entry.hooks : []) {
385
+ const id = guardIdOfCommand(hook?.command);
386
+ if (id !== undefined) names.add(id);
387
+ }
388
+ }
389
+ }
390
+ return names;
391
+ }
392
+
393
+ /**
394
+ * The guard a hook command runs.
395
+ * @param {unknown} command - A manifest hook's `command` field.
396
+ * @returns {string | undefined} The guard id, or nothing.
397
+ */
398
+ function guardIdOfCommand(command) {
399
+ if (typeof command !== "string") return undefined;
400
+ const [word] = command.split(WHITESPACE);
401
+ const base = word.slice(word.lastIndexOf("/") + 1);
402
+ const extension = GUARD_EXTENSIONS.find(candidate =>
403
+ base.endsWith(candidate)
404
+ );
405
+ if (extension === undefined) return undefined;
406
+ const stem = base.slice(0, -extension.length);
407
+ return GUARD_ID_SHAPE.test(stem) ? stem : undefined;
408
+ }
409
+
410
+ /** How each `registries` entry's `reader` is resolved. */
411
+ export const REGISTRY_READERS = Object.freeze({
412
+ "codex-hooks-manifest": codexHooksManifest,
413
+ "dispatcher-roster": dispatcherRoster,
414
+ });
415
+
416
+ /**
417
+ * The registration file that proves a surface RUNS a guard.
418
+ * @param {string} repoRoot - Absolute path of the tree being scanned.
419
+ * @param {string} guardId - Guard id from {@link guardIdFor}.
420
+ * @param {string} surfaceId - Surface id from {@link SURFACES}.
421
+ * @returns {string | undefined} Repo-relative path, or nothing if none does.
422
+ */
423
+ export function registryFor(repoRoot, guardId, surfaceId) {
424
+ const surface = SURFACES.find(candidate => candidate.id === surfaceId);
425
+ for (const registry of surface?.registries ?? []) {
426
+ const absolute = path.join(repoRoot, registry.path);
427
+ if (!existsSync(absolute)) continue;
428
+ const read = REGISTRY_READERS[registry.reader];
429
+ if (read !== undefined && read(readFileSync(absolute, "utf8")).has(guardId))
430
+ return registry.path;
431
+ }
432
+ return undefined;
433
+ }
434
+
435
+ /**
436
+ * The file that contradicts a claim of no port on one surface.
437
+ *
438
+ * A port file OR a registration; either is proof the surface carries the
439
+ * guard, and a note claiming otherwise is refused on whichever is found.
440
+ * @param {string} repoRoot - Absolute path of the tree being scanned.
441
+ * @param {string} guardId - Guard id from {@link guardIdFor}.
442
+ * @param {string} surfaceId - Surface id from {@link SURFACES}.
443
+ * @returns {string | undefined} Repo-relative path, or nothing.
444
+ */
445
+ export function evidenceFor(repoRoot, guardId, surfaceId) {
446
+ return (
447
+ portFor(repoRoot, guardId, surfaceId) ??
448
+ registryFor(repoRoot, guardId, surfaceId)
449
+ );
450
+ }
451
+
452
+ /**
453
+ * Consecutive comment lines, joined into blocks.
454
+ *
455
+ * Only comments are read. A guard's body is code, and a string literal in it
456
+ * that happens to read like a parity note is not one.
457
+ * Each block records where every contributing line begins inside the joined
458
+ * text, so a claim can be reported at the line it is written on rather than at
459
+ * the top of a header that may be seventy lines above it.
460
+ * @param {string} source - Full file contents.
461
+ * @returns {{ line: number, text: string, spans: { at: number, line: number }[] }[]}
462
+ * Blocks, in file order.
463
+ */
464
+ export function commentBlocks(source) {
465
+ const blocks = [];
466
+ source.split("\n").forEach((raw, index) => {
467
+ const stripped = commentTextOf(raw);
468
+ if (stripped === undefined) {
469
+ blocks.push(undefined);
470
+ return;
471
+ }
472
+ const previous = blocks.at(-1);
473
+ if (previous === undefined) {
474
+ blocks.push({
475
+ line: index + 1,
476
+ text: stripped,
477
+ spans: [{ at: 0, line: index + 1 }],
478
+ });
479
+ return;
480
+ }
481
+ blocks[blocks.length - 1] = {
482
+ line: previous.line,
483
+ text: `${previous.text} ${stripped}`,
484
+ spans: [
485
+ ...previous.spans,
486
+ { at: previous.text.length + 1, line: index + 1 },
487
+ ],
488
+ };
489
+ });
490
+ return blocks.filter(block => block !== undefined);
491
+ }
492
+
493
+ /**
494
+ * The source line an offset inside a block's joined text came from.
495
+ * @param {{ spans: { at: number, line: number }[], line: number }} block - Block.
496
+ * @param {number} offset - Character offset within the block's joined text.
497
+ * @returns {number} One-based source line.
498
+ */
499
+ export function lineAt(block, offset) {
500
+ return (
501
+ block.spans.filter(span => span.at <= offset).at(-1)?.line ?? block.line
502
+ );
503
+ }
504
+
505
+ /**
506
+ * A comment line's text, with its marker removed.
507
+ * @param {string} raw - One line of source.
508
+ * @returns {string | undefined} The text, or nothing when it is not a comment.
509
+ */
510
+ function commentTextOf(raw) {
511
+ const trimmed = raw.trim();
512
+ if (trimmed.startsWith("#!")) return undefined;
513
+ const marker = ["#", "//", "*/", "/**", "/*", "*"].find(candidate =>
514
+ trimmed.startsWith(candidate)
515
+ );
516
+ return marker === undefined ? undefined : trimmed.slice(marker.length).trim();
517
+ }
518
+
519
+ /**
520
+ * How a claim candidate advances over one token.
521
+ * @param {{ open: boolean, offset: number, named: string[] }} state - Progress.
522
+ * @param {{ text: string, index: number }} token - Word or terminator.
523
+ * @returns {{ state: object, surfaces: string[] | undefined, offset: number }}
524
+ * The next state, plus the surfaces of a claim that just closed.
525
+ */
526
+ function advance(state, token) {
527
+ const closed = { open: false, offset: 0, named: [] };
528
+ const word = token.text.toLowerCase();
529
+ if (TERMINATORS.has(token.text)) return { state: closed };
530
+ if (!state.open) {
531
+ return NEGATIONS.has(word)
532
+ ? { state: { open: true, offset: token.index, named: [] } }
533
+ : { state: closed };
534
+ }
535
+ if (CLAIM_TARGETS.has(word)) {
536
+ const surfaces =
537
+ state.named.length > 0 ? [...new Set(state.named)] : undefined;
538
+ return { state: closed, surfaces, offset: state.offset };
539
+ }
540
+ const surface = SURFACE_BY_ALIAS.get(word);
541
+ if (surface) {
542
+ return { state: { ...state, named: [...state.named, surface] } };
543
+ }
544
+ return FILLER.has(word) ? { state } : { state: closed };
545
+ }
546
+
547
+ /**
548
+ * The parity claims one comment block makes.
549
+ * @param {string} text - The block's joined text.
550
+ * @returns {{ offset: number, surfaces: string[], text: string }[]} Claims.
551
+ */
552
+ export function claimsIn(text) {
553
+ const claims = [];
554
+ let state = { open: false, offset: 0, named: [] };
555
+ for (const match of text.matchAll(TOKEN_PATTERN)) {
556
+ const step = advance(state, { text: match[0], index: match.index });
557
+ state = step.state;
558
+ if (!step.surfaces) continue;
559
+ claims.push({
560
+ offset: step.offset,
561
+ surfaces: step.surfaces,
562
+ text: text.slice(step.offset, match.index + match[0].length),
563
+ });
564
+ }
565
+ return claims;
566
+ }
567
+
568
+ /**
569
+ * Guard source files under one directory of a tree.
570
+ * @param {string} repoRoot - Absolute path of the tree being scanned.
571
+ * @param {string} directory - Repo-relative directory to read.
572
+ * @returns {{ relative: string, guardId: string }[]} Guard sources found.
573
+ */
574
+ function guardsUnder(repoRoot, directory) {
575
+ const absolute = path.join(repoRoot, directory);
576
+ try {
577
+ if (!statSync(absolute).isDirectory()) return [];
578
+ } catch {
579
+ // probe-direction: fail-closed — an unreadable directory contributes no
580
+ // files, so the zero-sources exit-2 rule fires rather than a clean tick.
581
+ return [];
582
+ }
583
+ return readdirSync(absolute, { withFileTypes: true })
584
+ .filter(entry => entry.isFile())
585
+ .map(entry => ({
586
+ relative: `${directory}/${entry.name}`,
587
+ guardId: guardIdFor(`${directory}/${entry.name}`),
588
+ }))
589
+ .filter(guard => guard.guardId !== undefined);
590
+ }
591
+
592
+ /**
593
+ * Read every guard source and refuse the claims the tree contradicts.
594
+ * @param {string} repoRoot - Absolute path of the tree to scan.
595
+ * @param {readonly string[]} [directories] - Repo-relative dirs to read.
596
+ * @returns {ParityReport} The completed sweep.
597
+ */
598
+ export function sweep(repoRoot, directories = SCANNED_DIRS) {
599
+ const report = { files: 0, claims: 0, findings: [] };
600
+ const guards = directories.flatMap(directory =>
601
+ guardsUnder(repoRoot, directory)
602
+ );
603
+ for (const guard of guards) {
604
+ report.files += 1;
605
+ const source = readFileSync(path.join(repoRoot, guard.relative), "utf8");
606
+ for (const block of commentBlocks(source)) {
607
+ inspectBlock(repoRoot, guard, block, report);
608
+ }
609
+ }
610
+ report.findings.sort(
611
+ (left, right) =>
612
+ left.file.localeCompare(right.file) ||
613
+ left.line - right.line ||
614
+ left.surface.localeCompare(right.surface)
615
+ );
616
+ return report;
617
+ }
618
+
619
+ /**
620
+ * Refuse the claims one comment block makes that the tree contradicts.
621
+ * @param {string} repoRoot - Absolute path of the tree being scanned.
622
+ * @param {{ relative: string, guardId: string }} guard - The guard source.
623
+ * @param {{ line: number, text: string }} block - One comment block.
624
+ * @param {ParityReport} report - Accumulating report, mutated in place.
625
+ * @returns {void}
626
+ */
627
+ function inspectBlock(repoRoot, guard, block, report) {
628
+ for (const claim of claimsIn(block.text)) {
629
+ report.claims += 1;
630
+ for (const surface of claim.surfaces) {
631
+ const evidence = evidenceFor(repoRoot, guard.guardId, surface);
632
+ if (!evidence) continue;
633
+ report.findings.push({
634
+ file: guard.relative,
635
+ line: lineAt(block, claim.offset),
636
+ guard: guard.guardId,
637
+ surface,
638
+ evidence,
639
+ claim: claim.text,
640
+ });
641
+ }
642
+ }
643
+ }
644
+
645
+ /**
646
+ * Render the human-readable report.
647
+ * @param {ParityReport} report - Result.
648
+ * @returns {string} The report text.
649
+ */
650
+ export function formatReport(report) {
651
+ const lines = [
652
+ `check:guard-parity-notes — read ${report.files} guard source(s) and recognised ${report.claims} parity claim(s).`,
653
+ " Not audited by this sweep:",
654
+ ...BLIND_SPOTS.map(spot => ` · ${spot}`),
655
+ ];
656
+ if (report.files === 0) {
657
+ lines.push(
658
+ " ✖ ZERO guard sources read. A sweep that read nothing cannot report a clean tree; treating this as a failure, not an all-clear."
659
+ );
660
+ return lines.join("\n");
661
+ }
662
+ for (const finding of report.findings) {
663
+ lines.push(
664
+ ` ✖ ${finding.file}:${finding.line} — ${finding.guard} claims no ${finding.surface} port`,
665
+ ` "${finding.claim}"`,
666
+ ` contradicted by ${finding.evidence}`
667
+ );
668
+ }
669
+ if (report.findings.length === 0) {
670
+ lines.push(
671
+ " ✔ Every parity note that records a missing port names a surface the tree agrees is missing."
672
+ );
673
+ return lines.join("\n");
674
+ }
675
+ lines.push(
676
+ "",
677
+ "Fix: correct the note to name only the surfaces that are genuinely absent, and delete it outright once none are.",
678
+ "The tree is the authority — a note is a reader's shortcut, and a shortcut that points the wrong way costs more than no shortcut at all."
679
+ );
680
+ return lines.join("\n");
681
+ }
682
+
683
+ /**
684
+ * CLI entry point.
685
+ * @returns {void}
686
+ */
687
+ export function main() {
688
+ const args = process.argv.slice(2);
689
+ const unknown = args.find(arg => arg.startsWith("--") && arg !== "--json");
690
+ if (unknown) {
691
+ console.error(`check:guard-parity-notes: unknown flag ${unknown}`);
692
+ process.exitCode = 2;
693
+ return;
694
+ }
695
+ const json = args.includes("--json");
696
+ const repoRoot = path.resolve(args.find(arg => !arg.startsWith("--")) ?? ".");
697
+ const report = sweep(repoRoot);
698
+ console.log(
699
+ json
700
+ ? JSON.stringify({ ...report, blindSpots: BLIND_SPOTS }, null, 2)
701
+ : formatReport(report)
702
+ );
703
+ if (report.files === 0) process.exitCode = 2;
704
+ else if (report.findings.length > 0) process.exitCode = 1;
705
+ }
706
+
707
+ if (invokedAsScript(import.meta.url)) {
708
+ main();
709
+ }