@codyswann/lisa 4.55.1 → 4.55.3

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 (434) hide show
  1. package/all/copy-overwrite/scripts/check-orphaned-branches.mjs +49 -13
  2. package/all/copy-overwrite/scripts/lisa-enforcement-fallback.sh +9 -1
  3. package/all/copy-overwrite/scripts/lisa-hooks/block-blind-automerge.sh +10 -5
  4. package/all/copy-overwrite/scripts/lisa-hooks/block-direct-issue-create.sh +4 -0
  5. package/all/copy-overwrite/scripts/lisa-hooks/block-managed-file-edits.sh +73 -2
  6. package/all/copy-overwrite/scripts/lisa-hooks/guard-dedupe.bash +63 -29
  7. package/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +2 -1
  8. package/all/copy-overwrite/scripts/lisa-hooks/worktree-binding-guard.sh +2 -1
  9. package/all/copy-overwrite/scripts/lisa-run-gates.mjs +7 -2
  10. package/all/copy-overwrite/scripts/lisa-work-item.mjs +50 -14
  11. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  12. package/dist/core/lisa-owned-hash-ledger.js +36 -0
  13. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  14. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  15. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  16. package/dist/core/upstream-evidence-manifest.js +89 -139
  17. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  18. package/dist/opencode/plugin-templates/block-managed-file-edits.sh +76 -2
  19. package/dist/opencode/plugin-templates/block-no-verify.sh +3 -0
  20. package/dist/opencode/plugin-templates/guard-dedupe.bash +66 -29
  21. package/dist/opencode/plugin-templates/parity-safety-net-heredoc.py +3 -0
  22. package/dist/opencode/plugin-templates/parity-safety-net.sh +5 -1
  23. package/dist/opencode/support-file-resolver.d.ts.map +1 -1
  24. package/dist/opencode/support-file-resolver.js +3 -1
  25. package/dist/opencode/support-file-resolver.js.map +1 -1
  26. package/expo/copy-overwrite/plugins/withAndroidSplashNoClientExit.js +23 -21
  27. package/expo/create-only/.github/required-checks.json +1 -1
  28. package/nestjs/create-only/.github/required-checks.json +1 -1
  29. package/package.json +4 -4
  30. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  31. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  32. package/plugins/lisa/.codex-plugin/skills/lisa-github-build-intake/SKILL.md +18 -10
  33. package/plugins/lisa/.codex-plugin/skills/lisa-jira-build-intake/SKILL.md +18 -9
  34. package/plugins/lisa/.codex-plugin/skills/lisa-linear-access/SKILL.md +45 -7
  35. package/plugins/lisa/.codex-plugin/skills/lisa-linear-build-intake/SKILL.md +18 -7
  36. package/plugins/lisa/.codex-plugin/skills/lisa-linear-prd-intake/SKILL.md +12 -2
  37. package/plugins/lisa/.codex-plugin/skills/lisa-linear-validate-issue/SKILL.md +1 -18
  38. package/plugins/lisa/.codex-plugin/skills/lisa-linear-verify/SKILL.md +0 -17
  39. package/plugins/lisa/.codex-plugin/skills/lisa-linear-write-issue/SKILL.md +3 -34
  40. package/plugins/lisa/.codex-plugin/skills/lisa-prd-ticket-coverage/SKILL.md +2 -2
  41. package/plugins/lisa/.codex-plugin/skills/lisa-repair-intake/SKILL.md +46 -6
  42. package/plugins/lisa/.codex-plugin/skills/lisa-rework-triage/SKILL.md +2 -1
  43. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/providers.mjs +2 -1
  44. package/plugins/lisa/.codex-plugin/skills/lisa-setup-linear/SKILL.md +1 -1
  45. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/preflight-tools.mjs +7 -2
  46. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +11 -4
  47. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/toolchain.mjs +17 -5
  48. package/plugins/lisa/.codex-plugin/skills/lisa-track/SKILL.md +18 -4
  49. package/plugins/lisa/hooks/block-blind-automerge.sh +13 -5
  50. package/plugins/lisa/hooks/block-direct-issue-create.sh +7 -0
  51. package/plugins/lisa/hooks/block-instruction-file-edits.sh +3 -0
  52. package/plugins/lisa/hooks/block-managed-file-edits.sh +76 -2
  53. package/plugins/lisa/hooks/block-no-verify.sh +3 -0
  54. package/plugins/lisa/hooks/block-shell-json-parsing.sh +3 -0
  55. package/plugins/lisa/hooks/guard-dedupe.bash +66 -29
  56. package/plugins/lisa/hooks/parity-safety-net-heredoc.py +3 -0
  57. package/plugins/lisa/hooks/parity-safety-net.sh +5 -1
  58. package/plugins/lisa/hooks/worktree-binding-guard.mjs +3 -0
  59. package/plugins/lisa/hooks/worktree-binding-guard.sh +5 -1
  60. package/plugins/lisa/rules/eager/00-rule-index.md +1 -0
  61. package/plugins/lisa/rules/eager/base-rules.md +3 -1
  62. package/plugins/lisa/rules/eager/withdrawn-rulings.md +14 -3
  63. package/plugins/lisa/rules/reference/automation-runbook-contract.md +1 -1
  64. package/plugins/lisa/rules/reference/bdd-e2e-coverage.md +1 -1
  65. package/plugins/lisa/rules/reference/blocker-containment.md +3 -3
  66. package/plugins/lisa/rules/reference/claim-archaeology.md +1 -1
  67. package/plugins/lisa/rules/reference/claim-evidence-mapping.md +1 -1
  68. package/plugins/lisa/rules/reference/claim-time-guards.md +1 -1
  69. package/plugins/lisa/rules/reference/control-reachability.md +1 -1
  70. package/plugins/lisa/rules/reference/convergent-review.md +1 -1
  71. package/plugins/lisa/rules/reference/credential-substrate-precedence.md +1 -1
  72. package/plugins/lisa/rules/reference/dependency-decision-records.md +1 -1
  73. package/plugins/lisa/rules/reference/dependency-internalization-kit.md +1 -1
  74. package/plugins/lisa/rules/reference/dependency-trust-classes.md +1 -1
  75. package/plugins/lisa/rules/reference/deployed-state-readback.md +1 -1
  76. package/plugins/lisa/rules/reference/derived-branch-plan.md +1 -1
  77. package/plugins/lisa/rules/reference/design-source-of-truth.md +1 -1
  78. package/plugins/lisa/rules/reference/design-value-binding.md +1 -1
  79. package/plugins/lisa/rules/reference/documentation-source-paths.md +1 -1
  80. package/plugins/lisa/rules/reference/leaf-only-lifecycle.md +1 -1
  81. package/plugins/lisa/rules/reference/observability-audit.md +1 -1
  82. package/plugins/lisa/rules/reference/prd-definition-of-ready.md +1 -1
  83. package/plugins/lisa/rules/reference/prd-lifecycle-rollup.md +1 -1
  84. package/plugins/lisa/rules/reference/pre-flight-autofill.md +1 -1
  85. package/plugins/lisa/rules/reference/promotion-contract.md +1 -1
  86. package/plugins/lisa/rules/reference/readiness-rubric.md +1 -1
  87. package/plugins/lisa/rules/reference/ready-role-filing.md +1 -1
  88. package/plugins/lisa/rules/reference/rejection-detection.md +1 -1
  89. package/plugins/lisa/rules/reference/repo-scope-split.md +9 -10
  90. package/plugins/lisa/rules/reference/reset-seed-coverage.md +1 -1
  91. package/plugins/lisa/rules/reference/usage-accounting.md +1 -1
  92. package/plugins/lisa/rules/reference/work-item-definition-of-ready.md +1 -1
  93. package/plugins/lisa/scripts/linear-state-write-target.mjs +2 -2
  94. package/plugins/lisa/scripts/qa-signal-lifecycle.mjs +24 -11
  95. package/plugins/lisa/scripts/resolve-lifecycle-role.mjs +2 -7
  96. package/plugins/lisa/skills/lisa-github-build-intake/SKILL.md +18 -10
  97. package/plugins/lisa/skills/lisa-jira-build-intake/SKILL.md +18 -9
  98. package/plugins/lisa/skills/lisa-linear-access/SKILL.md +45 -7
  99. package/plugins/lisa/skills/lisa-linear-build-intake/SKILL.md +18 -7
  100. package/plugins/lisa/skills/lisa-linear-prd-intake/SKILL.md +12 -2
  101. package/plugins/lisa/skills/lisa-linear-validate-issue/SKILL.md +1 -18
  102. package/plugins/lisa/skills/lisa-linear-verify/SKILL.md +0 -17
  103. package/plugins/lisa/skills/lisa-linear-write-issue/SKILL.md +3 -34
  104. package/plugins/lisa/skills/lisa-prd-ticket-coverage/SKILL.md +2 -2
  105. package/plugins/lisa/skills/lisa-repair-intake/SKILL.md +46 -6
  106. package/plugins/lisa/skills/lisa-rework-triage/SKILL.md +2 -1
  107. package/plugins/lisa/skills/lisa-secrets-access/scripts/providers.mjs +2 -1
  108. package/plugins/lisa/skills/lisa-setup-linear/SKILL.md +1 -1
  109. package/plugins/lisa/skills/lisa-setup-remote-env/scripts/preflight-tools.mjs +7 -2
  110. package/plugins/lisa/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +11 -4
  111. package/plugins/lisa/skills/lisa-setup-remote-env/scripts/toolchain.mjs +17 -5
  112. package/plugins/lisa/skills/lisa-track/SKILL.md +18 -4
  113. package/plugins/lisa-agy/hooks/block-blind-automerge.sh +13 -5
  114. package/plugins/lisa-agy/hooks/block-direct-issue-create.sh +7 -0
  115. package/plugins/lisa-agy/hooks/block-instruction-file-edits.sh +3 -0
  116. package/plugins/lisa-agy/hooks/block-managed-file-edits.sh +76 -2
  117. package/plugins/lisa-agy/hooks/block-shell-json-parsing.sh +3 -0
  118. package/plugins/lisa-agy/hooks/guard-dedupe.bash +66 -29
  119. package/plugins/lisa-agy/hooks/parity-safety-net-heredoc.py +3 -0
  120. package/plugins/lisa-agy/hooks/parity-safety-net.sh +5 -1
  121. package/plugins/lisa-agy/plugin.json +1 -1
  122. package/plugins/lisa-agy/scripts/linear-state-write-target.mjs +2 -2
  123. package/plugins/lisa-agy/scripts/qa-signal-lifecycle.mjs +24 -11
  124. package/plugins/lisa-agy/scripts/resolve-lifecycle-role.mjs +2 -7
  125. package/plugins/lisa-agy/skills/lisa-github-build-intake/SKILL.md +18 -10
  126. package/plugins/lisa-agy/skills/lisa-jira-build-intake/SKILL.md +18 -9
  127. package/plugins/lisa-agy/skills/lisa-linear-access/SKILL.md +45 -7
  128. package/plugins/lisa-agy/skills/lisa-linear-build-intake/SKILL.md +18 -7
  129. package/plugins/lisa-agy/skills/lisa-linear-prd-intake/SKILL.md +12 -2
  130. package/plugins/lisa-agy/skills/lisa-linear-validate-issue/SKILL.md +1 -18
  131. package/plugins/lisa-agy/skills/lisa-linear-verify/SKILL.md +0 -17
  132. package/plugins/lisa-agy/skills/lisa-linear-write-issue/SKILL.md +3 -34
  133. package/plugins/lisa-agy/skills/lisa-prd-ticket-coverage/SKILL.md +2 -2
  134. package/plugins/lisa-agy/skills/lisa-repair-intake/SKILL.md +46 -6
  135. package/plugins/lisa-agy/skills/lisa-rework-triage/SKILL.md +2 -1
  136. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/providers.mjs +2 -1
  137. package/plugins/lisa-agy/skills/lisa-setup-linear/SKILL.md +1 -1
  138. package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/preflight-tools.mjs +7 -2
  139. package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +11 -4
  140. package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/toolchain.mjs +17 -5
  141. package/plugins/lisa-agy/skills/lisa-track/SKILL.md +18 -4
  142. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  143. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  144. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  145. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  146. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  147. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  148. package/plugins/lisa-copilot/hooks/block-blind-automerge.sh +13 -5
  149. package/plugins/lisa-copilot/hooks/block-direct-issue-create.sh +7 -0
  150. package/plugins/lisa-copilot/hooks/block-instruction-file-edits.sh +3 -0
  151. package/plugins/lisa-copilot/hooks/block-managed-file-edits.sh +76 -2
  152. package/plugins/lisa-copilot/hooks/block-no-verify.sh +3 -0
  153. package/plugins/lisa-copilot/hooks/block-shell-json-parsing.sh +3 -0
  154. package/plugins/lisa-copilot/hooks/guard-dedupe.bash +66 -29
  155. package/plugins/lisa-copilot/hooks/parity-safety-net-heredoc.py +3 -0
  156. package/plugins/lisa-copilot/hooks/parity-safety-net.sh +5 -1
  157. package/plugins/lisa-copilot/hooks/worktree-binding-guard.mjs +3 -0
  158. package/plugins/lisa-copilot/hooks/worktree-binding-guard.sh +5 -1
  159. package/plugins/lisa-copilot/rules/eager/00-rule-index.md +1 -0
  160. package/plugins/lisa-copilot/rules/eager/base-rules.md +3 -1
  161. package/plugins/lisa-copilot/rules/eager/withdrawn-rulings.md +14 -3
  162. package/plugins/lisa-copilot/rules/reference/automation-runbook-contract.md +1 -1
  163. package/plugins/lisa-copilot/rules/reference/bdd-e2e-coverage.md +1 -1
  164. package/plugins/lisa-copilot/rules/reference/blocker-containment.md +3 -3
  165. package/plugins/lisa-copilot/rules/reference/claim-archaeology.md +1 -1
  166. package/plugins/lisa-copilot/rules/reference/claim-evidence-mapping.md +1 -1
  167. package/plugins/lisa-copilot/rules/reference/claim-time-guards.md +1 -1
  168. package/plugins/lisa-copilot/rules/reference/control-reachability.md +1 -1
  169. package/plugins/lisa-copilot/rules/reference/convergent-review.md +1 -1
  170. package/plugins/lisa-copilot/rules/reference/credential-substrate-precedence.md +1 -1
  171. package/plugins/lisa-copilot/rules/reference/dependency-decision-records.md +1 -1
  172. package/plugins/lisa-copilot/rules/reference/dependency-internalization-kit.md +1 -1
  173. package/plugins/lisa-copilot/rules/reference/dependency-trust-classes.md +1 -1
  174. package/plugins/lisa-copilot/rules/reference/deployed-state-readback.md +1 -1
  175. package/plugins/lisa-copilot/rules/reference/derived-branch-plan.md +1 -1
  176. package/plugins/lisa-copilot/rules/reference/design-source-of-truth.md +1 -1
  177. package/plugins/lisa-copilot/rules/reference/design-value-binding.md +1 -1
  178. package/plugins/lisa-copilot/rules/reference/documentation-source-paths.md +1 -1
  179. package/plugins/lisa-copilot/rules/reference/leaf-only-lifecycle.md +1 -1
  180. package/plugins/lisa-copilot/rules/reference/observability-audit.md +1 -1
  181. package/plugins/lisa-copilot/rules/reference/prd-definition-of-ready.md +1 -1
  182. package/plugins/lisa-copilot/rules/reference/prd-lifecycle-rollup.md +1 -1
  183. package/plugins/lisa-copilot/rules/reference/pre-flight-autofill.md +1 -1
  184. package/plugins/lisa-copilot/rules/reference/promotion-contract.md +1 -1
  185. package/plugins/lisa-copilot/rules/reference/readiness-rubric.md +1 -1
  186. package/plugins/lisa-copilot/rules/reference/ready-role-filing.md +1 -1
  187. package/plugins/lisa-copilot/rules/reference/rejection-detection.md +1 -1
  188. package/plugins/lisa-copilot/rules/reference/repo-scope-split.md +9 -10
  189. package/plugins/lisa-copilot/rules/reference/reset-seed-coverage.md +1 -1
  190. package/plugins/lisa-copilot/rules/reference/usage-accounting.md +1 -1
  191. package/plugins/lisa-copilot/rules/reference/work-item-definition-of-ready.md +1 -1
  192. package/plugins/lisa-copilot/scripts/linear-state-write-target.mjs +2 -2
  193. package/plugins/lisa-copilot/scripts/qa-signal-lifecycle.mjs +24 -11
  194. package/plugins/lisa-copilot/scripts/resolve-lifecycle-role.mjs +2 -7
  195. package/plugins/lisa-copilot/skills/lisa-github-build-intake/SKILL.md +18 -10
  196. package/plugins/lisa-copilot/skills/lisa-jira-build-intake/SKILL.md +18 -9
  197. package/plugins/lisa-copilot/skills/lisa-linear-access/SKILL.md +45 -7
  198. package/plugins/lisa-copilot/skills/lisa-linear-build-intake/SKILL.md +18 -7
  199. package/plugins/lisa-copilot/skills/lisa-linear-prd-intake/SKILL.md +12 -2
  200. package/plugins/lisa-copilot/skills/lisa-linear-validate-issue/SKILL.md +1 -18
  201. package/plugins/lisa-copilot/skills/lisa-linear-verify/SKILL.md +0 -17
  202. package/plugins/lisa-copilot/skills/lisa-linear-write-issue/SKILL.md +3 -34
  203. package/plugins/lisa-copilot/skills/lisa-prd-ticket-coverage/SKILL.md +2 -2
  204. package/plugins/lisa-copilot/skills/lisa-repair-intake/SKILL.md +46 -6
  205. package/plugins/lisa-copilot/skills/lisa-rework-triage/SKILL.md +2 -1
  206. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/providers.mjs +2 -1
  207. package/plugins/lisa-copilot/skills/lisa-setup-linear/SKILL.md +1 -1
  208. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/preflight-tools.mjs +7 -2
  209. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +11 -4
  210. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/toolchain.mjs +17 -5
  211. package/plugins/lisa-copilot/skills/lisa-track/SKILL.md +18 -4
  212. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  213. package/plugins/lisa-cursor/hooks/block-blind-automerge.sh +13 -5
  214. package/plugins/lisa-cursor/hooks/block-direct-issue-create.sh +7 -0
  215. package/plugins/lisa-cursor/hooks/block-instruction-file-edits.sh +3 -0
  216. package/plugins/lisa-cursor/hooks/block-managed-file-edits.sh +76 -2
  217. package/plugins/lisa-cursor/hooks/block-no-verify.sh +3 -0
  218. package/plugins/lisa-cursor/hooks/block-shell-json-parsing.sh +3 -0
  219. package/plugins/lisa-cursor/hooks/guard-dedupe.bash +66 -29
  220. package/plugins/lisa-cursor/hooks/parity-safety-net-heredoc.py +3 -0
  221. package/plugins/lisa-cursor/hooks/parity-safety-net.sh +5 -1
  222. package/plugins/lisa-cursor/hooks/worktree-binding-guard.mjs +3 -0
  223. package/plugins/lisa-cursor/hooks/worktree-binding-guard.sh +5 -1
  224. package/plugins/lisa-cursor/rules/00-rule-index.mdc +1 -0
  225. package/plugins/lisa-cursor/rules/automation-runbook-contract-reference.mdc +1 -1
  226. package/plugins/lisa-cursor/rules/base-rules.mdc +3 -1
  227. package/plugins/lisa-cursor/rules/bdd-e2e-coverage-reference.mdc +1 -1
  228. package/plugins/lisa-cursor/rules/blocker-containment-reference.mdc +3 -3
  229. package/plugins/lisa-cursor/rules/claim-archaeology-reference.mdc +1 -1
  230. package/plugins/lisa-cursor/rules/claim-evidence-mapping-reference.mdc +1 -1
  231. package/plugins/lisa-cursor/rules/claim-time-guards-reference.mdc +1 -1
  232. package/plugins/lisa-cursor/rules/control-reachability-reference.mdc +1 -1
  233. package/plugins/lisa-cursor/rules/convergent-review-reference.mdc +1 -1
  234. package/plugins/lisa-cursor/rules/credential-substrate-precedence-reference.mdc +1 -1
  235. package/plugins/lisa-cursor/rules/dependency-decision-records-reference.mdc +1 -1
  236. package/plugins/lisa-cursor/rules/dependency-internalization-kit-reference.mdc +1 -1
  237. package/plugins/lisa-cursor/rules/dependency-trust-classes-reference.mdc +1 -1
  238. package/plugins/lisa-cursor/rules/deployed-state-readback-reference.mdc +1 -1
  239. package/plugins/lisa-cursor/rules/derived-branch-plan-reference.mdc +1 -1
  240. package/plugins/lisa-cursor/rules/design-source-of-truth-reference.mdc +1 -1
  241. package/plugins/lisa-cursor/rules/design-value-binding-reference.mdc +1 -1
  242. package/plugins/lisa-cursor/rules/documentation-source-paths-reference.mdc +1 -1
  243. package/plugins/lisa-cursor/rules/leaf-only-lifecycle-reference.mdc +1 -1
  244. package/plugins/lisa-cursor/rules/observability-audit-reference.mdc +1 -1
  245. package/plugins/lisa-cursor/rules/prd-definition-of-ready-reference.mdc +1 -1
  246. package/plugins/lisa-cursor/rules/prd-lifecycle-rollup-reference.mdc +1 -1
  247. package/plugins/lisa-cursor/rules/pre-flight-autofill-reference.mdc +1 -1
  248. package/plugins/lisa-cursor/rules/promotion-contract-reference.mdc +1 -1
  249. package/plugins/lisa-cursor/rules/readiness-rubric-reference.mdc +1 -1
  250. package/plugins/lisa-cursor/rules/ready-role-filing-reference.mdc +1 -1
  251. package/plugins/lisa-cursor/rules/rejection-detection-reference.mdc +1 -1
  252. package/plugins/lisa-cursor/rules/repo-scope-split-reference.mdc +9 -10
  253. package/plugins/lisa-cursor/rules/reset-seed-coverage-reference.mdc +1 -1
  254. package/plugins/lisa-cursor/rules/usage-accounting-reference.mdc +1 -1
  255. package/plugins/lisa-cursor/rules/withdrawn-rulings.mdc +14 -3
  256. package/plugins/lisa-cursor/rules/work-item-definition-of-ready-reference.mdc +1 -1
  257. package/plugins/lisa-cursor/scripts/linear-state-write-target.mjs +2 -2
  258. package/plugins/lisa-cursor/scripts/qa-signal-lifecycle.mjs +24 -11
  259. package/plugins/lisa-cursor/scripts/resolve-lifecycle-role.mjs +2 -7
  260. package/plugins/lisa-cursor/skills/lisa-github-build-intake/SKILL.md +18 -10
  261. package/plugins/lisa-cursor/skills/lisa-jira-build-intake/SKILL.md +18 -9
  262. package/plugins/lisa-cursor/skills/lisa-linear-access/SKILL.md +45 -7
  263. package/plugins/lisa-cursor/skills/lisa-linear-build-intake/SKILL.md +18 -7
  264. package/plugins/lisa-cursor/skills/lisa-linear-prd-intake/SKILL.md +12 -2
  265. package/plugins/lisa-cursor/skills/lisa-linear-validate-issue/SKILL.md +1 -18
  266. package/plugins/lisa-cursor/skills/lisa-linear-verify/SKILL.md +0 -17
  267. package/plugins/lisa-cursor/skills/lisa-linear-write-issue/SKILL.md +3 -34
  268. package/plugins/lisa-cursor/skills/lisa-prd-ticket-coverage/SKILL.md +2 -2
  269. package/plugins/lisa-cursor/skills/lisa-repair-intake/SKILL.md +46 -6
  270. package/plugins/lisa-cursor/skills/lisa-rework-triage/SKILL.md +2 -1
  271. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/providers.mjs +2 -1
  272. package/plugins/lisa-cursor/skills/lisa-setup-linear/SKILL.md +1 -1
  273. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/preflight-tools.mjs +7 -2
  274. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +11 -4
  275. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/toolchain.mjs +17 -5
  276. package/plugins/lisa-cursor/skills/lisa-track/SKILL.md +18 -4
  277. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  278. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  279. package/plugins/lisa-expo-agy/plugin.json +1 -1
  280. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  281. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  282. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  283. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  284. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  285. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  286. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  287. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  288. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  289. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  290. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  291. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  292. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  293. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  294. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  295. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  296. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  297. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  298. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  299. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  300. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  301. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  302. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  303. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  304. package/plugins/lisa-rails-agy/plugin.json +1 -1
  305. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  306. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  307. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  308. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  309. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  310. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  311. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  312. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  313. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  314. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  315. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  316. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  317. package/plugins/materialized-artifacts.json +11 -0
  318. package/plugins/src/base/hooks/block-blind-automerge.sh +10 -5
  319. package/plugins/src/base/hooks/block-direct-issue-create.sh +4 -0
  320. package/plugins/src/base/hooks/block-managed-file-edits.sh +73 -2
  321. package/plugins/src/base/hooks/guard-dedupe.bash +63 -29
  322. package/plugins/src/base/hooks/parity-safety-net.sh +2 -1
  323. package/plugins/src/base/hooks/worktree-binding-guard.sh +2 -1
  324. package/plugins/src/base/rules/eager/00-rule-index.md +1 -0
  325. package/plugins/src/base/rules/eager/base-rules.md +3 -1
  326. package/plugins/src/base/rules/eager/withdrawn-rulings.md +14 -3
  327. package/plugins/src/base/rules/reference/automation-runbook-contract.md +1 -1
  328. package/plugins/src/base/rules/reference/bdd-e2e-coverage.md +1 -1
  329. package/plugins/src/base/rules/reference/blocker-containment.md +3 -3
  330. package/plugins/src/base/rules/reference/claim-archaeology.md +1 -1
  331. package/plugins/src/base/rules/reference/claim-evidence-mapping.md +1 -1
  332. package/plugins/src/base/rules/reference/claim-time-guards.md +1 -1
  333. package/plugins/src/base/rules/reference/control-reachability.md +1 -1
  334. package/plugins/src/base/rules/reference/convergent-review.md +1 -1
  335. package/plugins/src/base/rules/reference/credential-substrate-precedence.md +1 -1
  336. package/plugins/src/base/rules/reference/dependency-decision-records.md +1 -1
  337. package/plugins/src/base/rules/reference/dependency-internalization-kit.md +1 -1
  338. package/plugins/src/base/rules/reference/dependency-trust-classes.md +1 -1
  339. package/plugins/src/base/rules/reference/deployed-state-readback.md +1 -1
  340. package/plugins/src/base/rules/reference/derived-branch-plan.md +1 -1
  341. package/plugins/src/base/rules/reference/design-source-of-truth.md +1 -1
  342. package/plugins/src/base/rules/reference/design-value-binding.md +1 -1
  343. package/plugins/src/base/rules/reference/documentation-source-paths.md +1 -1
  344. package/plugins/src/base/rules/reference/leaf-only-lifecycle.md +1 -1
  345. package/plugins/src/base/rules/reference/observability-audit.md +1 -1
  346. package/plugins/src/base/rules/reference/prd-definition-of-ready.md +1 -1
  347. package/plugins/src/base/rules/reference/prd-lifecycle-rollup.md +1 -1
  348. package/plugins/src/base/rules/reference/pre-flight-autofill.md +1 -1
  349. package/plugins/src/base/rules/reference/promotion-contract.md +1 -1
  350. package/plugins/src/base/rules/reference/readiness-rubric.md +1 -1
  351. package/plugins/src/base/rules/reference/ready-role-filing.md +1 -1
  352. package/plugins/src/base/rules/reference/rejection-detection.md +1 -1
  353. package/plugins/src/base/rules/reference/repo-scope-split.md +9 -10
  354. package/plugins/src/base/rules/reference/reset-seed-coverage.md +1 -1
  355. package/plugins/src/base/rules/reference/usage-accounting.md +1 -1
  356. package/plugins/src/base/rules/reference/work-item-definition-of-ready.md +1 -1
  357. package/plugins/src/base/scripts/linear-state-write-target.mjs +2 -2
  358. package/plugins/src/base/scripts/qa-signal-lifecycle.mjs +24 -11
  359. package/plugins/src/base/scripts/resolve-lifecycle-role.mjs +2 -7
  360. package/plugins/src/base/skills/lisa-github-build-intake/SKILL.md +18 -10
  361. package/plugins/src/base/skills/lisa-jira-build-intake/SKILL.md +18 -9
  362. package/plugins/src/base/skills/lisa-linear-access/SKILL.md +45 -7
  363. package/plugins/src/base/skills/lisa-linear-build-intake/SKILL.md +18 -7
  364. package/plugins/src/base/skills/lisa-linear-prd-intake/SKILL.md +12 -2
  365. package/plugins/src/base/skills/lisa-linear-validate-issue/SKILL.md +1 -18
  366. package/plugins/src/base/skills/lisa-linear-verify/SKILL.md +0 -17
  367. package/plugins/src/base/skills/lisa-linear-write-issue/SKILL.md +3 -34
  368. package/plugins/src/base/skills/lisa-prd-ticket-coverage/SKILL.md +2 -2
  369. package/plugins/src/base/skills/lisa-repair-intake/SKILL.md +46 -6
  370. package/plugins/src/base/skills/lisa-rework-triage/SKILL.md +2 -1
  371. package/plugins/src/base/skills/lisa-secrets-access/scripts/providers.mjs +2 -1
  372. package/plugins/src/base/skills/lisa-setup-linear/SKILL.md +1 -1
  373. package/plugins/src/base/skills/lisa-setup-remote-env/scripts/preflight-tools.mjs +7 -2
  374. package/plugins/src/base/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +11 -4
  375. package/plugins/src/base/skills/lisa-setup-remote-env/scripts/toolchain.mjs +17 -5
  376. package/plugins/src/base/skills/lisa-track/SKILL.md +18 -4
  377. package/scripts/build-plugins.sh +4 -0
  378. package/scripts/copy-opencode-plugin-templates.mjs +5 -1
  379. package/scripts/lisa-enforcement-fallback.sh +9 -1
  380. package/scripts/two-channel-couplings.json +5 -33
  381. package/typescript/copy-overwrite/scripts/check-skipped-required-checks.mjs +551 -13
  382. package/typescript/create-only/.github/required-checks.json +1 -1
  383. package/plugins/lisa/.codex-plugin/skills/lisa-qa-checklist/SKILL.md +0 -70
  384. package/plugins/lisa/.codex-plugin/skills/lisa-qa-checklist/agents/openai.yaml +0 -4
  385. package/plugins/lisa/.codex-plugin/skills/lisa-qa-clear/SKILL.md +0 -86
  386. package/plugins/lisa/.codex-plugin/skills/lisa-qa-clear/agents/openai.yaml +0 -4
  387. package/plugins/lisa/.codex-plugin/skills/lisa-qa-fail/SKILL.md +0 -119
  388. package/plugins/lisa/.codex-plugin/skills/lisa-qa-fail/agents/openai.yaml +0 -4
  389. package/plugins/lisa/.codex-plugin/skills/lisa-qa-queue/SKILL.md +0 -94
  390. package/plugins/lisa/.codex-plugin/skills/lisa-qa-queue/agents/openai.yaml +0 -4
  391. package/plugins/lisa/commands/qa-checklist.md +0 -6
  392. package/plugins/lisa/commands/qa-clear.md +0 -6
  393. package/plugins/lisa/commands/qa-fail.md +0 -6
  394. package/plugins/lisa/commands/qa-queue.md +0 -6
  395. package/plugins/lisa/skills/lisa-qa-checklist/SKILL.md +0 -70
  396. package/plugins/lisa/skills/lisa-qa-checklist/agents/openai.yaml +0 -4
  397. package/plugins/lisa/skills/lisa-qa-clear/SKILL.md +0 -86
  398. package/plugins/lisa/skills/lisa-qa-clear/agents/openai.yaml +0 -4
  399. package/plugins/lisa/skills/lisa-qa-fail/SKILL.md +0 -119
  400. package/plugins/lisa/skills/lisa-qa-fail/agents/openai.yaml +0 -4
  401. package/plugins/lisa/skills/lisa-qa-queue/SKILL.md +0 -94
  402. package/plugins/lisa/skills/lisa-qa-queue/agents/openai.yaml +0 -4
  403. package/plugins/lisa-agy/commands/lisa/qa-checklist.md +0 -6
  404. package/plugins/lisa-agy/commands/lisa/qa-clear.md +0 -6
  405. package/plugins/lisa-agy/commands/lisa/qa-fail.md +0 -6
  406. package/plugins/lisa-agy/commands/lisa/qa-queue.md +0 -6
  407. package/plugins/lisa-agy/skills/lisa-qa-checklist/SKILL.md +0 -70
  408. package/plugins/lisa-agy/skills/lisa-qa-clear/SKILL.md +0 -86
  409. package/plugins/lisa-agy/skills/lisa-qa-fail/SKILL.md +0 -119
  410. package/plugins/lisa-agy/skills/lisa-qa-queue/SKILL.md +0 -94
  411. package/plugins/lisa-copilot/commands/lisa/qa-checklist.md +0 -6
  412. package/plugins/lisa-copilot/commands/lisa/qa-clear.md +0 -6
  413. package/plugins/lisa-copilot/commands/lisa/qa-fail.md +0 -6
  414. package/plugins/lisa-copilot/commands/lisa/qa-queue.md +0 -6
  415. package/plugins/lisa-copilot/skills/lisa-qa-checklist/SKILL.md +0 -70
  416. package/plugins/lisa-copilot/skills/lisa-qa-clear/SKILL.md +0 -86
  417. package/plugins/lisa-copilot/skills/lisa-qa-fail/SKILL.md +0 -119
  418. package/plugins/lisa-copilot/skills/lisa-qa-queue/SKILL.md +0 -94
  419. package/plugins/lisa-cursor/commands/lisa/qa-checklist.md +0 -6
  420. package/plugins/lisa-cursor/commands/lisa/qa-clear.md +0 -6
  421. package/plugins/lisa-cursor/commands/lisa/qa-fail.md +0 -6
  422. package/plugins/lisa-cursor/commands/lisa/qa-queue.md +0 -6
  423. package/plugins/lisa-cursor/skills/lisa-qa-checklist/SKILL.md +0 -70
  424. package/plugins/lisa-cursor/skills/lisa-qa-clear/SKILL.md +0 -86
  425. package/plugins/lisa-cursor/skills/lisa-qa-fail/SKILL.md +0 -119
  426. package/plugins/lisa-cursor/skills/lisa-qa-queue/SKILL.md +0 -94
  427. package/plugins/src/base/commands/qa-checklist.md +0 -6
  428. package/plugins/src/base/commands/qa-clear.md +0 -6
  429. package/plugins/src/base/commands/qa-fail.md +0 -6
  430. package/plugins/src/base/commands/qa-queue.md +0 -6
  431. package/plugins/src/base/skills/lisa-qa-checklist/SKILL.md +0 -70
  432. package/plugins/src/base/skills/lisa-qa-clear/SKILL.md +0 -86
  433. package/plugins/src/base/skills/lisa-qa-fail/SKILL.md +0 -119
  434. package/plugins/src/base/skills/lisa-qa-queue/SKILL.md +0 -94
@@ -90,7 +90,7 @@ import { invokedAsScript } from "./lib/invoked-as-script.mjs";
90
90
  // resolves there exactly as it does here. Imported rather than reimplemented:
91
91
  // a second lane parser is a second answer to `whose is this?`, and two
92
92
  // answers is the state this report exists to end.
93
- import { parseLaneId, workItemLines } from "./lisa-work-item.mjs";
93
+ import { parseLaneId, soleWorkItem, workItemLines } from "./lisa-work-item.mjs";
94
94
 
95
95
  /** Branch name prefixes that are never work-in-flight. */
96
96
  const IGNORED_PREFIXES = Object.freeze(["backup/", "gh-readonly-queue/"]);
@@ -138,8 +138,8 @@ const REF_SOURCE = Object.freeze({ NAME: "branch name", TRAILER: "trailer" });
138
138
  */
139
139
  const REF_IN_SEGMENT = /^(?:[a-z]+-)*[a-z]{0,4}(\d{2,})(?![0-9a-z])/u;
140
140
 
141
- /** A work-item reference inside a `Work-Item:` trailer value. */
142
- const REF_IN_TRAILER = /(?:^|\D)(\d{2,})/u;
141
+ /** A failed tip read must remain distinguishable from a missing lane stamp. */
142
+ export const UNREADABLE_LANE = "unreadable";
143
143
 
144
144
  /** `20260904`: a compact `yyyymmdd` date, never a work item. */
145
145
  const COMPACT_DATE = /^20\d{6}$/u;
@@ -275,23 +275,50 @@ export function branchesAhead(remote = "origin", base = "main") {
275
275
  * unread. See the `work-item-trailer-definition` rule (#3747).
276
276
  *
277
277
  * The branch name remains the fallback for a branch that genuinely carries no
278
- * trailer, reported as an inference rather than as a fact.
278
+ * trailer, reported as an inference rather than as a fact. A branch naming
279
+ * several items cannot be classified from one item's state: leave it unresolved
280
+ * rather than choosing whichever commit git happened to print first.
279
281
  * @param {string} branch Short branch name.
280
282
  * @param {string} base Default branch name.
281
283
  * @param {string} remote Remote name.
282
284
  * @param {typeof run} exec Command runner.
285
+ * @param {object} [contract] Tracker contract; the canonical reader resolves it when omitted.
283
286
  * @returns {{ref: string, refSource: string} | undefined} The reference.
284
287
  */
285
- export function workItemRef(branch, base, remote = "origin", exec = run) {
288
+ export function workItemRef(
289
+ branch,
290
+ base,
291
+ remote = "origin",
292
+ exec = run,
293
+ contract
294
+ ) {
286
295
  const messages = exec("git", [
287
296
  "log",
288
297
  "--format=%B",
289
298
  `${remote}/${base}..${remote}/${branch}`,
290
299
  ]);
291
- const [fromTrailer] = workItemLines(messages ?? "");
292
- if (fromTrailer) {
293
- const digits = REF_IN_TRAILER.exec(fromTrailer);
294
- if (digits) return { ref: digits[1], refSource: REF_SOURCE.TRAILER };
300
+ if (messages === undefined) return undefined;
301
+ const values = workItemLines(messages);
302
+ // The canonical reader omits empty values. Count raw column-zero prefixes
303
+ // too, so an empty declaration stays invalid even beside a valid trailer.
304
+ const declarations = messages
305
+ .split(/\r?\n/)
306
+ .filter(line => /^work-item:/i.test(line));
307
+ if (declarations.length !== values.length) return undefined;
308
+ if (values.length > 0) {
309
+ try {
310
+ const canonical = soleWorkItem(messages, contract, "branch commits");
311
+ // This collector reads GitHub issues. A valid reference for a different
312
+ // tracker is not a GitHub issue number and cannot be looked up with gh.
313
+ const ref = canonical.split("#")[1];
314
+ return ref ? { ref, refSource: REF_SOURCE.TRAILER } : undefined;
315
+ } catch {
316
+ // probe-direction: fail-closed — an invalid binding leaves the branch
317
+ // unresolved, so a guessed issue cannot authorize its classification.
318
+ // Malformed and conflicting bindings are unknown, never permission to
319
+ // replace the commit evidence with a guess from the branch name.
320
+ return undefined;
321
+ }
295
322
  }
296
323
  const fromName = refInBranchName(branch);
297
324
  return fromName ? { ref: fromName, refSource: REF_SOURCE.NAME } : undefined;
@@ -406,12 +433,19 @@ function parseJson(text) {
406
433
  * @param {string} base Default branch name.
407
434
  * @param {string} remote Remote name.
408
435
  * @param {typeof run} exec Command runner.
436
+ * @param {object} [contract] Tracker contract, forwarded to the canonical trailer reader.
409
437
  * @returns {Map<string, WorkItem>} States, keyed by branch.
410
438
  */
411
- export function workItemStates(branches, base, remote = "origin", exec = run) {
439
+ export function workItemStates(
440
+ branches,
441
+ base,
442
+ remote = "origin",
443
+ exec = run,
444
+ contract
445
+ ) {
412
446
  const states = new Map();
413
447
  for (const branch of branches) {
414
- const found = workItemRef(branch, base, remote, exec);
448
+ const found = workItemRef(branch, base, remote, exec, contract);
415
449
  if (!found) continue;
416
450
  const view = parseJson(
417
451
  exec("gh", ["issue", "view", found.ref, "--json", "number,state,url"])
@@ -486,7 +520,7 @@ export function divergences(branches, base, remote = "origin", exec = run) {
486
520
  * @param {ReadonlyArray<string>} branches Branch names, without the remote.
487
521
  * @param {string} remote Remote the branches live on.
488
522
  * @param {(command: string, args: string[]) => (string|undefined)} [exec] Runner.
489
- * @returns {Map<string, string>} Lane id by branch, absent where none is stamped.
523
+ * @returns {Map<string, string>} Lane id or UNREADABLE_LANE by branch, absent only when no stamp was found.
490
524
  */
491
525
  export function branchLanes(branches, remote = "origin", exec = run) {
492
526
  const lanes = new Map();
@@ -497,7 +531,7 @@ export function branchLanes(branches, remote = "origin", exec = run) {
497
531
  "--format=%B",
498
532
  `${remote}/${branch}`,
499
533
  ]);
500
- const lane = message === undefined ? undefined : parseLaneId(message);
534
+ const lane = message === undefined ? UNREADABLE_LANE : parseLaneId(message);
501
535
  if (lane !== undefined) lanes.set(branch, lane);
502
536
  }
503
537
  return lanes;
@@ -691,6 +725,8 @@ function renderRow(row, base) {
691
725
  * @returns {string} The routing clause.
692
726
  */
693
727
  function routing(row) {
728
+ if (row.lane === UNREADABLE_LANE)
729
+ return "tip commit could not be read, so its lane is unknown — retry the read before routing this branch";
694
730
  if (row.lane === undefined)
695
731
  return "no lane on the tip commit, so this cannot be routed to a session — it predates the stamp, or was made outside a Lisa hook";
696
732
  return `produced by ${row.lane} — a session confirms this is its own with \`node scripts/lisa-work-item.mjs lane\``;
@@ -856,8 +856,16 @@ if [ -n "$notice_temp_base" ] && [ "$notice_state_trusted" -eq 1 ]; then
856
856
  if [ ! -e "$guard_memo_dir" ] && [ ! -L "$guard_memo_dir" ]; then
857
857
  (umask 077 && mkdir "$guard_memo_dir") 2>/dev/null || true
858
858
  fi
859
+ # Unlike shared temp ancestors, memo leaves may not be writable by another
860
+ # account even with the sticky bit: it could pre-create the session child.
861
+ guard_memo_mode=""
862
+ guard_memo_mode="$(stat -c '%a' "$guard_memo_dir" 2>/dev/null)" ||
863
+ guard_memo_mode="$(stat -f '%p' "$guard_memo_dir" 2>/dev/null)" ||
864
+ guard_memo_mode=""
865
+ case "$guard_memo_mode" in "" | *[!0-7]*) guard_memo_mode=777 ;; esac
859
866
  if [ -d "$guard_memo_dir" ] && [ ! -L "$guard_memo_dir" ] && \
860
- [ -O "$guard_memo_dir" ]; then
867
+ [ -O "$guard_memo_dir" ] &&
868
+ [ $((8#$guard_memo_mode & 8#0022)) -eq 0 ]; then
861
869
  export LISA_GUARD_MEMO_DIR="$guard_memo_dir"
862
870
  export LISA_GUARD_MEMO_UID="$notice_uid"
863
871
  fi
@@ -283,7 +283,8 @@ NODE_ID_PATTERN = re.compile(r"^[A-Za-z0-9_=-]+$")
283
283
  NODE_QUERY = (
284
284
  "query($id:ID!){node(id:$id){... on PullRequest"
285
285
  "{number url reviewDecision state baseRefName "
286
- "commits(last:1){nodes{commit{statusCheckRollup{contexts(first:100){nodes{"
286
+ "commits(last:1){nodes{commit{statusCheckRollup{contexts(first:100){"
287
+ "pageInfo{hasNextPage} nodes{"
287
288
  "__typename ... on CheckRun{name conclusion} "
288
289
  "... on StatusContext{context state}"
289
290
  "}}}}}}}}}"
@@ -297,9 +298,13 @@ NODE_QUERY = (
297
298
  # checks, which is genuinely not failing and gets an empty list. A node without
298
299
  # the scaffolding is a probe that never asked, and it must leave the key absent
299
300
  # so the re-target path can tell the two apart instead of reading both as green.
301
+ # A truncated page is equally unreadable: a failing check may be on the next
302
+ # page, so a green first page cannot authorize an uncovered-base re-target.
300
303
  NODE_JQ = (
301
304
  "if .data.node == null then null else .data.node as $n "
302
- "| if ($n | has(\"commits\")) then ($n | del(.commits)) "
305
+ "| if ($n.commits.nodes[0].commit.statusCheckRollup.contexts.pageInfo.hasNextPage == true) "
306
+ "then ($n | del(.commits)) "
307
+ "elif ($n | has(\"commits\")) then ($n | del(.commits)) "
303
308
  "+ {statusCheckRollup: "
304
309
  "((($n.commits.nodes[0].commit.statusCheckRollup.contexts.nodes)) // [])} "
305
310
  "else $n end end"
@@ -1027,9 +1032,9 @@ not read the pull request's checks.
1027
1032
 
1028
1033
  That ref has ZERO required status checks, so the one thing standing between the
1029
1034
  move and a permission-free gate bypass is whether the PR is currently failing —
1030
- and the probe returned no `statusCheckRollup` at all. An absent rollup is not an
1031
- empty one. Reporting the move as the sanctioned green-PR batching case here
1032
- would be a verdict about a question that was never asked.
1035
+ and the probe returned no complete `statusCheckRollup`. An absent or truncated
1036
+ rollup is not an empty one. Reporting the move as the sanctioned green-PR
1037
+ batching case here would claim a result the probe did not establish.
1033
1038
 
1034
1039
  Re-target through `gh pr edit --base` instead, which reads the checks, or fix
1035
1040
  the probe. Do not report the PR as batched until something has actually looked.
@@ -2109,6 +2109,10 @@ def unparseable_reads_only(text):
2109
2109
  Returns:
2110
2110
  True when every segment's command word is a known reader.
2111
2111
  """
2112
+ # Unlexable operands cannot be certified as data when shell substitutions
2113
+ # may execute inside them. Preserve the plain apostrophe reader exemption.
2114
+ if any(operator in text for operator in ("$(", "`", "<(", ">(")):
2115
+ return False
2112
2116
  words = raw_command_words(text)
2113
2117
  return bool(words) and all(word in READ_ONLY_PROGRAMS for word in words)
2114
2118
 
@@ -569,6 +569,7 @@ EOF
569
569
  import os
570
570
  import re
571
571
  import shlex
572
+ import sys
572
573
 
573
574
  command = os.environ.get("MANAGED_EDIT_COMMAND", "")
574
575
  project = os.environ.get("MANAGED_EDIT_PROJECT", "") or os.getcwd()
@@ -579,6 +580,7 @@ project = os.environ.get("MANAGED_EDIT_PROJECT", "") or os.getcwd()
579
580
  FOLLOW_MAX_BYTES = 262144
580
581
  FOLLOW_MAX_FILES = 8
581
582
  FOLLOW_MAX_DEPTH = 3
583
+ ENV_SPLIT_UNRESOLVED = 42
582
584
 
583
585
  # Spelled with chr() rather than backslash escapes on purpose. This source is
584
586
  # embedded in a shell heredoc and copied verbatim through several generators
@@ -610,7 +612,7 @@ FOLLOW_WRAPPERS = {
610
612
  # positional count: the walk below re-enters its assignment skip on the
611
613
  # next iteration and steps over as many as are present.
612
614
  "env": (
613
- frozenset({"-C", "--chdir", "-P", "-S", "--split-string", "-u", "--unset"}),
615
+ frozenset({"-C", "--chdir", "-P", "-u", "--unset"}),
614
616
  0,
615
617
  ),
616
618
  "exec": (frozenset({"-a"}), 0),
@@ -744,6 +746,31 @@ def statements(tokens):
744
746
  return grouped
745
747
 
746
748
 
749
+ class UnresolvedEnvSplit(ValueError):
750
+ """Split-string syntax whose executed arguments cannot be proven here."""
751
+
752
+
753
+ def parse_env_split(split_string):
754
+ """Parse only the env -S subset shared with ordinary quoted arguments.
755
+
756
+ GNU env has its own escapes, comments and environment interpolation. Do
757
+ not pretend shlex implements those, or invent environment values in a
758
+ pre-execution guard. The operator can spell the resulting command directly.
759
+
760
+ Args:
761
+ split_string: The literal argument supplied to env's split option.
762
+
763
+ Returns:
764
+ Arguments for the supported subset; otherwise raises UnresolvedEnvSplit.
765
+ """
766
+ if any(marker in split_string for marker in (BACKSLASH, "#", "$", chr(96), chr(11), chr(12))):
767
+ raise UnresolvedEnvSplit("escapes, comments, shell substitution, variable expansion or control whitespace")
768
+ try:
769
+ return shlex.split(split_string, comments=False, posix=True)
770
+ except ValueError as error:
771
+ raise UnresolvedEnvSplit("unbalanced quoting") from error
772
+
773
+
747
774
  def command_word(statement):
748
775
  """The program a statement runs, and the tokens after it.
749
776
 
@@ -760,6 +787,7 @@ def command_word(statement):
760
787
  its command word is itself the executed script.
761
788
  """
762
789
  index = 0
790
+ split_expansions = 0
763
791
  while index < len(statement):
764
792
  token = statement[index]
765
793
  if "=" in token and not token.startswith(("=", "-")):
@@ -775,6 +803,36 @@ def command_word(statement):
775
803
  if option == "--":
776
804
  index += 1
777
805
  break
806
+ # env -S inserts the split string back into env's argument list;
807
+ # it is executable input, not an option value to discard. Keep
808
+ # shell -c's quoted operand intact while expanding the env layer.
809
+ # The known no-operand flags can be combined before S (-vS/-ivS).
810
+ # Stop at S: the rest belongs to its argument, not more flags.
811
+ short_split = re.match(r"^-[iv0]*S(.*)$", option)
812
+ if program == "env" and (
813
+ option == "--split-string"
814
+ or option.startswith("--split-string=")
815
+ or short_split is not None
816
+ ):
817
+ consumed = 1
818
+ if option == "--split-string" or (
819
+ short_split is not None and not short_split.group(1)
820
+ ):
821
+ if index + 1 >= len(statement):
822
+ raise UnresolvedEnvSplit("missing split-string operand")
823
+ split_string = statement[index + 1]
824
+ consumed = 2
825
+ elif option.startswith("--split-string="):
826
+ split_string = option.split("=", 1)[1]
827
+ else:
828
+ split_string = short_split.group(1)
829
+ if split_expansions >= 64:
830
+ raise UnresolvedEnvSplit("nested split-string limit")
831
+ expanded = parse_env_split(split_string)
832
+ statement = [token] + expanded + statement[index + consumed :]
833
+ split_expansions += 1
834
+ index = 0
835
+ break
778
836
  index += 2 if option in separate else 1
779
837
  index += positional
780
838
  return (None, None, [])
@@ -988,7 +1046,11 @@ def collect(text, depth, seen, out):
988
1046
 
989
1047
 
990
1048
  targets = []
991
- collect(command, 0, set(), targets)
1049
+ try:
1050
+ collect(command, 0, set(), targets)
1051
+ except UnresolvedEnvSplit as error:
1052
+ sys.stderr.write(str(error) + NEWLINE)
1053
+ sys.exit(ENV_SPLIT_UNRESOLVED)
992
1054
  for target in targets:
993
1055
  cleaned = target.strip().strip("'\"")
994
1056
  if cleaned:
@@ -1006,6 +1068,15 @@ PY
1006
1068
  else
1007
1069
  analyzer_status=$?
1008
1070
  fi
1071
+ if [ "$analyzer_status" -eq 42 ]; then
1072
+ printf 'Blocked: cannot resolve env --split-string for managed-file protection.\n' >&2
1073
+ printf 'Use an explicit command without split-string escapes, comments or variable expansion.\n' >&2
1074
+ while IFS= read -r analyzer_line; do
1075
+ [ -n "$analyzer_line" ] && printf ' %s\n' "$analyzer_line" >&2
1076
+ done <"$analyzer_stderr"
1077
+ rm -f "$analyzer_stderr"
1078
+ exit 2
1079
+ fi
1009
1080
  if [ "$analyzer_status" -ne 0 ]; then
1010
1081
  printf 'block-managed-file-edits: Bash analyzer failed (exit %s); Bash write protection is NOT active\n' "$analyzer_status" >&2
1011
1082
  while IFS= read -r analyzer_line; do
@@ -54,12 +54,9 @@
54
54
  # refusal reaching the agent without the message that explains it. On a
55
55
  # refusal every channel evaluates and speaks exactly as it does today.
56
56
  #
57
- # COST ON THE PATH THAT GETS NO BENEFIT. A host with one channel must not pay
58
- # for a mechanism it cannot use. The channel check is builtins only — one small
59
- # file read, no subshell, no fork — and returns before the digest is computed
60
- # unless a second channel has actually been seen. The digest, and the one `stat`
61
- # that feeds it the per-call discriminator, are reached only where a second
62
- # channel is live, which is exactly where they are repaid.
57
+ # State ownership, modes and ancestry are verified before channel discovery.
58
+ # After that validation the channel check uses only builtins, and returns
59
+ # before digesting code or sizing the transcript unless another channel is live.
63
60
 
64
61
  # Key this guard would memoise under, once computed. Empty means "record
65
62
  # nothing on exit".
@@ -77,14 +74,25 @@ lisa_guard_memo_ready=0
77
74
  # Digest tool for the memo key, resolved once. Empty disables the memo.
78
75
  lisa_guard_memo_digest_tool=""
79
76
 
80
- # Whether a directory is a real directory this user owns, and not a symlink —
81
- # builtins only, so this costs no process.
82
- #
83
- # `-O` is the load-bearing test: a memo planted by another user would suppress a
84
- # refusal, so a directory this user does not own is never used. The dispatcher
85
- # validates the whole parent chain before exporting `LISA_GUARD_MEMO_DIR`; this
86
- # re-checks the leaf rather than trusting the environment variable, because an
87
- # exported path is an input like any other.
77
+ # Read ownership and all permission bits without accepting output from a
78
+ # failed platform-specific stat invocation.
79
+ # $1 - directory; sets lisa_guard_memo_owner and lisa_guard_memo_mode
80
+ lisa_guard_memo_stat() {
81
+ local facts=""
82
+ facts="$(stat -c '%u %a' "$1" 2>/dev/null)" ||
83
+ facts="$(stat -f '%u %p' "$1" 2>/dev/null)" || return 1
84
+ lisa_guard_memo_owner="${facts%% *}"
85
+ lisa_guard_memo_mode="${facts#* }"
86
+ case "$lisa_guard_memo_owner:$lisa_guard_memo_mode" in
87
+ *[!0-9:]* | :* | *:) return 1 ;;
88
+ esac
89
+ case "$lisa_guard_memo_mode" in *[!0-7]*) return 1 ;; esac
90
+ lisa_guard_memo_mode=$((8#$lisa_guard_memo_mode & 8#7777))
91
+ }
92
+
93
+ # Memo leaves must be private even when a sticky shared temp ancestor is safe.
94
+ # Ownership alone allows another local account to plant a session/allow file
95
+ # in an owned but group- or world-writable directory.
88
96
  #
89
97
  # $1 - candidate directory
90
98
  lisa_guard_memo_dir_usable() {
@@ -92,23 +100,42 @@ lisa_guard_memo_dir_usable() {
92
100
  [ -d "$1" ] || return 1
93
101
  [ ! -L "$1" ] || return 1
94
102
  [ -O "$1" ] || return 1
103
+ lisa_guard_memo_stat "$1" || return 1
104
+ [ $((lisa_guard_memo_mode & 8#0022)) -eq 0 ]
105
+ }
106
+
107
+ # Resolve parents physically and verify every ancestor. Plugin hooks do not
108
+ # inherit the dispatcher's trust walk, and exported paths are still inputs.
109
+ # $1 - candidate memo base; sets lisa_guard_memo_base only on success
110
+ lisa_guard_memo_trust_base() {
111
+ lisa_guard_memo_dir_usable "$1" || return 1
112
+ local physical=""
113
+ physical="$(cd "$1" 2>/dev/null && pwd -P)" || return 1
114
+ local current="$physical"
115
+ while :; do
116
+ lisa_guard_memo_stat "$current" || return 1
117
+ [ "$lisa_guard_memo_owner" = 0 ] || [ -O "$current" ] || return 1
118
+ if [ $((lisa_guard_memo_mode & 8#0022)) -ne 0 ] &&
119
+ [ $((lisa_guard_memo_mode & 8#1000)) -eq 0 ]; then
120
+ return 1
121
+ fi
122
+ [ "$current" != / ] || break
123
+ current="${current%/*}"
124
+ [ -n "$current" ] || current=/
125
+ done
126
+ lisa_guard_memo_base="$physical"
95
127
  }
96
128
 
97
129
  # Establish the memo base directory.
98
130
  #
99
- # Preferred source is `LISA_GUARD_MEMO_DIR`, exported by the dispatcher, which
100
- # has already resolved `TMPDIR` physically and walked every parent for ownership
101
- # and mode. Guards it spawns therefore pay nothing for that walk. A guard
102
- # reached by the plugin channel has no such parent, so it creates the directory
103
- # itself under `TMPDIR` — private by umask, and used only when this user owns
104
- # it. If neither works the memo is unavailable and every guard evaluates, which
105
- # is today's behaviour.
131
+ # Preferred source is `LISA_GUARD_MEMO_DIR`, exported by the dispatcher. Both
132
+ # this input and the plugin channel's TMPDIR fallback receive the same trust
133
+ # checks. Missing state is created privately; untrusted state disables memo use.
106
134
  lisa_guard_memo_resolve_base() {
107
135
  [ -z "$lisa_guard_memo_base" ] || return 0
108
136
 
109
137
  local candidate="${LISA_GUARD_MEMO_DIR:-}"
110
- if lisa_guard_memo_dir_usable "$candidate"; then
111
- lisa_guard_memo_base="$candidate"
138
+ if lisa_guard_memo_trust_base "$candidate"; then
112
139
  return 0
113
140
  fi
114
141
 
@@ -121,8 +148,7 @@ lisa_guard_memo_resolve_base() {
121
148
  if [ ! -e "$candidate" ] && [ ! -L "$candidate" ]; then
122
149
  (umask 077 && mkdir -p "$candidate") 2>/dev/null || true
123
150
  fi
124
- if lisa_guard_memo_dir_usable "$candidate"; then
125
- lisa_guard_memo_base="$candidate"
151
+ if lisa_guard_memo_trust_base "$candidate"; then
126
152
  return 0
127
153
  fi
128
154
  return 1
@@ -168,6 +194,7 @@ lisa_guard_memo_sweep() {
168
194
  #
169
195
  # $1 - guard name, used only to name the memo file
170
196
  # $2 - the raw tool payload this guard was handed on stdin
197
+ # Remaining arguments - files containing delegated verdict code
171
198
  lisa_guard_dedupe() {
172
199
  local guard_name="${1:-}"
173
200
  local payload="${2:-}"
@@ -197,8 +224,7 @@ lisa_guard_dedupe() {
197
224
  fresh_session=1
198
225
  fi
199
226
  fi
200
- [ -d "$session_dir" ] || return 0
201
- [ ! -L "$session_dir" ] || return 0
227
+ lisa_guard_memo_dir_usable "$session_dir" || return 0
202
228
 
203
229
  # Swept once, by the process that created the session directory, so a
204
230
  # long-lived machine does not accumulate memo state. Off the hot path by
@@ -281,11 +307,18 @@ lisa_guard_dedupe() {
281
307
  case "$transcript_size" in "" | *[!0-9]*) return 0 ;; esac
282
308
 
283
309
  # One command substitution, covering everything the verdict depends on: the
284
- # guard's own bytes, this library's bytes, the payload, and which call it is.
310
+ # guard's own bytes, this library, delegated code, payload, and call identity.
311
+ # Keep literal bytes (including trailing newlines), and propagate every read
312
+ # failure. A successful digest of a partial stream is not proof of identity.
285
313
  local key=""
286
314
  key="$(
315
+ set -o pipefail
287
316
  {
288
- cat "$self_script" "${BASH_SOURCE[0]}" 2>/dev/null
317
+ local source_file=""
318
+ for source_file in "$self_script" "${BASH_SOURCE[0]}" "${@:3}"; do
319
+ cat "$source_file" 2>/dev/null || exit 1
320
+ printf '\0'
321
+ done
289
322
  printf '\n%s\n' "$payload"
290
323
  printf '%s\n' "$transcript_size"
291
324
  } | if [ "$lisa_guard_memo_digest_tool" = "shasum" ]; then
@@ -296,6 +329,7 @@ lisa_guard_dedupe() {
296
329
  )" || key=""
297
330
  key="${key%% *}"
298
331
  case "$key" in "" | *[!0-9a-f]*) return 0 ;; esac
332
+ [ "${#key}" -eq 64 ] || return 0
299
333
 
300
334
  # One memo file per guard per session, rewritten as the call moves on, so the
301
335
  # state directory stays a fixed size however long a session runs. A saturated
@@ -139,7 +139,8 @@ if [ -r "$lisa_guard_dedupe_lib" ]; then
139
139
  # shellcheck source=guard-dedupe.bash
140
140
  . "$lisa_guard_dedupe_lib"
141
141
  trap 'lisa_guard_dedupe_record $?' EXIT
142
- lisa_guard_dedupe parity-safety-net "$input"
142
+ lisa_guard_dedupe parity-safety-net "$input" \
143
+ "$lisa_guard_hook_dir/parity-safety-net-heredoc.py"
143
144
  fi
144
145
 
145
146
  tool_name="$(printf '%s' "$input" | jq -r '.tool_name // empty')"
@@ -57,7 +57,8 @@ if [ -r "$lisa_guard_dedupe_lib" ]; then
57
57
  # shellcheck source=guard-dedupe.bash
58
58
  . "$lisa_guard_dedupe_lib"
59
59
  trap 'lisa_guard_dedupe_record $?' EXIT
60
- lisa_guard_dedupe worktree-binding-guard "$input"
60
+ lisa_guard_dedupe worktree-binding-guard "$input" \
61
+ "$lisa_guard_hook_dir/worktree-binding-guard.mjs"
61
62
  fi
62
63
 
63
64
  if ! command -v node >/dev/null 2>&1; then
@@ -679,7 +679,7 @@ function summarise(result, priorKills = []) {
679
679
  `🛑 ${result.moment}: INTERRUPTED, not completed — ` +
680
680
  `${result.interrupted}. ${result.passed.length} of ` +
681
681
  `${result.total} gate(s) were proved before it stopped; the rest ` +
682
- `were not run. This is NOT a pass.`
682
+ `were not proved. This is NOT a pass.`
683
683
  );
684
684
  }
685
685
  return lines;
@@ -817,7 +817,7 @@ function verdictFor(gate, { proved, blockedBy, exec, siblings, declarations }) {
817
817
  * @param {function({kind: string, gateId: string}): boolean} [options.recordKill]
818
818
  * Kill-marker writer, injectable so marker cardinality can be proved without
819
819
  * writing to the host's shared marker directory.
820
- * @param {function(): (string|null)} [options.interrupted] Asks, between gates,
820
+ * @param {function(): (string|null)} [options.interrupted] Asks, between gates and before returning,
821
821
  * whether the run this chain serves is still there. Injectable for the same
822
822
  * reason `exec` is: a test states that the caller went away instead of having
823
823
  * to kill a real hook mid-chain.
@@ -941,6 +941,11 @@ export function runGates({
941
941
  blockedBy = gate.id;
942
942
  }
943
943
 
944
+ // The caller may disappear while the FINAL gate executes. There is then no
945
+ // next iteration to observe it, and an optional gate cannot block on its own.
946
+ // Record the interruption before this result can claim completion.
947
+ interruption ??= interrupted();
948
+
944
949
  const bucket = state => results.filter(entry => entry.state === state);
945
950
  const result = {
946
951
  moment,