@toolu/opencode 6.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (331) hide show
  1. package/generated/GENERATED-NOTES.md +16 -0
  2. package/generated/agents/toolu--architect.md +52 -0
  3. package/generated/agents/toolu--deep-explore.md +65 -0
  4. package/generated/agents/toolu--implementer.md +50 -0
  5. package/generated/agents/toolu--quick-task.md +54 -0
  6. package/generated/agents/toolu--research-agent.md +101 -0
  7. package/generated/commands/toolu--commit.md +8 -0
  8. package/generated/commands/toolu--review-and-commit.md +8 -0
  9. package/generated/opencode.toolu.json +116 -0
  10. package/generated/skills/toolu--brainstorm/SKILL.md +57 -0
  11. package/generated/skills/toolu--brainstorm/references/design-questions.md +36 -0
  12. package/generated/skills/toolu--commit--skill/SKILL.md +11 -0
  13. package/generated/skills/toolu--debug/SKILL.md +54 -0
  14. package/generated/skills/toolu--deep-research/SKILL.md +51 -0
  15. package/generated/skills/toolu--execution/SKILL.md +77 -0
  16. package/generated/skills/toolu--orchestrator/SKILL.md +145 -0
  17. package/generated/skills/toolu--orchestrator/references/delegation-cost.md +104 -0
  18. package/generated/skills/toolu--orchestrator/references/model-routing.md +112 -0
  19. package/generated/skills/toolu--plan/SKILL.md +56 -0
  20. package/generated/skills/toolu--plan/references/ledger.md +18 -0
  21. package/generated/skills/toolu--plan-review/SKILL.md +48 -0
  22. package/generated/skills/toolu--review-and-commit--skill/SKILL.md +10 -0
  23. package/generated/skills/toolu--setup/SKILL.md +24 -0
  24. package/generated/skills/toolu--setup/scripts/setup.sh +206 -0
  25. package/generated/skills/toolu--spec/SKILL.md +67 -0
  26. package/generated/skills/toolu--spec-review/SKILL.md +50 -0
  27. package/generated/skills/toolu--test/SKILL.md +49 -0
  28. package/package.json +51 -0
  29. package/plugins/agent-browser/.claude-plugin/plugin.json +18 -0
  30. package/plugins/agent-browser/.codex-plugin/plugin.json +7 -0
  31. package/plugins/agent-browser/README.md +18 -0
  32. package/plugins/agent-browser/hooks/hooks.json +15 -0
  33. package/plugins/agent-browser/hooks/session-start.sh +42 -0
  34. package/plugins/agent-browser/skills/agent-browser/SKILL.md +77 -0
  35. package/plugins/agent-browser/skills/agent-browser/scripts/agent-browser.sh +80 -0
  36. package/plugins/ast-grep/.claude-plugin/plugin.json +18 -0
  37. package/plugins/ast-grep/.codex-plugin/plugin.json +7 -0
  38. package/plugins/ast-grep/README.md +21 -0
  39. package/plugins/ast-grep/hooks/hooks.json +15 -0
  40. package/plugins/ast-grep/hooks/post-tools.d/byte-savings.sh +67 -0
  41. package/plugins/ast-grep/hooks/pre-tools.d/search-nudge.sh +170 -0
  42. package/plugins/ast-grep/hooks/register.sh +68 -0
  43. package/plugins/ast-grep/scripts/byte-savings-report.sh +28 -0
  44. package/plugins/ast-grep/skills/ast-grep/SKILL.md +127 -0
  45. package/plugins/ast-grep/skills/ast-grep/references/ast-grep-advanced.md +44 -0
  46. package/plugins/ast-grep/skills/ast-grep/scripts/ast-grep.sh +103 -0
  47. package/plugins/context7/.claude-plugin/plugin.json +18 -0
  48. package/plugins/context7/.codex-plugin/plugin.json +7 -0
  49. package/plugins/context7/README.md +19 -0
  50. package/plugins/context7/hooks/hooks.json +15 -0
  51. package/plugins/context7/hooks/session-start.sh +42 -0
  52. package/plugins/context7/skills/context7/SKILL.md +64 -0
  53. package/plugins/context7/skills/context7/scripts/search.sh +151 -0
  54. package/plugins/exa-search/.claude-plugin/plugin.json +18 -0
  55. package/plugins/exa-search/.codex-plugin/plugin.json +7 -0
  56. package/plugins/exa-search/README.md +19 -0
  57. package/plugins/exa-search/hooks/hooks.json +15 -0
  58. package/plugins/exa-search/hooks/session-start.sh +34 -0
  59. package/plugins/exa-search/skills/exa-search/SKILL.md +91 -0
  60. package/plugins/exa-search/skills/exa-search/scripts/search.sh +221 -0
  61. package/plugins/jev/.claude-plugin/plugin.json +19 -0
  62. package/plugins/jev/.codex-plugin/plugin.json +7 -0
  63. package/plugins/jev/README.md +61 -0
  64. package/plugins/jev/hooks/hooks.json +25 -0
  65. package/plugins/jev/hooks/lib/common.sh +49 -0
  66. package/plugins/jev/hooks/session-start.sh +45 -0
  67. package/plugins/jev/hooks/user-prompt-submit.sh +41 -0
  68. package/plugins/jev/skills/jev/SKILL.md +81 -0
  69. package/plugins/jev/skills/jev/evals/README.md +98 -0
  70. package/plugins/jev/skills/jev/references/problem-solving.md +198 -0
  71. package/plugins/jev/skills/jev/scripts/jev.sh +405 -0
  72. package/plugins/jira/.claude-plugin/plugin.json +18 -0
  73. package/plugins/jira/.codex-plugin/plugin.json +7 -0
  74. package/plugins/jira/README.md +42 -0
  75. package/plugins/jira/hooks/hooks.json +15 -0
  76. package/plugins/jira/hooks/session-start.sh +34 -0
  77. package/plugins/jira/skills/jira/SKILL.md +123 -0
  78. package/plugins/jira/skills/jira/scripts/jira.sh +70 -0
  79. package/plugins/jira/skills/jira/scripts/lib/adf.sh +17 -0
  80. package/plugins/jira/skills/jira/scripts/lib/attachment.sh +94 -0
  81. package/plugins/jira/skills/jira/scripts/lib/board.sh +58 -0
  82. package/plugins/jira/skills/jira/scripts/lib/http.sh +184 -0
  83. package/plugins/jira/skills/jira/scripts/lib/issue.sh +159 -0
  84. package/plugins/jira/skills/jira/scripts/lib/paginate.sh +45 -0
  85. package/plugins/jira/skills/jira/scripts/lib/plan-parse.sh +70 -0
  86. package/plugins/jira/skills/jira/scripts/lib/plan-run.sh +115 -0
  87. package/plugins/jira/skills/jira/scripts/lib/plan-store.sh +168 -0
  88. package/plugins/jira/skills/jira/scripts/lib/plan.sh +108 -0
  89. package/plugins/jira/skills/jira/scripts/lib/project.sh +42 -0
  90. package/plugins/jira/skills/jira/scripts/lib/raw.sh +15 -0
  91. package/plugins/jira/skills/jira/scripts/lib/search.sh +41 -0
  92. package/plugins/jira/skills/jira/scripts/lib/sprint.sh +88 -0
  93. package/plugins/jira/skills/jira/scripts/lib/user.sh +45 -0
  94. package/plugins/jira/skills/jira/scripts/lib/worklog.sh +56 -0
  95. package/plugins/pr-babysit/.claude-plugin/plugin.json +25 -0
  96. package/plugins/pr-babysit/.codex-plugin/plugin.json +7 -0
  97. package/plugins/pr-babysit/README.md +38 -0
  98. package/plugins/pr-babysit/commands/babysit.md +13 -0
  99. package/plugins/pr-babysit/hooks/check-toolu.sh +20 -0
  100. package/plugins/pr-babysit/hooks/hooks.json +15 -0
  101. package/plugins/pr-babysit/scripts/babysit-tick.sh +133 -0
  102. package/plugins/pr-babysit/scripts/collect-pr.sh +224 -0
  103. package/plugins/pr-babysit/scripts/lib/common.sh +146 -0
  104. package/plugins/pr-babysit/scripts/lib/gh.sh +150 -0
  105. package/plugins/pr-babysit/scripts/lib/lock.sh +84 -0
  106. package/plugins/pr-babysit/scripts/lib/normalize.sh +98 -0
  107. package/plugins/pr-babysit/scripts/lib/state.sh +37 -0
  108. package/plugins/pr-babysit/scripts/parse-verdict.sh +171 -0
  109. package/plugins/pr-babysit/scripts/record.sh +69 -0
  110. package/plugins/pr-babysit/scripts/reduce-state.sh +245 -0
  111. package/plugins/pr-babysit/scripts/reply-thread.sh +90 -0
  112. package/plugins/pr-babysit/scripts/resolve-thread.sh +71 -0
  113. package/plugins/pr-babysit/skills/babysit/SKILL.md +31 -0
  114. package/plugins/pr-babysit/skills/babysit/references/helper.md +209 -0
  115. package/plugins/pr-babysit/workflows/babysit.md +722 -0
  116. package/plugins/python-quality/.claude-plugin/plugin.json +24 -0
  117. package/plugins/python-quality/.codex-plugin/plugin.json +6 -0
  118. package/plugins/python-quality/README.md +23 -0
  119. package/plugins/python-quality/hooks/check-toolu.sh +20 -0
  120. package/plugins/python-quality/hooks/concerns/00-preamble.sh +85 -0
  121. package/plugins/python-quality/hooks/concerns/10-size-file.sh +6 -0
  122. package/plugins/python-quality/hooks/concerns/20-tests.sh +37 -0
  123. package/plugins/python-quality/hooks/concerns/30-suppression.sh +33 -0
  124. package/plugins/python-quality/hooks/concerns/50-size-fn.sh +49 -0
  125. package/plugins/python-quality/hooks/concerns/70-no-mocks.sh +76 -0
  126. package/plugins/python-quality/hooks/concerns/90-docs.sh +52 -0
  127. package/plugins/python-quality/hooks/concerns/99-finalize.sh +36 -0
  128. package/plugins/python-quality/hooks/hooks.json +19 -0
  129. package/plugins/python-quality/hooks/register.sh +65 -0
  130. package/plugins/rust-quality/.claude-plugin/plugin.json +24 -0
  131. package/plugins/rust-quality/.codex-plugin/plugin.json +6 -0
  132. package/plugins/rust-quality/README.md +24 -0
  133. package/plugins/rust-quality/hooks/check-toolu.sh +20 -0
  134. package/plugins/rust-quality/hooks/concerns/00-preamble.sh +93 -0
  135. package/plugins/rust-quality/hooks/concerns/10-size-file.sh +10 -0
  136. package/plugins/rust-quality/hooks/concerns/20-tests.sh +84 -0
  137. package/plugins/rust-quality/hooks/concerns/30-suppression.sh +26 -0
  138. package/plugins/rust-quality/hooks/concerns/40-unsafe.sh +39 -0
  139. package/plugins/rust-quality/hooks/concerns/50-size-fn.sh +38 -0
  140. package/plugins/rust-quality/hooks/concerns/55-size-impl.sh +28 -0
  141. package/plugins/rust-quality/hooks/concerns/60-error-handling.sh +149 -0
  142. package/plugins/rust-quality/hooks/concerns/70-no-mocks.sh +139 -0
  143. package/plugins/rust-quality/hooks/concerns/90-docs.sh +32 -0
  144. package/plugins/rust-quality/hooks/concerns/99-finalize.sh +36 -0
  145. package/plugins/rust-quality/hooks/hooks.json +19 -0
  146. package/plugins/rust-quality/hooks/register.sh +65 -0
  147. package/plugins/statusline/.claude-plugin/plugin.json +19 -0
  148. package/plugins/statusline/.codex-plugin/plugin.json +7 -0
  149. package/plugins/statusline/README.md +108 -0
  150. package/plugins/statusline/commands/setup.md +30 -0
  151. package/plugins/statusline/hooks/hooks.json +15 -0
  152. package/plugins/statusline/hooks/session-start.sh +45 -0
  153. package/plugins/statusline/scripts/collect-status.sh +147 -0
  154. package/plugins/statusline/scripts/setup.sh +93 -0
  155. package/plugins/statusline/scripts/status.sh +55 -0
  156. package/plugins/statusline/skills/status/SKILL.md +13 -0
  157. package/plugins/statusline/statusline.sh +177 -0
  158. package/plugins/toolu/.claude-plugin/plugin.json +19 -0
  159. package/plugins/toolu/.codex-plugin/plugin.json +7 -0
  160. package/plugins/toolu/README.md +35 -0
  161. package/plugins/toolu/agents/.gitkeep +0 -0
  162. package/plugins/toolu/agents/architect.md +52 -0
  163. package/plugins/toolu/agents/deep-explore.md +65 -0
  164. package/plugins/toolu/agents/implementer.md +50 -0
  165. package/plugins/toolu/agents/quick-task.md +54 -0
  166. package/plugins/toolu/agents/research-agent.md +107 -0
  167. package/plugins/toolu/assets/agents/architect.toml +9 -0
  168. package/plugins/toolu/assets/agents/deep-explore.toml +9 -0
  169. package/plugins/toolu/assets/agents/implementer.toml +9 -0
  170. package/plugins/toolu/assets/agents/quick-task.toml +9 -0
  171. package/plugins/toolu/assets/agents/research-agent.toml +9 -0
  172. package/plugins/toolu/commands/.gitkeep +0 -0
  173. package/plugins/toolu/commands/commit.md +5 -0
  174. package/plugins/toolu/commands/review-and-commit.md +5 -0
  175. package/plugins/toolu/hooks/docs/gates.md +157 -0
  176. package/plugins/toolu/hooks/docs/model-routing.md +14 -0
  177. package/plugins/toolu/hooks/docs/post-compaction.md +3 -0
  178. package/plugins/toolu/hooks/docs/push-review.md +123 -0
  179. package/plugins/toolu/hooks/docs/session-start-python.md +3 -0
  180. package/plugins/toolu/hooks/docs/session-start-rust.md +4 -0
  181. package/plugins/toolu/hooks/docs/session-start-ts.md +3 -0
  182. package/plugins/toolu/hooks/docs/session-start.md +16 -0
  183. package/plugins/toolu/hooks/hooks.json +76 -0
  184. package/plugins/toolu/hooks/lib/config.sh +355 -0
  185. package/plugins/toolu/hooks/lib/detect.sh +929 -0
  186. package/plugins/toolu/hooks/lib/diff-sha.sh +32 -0
  187. package/plugins/toolu/hooks/lib/dispatch.sh +365 -0
  188. package/plugins/toolu/hooks/lib/docs-sync-config.sh +81 -0
  189. package/plugins/toolu/hooks/lib/edit-records.sh +157 -0
  190. package/plugins/toolu/hooks/lib/gate-file.sh +201 -0
  191. package/plugins/toolu/hooks/lib/gate-mode.sh +244 -0
  192. package/plugins/toolu/hooks/lib/host.sh +205 -0
  193. package/plugins/toolu/hooks/lib/permissions.sh +125 -0
  194. package/plugins/toolu/hooks/lib/plan-ledger-parse.sh +219 -0
  195. package/plugins/toolu/hooks/lib/plan-ledger-preflight.sh +137 -0
  196. package/plugins/toolu/hooks/lib/plan-ledger.sh +749 -0
  197. package/plugins/toolu/hooks/lib/push-waiver.sh +116 -0
  198. package/plugins/toolu/hooks/lib/quality-config.sh +184 -0
  199. package/plugins/toolu/hooks/lib/registry.sh +72 -0
  200. package/plugins/toolu/hooks/lib/state-sweeper.sh +194 -0
  201. package/plugins/toolu/hooks/lib/telemetry.sh +94 -0
  202. package/plugins/toolu/hooks/lib/verdict.sh +477 -0
  203. package/plugins/toolu/hooks/post-tools/mod.sh +42 -0
  204. package/plugins/toolu/hooks/post-tools/modules/gate-status.sh +140 -0
  205. package/plugins/toolu/hooks/post-tools/modules/push-waiver.sh +97 -0
  206. package/plugins/toolu/hooks/pre-compact.sh +18 -0
  207. package/plugins/toolu/hooks/pre-tools/agent-tier.sh +111 -0
  208. package/plugins/toolu/hooks/pre-tools/mod.sh +37 -0
  209. package/plugins/toolu/hooks/pre-tools/modules/bash-commands.sh +187 -0
  210. package/plugins/toolu/hooks/pre-tools/modules/code-edit-rules.sh +78 -0
  211. package/plugins/toolu/hooks/pre-tools/modules/commit-gate.sh +70 -0
  212. package/plugins/toolu/hooks/pre-tools/modules/docs-sync.sh +137 -0
  213. package/plugins/toolu/hooks/pre-tools/modules/mcp-blocker.sh +151 -0
  214. package/plugins/toolu/hooks/pre-tools/modules/plan-ledger.sh +201 -0
  215. package/plugins/toolu/hooks/pre-tools/modules/protected-files.sh +177 -0
  216. package/plugins/toolu/hooks/pre-tools/modules/push-review.sh +273 -0
  217. package/plugins/toolu/hooks/pre-tools/modules/quality-gate.sh +87 -0
  218. package/plugins/toolu/hooks/session-start.sh +379 -0
  219. package/plugins/toolu/hooks/user-prompt-submit.sh +197 -0
  220. package/plugins/toolu/scripts/context-budget.sh +111 -0
  221. package/plugins/toolu/scripts/debug-log.sh +98 -0
  222. package/plugins/toolu/scripts/debug-stack.sh +108 -0
  223. package/plugins/toolu/scripts/debug-testfail.sh +97 -0
  224. package/plugins/toolu/settings/.gitkeep +0 -0
  225. package/plugins/toolu/settings/README.md +127 -0
  226. package/plugins/toolu/settings/bash-allowlist.txt +12 -0
  227. package/plugins/toolu/settings/bash-denylist.txt +25 -0
  228. package/plugins/toolu/settings/code-edit-rules.json +67 -0
  229. package/plugins/toolu/settings/commit-prefixes.txt +12 -0
  230. package/plugins/toolu/settings/mcp-blocklist.txt +8 -0
  231. package/plugins/toolu/settings/permissions.fragment.json +36 -0
  232. package/plugins/toolu/settings/protected-files.txt +30 -0
  233. package/plugins/toolu/settings/rust-unsafe-exemptions.txt +5 -0
  234. package/plugins/toolu/settings/toolu.config.example.json +130 -0
  235. package/plugins/toolu/skills/.gitkeep +0 -0
  236. package/plugins/toolu/skills/brainstorm/SKILL.md +57 -0
  237. package/plugins/toolu/skills/brainstorm/references/design-questions.md +36 -0
  238. package/plugins/toolu/skills/commit/SKILL.md +11 -0
  239. package/plugins/toolu/skills/debug/SKILL.md +54 -0
  240. package/plugins/toolu/skills/debug/evals/README.md +17 -0
  241. package/plugins/toolu/skills/debug/evals/cases.json +24 -0
  242. package/plugins/toolu/skills/debug/evals/results.json +34 -0
  243. package/plugins/toolu/skills/deep-research/SKILL.md +51 -0
  244. package/plugins/toolu/skills/execution/SKILL.md +77 -0
  245. package/plugins/toolu/skills/orchestrator/SKILL.md +145 -0
  246. package/plugins/toolu/skills/orchestrator/references/delegation-cost.md +104 -0
  247. package/plugins/toolu/skills/orchestrator/references/model-routing.md +112 -0
  248. package/plugins/toolu/skills/plan/SKILL.md +56 -0
  249. package/plugins/toolu/skills/plan/references/ledger.md +18 -0
  250. package/plugins/toolu/skills/plan-review/SKILL.md +48 -0
  251. package/plugins/toolu/skills/review-and-commit/SKILL.md +10 -0
  252. package/plugins/toolu/skills/setup/SKILL.md +24 -0
  253. package/plugins/toolu/skills/setup/scripts/setup.sh +206 -0
  254. package/plugins/toolu/skills/spec/SKILL.md +67 -0
  255. package/plugins/toolu/skills/spec-review/SKILL.md +50 -0
  256. package/plugins/toolu/skills/test/SKILL.md +49 -0
  257. package/plugins/toolu/workflows/commit.md +21 -0
  258. package/plugins/toolu/workflows/host-mapping.md +22 -0
  259. package/plugins/toolu/workflows/review-and-commit.md +54 -0
  260. package/plugins/toolu/workflows/semantic-judgments.md +35 -0
  261. package/plugins/toolu-review/.claude-plugin/plugin.json +18 -0
  262. package/plugins/toolu-review/.codex-plugin/plugin.json +7 -0
  263. package/plugins/toolu-review/README.md +17 -0
  264. package/plugins/toolu-review/hooks/hooks.json +15 -0
  265. package/plugins/toolu-review/hooks/session-start.sh +34 -0
  266. package/plugins/toolu-review/skills/review/SKILL.md +90 -0
  267. package/plugins/toolu-review/skills/review/scripts/write-state.sh +189 -0
  268. package/plugins/ts-quality/.claude-plugin/plugin.json +24 -0
  269. package/plugins/ts-quality/.codex-plugin/plugin.json +6 -0
  270. package/plugins/ts-quality/README.md +24 -0
  271. package/plugins/ts-quality/hooks/check-toolu.sh +20 -0
  272. package/plugins/ts-quality/hooks/concerns/00-preamble.sh +123 -0
  273. package/plugins/ts-quality/hooks/concerns/10-imports.sh +33 -0
  274. package/plugins/ts-quality/hooks/concerns/15-type-as.sh +9 -0
  275. package/plugins/ts-quality/hooks/concerns/20-tests.sh +26 -0
  276. package/plugins/ts-quality/hooks/concerns/25-size-file.sh +40 -0
  277. package/plugins/ts-quality/hooks/concerns/30-size-fn.sh +53 -0
  278. package/plugins/ts-quality/hooks/concerns/35-react-hooks.sh +7 -0
  279. package/plugins/ts-quality/hooks/concerns/40-factory.sh +5 -0
  280. package/plugins/ts-quality/hooks/concerns/45-typeguard.sh +6 -0
  281. package/plugins/ts-quality/hooks/concerns/50-type-dup.sh +16 -0
  282. package/plugins/ts-quality/hooks/concerns/55-naming.sh +11 -0
  283. package/plugins/ts-quality/hooks/concerns/60-console.sh +6 -0
  284. package/plugins/ts-quality/hooks/concerns/65-suppression.sh +12 -0
  285. package/plugins/ts-quality/hooks/concerns/70-ui-confirm.sh +8 -0
  286. package/plugins/ts-quality/hooks/concerns/72-ui-radix.sh +12 -0
  287. package/plugins/ts-quality/hooks/concerns/74-react-props.sh +6 -0
  288. package/plugins/ts-quality/hooks/concerns/76-toast.sh +10 -0
  289. package/plugins/ts-quality/hooks/concerns/78-error-ast.sh +242 -0
  290. package/plugins/ts-quality/hooks/concerns/80-throw-literal.sh +23 -0
  291. package/plugins/ts-quality/hooks/concerns/85-no-mocks.sh +109 -0
  292. package/plugins/ts-quality/hooks/concerns/90-duplication.sh +30 -0
  293. package/plugins/ts-quality/hooks/concerns/92-docs.sh +36 -0
  294. package/plugins/ts-quality/hooks/concerns/94-handler.sh +15 -0
  295. package/plugins/ts-quality/hooks/concerns/99-finalize.sh +46 -0
  296. package/plugins/ts-quality/hooks/hooks.json +19 -0
  297. package/plugins/ts-quality/hooks/register.sh +65 -0
  298. package/src/adapter/__tests__/evaluate.test.ts +82 -0
  299. package/src/adapter/__tests__/permission-map.test.ts +91 -0
  300. package/src/adapter/evaluate.ts +54 -0
  301. package/src/adapter/permission-map.ts +177 -0
  302. package/src/bootstrap/__tests__/bootstrap.test.ts +148 -0
  303. package/src/bootstrap/entrypoint.ts +18 -0
  304. package/src/bootstrap/index.ts +5 -0
  305. package/src/bootstrap/readiness.ts +59 -0
  306. package/src/bootstrap/result.ts +21 -0
  307. package/src/bootstrap/runtime.ts +99 -0
  308. package/src/bootstrap/test-helpers.ts +25 -0
  309. package/src/host/__tests__/host.test.ts +37 -0
  310. package/src/host/detect.ts +46 -0
  311. package/src/host/index.ts +10 -0
  312. package/src/host/roots.ts +59 -0
  313. package/src/host/types.ts +14 -0
  314. package/src/inventory/__tests__/inventory.test.ts +69 -0
  315. package/src/inventory/index.ts +9 -0
  316. package/src/inventory/manifest.ts +46 -0
  317. package/src/inventory/scan.ts +66 -0
  318. package/src/inventory/selection.ts +92 -0
  319. package/src/inventory/types.ts +22 -0
  320. package/src/lifecycle/__tests__/lifecycle.test.ts +7 -0
  321. package/src/lifecycle/index.ts +2 -0
  322. package/src/lifecycle/table.ts +42 -0
  323. package/src/plugin/__tests__/plugin-stub.test.ts +6 -0
  324. package/src/plugin/plugin-stub.ts +7 -0
  325. package/src/plugin/toolu.ts +142 -0
  326. package/src/preflight/__tests__/preflight.test.ts +20 -0
  327. package/src/preflight/check.ts +75 -0
  328. package/src/preflight/index.ts +2 -0
  329. package/src/select/__tests__/select.test.ts +52 -0
  330. package/src/select/index.ts +2 -0
  331. package/src/select/resolve.ts +87 -0
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env bash
2
+ # reply-thread.sh — post one reply (review thread, conversation, or review-
3
+ # level) and record it in slot state so it is never posted twice.
4
+ #
5
+ # Usage: reply-thread.sh --state-file <path> --kind thread|conversation|review
6
+ # (--thread <graphqlId> --root-comment <databaseId> --in-reply-to <databaseId>
7
+ # | --comment-id <id> | --review-id <id>)
8
+ # --body-file <path> [--timeout SECONDS]
9
+ #
10
+ # Idempotency key = the reviewer comment being answered:
11
+ # thread:<threadId>@<inReplyTo> · conversation:<id> · review:<id>
12
+ # A key already present in actions.replied → exit 4 duplicate_reply, nothing
13
+ # posted. A later reviewer follow-up carries a new inReplyTo, so it is
14
+ # answerable. The body comes from a file so untrusted text never enters argv.
15
+ # The agent writes the words; this script makes the call land or says why not.
16
+ # Exit: 0 posted (stdout {ok,key,commentId,url}) · 2 usage · 3 api_error |
17
+ # state_malformed · 4 duplicate_reply · 75 locked.
18
+ set -euo pipefail
19
+
20
+ PB_SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)
21
+ # shellcheck source=lib/common.sh
22
+ . "$PB_SCRIPT_DIR/lib/common.sh"
23
+ # shellcheck source=lib/lock.sh
24
+ . "$PB_SCRIPT_DIR/lib/lock.sh"
25
+ # shellcheck source=lib/gh.sh
26
+ . "$PB_SCRIPT_DIR/lib/gh.sh"
27
+ # shellcheck source=lib/state.sh
28
+ . "$PB_SCRIPT_DIR/lib/state.sh"
29
+
30
+ state_file=""; kind=""; thread=""; root=""; in_reply_to=""; comment_id=""; review_id=""; body_file=""
31
+ while [ $# -gt 0 ]; do
32
+ case "$1" in
33
+ --state-file) state_file="${2:-}"; shift 2 ;;
34
+ --kind) kind="${2:-}"; shift 2 ;;
35
+ --thread) thread="${2:-}"; shift 2 ;;
36
+ --root-comment) root="${2:-}"; shift 2 ;;
37
+ --in-reply-to) in_reply_to="${2:-}"; shift 2 ;;
38
+ --comment-id) comment_id="${2:-}"; shift 2 ;;
39
+ --review-id) review_id="${2:-}"; shift 2 ;;
40
+ --body-file) body_file="${2:-}"; shift 2 ;;
41
+ --timeout) PB_GH_TIMEOUT="${2:-}"; shift 2 ;;
42
+ *) pb_fail usage "reply-thread.sh: unknown argument: $1" ;;
43
+ esac
44
+ done
45
+ [ -n "$state_file" ] || pb_fail usage "reply-thread.sh: --state-file required"
46
+ [ -n "$body_file" ] && [ -f "$body_file" ] || pb_fail usage "reply-thread.sh: --body-file <existing file> required"
47
+ [ -s "$body_file" ] || pb_fail usage "reply-thread.sh: reply body is empty"
48
+ # GitHub caps comment bodies at 65536 characters; refuse before the request.
49
+ [ "$(wc -c <"$body_file" | tr -d ' ')" -le 65536 ] || pb_fail usage "reply-thread.sh: reply body exceeds GitHub's 65536-character limit"
50
+ case "$kind" in
51
+ thread)
52
+ [[ "$thread" =~ ^[A-Za-z0-9_=-]+$ ]] || pb_fail usage "reply-thread.sh: --thread <graphqlId> required for --kind thread"
53
+ [[ "$root" =~ ^[0-9]+$ ]] || pb_fail usage "reply-thread.sh: --root-comment <databaseId> required for --kind thread"
54
+ [[ "$in_reply_to" =~ ^[0-9]+$ ]] || pb_fail usage "reply-thread.sh: --in-reply-to <databaseId> required for --kind thread"
55
+ key="thread:${thread}@${in_reply_to}" ;;
56
+ conversation)
57
+ [[ "$comment_id" =~ ^[0-9]+$ ]] || pb_fail usage "reply-thread.sh: --comment-id <id> required for --kind conversation"
58
+ key="conversation:${comment_id}" ;;
59
+ review)
60
+ [[ "$review_id" =~ ^[0-9]+$ ]] || pb_fail usage "reply-thread.sh: --review-id <id> required for --kind review"
61
+ key="review:${review_id}" ;;
62
+ *) pb_fail usage "reply-thread.sh: --kind must be thread, conversation or review" ;;
63
+ esac
64
+
65
+ pb_require jq gh
66
+ pb_init
67
+ pb_mktmpdir
68
+ pb_lock_acquire "$state_file" || pb_lock_fail "$state_file"
69
+ pb_state_load "$state_file"
70
+
71
+ # Idempotency: refuse before any request.
72
+ if jq -e --arg k "$key" '.actions.replied[$k] != null' "$state_file" >/dev/null 2>&1; then
73
+ pb_fail duplicate_reply "reply-thread.sh: a reply to $key is already recorded; not posting again" \
74
+ "$(jq -c --arg k "$key" '{key:$k, recorded:.actions.replied[$k]}' "$state_file")"
75
+ fi
76
+
77
+ case "$kind" in
78
+ thread) endpoint="repos/$PB_STATE_REPO/pulls/$PB_STATE_NUMBER/comments/$root/replies" ;;
79
+ conversation|review) endpoint="repos/$PB_STATE_REPO/issues/$PB_STATE_NUMBER/comments" ;;
80
+ esac
81
+ jq -n --rawfile body "$body_file" '{body:$body}' >"$PB_TMPDIR/payload.json"
82
+ pb_gh "$PB_TMPDIR/reply.json" api --method POST "$endpoint" --input "$PB_TMPDIR/payload.json" || pb_gh_fail reply
83
+ jq -e '.id | type == "number"' "$PB_TMPDIR/reply.json" >/dev/null 2>&1 \
84
+ || pb_fail invalid_json "reply-thread.sh: reply response carried no comment id" '{"source":"reply"}'
85
+
86
+ pb_state_update "$state_file" \
87
+ '.actions.replied[$k] = {commentId: $r.id, url: $r.html_url, at: $now, headSha: $head, kind: $kind}' \
88
+ --arg k "$key" --arg now "$(pb_now)" --arg head "$PB_STATE_HEAD" --arg kind "$kind" \
89
+ --argjson r "$(jq -c '{id, html_url}' "$PB_TMPDIR/reply.json")"
90
+ jq -c --arg k "$key" '{ok:true, key:$k, commentId:.id, url:.html_url}' "$PB_TMPDIR/reply.json"
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env bash
2
+ # resolve-thread.sh — resolve one review thread and CONFIRM it from the
3
+ # mutation response before recording it. A reply is never a resolve.
4
+ #
5
+ # Usage: resolve-thread.sh --state-file <path> --thread <graphqlId> [--timeout SECONDS]
6
+ #
7
+ # The mutation runs up to 3 times until the response shows
8
+ # thread.isResolved == true. Still false → exit 5 resolve_unconfirmed and the
9
+ # thread stays out of actions.resolved, so the next tick's resolution audit
10
+ # picks it up again. A thread already recorded as confirmed → exit 0 without
11
+ # any request. Transport failure after lib/gh.sh's bounded retry → exit 3.
12
+ # Exit: 0 confirmed (stdout {ok,thread,confirmed,attempts}) · 2 usage ·
13
+ # 3 api_error | state_malformed · 5 resolve_unconfirmed · 75 locked.
14
+ set -euo pipefail
15
+
16
+ PB_SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)
17
+ # shellcheck source=lib/common.sh
18
+ . "$PB_SCRIPT_DIR/lib/common.sh"
19
+ # shellcheck source=lib/lock.sh
20
+ . "$PB_SCRIPT_DIR/lib/lock.sh"
21
+ # shellcheck source=lib/gh.sh
22
+ . "$PB_SCRIPT_DIR/lib/gh.sh"
23
+ # shellcheck source=lib/state.sh
24
+ . "$PB_SCRIPT_DIR/lib/state.sh"
25
+
26
+ state_file=""; thread=""
27
+ while [ $# -gt 0 ]; do
28
+ case "$1" in
29
+ --state-file) state_file="${2:-}"; shift 2 ;;
30
+ --thread) thread="${2:-}"; shift 2 ;;
31
+ --timeout) PB_GH_TIMEOUT="${2:-}"; shift 2 ;;
32
+ *) pb_fail usage "resolve-thread.sh: unknown argument: $1" ;;
33
+ esac
34
+ done
35
+ [ -n "$state_file" ] || pb_fail usage "resolve-thread.sh: --state-file required"
36
+ # GraphQL node ids are base64url-ish; anything else is a caller bug, not a request.
37
+ [[ "$thread" =~ ^[A-Za-z0-9_=-]+$ ]] || pb_fail usage "resolve-thread.sh: --thread <graphqlId> required (got '${thread:-}')"
38
+
39
+ pb_require jq gh
40
+ pb_init
41
+ pb_mktmpdir
42
+ pb_lock_acquire "$state_file" || pb_lock_fail "$state_file"
43
+ pb_state_load "$state_file"
44
+
45
+ if jq -e --arg t "$thread" '.actions.resolved[$t].confirmed == true' "$state_file" >/dev/null 2>&1; then
46
+ jq -c --arg t "$thread" '{ok:true, thread:$t, confirmed:true, attempts:0, alreadyResolved:true, at:.actions.resolved[$t].at}' "$state_file"
47
+ exit 0
48
+ fi
49
+
50
+ mutation='mutation($threadId:ID!){ resolveReviewThread(input:{threadId:$threadId}){ thread{ id isResolved } } }'
51
+ attempt=0; confirmed=false; last=""
52
+ while [ "$attempt" -lt 3 ]; do
53
+ attempt=$((attempt + 1))
54
+ pb_gh "$PB_TMPDIR/resolve.json" api graphql -f threadId="$thread" -f query="$mutation" || pb_gh_fail resolve
55
+ pb_gh_json_ok "$PB_TMPDIR/resolve.json" || pb_fail invalid_json "resolve-thread.sh: mutation response carried errors[]" \
56
+ "$(jq -c '{source:"resolve", errors:(.errors // [])}' "$PB_TMPDIR/resolve.json")"
57
+ last=$(jq -c '.data.resolveReviewThread.thread // null' "$PB_TMPDIR/resolve.json")
58
+ if [ "$(jq -r '.isResolved // false' <<<"$last")" = true ]; then confirmed=true; break; fi
59
+ echo "resolve-thread.sh: attempt $attempt returned isResolved=false for $thread; retrying" >&2
60
+ sleep 1
61
+ done
62
+
63
+ if [ "$confirmed" != true ]; then
64
+ pb_fail resolve_unconfirmed "resolve-thread.sh: $thread still unresolved after $attempt attempt(s)" \
65
+ "$(jq -nc --arg t "$thread" --argjson attempts "$attempt" --argjson last "${last:-null}" '{thread:$t, attempts:$attempts, lastResponse:$last}')"
66
+ fi
67
+
68
+ pb_state_update "$state_file" \
69
+ '.actions.resolved[$t] = {confirmed: true, at: $now, attempts: $attempts, headSha: $head}' \
70
+ --arg t "$thread" --arg now "$(pb_now)" --argjson attempts "$attempt" --arg head "$PB_STATE_HEAD"
71
+ jq -nc --arg t "$thread" --argjson attempts "$attempt" '{ok:true, thread:$t, confirmed:true, attempts:$attempts}'
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: babysit
3
+ description: Use when the user explicitly asks Codex, or an authorized verified execution handoff invokes it, to monitor and autonomously clear the current branch's pull request until CI, review threads, and the review-bot verdict are all green.
4
+ ---
5
+
6
+ # Babysit a PR
7
+
8
+ This no-argument invocation explicitly authorizes one durable babysitting goal
9
+ for the current repository and PR. A verified execution handoff is sufficient authorization
10
+ when execution already confirmed delivery authorization, GitHub
11
+ auth, a non-default branch, and this installed plugin; do not ask again or
12
+ introduce handoff arguments. Read [the canonical workflow](../../workflows/babysit.md)
13
+ completely and follow only its Codex controller branches plus every shared
14
+ strict-clearance step. Each tick is one command — the shipped
15
+ `scripts/babysit-tick.sh` under this plugin's root (`../../scripts/babysit-tick.sh`
16
+ relative to this file), with `--state-file` set to the Codex slot path; its output contract is [references/helper.md](references/helper.md).
17
+ Trust that result: never write a polling script or controller of your own,
18
+ never re-fetch with ad-hoc `gh` calls what the result already reports, and act
19
+ through `reply-thread.sh`, `resolve-thread.sh` and `record.sh`.
20
+
21
+ Use `get_goal` before `create_goal`; keep one active goal for the resolved
22
+ repository/PR. Continue with bounded cycles: use the native `wait` mechanism for
23
+ at most 60 seconds, persist the exact slot state, and allow later goal
24
+ continuations to resume. Pending checks are not completion or blockage.
25
+
26
+ Call `update_goal` with `complete` only after the same-cycle success audit proves
27
+ CI green, zero unresolved threads, and an approved zero-finding bot verdict.
28
+ Call it with `blocked` only for a genuine human-only escalation after the same
29
+ blocker has met Codex's consecutive-goal-turn threshold. For `stop` or `cancel`,
30
+ perform the canonical local cleanup and tell the user to cancel through Codex's
31
+ goal control; cancellation is not a completion status.
@@ -0,0 +1,209 @@
1
+ # pr-babysit helper contract
2
+
3
+ The tick helper is the deterministic half of babysitting. It lives under the
4
+ plugin's `scripts/` directory, is plain bash (`gh` and `jq` only; bash 3.2 — `git`
5
+ is used by the workflow's worktree steps, not by these scripts), and
6
+ is the same on Claude Code and Codex — only `--state-file` differs by host.
7
+ Read this file instead of the script sources: every field the agent may act
8
+ on is listed here. Fields not listed are not part of the contract.
9
+
10
+ ## Scripts
11
+
12
+ | Script | Role | Exit codes |
13
+ |---|---|---|
14
+ | `babysit-tick.sh` | **The tick.** Lock the slot → collect → reduce → persist → print the result. The only read-side command the workflow runs. | `0` result · `2` usage · `3` structured error (state preserved, `pr.lastError` stamped) · `75` slot locked |
15
+ | `collect-pr.sh` | Read side: PR metadata, review threads (paginated, nested comment pages), issue comments, reviews; head re-checked after the fan-out; bot verdict via `parse-verdict.sh`. Writes one snapshot. | `0` · `2` · `3` |
16
+ | `reduce-state.sh` | Pure decision layer: snapshot + prior state + `--now` → next state + result. No network, no clock. | `0` · `2` · `3` |
17
+ | `reply-thread.sh` | Write side: post one reply (thread / conversation / review-level), idempotent per reviewer comment. | `0` · `2` · `3` · `4` duplicate_reply · `75` |
18
+ | `resolve-thread.sh` | Write side: `resolveReviewThread`, confirmed from the response, retried, recorded. | `0` · `2` · `3` · `5` resolve_unconfirmed · `75` |
19
+ | `record.sh` | Hand agent decisions to the reducer: `round`, `flag-injection`, `status`. | `0` · `2` · `3` · `75` |
20
+ | `parse-verdict.sh` | Existing verdict parser; unchanged. | — |
21
+
22
+ Structured errors are one JSON document on stdout:
23
+ `{"version":1,"errors":[{"code":"…","message":"…", …}]}`. `code` is a closed set:
24
+ `usage`, `gh_unavailable`, `jq_required`, `api_error`, `invalid_json`,
25
+ `head_moved`, `state_malformed`, `slot_mismatch`, `locked`, `duplicate_reply`,
26
+ `resolve_unconfirmed`.
27
+
28
+ ## `babysit-tick.sh`
29
+
30
+ ```
31
+ babysit-tick.sh --repo <owner/repo> --pr <n> --state-file <path>
32
+ [--snapshot-out <path>] [--snapshot-in <path>]
33
+ [--page-size N] [--timeout SECONDS] [--now <iso8601>]
34
+ ```
35
+
36
+ - `--state-file` — the host's slot path, verbatim: `/tmp/pr-babysit-<slot>.json`
37
+ (Claude) or `<repo>/.codex/tmp/pr-babysit/<slot>.json` (Codex). Created on the
38
+ first tick, resumed after.
39
+ - `--snapshot-out` — full snapshot (default `<state-file minus .json>.snapshot.json`).
40
+ - `--snapshot-in` — replay a captured snapshot instead of collecting (tests,
41
+ debugging). The workflow never passes it.
42
+ - `--timeout` — per `gh` call (default 60 s); every call retries 3× with 2 s /
43
+ 4 s waits on timeouts, 5xx, 429, rate-limit 403 and connection errors, and
44
+ never on other 4xx.
45
+ - `--page-size` — GraphQL/REST page size (default 100). Exists so pagination is
46
+ testable on small real PRs.
47
+
48
+ The PR head is read before and after the fan-out; if it moved the collection
49
+ runs once more, then fails with `head_moved`. A tick never writes a partial
50
+ state: state and snapshot are written atomically (temp file + rename) and a
51
+ crash leaves the previous state and no temp file.
52
+
53
+ ## Result (`version: 1`)
54
+
55
+ What the agent acts on. Printed on stdout by `babysit-tick.sh`.
56
+
57
+ | Field | Meaning |
58
+ |---|---|
59
+ | `slot` | `<owner>-<repo>-<number>`, lowercase. |
60
+ | `changed` | Any of `ciStatus, reviewDecision, mergeable, unresolvedThreads, headSha, botVerdict, botState, botFindingKeys` differs from the previous tick. `false` → silent tick. |
61
+ | `decision` | `keep_going` · `success` · `escalate` — the Step 6 rules, already applied. |
62
+ | `reasons[]` | `{code, detail}`; escalation codes first. Closed set below. |
63
+ | `pr` | `number, url, head, branch, base, author, state, mergeable, reviewDecision`. |
64
+ | `ci.status` | `pass` · `pending` · `fail` over `statusCheckRollup` (`CheckRun` and legacy `StatusContext`); an empty rollup is `pending`. |
65
+ | `ci.checks[]` | `{name, status, url}` per check. |
66
+ | `verdict.state` | `absent` · `unknown` · `in_progress` · `complete` · `provider_error` (parse-verdict.sh states, plus `absent` when no CI-reviewer comment exists). |
67
+ | `verdict.verdict` | `approved` · `changes` · `none`. |
68
+ | `verdict.findingsCount`, `findingKeys[]` | Round-level recurrence keys (`path:line:hash`). Never the finding source — the inline threads are. |
69
+ | `verdict.mustFix[]` | Top-N must-fix prose lines. Surface to the human; not resolvable threads. |
70
+ | `verdict.commentUrl`, `commentId` | The bot comment read. |
71
+ | `verdict.degraded`, `degradedReason` | `true` with `review_absent` or `review_unknown_format` when the verdict cannot be read; success is then the documented manual-verify fallback, never silent. |
72
+ | `verdict.sameRunAsLastTick` | The same bot comment id and `updatedAt` as last tick — a sticky verdict, not a new rejection. |
73
+ | `threads.total`, `unresolved` | All threads; the Resolution-audit set (`!isResolved && !isOutdated && !flagged`). `unresolved` must be `0` for success. |
74
+ | `threads.actionable[]` | Threads needing a NEW disposition this tick: `{id, path, line, isOutdated, rootCommentId, inReplyTo, authorClass, lastCommentAuthor, lastCommentAt, injectionSuspect, injectionPattern, comments[]}`. `authorClass` ∈ `ci_reviewer` (exact login set `github-actions`, `github-actions[bot]`, `claude`, `claude[bot]` — current Toolu Code Review `@v8` posts as `github-actions[bot]`; `claude` kept for legacy), `human`. Non-CI bots are excluded. `comments[]` is the full chain, bodies untruncated. |
75
+ | `threads.staleUnresolved[]` | Audit members that are NOT actionable: the PR author replied but no resolve landed. Resolve them without a new reply. |
76
+ | `threads.skippedOutdated[]` | Outdated CI-reviewer threads: skipped silently. |
77
+ | `threads.flaggedInjection[]` | Threads the agent recorded with `record.sh flag-injection`. |
78
+ | `conversation.actionable[]` | Human issue comments with no later author comment and no recorded reply. |
79
+ | `reviews.actionable[]` | Human non-`APPROVED` reviews with a body and no recorded reply. |
80
+ | `recurrence` | `{streak, lastRoundHadRejection, recurringKeys[], fixAttempts}` — the Step 4 gate inputs. |
81
+ | `backoff` | `{idleStreak, intervalMinutes, waitSeconds}` — Claude cron interval / Codex bounded wait for this tick. |
82
+ | `errors[]` | Always empty on exit 0. |
83
+ | `snapshotPath`, `statePath` | Where the full evidence lives. |
84
+
85
+ ### `reasons[].code` (closed set)
86
+
87
+ `ci_pending`, `ci_failed`, `ci_pass`, `threads_unresolved`,
88
+ `threads_stale_unresolved`, `threads_clear`, `review_absent`,
89
+ `review_in_progress`, `review_changes`, `review_approved`,
90
+ `review_unknown_format`, `provider_error`, `provider_error_repeated`,
91
+ `manual_verify`, `pr_closed`, `pr_merged`, `merge_conflict`,
92
+ `mergeable_unknown`, `fix_attempts_exhausted`, `recurrence_after_rejection`,
93
+ `recurrence_streak`, `unchanged`.
94
+
95
+ ### Decision rules
96
+
97
+ - `success` — `pr.state == OPEN`, `ci.status == pass`, `threads.unresolved == 0`,
98
+ `mergeable != UNKNOWN`, and the verdict is `complete`/`approved`/zero findings
99
+ — or `degraded` (reasons then include `manual_verify`).
100
+ - `escalate` — PR merged/closed, `mergeable == CONFLICTING`, `fixAttempts ≥ 5`,
101
+ finding keys recurring after a Won't-fix round, a recurrence streak of 2, or
102
+ `provider_error` twice on the same head.
103
+ - `keep_going` — everything else, including pending CI, an in-progress review,
104
+ an unchanged tick, and `mergeable == UNKNOWN`.
105
+
106
+ Recurrence only advances on a NEW verdict run (`sameRunAsLastTick: false`) and
107
+ only against the keys the agent rotated with `record.sh round`.
108
+
109
+ ## State (`version: 2`)
110
+
111
+ Persisted at `--state-file`; owned by the helper. The agent reads it only
112
+ through the result, and writes it only through `record.sh`, `reply-thread.sh`
113
+ and `resolve-thread.sh`.
114
+
115
+ | Field | Meaning |
116
+ |---|---|
117
+ | `slot`, `repo`, `number`, `cronName` | Slot identity; a state for another PR is refused (`slot_mismatch`). |
118
+ | `lastUpdate`, `totalTicks`, `idleStreak`, `currentInterval`, `waitSeconds` | Tick bookkeeping and backoff. |
119
+ | `status` | `active` · `complete` · `escalated` · `cancelled` — set only by `record.sh status`. |
120
+ | `worktree` | Codex's exact worktree path for this slot, or `null`. |
121
+ | `pr.key`, `ciStatus`, `reviewDecision`, `mergeable`, `unresolvedThreads`, `headSha` | Last observed values (change detection). |
122
+ | `pr.fixAttempts` | Bumped by `record.sh round --fix-pushed`, cap 5. |
123
+ | `pr.botVerdict`, `botState`, `botCommentId`, `botCommentUpdatedAt`, `botFindingKeys` | Last verdict and its run identity. |
124
+ | `pr.lastRoundFindingKeys`, `lastRoundHadRejection`, `recurrenceStreak` | Step 4 gate memory; rotated by `record.sh round`. |
125
+ | `pr.unresolvedAfterClearance` | Audit count at the last tick; non-zero on a completed tick is a bug. |
126
+ | `pr.lastError` | `{code, message, at}` of the last failed tick, or `null`. |
127
+ | `actions.replied` | `key → {commentId, url, at, headSha, kind}`; keys `thread:<id>@<inReplyTo>`, `conversation:<id>`, `review:<id>`. |
128
+ | `actions.resolved` | `threadId → {confirmed, at, attempts, headSha}`. |
129
+ | `actions.flagged` | `threadId → {reason, at}`. |
130
+ | `lastGoodSnapshot` | Path of the last snapshot that produced a result. |
131
+
132
+ ## Write side
133
+
134
+ ```
135
+ reply-thread.sh --state-file <path> --kind thread --thread <id> --root-comment <databaseId> --in-reply-to <databaseId> --body-file <file>
136
+ reply-thread.sh --state-file <path> --kind conversation --comment-id <id> --body-file <file>
137
+ reply-thread.sh --state-file <path> --kind review --review-id <id> --body-file <file>
138
+ resolve-thread.sh --state-file <path> --thread <id>
139
+ record.sh round --state-file <path> --had-rejection true|false [--fix-pushed]
140
+ record.sh flag-injection --state-file <path> --thread <id>
141
+ record.sh status --state-file <path> --status complete|escalated|cancelled
142
+ ```
143
+
144
+ - `--thread`, `--root-comment`, `--in-reply-to` come straight from
145
+ `threads.actionable[]` (`id`, `rootCommentId`, `inReplyTo`).
146
+ - The body comes from a file so untrusted text never enters argv.
147
+ - A reply is idempotent per reviewer comment: the same `--in-reply-to` twice is
148
+ exit `4` and nothing is posted; a reviewer follow-up has a new `inReplyTo`.
149
+ - `resolve-thread.sh` returns `0` only after the mutation response shows
150
+ `isResolved: true`; three false responses → exit `5`, nothing recorded, and
151
+ the thread stays in `staleUnresolved` next tick. A confirmed thread is not
152
+ re-requested.
153
+ - `record.sh round` runs once per round, after this round's replies and before
154
+ the push: it rotates `botFindingKeys → lastRoundFindingKeys`, sets
155
+ `lastRoundHadRejection`, and with `--fix-pushed` bumps `fixAttempts`.
156
+
157
+ ## Examples (captured from Falconiere/toolu#165)
158
+
159
+ `decision: success` — open PR, green CI, approved verdict, no open threads:
160
+
161
+ ```json
162
+ {"version":1,"slot":"falconiere-toolu-165","changed":true,"decision":"success",
163
+ "reasons":[{"code":"ci_pass","detail":"5 check(s) passed"},
164
+ {"code":"threads_clear","detail":"no unresolved review threads"},
165
+ {"code":"review_approved","detail":"bot verdict approved with zero findings"}],
166
+ "pr":{"number":165,"url":"https://github.com/Falconiere/toolu/pull/165","head":"a29e6c97379674323292578f9f0cfbffa00e375a",
167
+ "branch":"feat/python-quality","base":"main","author":"Falconiere","state":"OPEN","mergeable":"MERGEABLE","reviewDecision":"REVIEW_REQUIRED"},
168
+ "ci":{"status":"pass","checks":[{"name":"shellcheck","status":"pass","url":"https://github.com/Falconiere/toolu/actions/runs/33431959050/job/99619201007"}]},
169
+ "verdict":{"state":"complete","verdict":"approved","findingsCount":0,"findingKeys":[],"mustFix":[],
170
+ "commentUrl":"https://github.com/Falconiere/toolu/pull/165#issuecomment-5483377990","commentId":5483377990,
171
+ "degraded":false,"degradedReason":null,"sameRunAsLastTick":false},
172
+ "threads":{"total":6,"unresolved":0,"actionable":[],"staleUnresolved":[],"skippedOutdated":[],"flaggedInjection":[]},
173
+ "conversation":{"actionable":[]},"reviews":{"actionable":[]},
174
+ "recurrence":{"streak":0,"lastRoundHadRejection":false,"recurringKeys":[],"fixAttempts":0},
175
+ "backoff":{"idleStreak":0,"intervalMinutes":3,"waitSeconds":15},
176
+ "errors":[],"snapshotPath":"/tmp/pr-babysit-falconiere-toolu-165.snapshot.json","statePath":"/tmp/pr-babysit-falconiere-toolu-165.json"}
177
+ ```
178
+
179
+ `decision: keep_going` — review still running, one CI-reviewer thread open (comment bodies trimmed here):
180
+
181
+ ```json
182
+ {"decision":"keep_going",
183
+ "reasons":[{"code":"ci_pass","detail":"5 check(s) passed"},
184
+ {"code":"threads_unresolved","detail":"1 actionable thread(s)"},
185
+ {"code":"review_in_progress","detail":"review bot still running"}],
186
+ "verdict":{"state":"in_progress","verdict":"none","findingsCount":0,"sameRunAsLastTick":false},
187
+ "threads":{"total":6,"unresolved":1,
188
+ "actionable":[{"id":"PRRT_kwDOSzUwAc6d2S5O","path":"plugins/rust-quality/hooks/concerns/20-tests.sh","line":26,
189
+ "isOutdated":false,"rootCommentId":3897681281,"inReplyTo":3897751832,"authorClass":"ci_reviewer",
190
+ "lastCommentAuthor":"github-actions","lastCommentAt":"2026-08-31T19:39:25Z","injectionSuspect":false,"injectionPattern":null,
191
+ "comments":[{"id":"PRRC_kwDOSzUwAc7oUeWB","databaseId":3897681281,"body":"**medium** _(CORRECTNESS)_: …","author":"github-actions","authorType":"Bot","createdAt":"2026-08-31T19:29:22Z","url":"…"},
192
+ {"id":"PRRC_kwDOSzUwAc7oUuxU","databaseId":3897748564,"body":"No change — …","author":"Falconiere","authorType":"User","createdAt":"2026-08-31T19:38:58Z","url":"…"},
193
+ {"id":"PRRC_kwDOSzUwAc7oUvkY","databaseId":3897751832,"body":"**Still flagging after re-review.** …","author":"github-actions","authorType":"Bot","createdAt":"2026-08-31T19:39:25Z","url":"…"}]}],
194
+ "staleUnresolved":[],"skippedOutdated":[],"flaggedInjection":[]}}
195
+ ```
196
+
197
+ `decision: escalate` — the PR was merged externally:
198
+
199
+ ```json
200
+ {"decision":"escalate","changed":true,
201
+ "reasons":[{"code":"pr_merged","detail":"PR is merged"},{"code":"ci_pass","detail":"5 check(s) passed"},
202
+ {"code":"threads_clear","detail":"no unresolved review threads"},{"code":"review_approved","detail":"bot verdict approved with zero findings"}]}
203
+ ```
204
+
205
+ Structured error — the slot is held by another controller:
206
+
207
+ ```json
208
+ {"version":1,"errors":[{"code":"locked","message":"slot is held by another controller","pid":9103,"since":1789871015}]}
209
+ ```