@codyswann/lisa 4.55.2 → 4.55.4

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 (395) 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 +92 -80
  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 +38 -15
  33. package/plugins/lisa/.codex-plugin/skills/lisa-github-write-issue/SKILL.md +15 -1
  34. package/plugins/lisa/.codex-plugin/skills/lisa-jira-build-intake/SKILL.md +37 -13
  35. package/plugins/lisa/.codex-plugin/skills/lisa-jira-write-ticket/SKILL.md +15 -1
  36. package/plugins/lisa/.codex-plugin/skills/lisa-linear-access/SKILL.md +56 -7
  37. package/plugins/lisa/.codex-plugin/skills/lisa-linear-build-intake/SKILL.md +36 -11
  38. package/plugins/lisa/.codex-plugin/skills/lisa-linear-prd-intake/SKILL.md +12 -2
  39. package/plugins/lisa/.codex-plugin/skills/lisa-linear-validate-issue/SKILL.md +1 -18
  40. package/plugins/lisa/.codex-plugin/skills/lisa-linear-verify/SKILL.md +0 -17
  41. package/plugins/lisa/.codex-plugin/skills/lisa-linear-write-issue/SKILL.md +18 -35
  42. package/plugins/lisa/.codex-plugin/skills/lisa-prd-ticket-coverage/SKILL.md +2 -2
  43. package/plugins/lisa/.codex-plugin/skills/lisa-repair-intake/SKILL.md +66 -12
  44. package/plugins/lisa/.codex-plugin/skills/lisa-rework-triage/SKILL.md +2 -1
  45. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/providers.mjs +2 -1
  46. package/plugins/lisa/.codex-plugin/skills/lisa-setup-linear/SKILL.md +1 -1
  47. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/preflight-tools.mjs +7 -2
  48. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +11 -4
  49. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/toolchain.mjs +17 -5
  50. package/plugins/lisa/.codex-plugin/skills/lisa-track/SKILL.md +32 -4
  51. package/plugins/lisa/.codex-plugin/skills/lisa-tracker-build-intake/SKILL.md +15 -1
  52. package/plugins/lisa/hooks/block-blind-automerge.sh +13 -5
  53. package/plugins/lisa/hooks/block-direct-issue-create.sh +7 -0
  54. package/plugins/lisa/hooks/block-instruction-file-edits.sh +3 -0
  55. package/plugins/lisa/hooks/block-managed-file-edits.sh +76 -2
  56. package/plugins/lisa/hooks/block-no-verify.sh +3 -0
  57. package/plugins/lisa/hooks/block-shell-json-parsing.sh +3 -0
  58. package/plugins/lisa/hooks/guard-dedupe.bash +66 -29
  59. package/plugins/lisa/hooks/parity-safety-net-heredoc.py +3 -0
  60. package/plugins/lisa/hooks/parity-safety-net.sh +5 -1
  61. package/plugins/lisa/hooks/worktree-binding-guard.mjs +3 -0
  62. package/plugins/lisa/hooks/worktree-binding-guard.sh +5 -1
  63. package/plugins/lisa/rules/eager/00-rule-index.md +1 -0
  64. package/plugins/lisa/rules/eager/base-rules.md +3 -1
  65. package/plugins/lisa/rules/eager/withdrawn-rulings.md +14 -3
  66. package/plugins/lisa/rules/reference/automation-runbook-contract.md +1 -1
  67. package/plugins/lisa/rules/reference/bdd-e2e-coverage.md +1 -1
  68. package/plugins/lisa/rules/reference/blocker-containment.md +3 -3
  69. package/plugins/lisa/rules/reference/claim-archaeology.md +1 -1
  70. package/plugins/lisa/rules/reference/claim-evidence-mapping.md +1 -1
  71. package/plugins/lisa/rules/reference/claim-time-guards.md +1 -1
  72. package/plugins/lisa/rules/reference/control-reachability.md +1 -1
  73. package/plugins/lisa/rules/reference/convergent-review.md +1 -1
  74. package/plugins/lisa/rules/reference/credential-substrate-precedence.md +1 -1
  75. package/plugins/lisa/rules/reference/dependency-decision-records.md +1 -1
  76. package/plugins/lisa/rules/reference/dependency-internalization-kit.md +1 -1
  77. package/plugins/lisa/rules/reference/dependency-trust-classes.md +1 -1
  78. package/plugins/lisa/rules/reference/deployed-state-readback.md +1 -1
  79. package/plugins/lisa/rules/reference/derived-branch-plan.md +1 -1
  80. package/plugins/lisa/rules/reference/design-source-of-truth.md +1 -1
  81. package/plugins/lisa/rules/reference/design-value-binding.md +1 -1
  82. package/plugins/lisa/rules/reference/documentation-source-paths.md +1 -1
  83. package/plugins/lisa/rules/reference/leaf-only-lifecycle.md +1 -1
  84. package/plugins/lisa/rules/reference/observability-audit.md +1 -1
  85. package/plugins/lisa/rules/reference/prd-definition-of-ready.md +1 -1
  86. package/plugins/lisa/rules/reference/prd-lifecycle-rollup.md +1 -1
  87. package/plugins/lisa/rules/reference/pre-flight-autofill.md +1 -1
  88. package/plugins/lisa/rules/reference/promotion-contract.md +1 -1
  89. package/plugins/lisa/rules/reference/readiness-rubric.md +1 -1
  90. package/plugins/lisa/rules/reference/ready-role-filing.md +37 -1
  91. package/plugins/lisa/rules/reference/rejection-detection.md +1 -1
  92. package/plugins/lisa/rules/reference/repo-scope-split.md +9 -10
  93. package/plugins/lisa/rules/reference/reset-seed-coverage.md +1 -1
  94. package/plugins/lisa/rules/reference/usage-accounting.md +1 -1
  95. package/plugins/lisa/rules/reference/work-item-definition-of-ready.md +1 -1
  96. package/plugins/lisa/scripts/intake-blocker-reprobe.mjs +85 -17
  97. package/plugins/lisa/scripts/qa-signal-lifecycle.mjs +5 -2
  98. package/plugins/lisa/skills/lisa-github-build-intake/SKILL.md +38 -15
  99. package/plugins/lisa/skills/lisa-github-write-issue/SKILL.md +15 -1
  100. package/plugins/lisa/skills/lisa-jira-build-intake/SKILL.md +37 -13
  101. package/plugins/lisa/skills/lisa-jira-write-ticket/SKILL.md +15 -1
  102. package/plugins/lisa/skills/lisa-linear-access/SKILL.md +56 -7
  103. package/plugins/lisa/skills/lisa-linear-build-intake/SKILL.md +36 -11
  104. package/plugins/lisa/skills/lisa-linear-prd-intake/SKILL.md +12 -2
  105. package/plugins/lisa/skills/lisa-linear-validate-issue/SKILL.md +1 -18
  106. package/plugins/lisa/skills/lisa-linear-verify/SKILL.md +0 -17
  107. package/plugins/lisa/skills/lisa-linear-write-issue/SKILL.md +18 -35
  108. package/plugins/lisa/skills/lisa-prd-ticket-coverage/SKILL.md +2 -2
  109. package/plugins/lisa/skills/lisa-repair-intake/SKILL.md +66 -12
  110. package/plugins/lisa/skills/lisa-rework-triage/SKILL.md +2 -1
  111. package/plugins/lisa/skills/lisa-secrets-access/scripts/providers.mjs +2 -1
  112. package/plugins/lisa/skills/lisa-setup-linear/SKILL.md +1 -1
  113. package/plugins/lisa/skills/lisa-setup-remote-env/scripts/preflight-tools.mjs +7 -2
  114. package/plugins/lisa/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +11 -4
  115. package/plugins/lisa/skills/lisa-setup-remote-env/scripts/toolchain.mjs +17 -5
  116. package/plugins/lisa/skills/lisa-track/SKILL.md +32 -4
  117. package/plugins/lisa/skills/lisa-tracker-build-intake/SKILL.md +15 -1
  118. package/plugins/lisa-agy/hooks/block-blind-automerge.sh +13 -5
  119. package/plugins/lisa-agy/hooks/block-direct-issue-create.sh +7 -0
  120. package/plugins/lisa-agy/hooks/block-instruction-file-edits.sh +3 -0
  121. package/plugins/lisa-agy/hooks/block-managed-file-edits.sh +76 -2
  122. package/plugins/lisa-agy/hooks/block-shell-json-parsing.sh +3 -0
  123. package/plugins/lisa-agy/hooks/guard-dedupe.bash +66 -29
  124. package/plugins/lisa-agy/hooks/parity-safety-net-heredoc.py +3 -0
  125. package/plugins/lisa-agy/hooks/parity-safety-net.sh +5 -1
  126. package/plugins/lisa-agy/plugin.json +1 -1
  127. package/plugins/lisa-agy/scripts/intake-blocker-reprobe.mjs +85 -17
  128. package/plugins/lisa-agy/scripts/qa-signal-lifecycle.mjs +5 -2
  129. package/plugins/lisa-agy/skills/lisa-github-build-intake/SKILL.md +38 -15
  130. package/plugins/lisa-agy/skills/lisa-github-write-issue/SKILL.md +15 -1
  131. package/plugins/lisa-agy/skills/lisa-jira-build-intake/SKILL.md +37 -13
  132. package/plugins/lisa-agy/skills/lisa-jira-write-ticket/SKILL.md +15 -1
  133. package/plugins/lisa-agy/skills/lisa-linear-access/SKILL.md +56 -7
  134. package/plugins/lisa-agy/skills/lisa-linear-build-intake/SKILL.md +36 -11
  135. package/plugins/lisa-agy/skills/lisa-linear-prd-intake/SKILL.md +12 -2
  136. package/plugins/lisa-agy/skills/lisa-linear-validate-issue/SKILL.md +1 -18
  137. package/plugins/lisa-agy/skills/lisa-linear-verify/SKILL.md +0 -17
  138. package/plugins/lisa-agy/skills/lisa-linear-write-issue/SKILL.md +18 -35
  139. package/plugins/lisa-agy/skills/lisa-prd-ticket-coverage/SKILL.md +2 -2
  140. package/plugins/lisa-agy/skills/lisa-repair-intake/SKILL.md +66 -12
  141. package/plugins/lisa-agy/skills/lisa-rework-triage/SKILL.md +2 -1
  142. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/providers.mjs +2 -1
  143. package/plugins/lisa-agy/skills/lisa-setup-linear/SKILL.md +1 -1
  144. package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/preflight-tools.mjs +7 -2
  145. package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +11 -4
  146. package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/toolchain.mjs +17 -5
  147. package/plugins/lisa-agy/skills/lisa-track/SKILL.md +32 -4
  148. package/plugins/lisa-agy/skills/lisa-tracker-build-intake/SKILL.md +15 -1
  149. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  150. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  151. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  152. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  153. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  154. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  155. package/plugins/lisa-copilot/hooks/block-blind-automerge.sh +13 -5
  156. package/plugins/lisa-copilot/hooks/block-direct-issue-create.sh +7 -0
  157. package/plugins/lisa-copilot/hooks/block-instruction-file-edits.sh +3 -0
  158. package/plugins/lisa-copilot/hooks/block-managed-file-edits.sh +76 -2
  159. package/plugins/lisa-copilot/hooks/block-no-verify.sh +3 -0
  160. package/plugins/lisa-copilot/hooks/block-shell-json-parsing.sh +3 -0
  161. package/plugins/lisa-copilot/hooks/guard-dedupe.bash +66 -29
  162. package/plugins/lisa-copilot/hooks/parity-safety-net-heredoc.py +3 -0
  163. package/plugins/lisa-copilot/hooks/parity-safety-net.sh +5 -1
  164. package/plugins/lisa-copilot/hooks/worktree-binding-guard.mjs +3 -0
  165. package/plugins/lisa-copilot/hooks/worktree-binding-guard.sh +5 -1
  166. package/plugins/lisa-copilot/rules/eager/00-rule-index.md +1 -0
  167. package/plugins/lisa-copilot/rules/eager/base-rules.md +3 -1
  168. package/plugins/lisa-copilot/rules/eager/withdrawn-rulings.md +14 -3
  169. package/plugins/lisa-copilot/rules/reference/automation-runbook-contract.md +1 -1
  170. package/plugins/lisa-copilot/rules/reference/bdd-e2e-coverage.md +1 -1
  171. package/plugins/lisa-copilot/rules/reference/blocker-containment.md +3 -3
  172. package/plugins/lisa-copilot/rules/reference/claim-archaeology.md +1 -1
  173. package/plugins/lisa-copilot/rules/reference/claim-evidence-mapping.md +1 -1
  174. package/plugins/lisa-copilot/rules/reference/claim-time-guards.md +1 -1
  175. package/plugins/lisa-copilot/rules/reference/control-reachability.md +1 -1
  176. package/plugins/lisa-copilot/rules/reference/convergent-review.md +1 -1
  177. package/plugins/lisa-copilot/rules/reference/credential-substrate-precedence.md +1 -1
  178. package/plugins/lisa-copilot/rules/reference/dependency-decision-records.md +1 -1
  179. package/plugins/lisa-copilot/rules/reference/dependency-internalization-kit.md +1 -1
  180. package/plugins/lisa-copilot/rules/reference/dependency-trust-classes.md +1 -1
  181. package/plugins/lisa-copilot/rules/reference/deployed-state-readback.md +1 -1
  182. package/plugins/lisa-copilot/rules/reference/derived-branch-plan.md +1 -1
  183. package/plugins/lisa-copilot/rules/reference/design-source-of-truth.md +1 -1
  184. package/plugins/lisa-copilot/rules/reference/design-value-binding.md +1 -1
  185. package/plugins/lisa-copilot/rules/reference/documentation-source-paths.md +1 -1
  186. package/plugins/lisa-copilot/rules/reference/leaf-only-lifecycle.md +1 -1
  187. package/plugins/lisa-copilot/rules/reference/observability-audit.md +1 -1
  188. package/plugins/lisa-copilot/rules/reference/prd-definition-of-ready.md +1 -1
  189. package/plugins/lisa-copilot/rules/reference/prd-lifecycle-rollup.md +1 -1
  190. package/plugins/lisa-copilot/rules/reference/pre-flight-autofill.md +1 -1
  191. package/plugins/lisa-copilot/rules/reference/promotion-contract.md +1 -1
  192. package/plugins/lisa-copilot/rules/reference/readiness-rubric.md +1 -1
  193. package/plugins/lisa-copilot/rules/reference/ready-role-filing.md +37 -1
  194. package/plugins/lisa-copilot/rules/reference/rejection-detection.md +1 -1
  195. package/plugins/lisa-copilot/rules/reference/repo-scope-split.md +9 -10
  196. package/plugins/lisa-copilot/rules/reference/reset-seed-coverage.md +1 -1
  197. package/plugins/lisa-copilot/rules/reference/usage-accounting.md +1 -1
  198. package/plugins/lisa-copilot/rules/reference/work-item-definition-of-ready.md +1 -1
  199. package/plugins/lisa-copilot/scripts/intake-blocker-reprobe.mjs +85 -17
  200. package/plugins/lisa-copilot/scripts/qa-signal-lifecycle.mjs +5 -2
  201. package/plugins/lisa-copilot/skills/lisa-github-build-intake/SKILL.md +38 -15
  202. package/plugins/lisa-copilot/skills/lisa-github-write-issue/SKILL.md +15 -1
  203. package/plugins/lisa-copilot/skills/lisa-jira-build-intake/SKILL.md +37 -13
  204. package/plugins/lisa-copilot/skills/lisa-jira-write-ticket/SKILL.md +15 -1
  205. package/plugins/lisa-copilot/skills/lisa-linear-access/SKILL.md +56 -7
  206. package/plugins/lisa-copilot/skills/lisa-linear-build-intake/SKILL.md +36 -11
  207. package/plugins/lisa-copilot/skills/lisa-linear-prd-intake/SKILL.md +12 -2
  208. package/plugins/lisa-copilot/skills/lisa-linear-validate-issue/SKILL.md +1 -18
  209. package/plugins/lisa-copilot/skills/lisa-linear-verify/SKILL.md +0 -17
  210. package/plugins/lisa-copilot/skills/lisa-linear-write-issue/SKILL.md +18 -35
  211. package/plugins/lisa-copilot/skills/lisa-prd-ticket-coverage/SKILL.md +2 -2
  212. package/plugins/lisa-copilot/skills/lisa-repair-intake/SKILL.md +66 -12
  213. package/plugins/lisa-copilot/skills/lisa-rework-triage/SKILL.md +2 -1
  214. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/providers.mjs +2 -1
  215. package/plugins/lisa-copilot/skills/lisa-setup-linear/SKILL.md +1 -1
  216. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/preflight-tools.mjs +7 -2
  217. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +11 -4
  218. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/toolchain.mjs +17 -5
  219. package/plugins/lisa-copilot/skills/lisa-track/SKILL.md +32 -4
  220. package/plugins/lisa-copilot/skills/lisa-tracker-build-intake/SKILL.md +15 -1
  221. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  222. package/plugins/lisa-cursor/hooks/block-blind-automerge.sh +13 -5
  223. package/plugins/lisa-cursor/hooks/block-direct-issue-create.sh +7 -0
  224. package/plugins/lisa-cursor/hooks/block-instruction-file-edits.sh +3 -0
  225. package/plugins/lisa-cursor/hooks/block-managed-file-edits.sh +76 -2
  226. package/plugins/lisa-cursor/hooks/block-no-verify.sh +3 -0
  227. package/plugins/lisa-cursor/hooks/block-shell-json-parsing.sh +3 -0
  228. package/plugins/lisa-cursor/hooks/guard-dedupe.bash +66 -29
  229. package/plugins/lisa-cursor/hooks/parity-safety-net-heredoc.py +3 -0
  230. package/plugins/lisa-cursor/hooks/parity-safety-net.sh +5 -1
  231. package/plugins/lisa-cursor/hooks/worktree-binding-guard.mjs +3 -0
  232. package/plugins/lisa-cursor/hooks/worktree-binding-guard.sh +5 -1
  233. package/plugins/lisa-cursor/rules/00-rule-index.mdc +1 -0
  234. package/plugins/lisa-cursor/rules/automation-runbook-contract-reference.mdc +1 -1
  235. package/plugins/lisa-cursor/rules/base-rules.mdc +3 -1
  236. package/plugins/lisa-cursor/rules/bdd-e2e-coverage-reference.mdc +1 -1
  237. package/plugins/lisa-cursor/rules/blocker-containment-reference.mdc +3 -3
  238. package/plugins/lisa-cursor/rules/claim-archaeology-reference.mdc +1 -1
  239. package/plugins/lisa-cursor/rules/claim-evidence-mapping-reference.mdc +1 -1
  240. package/plugins/lisa-cursor/rules/claim-time-guards-reference.mdc +1 -1
  241. package/plugins/lisa-cursor/rules/control-reachability-reference.mdc +1 -1
  242. package/plugins/lisa-cursor/rules/convergent-review-reference.mdc +1 -1
  243. package/plugins/lisa-cursor/rules/credential-substrate-precedence-reference.mdc +1 -1
  244. package/plugins/lisa-cursor/rules/dependency-decision-records-reference.mdc +1 -1
  245. package/plugins/lisa-cursor/rules/dependency-internalization-kit-reference.mdc +1 -1
  246. package/plugins/lisa-cursor/rules/dependency-trust-classes-reference.mdc +1 -1
  247. package/plugins/lisa-cursor/rules/deployed-state-readback-reference.mdc +1 -1
  248. package/plugins/lisa-cursor/rules/derived-branch-plan-reference.mdc +1 -1
  249. package/plugins/lisa-cursor/rules/design-source-of-truth-reference.mdc +1 -1
  250. package/plugins/lisa-cursor/rules/design-value-binding-reference.mdc +1 -1
  251. package/plugins/lisa-cursor/rules/documentation-source-paths-reference.mdc +1 -1
  252. package/plugins/lisa-cursor/rules/leaf-only-lifecycle-reference.mdc +1 -1
  253. package/plugins/lisa-cursor/rules/observability-audit-reference.mdc +1 -1
  254. package/plugins/lisa-cursor/rules/prd-definition-of-ready-reference.mdc +1 -1
  255. package/plugins/lisa-cursor/rules/prd-lifecycle-rollup-reference.mdc +1 -1
  256. package/plugins/lisa-cursor/rules/pre-flight-autofill-reference.mdc +1 -1
  257. package/plugins/lisa-cursor/rules/promotion-contract-reference.mdc +1 -1
  258. package/plugins/lisa-cursor/rules/readiness-rubric-reference.mdc +1 -1
  259. package/plugins/lisa-cursor/rules/ready-role-filing-reference.mdc +37 -1
  260. package/plugins/lisa-cursor/rules/rejection-detection-reference.mdc +1 -1
  261. package/plugins/lisa-cursor/rules/repo-scope-split-reference.mdc +9 -10
  262. package/plugins/lisa-cursor/rules/reset-seed-coverage-reference.mdc +1 -1
  263. package/plugins/lisa-cursor/rules/usage-accounting-reference.mdc +1 -1
  264. package/plugins/lisa-cursor/rules/withdrawn-rulings.mdc +14 -3
  265. package/plugins/lisa-cursor/rules/work-item-definition-of-ready-reference.mdc +1 -1
  266. package/plugins/lisa-cursor/scripts/intake-blocker-reprobe.mjs +85 -17
  267. package/plugins/lisa-cursor/scripts/qa-signal-lifecycle.mjs +5 -2
  268. package/plugins/lisa-cursor/skills/lisa-github-build-intake/SKILL.md +38 -15
  269. package/plugins/lisa-cursor/skills/lisa-github-write-issue/SKILL.md +15 -1
  270. package/plugins/lisa-cursor/skills/lisa-jira-build-intake/SKILL.md +37 -13
  271. package/plugins/lisa-cursor/skills/lisa-jira-write-ticket/SKILL.md +15 -1
  272. package/plugins/lisa-cursor/skills/lisa-linear-access/SKILL.md +56 -7
  273. package/plugins/lisa-cursor/skills/lisa-linear-build-intake/SKILL.md +36 -11
  274. package/plugins/lisa-cursor/skills/lisa-linear-prd-intake/SKILL.md +12 -2
  275. package/plugins/lisa-cursor/skills/lisa-linear-validate-issue/SKILL.md +1 -18
  276. package/plugins/lisa-cursor/skills/lisa-linear-verify/SKILL.md +0 -17
  277. package/plugins/lisa-cursor/skills/lisa-linear-write-issue/SKILL.md +18 -35
  278. package/plugins/lisa-cursor/skills/lisa-prd-ticket-coverage/SKILL.md +2 -2
  279. package/plugins/lisa-cursor/skills/lisa-repair-intake/SKILL.md +66 -12
  280. package/plugins/lisa-cursor/skills/lisa-rework-triage/SKILL.md +2 -1
  281. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/providers.mjs +2 -1
  282. package/plugins/lisa-cursor/skills/lisa-setup-linear/SKILL.md +1 -1
  283. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/preflight-tools.mjs +7 -2
  284. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +11 -4
  285. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/toolchain.mjs +17 -5
  286. package/plugins/lisa-cursor/skills/lisa-track/SKILL.md +32 -4
  287. package/plugins/lisa-cursor/skills/lisa-tracker-build-intake/SKILL.md +15 -1
  288. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  289. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  290. package/plugins/lisa-expo-agy/plugin.json +1 -1
  291. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  292. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  293. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  294. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  295. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  296. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  297. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  298. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  299. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  300. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  301. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  302. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  303. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  304. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  305. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  306. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  307. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  308. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  309. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  310. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  311. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  312. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  313. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  314. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  315. package/plugins/lisa-rails-agy/plugin.json +1 -1
  316. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  317. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  318. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  319. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  320. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  321. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  322. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  323. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  324. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  325. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  326. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  327. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  328. package/plugins/materialized-artifacts.json +11 -0
  329. package/plugins/src/base/hooks/block-blind-automerge.sh +10 -5
  330. package/plugins/src/base/hooks/block-direct-issue-create.sh +4 -0
  331. package/plugins/src/base/hooks/block-managed-file-edits.sh +73 -2
  332. package/plugins/src/base/hooks/guard-dedupe.bash +63 -29
  333. package/plugins/src/base/hooks/parity-safety-net.sh +2 -1
  334. package/plugins/src/base/hooks/worktree-binding-guard.sh +2 -1
  335. package/plugins/src/base/rules/eager/00-rule-index.md +1 -0
  336. package/plugins/src/base/rules/eager/base-rules.md +3 -1
  337. package/plugins/src/base/rules/eager/withdrawn-rulings.md +14 -3
  338. package/plugins/src/base/rules/reference/automation-runbook-contract.md +1 -1
  339. package/plugins/src/base/rules/reference/bdd-e2e-coverage.md +1 -1
  340. package/plugins/src/base/rules/reference/blocker-containment.md +3 -3
  341. package/plugins/src/base/rules/reference/claim-archaeology.md +1 -1
  342. package/plugins/src/base/rules/reference/claim-evidence-mapping.md +1 -1
  343. package/plugins/src/base/rules/reference/claim-time-guards.md +1 -1
  344. package/plugins/src/base/rules/reference/control-reachability.md +1 -1
  345. package/plugins/src/base/rules/reference/convergent-review.md +1 -1
  346. package/plugins/src/base/rules/reference/credential-substrate-precedence.md +1 -1
  347. package/plugins/src/base/rules/reference/dependency-decision-records.md +1 -1
  348. package/plugins/src/base/rules/reference/dependency-internalization-kit.md +1 -1
  349. package/plugins/src/base/rules/reference/dependency-trust-classes.md +1 -1
  350. package/plugins/src/base/rules/reference/deployed-state-readback.md +1 -1
  351. package/plugins/src/base/rules/reference/derived-branch-plan.md +1 -1
  352. package/plugins/src/base/rules/reference/design-source-of-truth.md +1 -1
  353. package/plugins/src/base/rules/reference/design-value-binding.md +1 -1
  354. package/plugins/src/base/rules/reference/documentation-source-paths.md +1 -1
  355. package/plugins/src/base/rules/reference/leaf-only-lifecycle.md +1 -1
  356. package/plugins/src/base/rules/reference/observability-audit.md +1 -1
  357. package/plugins/src/base/rules/reference/prd-definition-of-ready.md +1 -1
  358. package/plugins/src/base/rules/reference/prd-lifecycle-rollup.md +1 -1
  359. package/plugins/src/base/rules/reference/pre-flight-autofill.md +1 -1
  360. package/plugins/src/base/rules/reference/promotion-contract.md +1 -1
  361. package/plugins/src/base/rules/reference/readiness-rubric.md +1 -1
  362. package/plugins/src/base/rules/reference/ready-role-filing.md +37 -1
  363. package/plugins/src/base/rules/reference/rejection-detection.md +1 -1
  364. package/plugins/src/base/rules/reference/repo-scope-split.md +9 -10
  365. package/plugins/src/base/rules/reference/reset-seed-coverage.md +1 -1
  366. package/plugins/src/base/rules/reference/usage-accounting.md +1 -1
  367. package/plugins/src/base/rules/reference/work-item-definition-of-ready.md +1 -1
  368. package/plugins/src/base/scripts/intake-blocker-reprobe.mjs +85 -17
  369. package/plugins/src/base/scripts/qa-signal-lifecycle.mjs +5 -2
  370. package/plugins/src/base/skills/lisa-github-build-intake/SKILL.md +38 -15
  371. package/plugins/src/base/skills/lisa-github-write-issue/SKILL.md +15 -1
  372. package/plugins/src/base/skills/lisa-jira-build-intake/SKILL.md +37 -13
  373. package/plugins/src/base/skills/lisa-jira-write-ticket/SKILL.md +15 -1
  374. package/plugins/src/base/skills/lisa-linear-access/SKILL.md +56 -7
  375. package/plugins/src/base/skills/lisa-linear-build-intake/SKILL.md +36 -11
  376. package/plugins/src/base/skills/lisa-linear-prd-intake/SKILL.md +12 -2
  377. package/plugins/src/base/skills/lisa-linear-validate-issue/SKILL.md +1 -18
  378. package/plugins/src/base/skills/lisa-linear-verify/SKILL.md +0 -17
  379. package/plugins/src/base/skills/lisa-linear-write-issue/SKILL.md +18 -35
  380. package/plugins/src/base/skills/lisa-prd-ticket-coverage/SKILL.md +2 -2
  381. package/plugins/src/base/skills/lisa-repair-intake/SKILL.md +66 -12
  382. package/plugins/src/base/skills/lisa-rework-triage/SKILL.md +2 -1
  383. package/plugins/src/base/skills/lisa-secrets-access/scripts/providers.mjs +2 -1
  384. package/plugins/src/base/skills/lisa-setup-linear/SKILL.md +1 -1
  385. package/plugins/src/base/skills/lisa-setup-remote-env/scripts/preflight-tools.mjs +7 -2
  386. package/plugins/src/base/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +11 -4
  387. package/plugins/src/base/skills/lisa-setup-remote-env/scripts/toolchain.mjs +17 -5
  388. package/plugins/src/base/skills/lisa-track/SKILL.md +32 -4
  389. package/plugins/src/base/skills/lisa-tracker-build-intake/SKILL.md +15 -1
  390. package/scripts/build-plugins.sh +4 -0
  391. package/scripts/copy-opencode-plugin-templates.mjs +5 -1
  392. package/scripts/lisa-enforcement-fallback.sh +9 -1
  393. package/scripts/two-channel-couplings.json +5 -33
  394. package/typescript/copy-overwrite/scripts/check-skipped-required-checks.mjs +551 -13
  395. package/typescript/create-only/.github/required-checks.json +1 -1
@@ -259,18 +259,77 @@ export function humanGateHolds(body) {
259
259
  }
260
260
 
261
261
  /**
262
- * The reason keys every recorded release discharges, deduplicated.
262
+ * Normalize stable provider IDs without case folding or trusting display names.
263
+ * @param {unknown} value - Provider actor ID
264
+ * @returns {string} Stable comparison key, or empty when invalid
265
+ */
266
+ function stableActorId(value) {
267
+ if (typeof value === "string") return value.trim();
268
+ return Number.isSafeInteger(value) && value > 0 ? String(value) : "";
269
+ }
270
+
271
+ /**
272
+ * Recognize explicit provider bot metadata even for an allowlisted identity.
273
+ * @param {unknown} actor - Provider or normalized author metadata
274
+ * @returns {boolean} Whether the metadata identifies automation
275
+ */
276
+ function isKnownBot(actor) {
277
+ if (!actor || typeof actor !== "object") return false;
278
+ const kinds = [
279
+ actor.type,
280
+ actor.__typename,
281
+ actor.accountType,
282
+ actor.authorType,
283
+ ];
284
+ return (
285
+ actor.botActor != null ||
286
+ actor.isBot === true ||
287
+ actor.bot === true ||
288
+ kinds.some(kind => /^(?:bot|app)$/i.test(String(kind ?? "").trim()))
289
+ );
290
+ }
291
+
292
+ /**
293
+ * Require authenticated provider identity and separately supplied human trust.
294
+ * Adapters may normalize the ID to authorId, but must preserve bot metadata.
295
+ * Names, body text and self-asserted authorization flags never confer trust.
296
+ * @param {unknown} comment - Structured provider comment
297
+ * @param {ReadonlySet<string>} trustedIds - Explicitly trusted human actor IDs
298
+ * @returns {boolean} Whether the comment may discharge a human hold
299
+ */
300
+ function isTrustedHumanComment(comment, trustedIds) {
301
+ if (!comment || typeof comment !== "object" || Array.isArray(comment))
302
+ return false;
303
+ if ([comment, comment.user, comment.author].some(isKnownBot)) return false;
304
+ const id = stableActorId(
305
+ comment.authorId ??
306
+ comment.user?.id ??
307
+ comment.author?.id ??
308
+ comment.author?.accountId
309
+ );
310
+ return id.length > 0 && trustedIds.has(id);
311
+ }
312
+
313
+ /**
314
+ * The reason keys released by explicitly trusted human authors, deduplicated.
263
315
  *
264
- * Accepts comment bodies as strings or as `{ body }` objects, because the three
265
- * vendor readers return both shapes and a release that a vendor's payload shape
266
- * could hide would be a release path that exists on one tracker.
267
- * @param {unknown} comments - Comment bodies, any order
268
- * @returns {string[]} One key per recorded release
316
+ * Comments must retain authenticated provider authorship: normalized authorId,
317
+ * user.id, author.id or author.accountId. The caller supplies trusted human IDs
318
+ * independently of comment content. Raw strings, missing IDs, known bots and
319
+ * an absent or empty allowlist fail closed and cannot discharge a hold.
320
+ * @param {unknown} comments - Structured comments, any order
321
+ * @param {unknown} trustedHumanActorIds - Explicit trusted human provider IDs
322
+ * @returns {string[]} One key per authorized recorded release
269
323
  */
270
- export function humanGateReleases(comments) {
271
- const bodies = (Array.isArray(comments) ? comments : []).map(entry =>
272
- typeof entry === "string" ? entry : (entry?.body ?? "")
324
+ export function humanGateReleases(comments, trustedHumanActorIds = []) {
325
+ const trustedIds = new Set(
326
+ (Array.isArray(trustedHumanActorIds) ? trustedHumanActorIds : [])
327
+ .map(stableActorId)
328
+ .filter(Boolean)
273
329
  );
330
+ const bodies = (Array.isArray(comments) ? comments : [])
331
+ .filter(entry => isTrustedHumanComment(entry, trustedIds))
332
+ .map(entry => entry.body ?? "");
274
333
  const keys = bodies.flatMap(body =>
275
334
  declarativeText(body)
276
335
  .split("\n")
@@ -288,11 +347,13 @@ export function humanGateReleases(comments) {
288
347
  * the unsafe direction — the one this fix must not introduce while correcting
289
348
  * the safe one. An item with no declared body hold is discharged only by a
290
349
  * keyless release, which is the form that pairs with a label-only hold.
291
- * @param {{ body?: unknown, comments?: unknown }} input - The item's surfaces
350
+ * @param {{ body?: unknown, comments?: unknown, trustedHumanActorIds?: unknown }} input - The item's surfaces
292
351
  * @returns {boolean} True when nothing is left outstanding
293
352
  */
294
353
  export function humanGateDischarged(input = {}) {
295
- const released = new Set(humanGateReleases(input.comments));
354
+ const released = new Set(
355
+ humanGateReleases(input.comments, input.trustedHumanActorIds)
356
+ );
296
357
  const holds = humanGateHolds(input.body);
297
358
  if (holds.length === 0) return released.has("");
298
359
  return holds.every(reason => released.has(reason));
@@ -319,7 +380,7 @@ export function humanGateDischarged(input = {}) {
319
380
  * no releases, so a held item stays held. Holding a gate that may be stale
320
381
  * beats releasing one that is not, and every caller that never passes comments
321
382
  * keeps its current behaviour by construction.
322
- * @param {{ labels?: unknown, body?: unknown, comments?: unknown, humanNeededLabel?: unknown }} input - Candidate surfaces
383
+ * @param {{ labels?: unknown, body?: unknown, comments?: unknown, trustedHumanActorIds?: unknown, humanNeededLabel?: unknown }} input - Candidate surfaces
323
384
  * @returns {{ held: boolean, reason: string, declared: number, outstanding: string[], released: string[], labelPresent: boolean }} Held-ness verdict
324
385
  */
325
386
  export function humanGateVerdict(input = {}) {
@@ -330,7 +391,10 @@ export function humanGateVerdict(input = {}) {
330
391
  .toLowerCase();
331
392
  const labelPresent =
332
393
  configured.length > 0 && normalizeLabels(input.labels).includes(configured);
333
- const released = humanGateReleases(input.comments);
394
+ const released = humanGateReleases(
395
+ input.comments,
396
+ input.trustedHumanActorIds
397
+ );
334
398
  const releasedSet = new Set(released);
335
399
  const holds = humanGateHolds(input.body);
336
400
  const outstanding = holds.filter(reason => !releasedSet.has(reason));
@@ -370,9 +434,9 @@ export function humanGateVerdict(input = {}) {
370
434
  * It is also the only place a hold ENDS, which is why the release lives behind
371
435
  * the same call rather than beside it: a reader that could see the hold but not
372
436
  * its discharge is how the gate became one-way in the first place. Pass the
373
- * item's comments and a recorded release is honoured; omit them and the item
374
- * stays held.
375
- * @param {{ labels?: unknown, body?: unknown, comments?: unknown, humanNeededLabel?: unknown }} input - Candidate surfaces
437
+ * item's structured comments and trustedHumanActorIds to honor an authorized
438
+ * release; omit either and the item stays held.
439
+ * @param {{ labels?: unknown, body?: unknown, comments?: unknown, trustedHumanActorIds?: unknown, humanNeededLabel?: unknown }} input - Candidate surfaces
376
440
  * @returns {boolean} True when a person is holding this item
377
441
  */
378
442
  export function isHumanGated(input) {
@@ -423,6 +487,7 @@ function judgeProbe(probe) {
423
487
  * labels?: unknown
424
488
  * body?: unknown
425
489
  * comments?: unknown
490
+ * trustedHumanActorIds?: unknown
426
491
  * humanNeededLabel?: unknown
427
492
  * statedBlocker?: unknown
428
493
  * probe?: { discharged?: unknown, evidence?: unknown } | null
@@ -535,7 +600,7 @@ export const HUMAN_GATE_NOTE_MARKER = "<!-- [lisa-human-gate-reconciled] -->";
535
600
  * a parser keyed on the structured field would miss those while appearing to
536
601
  * work on every item that happens to have one — reproducing this defect one
537
602
  * layer down.
538
- * @param {{ labels?: unknown, body?: unknown, comments?: unknown, humanNeededLabel?: unknown }} input - Candidate surfaces
603
+ * @param {{ labels?: unknown, body?: unknown, comments?: unknown, trustedHumanActorIds?: unknown, humanNeededLabel?: unknown }} input - Candidate surfaces
539
604
  * @returns {{ claimable: boolean, reason: string, humanGated: boolean }} Claim verdict
540
605
  */
541
606
  export function classifyReadyCandidate(input = {}) {
@@ -564,6 +629,7 @@ export function classifyReadyCandidate(input = {}) {
564
629
  * labels?: unknown
565
630
  * body?: unknown
566
631
  * comments?: unknown
632
+ * trustedHumanActorIds?: unknown
567
633
  * humanNeededLabel?: unknown
568
634
  * readyLabel?: unknown
569
635
  * alreadyNotified?: unknown
@@ -670,6 +736,7 @@ const RESUME_INSTRUCTION = [
670
736
  * labels?: unknown
671
737
  * body?: unknown
672
738
  * comments?: unknown
739
+ * trustedHumanActorIds?: unknown
673
740
  * humanNeededLabel?: unknown
674
741
  * readyLabel?: unknown
675
742
  * lifecycleLabels?: unknown
@@ -831,6 +898,7 @@ export const NORMALIZATION_HOLD_NOTE_MARKER =
831
898
  * labels?: unknown
832
899
  * body?: unknown
833
900
  * comments?: unknown
901
+ * trustedHumanActorIds?: unknown
834
902
  * humanNeededLabel?: unknown
835
903
  * lifecycleLabels?: unknown
836
904
  * readyLabel?: unknown
@@ -239,8 +239,8 @@ export const VOID_PREDICATES = Object.freeze({
239
239
  "certified-role-reached": ({ role, resolvedRoles }) =>
240
240
  normalize(role) !== "" &&
241
241
  resolvedRoles.some(name => normalize(name) === normalize(role)),
242
- "human-gate-release-recorded": ({ body, comments }) =>
243
- humanGateDischarged({ body, comments }),
242
+ "human-gate-release-recorded": ({ body, comments, trustedHumanActorIds }) =>
243
+ humanGateDischarged({ body, comments, trustedHumanActorIds }),
244
244
  });
245
245
 
246
246
  /**
@@ -331,6 +331,7 @@ const labelNames = labels =>
331
331
  * @param {string} [options.signal] a key of {@link SIGNALS}
332
332
  * @param {readonly unknown[]} [options.labels] the item's labels
333
333
  * @param {readonly unknown[]} [options.comments] comment bodies, oldest first
334
+ * @param {readonly unknown[]} [options.trustedHumanActorIds] explicitly trusted human author identities
334
335
  * @param {unknown} [options.body] the item's description, for body-marker rows
335
336
  * @param {string} [options.role] the item's current lifecycle role name
336
337
  * @param {string} options.vendor `jira` | `linear` | `github`
@@ -341,6 +342,7 @@ export const evaluateSignal = ({
341
342
  signal = QA_FAILURE_SIGNAL,
342
343
  labels,
343
344
  comments,
345
+ trustedHumanActorIds,
344
346
  body,
345
347
  role = "",
346
348
  vendor,
@@ -380,6 +382,7 @@ export const evaluateSignal = ({
380
382
  role,
381
383
  body,
382
384
  comments,
385
+ trustedHumanActorIds,
383
386
  resolvedRoles: voidingRoles({ vendor, config }),
384
387
  };
385
388
  const unchecked = declared.voidConditions.filter(
@@ -6,6 +6,20 @@ allowed-tools: ["Skill", "Bash"]
6
6
 
7
7
  # GitHub Build Intake: $ARGUMENTS
8
8
 
9
+ ## Human-gate release authorization
10
+
11
+ A release requires a trusted human author, not just matching comment text. Follow
12
+ `ready-role-filing` — **Human-gate release authorization**: preserve tracker-supplied comment
13
+ author IDs and bot metadata, resolve `trustedHumanActorIds` only from an explicit user instruction
14
+ or existing human-authored trusted project policy, and pass it with structured `comments` to every
15
+ hold classifier, reconciliation, normalization and release planner. Never derive trust from the
16
+ comment body, a display name, the actor's own assertion, or an automation posting on its own behalf.
17
+ Missing policy, missing/unreadable author identity, raw body strings, untrusted actors and known bots
18
+ cannot discharge a hold. Keep the item held and report the missing authorization; do not silently
19
+ replace these inputs with an empty history or an inferred allowlist. Authorized matching releases
20
+ continue through the existing path and never override an independently declared caller hold.
21
+
22
+
9
23
  `$ARGUMENTS` is one of:
10
24
 
11
25
  1. A GitHub `org/repo` token (e.g., `acme/frontend-v2`).
@@ -215,14 +229,21 @@ nothing survives, exit on the denominator-stated summary from `summarizeDryLane`
215
229
  A blocker is a **claim with a timestamp, not a fact** — it goes stale the moment its condition comes
216
230
  true, and nothing re-read one before this phase. For each `$BLOCKED` candidate:
217
231
 
218
- 1. **Human gate first, and it is absolute.** An issue carrying the configured human-needed label
219
- (`github.labels.build.human_needed`, default `human-needed`) or a `[lisa-human-gate]` marker in
220
- its body is **never** auto-selected, whatever any probe says.
232
+ 1. **Read release comments before applying the human gate.** Pass all item comments, labels,
233
+ body, `trustedHumanActorIds` and configured `humanNeededLabel` to `classifyReadyCandidate(...)`. For a discharged hold,
234
+ call `planHumanGateRelease({ labels, body, comments, trustedHumanActorIds, humanNeededLabel, readyLabel, lifecycleLabels, alreadyNotified })`
235
+ and retain its plan. Apply human-marker cleanup only as planned; defer any ready-role restoration
236
+ until steps 2–5 have cleared the remaining blockers (map the role to the vendor's state or label).
237
+ A still-active hold is never auto-selected, whatever any probe says. An absent or unreadable
238
+ comment history cannot discharge a hold. A release removes only the human hold: continue the
239
+ remaining blocker checks below before selecting the item.
240
+
221
241
  2. **Extract the stated discharge condition**, then **probe it**. Machine-testable conditions — a
222
242
  version on trunk, a published package, a CI run history, an advisory's patched status — rot
223
243
  fastest and are cheapest to check. A human decision is not machine-testable; leave it.
224
- 3. **Classify with `classifyPreWorkCandidate(...)`** from `scripts/intake-blocker-reprobe.mjs`. A
225
- discharge with no recorded evidence is not a discharge, and neither is a candidate nothing
244
+ 3. **Classify with `classifyPreWorkCandidate(...)`** from `scripts/intake-blocker-reprobe.mjs`.
245
+ Include the structured `comments` and `trustedHumanActorIds` from step 1.
246
+ A discharge with no recorded evidence is not a discharge, and neither is a candidate nothing
226
247
  probed this cycle — the helper refuses both.
227
248
  4. **Record the result on the issue either way** via `formatReprobeNote(...)` as a comment, so the
228
249
  next cycle reads the answer rather than re-deriving it. Keep it idempotent.
@@ -317,7 +338,7 @@ other gate's verdict — however conclusive — may promote an item a person par
317
338
  drift, and a drifted gate fails *silently*, by quietly ceasing to match. Do **not** re-implement
318
339
  the test here, and do **not** key it on `reason=`: markers in the wild carry no `reason=` key at
319
340
  all and sit anywhere in the body, so a structured parse would miss them while appearing to work
320
- on every item that happens to have one. **Pass the item's `comments` alongside its labels and
341
+ on every item that happens to have one. **Pass the item's structured `comments` and `trustedHumanActorIds` alongside its labels and
321
342
  body.** A hold is ended by a release recorded in a comment, so a reader handed no comments cannot
322
343
  see the discharge — it goes on holding an item whose question was answered weeks ago, which is
323
344
  the defect this gate carried from the day it was written (CodySwannGT/lisa#3852). Omitting them
@@ -328,16 +349,17 @@ other gate's verdict — however conclusive — may promote an item a person par
328
349
  and re-rejected every cycle forever and seen by nothing — `lisa-repair-intake` sweeps items that
329
350
  are **not** in the ready role and excludes gated ones outright, so a ready-and-gated item falls
330
351
  outside its filter twice over. Call
331
- `planHumanGateReconciliation({ labels, body, humanNeededLabel, readyLabel, alreadyNotified })`
352
+ `planHumanGateReconciliation({ labels, body, comments, trustedHumanActorIds, humanNeededLabel, readyLabel, alreadyNotified })`
332
353
  and apply exactly the actions it returns: remove `$READY`, add the configured human-needed
333
354
  marker, and post `formatHumanGateNote()` once. The planner is idempotent by state, so an item
334
355
  already out of the lane and already marked yields no second mutation and no second comment. This
335
356
  is the same repair the leaf-only gate already performs for a ready item that must not be
336
357
  dispatched.
337
358
  4. **On `claimable: true` for an item that still carries a hold, RELEASE it — do not just proceed.**
338
- The hold left durable state behind: the item is out of the queue and flagged as needing a person,
339
- and answering the question does not undo either. Call
340
- `planHumanGateRelease({ labels, body, comments, humanNeededLabel, readyLabel, lifecycleLabels, alreadyNotified })`
359
+ This ready-lane candidate can retain a historical body marker and a human-needed label.
360
+ This step reconciles only candidates still in the ready lane; released holds outside it are
361
+ recovered by `lisa-repair-intake` step 2b (or Phase 2.5 when included in this cycle). Call
362
+ `planHumanGateRelease({ labels, body, comments, trustedHumanActorIds, humanNeededLabel, readyLabel, lifecycleLabels, alreadyNotified })`
341
363
  and apply exactly the actions it returns: remove the configured human-needed marker, add the
342
364
  configured ready role back, and post `formatHumanGateReleaseNote()` once. It is the exact inverse
343
365
  of step 3's planner and it refuses in both directions — an item still held plans nothing, and an
@@ -369,11 +391,12 @@ GitHub Issues live in one repo by definition, so the scanned repo's issues are u
369
391
  1. **Resolve the current repo** per `config-resolution` "Repo scoping" (`.repo` → `.github.repo` → `git remote get-url origin` basename). If unresolvable, stop and report.
370
392
  2. **Cheap path first.** Prefer candidates already carrying the `repo:<current>` label. Keep the Phase 2 scan broad so unlabeled issues are still seen, determined, and stamped.
371
393
  3. **Per candidate, apply the repo-scope decision (`repo-scope-split`):**
372
- - Carries `repo:<other>` → **skip** (leave it `ready` for that repo's own intake); next candidate.
373
- - **Unlabeled** → determine the target repo(s) from the issue + code surfaces, then **stamp** `repo:<name>` via `gh issue edit <n> --add-label "repo:<name>"` (create the label lazily) so later cycles filter cheaply; re-apply with the now-known repo. (An issue whose work is entirely in the scanned repo is simply labeled `repo:<current>`.)
374
- - **Container visibility is allowed.** A multi-repo Epic / Story / Spike may legitimately carry multiple `repo:<name>` labels for operator visibility. Do not split or claim it here; leave the repo markers intact and fall through to the leaf-only gate, which repairs the stale build-ready label instead of dispatching the container.
375
- - **Multi-repo leaf → split, never claim.** Run the `repo-scope-split` work-time procedure into single-repo siblings, each created **build-ready** (`build_ready: true`) and stamped with its own `repo:<name>`; the current repo's sibling becomes a normal candidate.
376
- - **Single-repo leaf for the current repo** → fall through to 3a (leaf-only gate) and 3b (claim).
394
+ - **Count distinct repository markers first**: collect all `repo:<name>` labels and, on JIRA, recognized repository components; deduplicate by repository. A container may carry multiple `repo:<name>` labels. Do not split or claim it here; send it to the leaf-only gate.
395
+ - **Multi-repo leaf → split, never claim.** More than one repository takes the work-time split before any wrong-repository skip. Each sibling is **build-ready** (`build_ready: true`) and stamped with its own `repo:<name>`; the current repo's sibling becomes a normal candidate.
396
+ - **Exactly one other repository** (`repo:<other>`) → **skip**, leaving the item ready for that repo's intake.
397
+ - **No repository marker** → determine target repo(s) from the ticket and code, stamp `repo:<name>` through the vendor access layer, and re-apply from the count.
398
+ - **Single-repo leaf for the current repo** → fall through to 3a and 3b.
399
+
377
400
  4. Continue until a claimable current-repo leaf is found (claim it; one per cycle) or the candidate set is exhausted — exit cleanly on the denominator-stated summary, naming the current repo alongside the swept lanes.
378
401
 
379
402
  #### 3a. Leaf-only claim gate (repair containers)
@@ -6,6 +6,20 @@ allowed-tools: ["Bash", "Skill", "Read"]
6
6
 
7
7
  # Write GitHub Issue: $ARGUMENTS
8
8
 
9
+ ## Human-gate release authorization
10
+
11
+ A release requires a trusted human author, not just matching comment text. Follow
12
+ `ready-role-filing` — **Human-gate release authorization**: preserve tracker-supplied comment
13
+ author IDs and bot metadata, resolve `trustedHumanActorIds` only from an explicit user instruction
14
+ or existing human-authored trusted project policy, and pass it with structured `comments` to every
15
+ hold classifier, reconciliation, normalization and release planner. Never derive trust from the
16
+ comment body, a display name, the actor's own assertion, or an automation posting on its own behalf.
17
+ Missing policy, missing/unreadable author identity, raw body strings, untrusted actors and known bots
18
+ cannot discharge a hold. Keep the item held and report the missing authorization; do not silently
19
+ replace these inputs with an empty history or an inferred allowlist. Authorized matching releases
20
+ continue through the existing path and never override an independently declared caller hold.
21
+
22
+
9
23
  Create or update a GitHub Issue with all required relationships, metadata, and quality gates. Every section below is mandatory. Thin issues are rejected.
10
24
 
11
25
  This skill is the GitHub counterpart of `lisa-jira-write-ticket`. The two skills share the same gates, description structure, acceptance-criteria format, and verification flow. The data-model translation is documented under "GitHub Issues data model" below — keep that table in sync with `lisa-github-validate-issue` so what one skill writes the other accepts.
@@ -323,7 +337,7 @@ If the label does not exist in the tracker, create it, or record that it could n
323
337
  proceed — the marker still holds. Never file the label *instead of* the marker.
324
338
 
325
339
  **Write a `reason=` the release can name.** The hold's reason is not decoration: a hold ends when a
326
- `[lisa-human-gate-release]` comment repeating that same `reason=` is recorded on the item, and the
340
+ `[lisa-human-gate-release]` comment repeating that same `reason=` is recorded on the item by an authorized human, and the
327
341
  next intake sweep then takes the marker label off and puts the item back in the build-ready role on
328
342
  its own. Matching is per-reason so that a hold declared *after* an earlier release is not born
329
343
  discharged. A keyless hold is legal and is discharged by a keyless release; a hold whose reason is a
@@ -6,6 +6,20 @@ allowed-tools: ["Skill", "Bash"]
6
6
 
7
7
  # JIRA Build Intake: $ARGUMENTS
8
8
 
9
+ ## Human-gate release authorization
10
+
11
+ A release requires a trusted human author, not just matching comment text. Follow
12
+ `ready-role-filing` — **Human-gate release authorization**: preserve tracker-supplied comment
13
+ author IDs and bot metadata, resolve `trustedHumanActorIds` only from an explicit user instruction
14
+ or existing human-authored trusted project policy, and pass it with structured `comments` to every
15
+ hold classifier, reconciliation, normalization and release planner. Never derive trust from the
16
+ comment body, a display name, the actor's own assertion, or an automation posting on its own behalf.
17
+ Missing policy, missing/unreadable author identity, raw body strings, untrusted actors and known bots
18
+ cannot discharge a hold. Keep the item held and report the missing authorization; do not silently
19
+ replace these inputs with an empty history or an inferred allowlist. Authorized matching releases
20
+ continue through the existing path and never override an independently declared caller hold.
21
+
22
+
9
23
  All Atlassian operations in this skill go through `lisa-atlassian-access`. Do not call MCP tools or `acli` directly.
10
24
 
11
25
  `$ARGUMENTS` is one of:
@@ -189,14 +203,21 @@ swept (see `automation-runbook-contract`).
189
203
  A blocker is a **claim with a timestamp, not a fact** — it goes stale the moment its condition comes
190
204
  true, and nothing re-read one before this phase. For each pre-work candidate outside `$READY`:
191
205
 
192
- 1. **Human gate first, and it is absolute.** A ticket carrying the configured human-needed label
193
- (`jira.labels.human_needed`, default `Human Needed`) or a `[lisa-human-gate]` marker in its
194
- description is **never** auto-selected, whatever any probe says.
206
+ 1. **Read release comments before applying the human gate.** Pass all item comments, labels,
207
+ body, `trustedHumanActorIds` and configured `humanNeededLabel` to `classifyReadyCandidate(...)`. For a discharged hold,
208
+ call `planHumanGateRelease({ labels, body, comments, trustedHumanActorIds, humanNeededLabel, readyLabel, lifecycleLabels, alreadyNotified })`
209
+ and retain its plan. Apply human-marker cleanup only as planned; defer any ready-role restoration
210
+ until steps 2–5 have cleared the remaining blockers (map the role to the vendor's state or label).
211
+ A still-active hold is never auto-selected, whatever any probe says. An absent or unreadable
212
+ comment history cannot discharge a hold. A release removes only the human hold: continue the
213
+ remaining blocker checks below before selecting the item.
214
+
195
215
  2. **Extract the stated discharge condition**, then **probe it**. Machine-testable conditions — a
196
216
  version on trunk, a published package, a CI run history, an advisory's patched status — rot
197
217
  fastest and are cheapest to check. A human decision is not machine-testable; leave it.
198
218
  3. **Classify with `classifyPreWorkCandidate(...)`** from
199
- `scripts/intake-blocker-reprobe.mjs`. A discharge with no recorded evidence is not a discharge,
219
+ `scripts/intake-blocker-reprobe.mjs`, including the structured `comments` and
220
+ `trustedHumanActorIds` from step 1. A discharge with no recorded evidence is not a discharge,
200
221
  and neither is a candidate nothing probed this cycle — the helper refuses both.
201
222
  4. **Record the result on the ticket either way** via `formatReprobeNote(...)` as a comment, so the
202
223
  next cycle reads the answer rather than re-deriving it. Keep it idempotent.
@@ -222,7 +243,7 @@ other gate's verdict — however conclusive — may promote an item a person par
222
243
  drift, and a drifted gate fails *silently*, by quietly ceasing to match. Do **not** re-implement
223
244
  the test here, and do **not** key it on `reason=`: markers in the wild carry no `reason=` key at
224
245
  all and sit anywhere in the body, so a structured parse would miss them while appearing to work
225
- on every item that happens to have one. **Pass the item's `comments` alongside its labels and
246
+ on every item that happens to have one. **Pass the item's structured `comments` and `trustedHumanActorIds` alongside its labels and
226
247
  body.** A hold is ended by a release recorded in a comment, so a reader handed no comments cannot
227
248
  see the discharge — it goes on holding an item whose question was answered weeks ago, which is
228
249
  the defect this gate carried from the day it was written (CodySwannGT/lisa#3852). Omitting them
@@ -233,16 +254,17 @@ other gate's verdict — however conclusive — may promote an item a person par
233
254
  and re-rejected every cycle forever and seen by nothing — `lisa-repair-intake` sweeps items that
234
255
  are **not** in the ready role and excludes gated ones outright, so a ready-and-gated item falls
235
256
  outside its filter twice over. Call
236
- `planHumanGateReconciliation({ labels, body, humanNeededLabel, readyLabel, alreadyNotified })`
257
+ `planHumanGateReconciliation({ labels, body, comments, trustedHumanActorIds, humanNeededLabel, readyLabel, alreadyNotified })`
237
258
  and apply exactly the actions it returns: remove `$READY`, add the configured human-needed
238
259
  marker, and post `formatHumanGateNote()` once. The planner is idempotent by state, so an item
239
260
  already out of the lane and already marked yields no second mutation and no second comment. This
240
261
  is the same repair the leaf-only gate already performs for a ready item that must not be
241
262
  dispatched.
242
263
  4. **On `claimable: true` for an item that still carries a hold, RELEASE it — do not just proceed.**
243
- The hold left durable state behind: the item is out of the queue and flagged as needing a person,
244
- and answering the question does not undo either. Call
245
- `planHumanGateRelease({ labels, body, comments, humanNeededLabel, readyLabel, lifecycleLabels, alreadyNotified })`
264
+ This ready-lane candidate can retain a historical body marker and a human-needed label.
265
+ This step reconciles only candidates still in the ready lane; released holds outside it are
266
+ recovered by `lisa-repair-intake` step 2b (or Phase 2.5 when included in this cycle). Call
267
+ `planHumanGateRelease({ labels, body, comments, trustedHumanActorIds, humanNeededLabel, readyLabel, lifecycleLabels, alreadyNotified })`
246
268
  and apply exactly the actions it returns: remove the configured human-needed marker, add the
247
269
  configured ready role back, and post `formatHumanGateReleaseNote()` once. It is the exact inverse
248
270
  of step 3's planner and it refuses in both directions — an item still held plans nothing, and an
@@ -274,10 +296,12 @@ A JIRA project can oversee multiple repos (`frontend` / `backend` / `infrastruct
274
296
  1. **Resolve the current repo** per `config-resolution` "Repo scoping" (`.repo` → `.github.repo` → `git remote get-url origin` basename). If unresolvable, stop and report — do not claim tickets you cannot scope.
275
297
  2. **Cheap path first.** Phase 1's query-time pre-filter has already dropped tickets explicitly stamped for a sibling repo, so the Phase 2 result set is current-repo-labeled + unlabeled tickets. Prefer candidates already carrying `repo:<current>` — a JIRA **label**, or a **component** equal to the repo name (accepted as an alias); the pre-filter is label-only, so a ticket scoped solely by a sibling-repo **component** can still appear and is skipped here. The result set still includes unlabeled tickets so they can be determined and stamped; this gate orders/filters what remains.
276
298
  3. **Per candidate, apply the repo-scope decision (`repo-scope-split`):**
277
- - Carries `repo:<other>` (label or component) → **skip** (leave it `ready` for that repo's own intake); next candidate.
278
- - **Unlabeled** → determine the target repo(s) from the ticket (description, AC, technical approach) confirmed against the code surfaces, then **stamp** `repo:<name>` via `lisa-atlassian-access` `operation: write-ticket` (add the label / set the component) so later cycles filter cheaply; re-apply with the now-known repo.
279
- - **Multi-repo leaf → split, never claim.** Run the `repo-scope-split` work-time procedure to break it into single-repo siblings, each created **build-ready** (`build_ready: true`) and stamped with its own `repo:<name>`; the current repo's sibling becomes a normal candidate.
280
- - **Single-repo leaf for the current repo** → fall through to 3a (leaf-only gate) and 3b (claim).
299
+ - **Count distinct repository markers first**: collect all `repo:<name>` labels and, on JIRA, recognized repository components; deduplicate by repository. A container may carry multiple `repo:<name>` labels. Do not split or claim it here; send it to the leaf-only gate.
300
+ - **Multi-repo leaf → split, never claim.** More than one repository takes the work-time split before any wrong-repository skip. Each sibling is **build-ready** (`build_ready: true`) and stamped with its own `repo:<name>`; the current repo's sibling becomes a normal candidate.
301
+ - **Exactly one other repository** (`repo:<other>`) → **skip**, leaving the item ready for that repo's intake.
302
+ - **No repository marker** → determine target repo(s) from the ticket and code, stamp `repo:<name>` through the vendor access layer, and re-apply from the count.
303
+ - **Single-repo leaf for the current repo** → fall through to 3a and 3b.
304
+
281
305
  4. Continue until a claimable current-repo leaf is found (claim it; one per cycle) or the candidate set is exhausted — exit cleanly on the denominator-stated summary, naming the current repo alongside the swept lanes.
282
306
 
283
307
  #### 3a. Leaf-only claim gate (skip / safe-block containers)
@@ -6,6 +6,20 @@ allowed-tools: ["Bash", "Skill"]
6
6
 
7
7
  # Write JIRA Ticket: $ARGUMENTS
8
8
 
9
+ ## Human-gate release authorization
10
+
11
+ A release requires a trusted human author, not just matching comment text. Follow
12
+ `ready-role-filing` — **Human-gate release authorization**: preserve tracker-supplied comment
13
+ author IDs and bot metadata, resolve `trustedHumanActorIds` only from an explicit user instruction
14
+ or existing human-authored trusted project policy, and pass it with structured `comments` to every
15
+ hold classifier, reconciliation, normalization and release planner. Never derive trust from the
16
+ comment body, a display name, the actor's own assertion, or an automation posting on its own behalf.
17
+ Missing policy, missing/unreadable author identity, raw body strings, untrusted actors and known bots
18
+ cannot discharge a hold. Keep the item held and report the missing authorization; do not silently
19
+ replace these inputs with an empty history or an inferred allowlist. Authorized matching releases
20
+ continue through the existing path and never override an independently declared caller hold.
21
+
22
+
9
23
  All Atlassian operations in this skill go through `lisa-atlassian-access`. Do not call MCP tools or `acli` directly.
10
24
 
11
25
  Create or update a JIRA ticket with all required relationships, metadata, and quality gates. Every section below is mandatory. Thin tickets are rejected.
@@ -308,7 +322,7 @@ If the label does not exist in the tracker, create it, or record that it could n
308
322
  proceed — the marker still holds. Never file the label *instead of* the marker.
309
323
 
310
324
  **Write a `reason=` the release can name.** The hold's reason is not decoration: a hold ends when a
311
- `[lisa-human-gate-release]` comment repeating that same `reason=` is recorded on the item, and the
325
+ `[lisa-human-gate-release]` comment repeating that same `reason=` is recorded on the item by an authorized human, and the
312
326
  next intake sweep then takes the marker label off and puts the item back in the build-ready role on
313
327
  its own. Matching is per-reason so that a hold declared *after* an earlier release is not born
314
328
  discharged. A keyless hold is legal and is discharged by a keyless release; a hold whose reason is a
@@ -188,7 +188,7 @@ Consumers pass business-shaped arguments only; they do not embed GraphQL.
188
188
 
189
189
  **This layer does not accept a state to write. It resolves one.** A `save-issue`
190
190
  that changes the workflow state declares `lifecycle_role:<ROLE>` — the semantic
191
- role it is applying (`ready`, `claimed`, `blocked`, `review`, `done`) — plus `env:<KEY>` when the role is the env-indexed
191
+ role it is applying (for example, `ready`, `claimed`, `blocked`, `review`, `done`; the configured role contract is authoritative) — plus `env:<KEY>` when the role is the env-indexed
192
192
  `done`. This layer then resolves that role against config and the team's own
193
193
  catalog, and sends the ID **it** resolved.
194
194
 
@@ -319,18 +319,67 @@ same mutation, same result shape, same error handling.
319
319
  ```graphql
320
320
  # list-comments project_id:<ID> — page via pageInfo so a long-lived PRD's
321
321
  # feedback history never silently truncates.
322
- query($id:String!){
322
+ query($id:String!,$after:String){
323
323
  project(id:$id){
324
- comments(first:100){
324
+ comments(first:100,after:$after){
325
325
  pageInfo{ hasNextPage endCursor }
326
- nodes{ id body createdAt url user{ name } }
326
+ nodes{ id body createdAt url user{ id name } botActor{ __typename } }
327
327
  }
328
328
  }
329
329
  }
330
330
  ```
331
331
 
332
- The Issue form reads through `issue(id:$id){ comments{...} }` with the identical
333
- node shape, so a caller consumes one result format for either anchor.
332
+ The Issue form uses `issue(id:$id)` with the same `first:100,after:$after`
333
+ arguments and node shape. For **both** anchors, use this access-layer adapter with
334
+ `linear_graphql` above; it accumulates every page and prints no partial history.
335
+ A read succeeds only after `hasNextPage` is false. Errors, malformed pages, and
336
+ missing or repeated continuation cursors fail the whole read.
337
+ Preserve `botActor` with each comment: its non-null presence identifies automation,
338
+ even if `user.id` is trusted. When normalizing comments, carry it forward or set
339
+ `isBot: true`; never discard this metadata before release authorization.
340
+
341
+ ```bash
342
+ linear_list_comment_pages() { # issue|project id -> complete JSON node array
343
+ local anchor="$1" id="$2" after=null nodes='[]' seen='[]'
344
+ local query variables response connection cursor
345
+ case "$anchor" in issue|project) ;; *) echo "Error: invalid comment anchor" >&2; return 1 ;; esac
346
+ query='query($id:String!,$after:String){'"$anchor"'(id:$id){comments(first:100,after:$after){pageInfo{hasNextPage endCursor} nodes{id body createdAt url user{id name} botActor{__typename}}}}}'
347
+ while :; do
348
+ variables=$(jq -cn --arg id "$id" --argjson after "$after" '{id:$id,after:$after}') || return 1
349
+ response=$(linear_graphql "$query" "$variables") || return 1
350
+ connection=$(printf '%s' "$response" | jq -ce --arg anchor "$anchor" '
351
+ if (.errors != null and .errors != []) then error("GraphQL comments read failed")
352
+ else .data[$anchor].comments end |
353
+ if (.nodes | type) != "array" or (.pageInfo.hasNextPage | type) != "boolean"
354
+ then error("Incomplete comments page") else . end') || return 1
355
+ nodes=$(jq -cn --argjson prior "$nodes" --argjson page "$connection" '$prior + $page.nodes') || return 1
356
+ if [ "$(printf '%s' "$connection" | jq -r '.pageInfo.hasNextPage')" = false ]; then
357
+ printf '%s\n' "$nodes"
358
+ return 0
359
+ fi
360
+ cursor=$(printf '%s' "$connection" | jq -ce '.pageInfo.endCursor | select(type == "string" and length > 0)') || {
361
+ echo "Error: incomplete comments history; missing cursor" >&2; return 1;
362
+ }
363
+ if jq -en --argjson seen "$seen" --argjson cursor "$cursor" '$seen | index($cursor) != null' >/dev/null; then
364
+ echo "Error: incomplete comments history; repeated cursor" >&2
365
+ return 1
366
+ fi
367
+ seen=$(jq -cn --argjson seen "$seen" --argjson cursor "$cursor" '$seen + [$cursor]') || return 1
368
+ after="$cursor"
369
+ done
370
+ }
371
+ ```
372
+
373
+ Keep the returned comment objects intact when a caller evaluates a human-gate release. The
374
+ `user.id` is the stable author identity; `user.name` is display text and never authorizes release.
375
+ Pass structured comments and an independently supplied `trustedHumanActorIds` allowlist to the
376
+ shared gate helpers (`ready-role-filing` — Human-gate release authorization). A body-only projection
377
+ loses identity and cannot discharge a hold. Preserve any explicit bot metadata from the provider;
378
+ if identity or trusted policy is unavailable, leave the hold active.
379
+
380
+ Call `linear_list_comment_pages project "$PROJECT_ID"` for `project_id`, or
381
+ `linear_list_comment_pages issue "$ISSUE_ID"` for `issue_id`. The caller receives
382
+ one complete node array; it must not interpret a failed command as zero comments.
334
383
 
335
384
  **Substrate.** The Linear MCP exposes comments on Issues only, so the
336
385
  `project_id` form resolves solely through the tier-1 `LINEAR_API_KEY` + GraphQL
@@ -339,7 +388,7 @@ That is not a gap in Linear: the precedence contract already puts GraphQL ahead
339
388
  of the MCP, so the preferred substrate has always been able to do this. Only
340
389
  this wrapper could not, which is what made callers fabricate an Issue to hold a
341
390
  Project's comments. If tier 1 is unavailable, fail with the layer's standard
342
- `Error:` result naming `save-comment project_id` — do **not** silently degrade
391
+ `Error:` result naming the requested `save-comment project_id` or `list-comments project_id` operation — do **not** silently degrade
343
392
  into creating an Issue to comment on.
344
393
 
345
394
  ## `history` — transition history (read-only)