@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,477 @@
1
+ #!/usr/bin/env bash
2
+ # Unified READ-ONLY view over the four toolu push gates (quality, plan, review,
3
+ # docs): "is this branch done, and why not" in one place, instead of four state
4
+ # files with no shared view. Never writes state and never runs a step's check —
5
+ # each gate function below MIRRORS the logic of its real enforcement point
6
+ # (quality-gate.sh, pre-tools/modules/{plan-ledger,push-review,docs-sync}.sh)
7
+ # without sourcing it, since those scripts are independently owned/evolving;
8
+ # drift between this view and a real gate is a bug to fix here, not a reason to
9
+ # couple to them.
10
+ #
11
+ # CLI:
12
+ # bash plugins/toolu/hooks/lib/verdict.sh status # human-readable table
13
+ # bash plugins/toolu/hooks/lib/verdict.sh json # machine-readable
14
+ #
15
+ # Exit codes: 0 ready, 1 blocked (>=1 gate fail/escalate), 2 error (not a git
16
+ # repo, or git/jq missing, or bad CLI usage).
17
+ #
18
+ # Push-review v2 schema (reviewed_files, version 2) is implemented HERE
19
+ # directly against the state file, mirroring rather than sourcing the
20
+ # push-review.sh gate (both are v2) — see spec component 3 / component 7.
21
+
22
+ set -o pipefail
23
+
24
+ _toolu_lib="${TOOLU_LIB_DIR:-${BASH_SOURCE%/*}}"
25
+ # shellcheck source=./detect.sh
26
+ . "$_toolu_lib/detect.sh"
27
+ # shellcheck source=./diff-sha.sh
28
+ . "$_toolu_lib/diff-sha.sh"
29
+ # shellcheck source=./config.sh
30
+ . "$_toolu_lib/config.sh"
31
+ # shellcheck source=./docs-sync-config.sh
32
+ . "$_toolu_lib/docs-sync-config.sh"
33
+ # shellcheck source=./plan-ledger-parse.sh
34
+ . "$_toolu_lib/plan-ledger-parse.sh"
35
+
36
+ # Reviewer allow-list — keep in sync with pre-tools/modules/push-review.sh's
37
+ # accepted_reviewers; parity is asserted by the parity test in
38
+ # __tests__/verdict.bats.
39
+ ACCEPTED_REVIEWERS='["code-review","toolu-review:review","code-review:xhigh","review","security-review"]'
40
+
41
+ # vd_gate STATE REASON [EXTRA_JSON]
42
+ # Print one gate object: {state, reason} merged with EXTRA_JSON (default "{}").
43
+ # EXTRA_JSON supplies gate-specific fields (plan's summary/ac_uncovered,
44
+ # review's reason_code/round) and must not itself carry state/reason.
45
+ vd_gate() {
46
+ local state="$1" reason="$2" extra="$3"
47
+ [ -n "$extra" ] || extra='{}'
48
+ jq -cn --arg state "$state" --arg reason "$reason" --argjson extra "$extra" \
49
+ '{state: $state, reason: $reason} + $extra'
50
+ }
51
+
52
+ # _vd_matches_any PATH GLOB_LIST -> 0 iff PATH fnmatches any newline-separated
53
+ # glob in GLOB_LIST. Local reimplementation of docs-sync.sh's own helper (that
54
+ # module is under concurrent edit; this is a ~10-line mirror, not a coupling).
55
+ _vd_matches_any() {
56
+ local path="$1" glob
57
+ while IFS= read -r glob; do
58
+ [ -z "$glob" ] && continue
59
+ # shellcheck disable=SC2254 # $glob is an intentional fnmatch pattern.
60
+ case "$path" in
61
+ $glob) return 0 ;;
62
+ esac
63
+ done <<< "$2"
64
+ return 1
65
+ }
66
+
67
+ # vd_gate_quality REPO_ROOT
68
+ # Mirrors quality-gate.sh: linked worktree -> skip (gate never enforces there,
69
+ # checked BEFORE the file so a worktree always reads skip regardless of a
70
+ # stale/failing file left over from the main checkout); else read the
71
+ # multi-slot gate file's top-level status.
72
+ vd_gate_quality() {
73
+ local repo_root="$1" gate_file git_dir common_dir
74
+
75
+ git_dir=$(git -C "$repo_root" rev-parse --path-format=absolute --git-dir 2>/dev/null || true)
76
+ common_dir=$(git -C "$repo_root" rev-parse --path-format=absolute --git-common-dir 2>/dev/null || true)
77
+ git_dir="${git_dir%/}"
78
+ common_dir="${common_dir%/}"
79
+ if [ -n "$git_dir" ] && [ -n "$common_dir" ] && [ "$git_dir" != "$common_dir" ]; then
80
+ vd_gate skip "gate disabled in linked worktree"
81
+ return
82
+ fi
83
+
84
+ gate_file="$(toolu_project_state_root "$repo_root")/quality-gate-status.json"
85
+ if [ ! -f "$gate_file" ]; then
86
+ vd_gate pass "no quality-gate failure recorded"
87
+ return
88
+ fi
89
+ if [ "$(jq -r '.status // ""' "$gate_file" 2>/dev/null)" != "failing" ]; then
90
+ vd_gate pass "quality gate passing"
91
+ return
92
+ fi
93
+ vd_gate fail "$(jq -r '.reason // "Quality gate failing"' "$gate_file" 2>/dev/null || echo "Quality gate failing")"
94
+ }
95
+
96
+ # vd_gate_plan REPO_ROOT BRANCH BASE CUR
97
+ # Mirrors pre-tools/modules/plan-ledger.sh's fresh-green push check: absent
98
+ # ledger + code files in the diff -> advise; absent ledger + no code files, or
99
+ # an empty (no-op) ledger -> skip; unparseable/schema-mismatched ledger -> fail
100
+ # (fail closed, matching the real gate); else fail iff any step isn't
101
+ # fresh-green. Always reports summary{total,fresh_green} (recomputed against
102
+ # CUR, never trusting a possibly-stale on-disk summary) and a report-only
103
+ # ac_uncovered count (mirrors lib/plan-ledger.sh's pl_ac_coverage_lines).
104
+ vd_gate_plan() {
105
+ local repo_root="$1" branch="$2" base="$3" cur="$4"
106
+ local slug ledger_dir state_file zero_extra='{"summary":{"total":0,"fresh_green":0},"ac_uncovered":0}'
107
+ slug=$(branch_slug "$branch")
108
+ ledger_dir="${LEDGER_DIR:-$(toolu_project_state_dir plan-ledger "$repo_root")}"
109
+ state_file="$ledger_dir/${slug}.json"
110
+
111
+ if [ ! -f "$state_file" ]; then
112
+ local code_files
113
+ code_files=$(git -C "$repo_root" diff --no-color "${base}...HEAD" --name-only 2>/dev/null \
114
+ | grep -E '\.(ts|tsx|js|jsx|rs|sh|py|go)$' || true)
115
+ if [ -n "$code_files" ]; then
116
+ vd_gate advise "no plan ledger for this change; if non-trivial, run plan" "$zero_extra"
117
+ else
118
+ vd_gate skip "no plan ledger and no code files in diff" "$zero_extra"
119
+ fi
120
+ return
121
+ fi
122
+
123
+ local ledger
124
+ if ! ledger=$(pl_read_ledger "$state_file" 2>/dev/null); then
125
+ vd_gate fail "unparseable ledger at $state_file" "$zero_extra"
126
+ return
127
+ fi
128
+ local version
129
+ version=$(jq -r '.version // ""' <<< "$ledger" 2>/dev/null)
130
+ if [ "$version" != "1" ]; then
131
+ vd_gate fail "ledger schema mismatch at $state_file (version=\"$version\", expected 1)" "$zero_extra"
132
+ return
133
+ fi
134
+
135
+ local total steps_count
136
+ total=$(jq -r '.summary.total // 0' <<< "$ledger" 2>/dev/null || echo 0)
137
+ steps_count=$(jq -r '.steps | length' <<< "$ledger" 2>/dev/null || echo 0)
138
+ if [ "$total" = "0" ] || [ "$steps_count" = "0" ]; then
139
+ vd_gate skip "empty plan ledger" "$zero_extra"
140
+ return
141
+ fi
142
+
143
+ local summary_json
144
+ summary_json=$(jq --arg cur "$cur" '
145
+ def is_fresh: (.status == "green") and (.diff_sha == $cur);
146
+ { total: (.steps | length), fresh_green: ([.steps[] | select(is_fresh)] | length) }
147
+ ' <<< "$ledger")
148
+
149
+ local blockers
150
+ blockers=$(jq -r --arg cur "$cur" '
151
+ .steps[]
152
+ | (if (.status == "green" and .diff_sha == $cur) then "green"
153
+ elif (.status == "green") then "stale"
154
+ else (.status // "pending") end) as $eff
155
+ | select($eff != "green")
156
+ | (.id // "?") + ": " + $eff
157
+ ' <<< "$ledger" 2>/dev/null || echo "")
158
+
159
+ local ac_uncovered=0 plan_doc spec_field spec_abs acs_json
160
+ plan_doc=$(jq -r '.plan_doc // ""' <<< "$ledger" 2>/dev/null || echo "")
161
+ if [ -n "$plan_doc" ]; then
162
+ local plan_abs="$plan_doc"
163
+ case "$plan_doc" in
164
+ /*) : ;;
165
+ *) [ -f "$plan_abs" ] || plan_abs="$repo_root/$plan_doc" ;;
166
+ esac
167
+ if [ -f "$plan_abs" ]; then
168
+ spec_field=$(pl_doc_field "$plan_abs" Spec)
169
+ case "$(printf '%s' "$spec_field" | tr '[:upper:]' '[:lower:]')" in
170
+ ""|none) : ;;
171
+ *)
172
+ spec_abs="$spec_field"
173
+ case "$spec_field" in
174
+ /*) : ;;
175
+ *) [ -f "$spec_abs" ] || spec_abs="$repo_root/$spec_field" ;;
176
+ esac
177
+ if [ -f "$spec_abs" ]; then
178
+ acs_json=$(pl_parse_acs "$spec_abs" | jq -Rsc 'split("\n") | map(select(length > 0))')
179
+ if [ -n "$acs_json" ] && [ "$acs_json" != "[]" ]; then
180
+ ac_uncovered=$(jq -n --argjson l "$ledger" --arg cur "$cur" --argjson acs "$acs_json" '
181
+ [ $acs[] as $ac
182
+ | ([$l.steps[] | select((.ac_refs // []) | index($ac))]) as $cov
183
+ | ($cov | any(.status == "green" and .diff_sha == $cur)) as $fresh
184
+ | select($fresh | not)
185
+ ] | length
186
+ ' 2>/dev/null) || ac_uncovered=0
187
+ fi
188
+ fi
189
+ ;;
190
+ esac
191
+ fi
192
+ fi
193
+
194
+ local extra
195
+ extra=$(jq -cn --argjson summary "$summary_json" --argjson ac "$ac_uncovered" '{summary: $summary, ac_uncovered: $ac}')
196
+
197
+ if [ -z "$blockers" ]; then
198
+ vd_gate pass "all plan-ledger steps fresh-green" "$extra"
199
+ else
200
+ vd_gate fail "steps not fresh-green: $(printf '%s' "$blockers" | tr '\n' ',' | sed 's/,$//')" "$extra"
201
+ fi
202
+ }
203
+
204
+ # vd_gate_review REPO_ROOT BRANCH BASE CUR
205
+ # Implements the push-review v2 schema directly (version normalizes to "2",
206
+ # reviewed_files must equal the sorted current diff) against the same state
207
+ # file pre-tools/modules/push-review.sh reads — independent of that gate's own
208
+ # v1-vs-v2 migration (spec component 3 + 7). reason_code is a closed set:
209
+ # empty-diff | stale-diff | schema-v1 | file-coverage | no-state | round-cap |
210
+ # findings | reviewer | pass.
211
+ vd_gate_review() {
212
+ local repo_root="$1" branch="$2" base="$3" cur="$4"
213
+ local empty_blob_sha="e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"
214
+
215
+ if [ "$branch" = "$base" ]; then
216
+ vd_gate skip "current branch is the base branch" '{"reason_code":null,"round":null}'
217
+ return
218
+ fi
219
+ if [ -z "$cur" ]; then
220
+ vd_gate skip "could not compute diff against $base" '{"reason_code":null,"round":null}'
221
+ return
222
+ fi
223
+ if [ "$cur" = "$empty_blob_sha" ]; then
224
+ vd_gate fail "diff against $base is empty; verify intent before pushing" \
225
+ '{"reason_code":"empty-diff","round":null}'
226
+ return
227
+ fi
228
+
229
+ local slug state_dir state_file
230
+ slug=$(branch_slug "$branch")
231
+ state_dir="${STATE_DIR:-$(toolu_project_state_dir push-review "$repo_root")}"
232
+ state_file="$state_dir/${slug}.json"
233
+
234
+ if [ ! -f "$state_file" ]; then
235
+ vd_gate fail "no push-review state file; run a reviewer and write the state" \
236
+ '{"reason_code":"no-state","round":null}'
237
+ return
238
+ fi
239
+
240
+ local state_json
241
+ state_json=$(cat "$state_file" 2>/dev/null) || state_json=""
242
+ if [ -z "$state_json" ] || ! jq -e . <<< "$state_json" >/dev/null 2>&1; then
243
+ # Present-but-garbage is a schema problem, not an absence — "no-state" is
244
+ # reserved for the file-absent case above, matching push-review.sh (an
245
+ # unparseable state there falls through to its version-mismatch "schema"
246
+ # deny, never its file-absent "no-state" one).
247
+ vd_gate fail "push-review state file is unparseable at $state_file" \
248
+ '{"reason_code":"schema","round":null}'
249
+ return
250
+ fi
251
+
252
+ local round_json
253
+ round_json=$(jq -c '(.review_round // 1) | if type == "number" then . else null end' <<< "$state_json" 2>/dev/null)
254
+ [ -n "$round_json" ] || round_json="null"
255
+
256
+ local version_norm state_sha state_findings
257
+ version_norm=$(jq -r '(.version // "") | tostring' <<< "$state_json" 2>/dev/null)
258
+ state_sha=$(jq -r '.diff_sha // ""' <<< "$state_json" 2>/dev/null)
259
+ state_findings=$(jq -r '(.findings_count // "") | tostring' <<< "$state_json" 2>/dev/null)
260
+
261
+ # schema-v1 is reserved for a state whose version normalizes to exactly "1"
262
+ # (the one-time-upgrade case push-review.sh gives its own deny message for);
263
+ # anything else malformed — version outside {1,2}, or a v2 missing diff_sha/
264
+ # findings_count — is the generic "schema" code, mirroring push-review.sh's
265
+ # own split between its version==1 check and its version!=2-or-corrupt check.
266
+ # A v2 state with reviewed_files missing/non-array deliberately falls THROUGH
267
+ # to the file-coverage check (reads as empty set, denies naming every changed
268
+ # path) — exactly the gate's documented choice; do not type-check it here.
269
+ if [ "$version_norm" = "1" ]; then
270
+ vd_gate fail "push-review state is schema v1; harness v2 requires reviewed_files — re-run the review to regenerate the state file" \
271
+ "$(jq -cn --argjson round "$round_json" '{reason_code:"schema-v1", round:$round}')"
272
+ return
273
+ fi
274
+ if [ "$version_norm" != "2" ] || [ -z "$state_sha" ] || [ -z "$state_findings" ]; then
275
+ vd_gate fail "push-review state file is corrupted or missing required v2 fields at $state_file" \
276
+ "$(jq -cn --argjson round "$round_json" '{reason_code:"schema", round:$round}')"
277
+ return
278
+ fi
279
+
280
+ if ! jq -e --argjson acc "$ACCEPTED_REVIEWERS" \
281
+ '(.reviewers // []) as $r | any($acc[]; . as $x | $r | index($x) != null)' \
282
+ <<< "$state_json" >/dev/null 2>&1; then
283
+ vd_gate fail "state file lists no accepted reviewer" \
284
+ "$(jq -cn --argjson round "$round_json" '{reason_code:"reviewer", round:$round}')"
285
+ return
286
+ fi
287
+
288
+ local round_num
289
+ round_num=$(jq -r '(.review_round // 1) | if type == "number" then (floor|tostring) else "" end' <<< "$state_json" 2>/dev/null)
290
+ if [ -n "$round_num" ] && [ "$round_num" -gt 5 ]; then
291
+ vd_gate escalate "review loop hit $round_num rounds (max 5) on an unchanged diff" \
292
+ "$(jq -cn --argjson round "$round_json" '{reason_code:"round-cap", round:$round}')"
293
+ return
294
+ fi
295
+
296
+ if [ "$state_sha" != "$cur" ]; then
297
+ vd_gate fail "diff changed since review (state stale)" \
298
+ "$(jq -cn --argjson round "$round_json" '{reason_code:"stale-diff", round:$round}')"
299
+ return
300
+ fi
301
+
302
+ local changed_sorted reviewed_sorted
303
+ changed_sorted=$(git -C "$repo_root" diff --no-color "${base}...HEAD" --name-only 2>/dev/null | sort -u)
304
+ reviewed_sorted=$(jq -r '.reviewed_files[]' <<< "$state_json" 2>/dev/null | sort -u)
305
+ if [ "$changed_sorted" != "$reviewed_sorted" ]; then
306
+ vd_gate fail "reviewed_files does not match the current diff's changed paths" \
307
+ "$(jq -cn --argjson round "$round_json" '{reason_code:"file-coverage", round:$round}')"
308
+ return
309
+ fi
310
+
311
+ if [ "$state_findings" != "0" ]; then
312
+ vd_gate fail "code review has open findings ($state_findings)" \
313
+ "$(jq -cn --argjson round "$round_json" '{reason_code:"findings", round:$round}')"
314
+ return
315
+ fi
316
+
317
+ vd_gate pass "review satisfied" "$(jq -cn --argjson round "$round_json" '{reason_code:"pass", round:$round}')"
318
+ }
319
+
320
+ # vd_gate_docs REPO_ROOT BRANCH
321
+ # Mirrors pre-tools/modules/docs-sync.sh's has_code/has_doc classification
322
+ # (via docs-sync-config.sh's glob readers) and attestation check, reading the
323
+ # attestation dir via docs-sync's OWN path resolution — NOT REPO_ROOT — so a
324
+ # worktree + CLAUDE_PROJECT_DIR setup is found the same way the real writer
325
+ # resolves it. "fail" is only reachable under docsSync.mode=block.
326
+ vd_gate_docs() {
327
+ local repo_root="$1" branch="$2" base mode
328
+
329
+ base="${DOCS_SYNC_BASE:-$(detect_base_branch "$repo_root")}"
330
+ mode=$(toolu_string "docsSync.mode" "advise" "advise" "block" "off")
331
+ if [ "$mode" = "off" ]; then
332
+ vd_gate skip "docsSync.mode is off"
333
+ return
334
+ fi
335
+
336
+ if [ -z "$branch" ] || [ "$branch" = "HEAD" ]; then
337
+ vd_gate skip "detached HEAD"
338
+ return
339
+ fi
340
+ if ! git -C "$repo_root" rev-parse --verify --quiet "$base" >/dev/null 2>&1; then
341
+ vd_gate skip "base branch '$base' not found locally"
342
+ return
343
+ fi
344
+ if [ "$branch" = "$base" ]; then
345
+ vd_gate skip "current branch is the base branch"
346
+ return
347
+ fi
348
+
349
+ local changed
350
+ changed=$(git -C "$repo_root" diff --no-color "${base}...HEAD" --name-only 2>/dev/null || echo "")
351
+ if [ -z "$changed" ]; then
352
+ vd_gate skip "no diff against $base"
353
+ return
354
+ fi
355
+
356
+ local surfaces surface_excludes code_surfaces
357
+ surfaces=$(docs_sync_surfaces)
358
+ surface_excludes=$(docs_sync_surface_excludes)
359
+ code_surfaces=$(docs_sync_code_surfaces)
360
+
361
+ local has_doc=0 has_code=0 path
362
+ while IFS= read -r path; do
363
+ [ -z "$path" ] && continue
364
+ if _vd_matches_any "$path" "$surfaces" && ! _vd_matches_any "$path" "$surface_excludes"; then
365
+ has_doc=1
366
+ fi
367
+ if _vd_matches_any "$path" "$code_surfaces"; then has_code=1; fi
368
+ done <<< "$changed"
369
+
370
+ if [ "$has_code" != "1" ] || [ "$has_doc" = "1" ]; then
371
+ vd_gate pass "doc surface in sync"
372
+ return
373
+ fi
374
+
375
+ # Code changed, no doc surface touched. Attestation dir mirrors docs-sync.sh
376
+ # EXACTLY (not repo_root): a worktree running with CLAUDE_PROJECT_DIR set
377
+ # must find the same file the writer wrote.
378
+ local diff_sha state_dir state_file
379
+ diff_sha=$(toolu_diff_sha "$repo_root" "$base") || diff_sha=""
380
+ state_dir="${DOCS_SYNC_STATE_DIR:-$(toolu_project_state_dir docs-sync)}"
381
+ state_file="$state_dir/$(branch_slug "$branch").json"
382
+ if [ -f "$state_file" ] && [ -n "$diff_sha" ]; then
383
+ local attested_sha
384
+ attested_sha=$(jq -r '.diff_sha // ""' "$state_file" 2>/dev/null || echo "")
385
+ if [ "$attested_sha" = "$diff_sha" ]; then
386
+ vd_gate pass "doc change attested as not needed"
387
+ return
388
+ fi
389
+ fi
390
+
391
+ if [ "$mode" = "block" ]; then
392
+ vd_gate fail "code changed without a doc update (docsSync.mode=block)"
393
+ else
394
+ vd_gate advise "code changed without a doc update"
395
+ fi
396
+ }
397
+
398
+ # vd_render_status JSON
399
+ # Print JSON's data as a compact, aligned human table (same data as `json`).
400
+ vd_render_status() {
401
+ local json="$1" branch base sha overall gate state reason
402
+ branch=$(jq -r '.branch' <<< "$json")
403
+ base=$(jq -r '.base_branch' <<< "$json")
404
+ sha=$(jq -r '.diff_sha' <<< "$json")
405
+ overall=$(jq -r '.overall' <<< "$json")
406
+
407
+ printf 'verdict: %s vs %s (diff %s)\n' "$branch" "$base" "${sha:0:12}"
408
+ printf '%-8s %-9s %s\n' "GATE" "STATE" "REASON"
409
+ for gate in quality plan review docs; do
410
+ state=$(jq -r --arg g "$gate" '.gates[$g].state' <<< "$json")
411
+ reason=$(jq -r --arg g "$gate" '.gates[$g].reason' <<< "$json")
412
+ printf '%-8s %-9s %s\n' "$gate" "$state" "$reason"
413
+ done
414
+ printf 'overall: %s\n' "$overall"
415
+ }
416
+
417
+ # main MODE (status|json) -> print the report, return 0 ready / 1 blocked / 2 error.
418
+ main() {
419
+ local mode="${1:-}"
420
+ case "$mode" in
421
+ status|json) : ;;
422
+ *) echo "verdict: usage: status | json" >&2; return 2 ;;
423
+ esac
424
+
425
+ command -v git >/dev/null 2>&1 || { echo "verdict: git is required" >&2; return 2; }
426
+ command -v jq >/dev/null 2>&1 || { echo "verdict: jq is required" >&2; return 2; }
427
+
428
+ local repo_root
429
+ repo_root=$(git rev-parse --show-toplevel 2>/dev/null) || repo_root=""
430
+ [ -n "$repo_root" ] || { echo "verdict: not a git repository" >&2; return 2; }
431
+
432
+ local branch base cur
433
+ branch=$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
434
+ base="${PUSH_REVIEW_BASE:-$(detect_base_branch "$repo_root")}"
435
+ cur=$(toolu_diff_sha "$repo_root" "$base") || cur=""
436
+
437
+ local g_quality g_plan g_review g_docs
438
+ g_quality=$(vd_gate_quality "$repo_root")
439
+ g_plan=$(vd_gate_plan "$repo_root" "$branch" "$base" "$cur")
440
+ g_review=$(vd_gate_review "$repo_root" "$branch" "$base" "$cur")
441
+ g_docs=$(vd_gate_docs "$repo_root" "$branch")
442
+
443
+ local overall
444
+ if ! overall=$(jq -rn --argjson q "$g_quality" --argjson p "$g_plan" --argjson r "$g_review" --argjson d "$g_docs" '
445
+ if ([$q, $p, $r, $d] | any(.state == "fail" or .state == "escalate"))
446
+ then "blocked" else "ready" end
447
+ ' 2>/dev/null) || [ -z "$overall" ]; then
448
+ echo "verdict: failed to assemble gate results (one of the four gate functions produced empty/invalid JSON)" >&2
449
+ return 2
450
+ fi
451
+
452
+ local out
453
+ out=$(jq -n \
454
+ --arg branch "$branch" --arg base "$base" --arg sha "$cur" \
455
+ --arg overall "$overall" --arg now "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
456
+ --argjson quality "$g_quality" --argjson plan "$g_plan" \
457
+ --argjson review "$g_review" --argjson docs "$g_docs" '
458
+ { version: 1, branch: $branch, base_branch: $base, diff_sha: $sha,
459
+ overall: $overall,
460
+ gates: { quality: $quality, plan: $plan, review: $review, docs: $docs },
461
+ generated_at: $now }
462
+ ')
463
+
464
+ if [ "$mode" = "json" ]; then
465
+ printf '%s\n' "$out"
466
+ else
467
+ vd_render_status "$out"
468
+ fi
469
+
470
+ [ "$overall" = "ready" ] && return 0 || return 1
471
+ }
472
+
473
+ # Guarded main: run only when executed directly, not when sourced.
474
+ if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
475
+ main "$@"
476
+ exit $?
477
+ fi
@@ -0,0 +1,42 @@
1
+ #!/bin/bash
2
+ # Post-tool-use hook dispatcher (entrypoint).
3
+ # Runs all scripts in .claude/hooks/post-tools/modules/*.sh sequentially.
4
+ # Each script receives $input, $tool_name, and $PROJECT_ROOT as env vars plus
5
+ # $input on stdin.
6
+ #
7
+ # Dispatch, decision short-circuit (PostToolUse uses decision:"block" +
8
+ # reason; permissionDecision is PreToolUse-only and ignored here), advisory
9
+ # merging, and module exit-code semantics live in lib/dispatch.sh.
10
+
11
+ HOOK_LIB="$(cd "$(dirname "$0")/../lib" && pwd)"
12
+ # shellcheck source=../lib/config.sh
13
+ . "$HOOK_LIB/config.sh"
14
+ # shellcheck source=../lib/dispatch.sh
15
+ . "$HOOK_LIB/dispatch.sh"
16
+ # shellcheck source=../lib/detect.sh
17
+ . "$HOOK_LIB/detect.sh"
18
+ # shellcheck source=../lib/registry.sh
19
+ . "$HOOK_LIB/registry.sh"
20
+
21
+ if ! toolu_enabled hooks post-tools; then
22
+ cat > /dev/null 2>&1 || true
23
+ exit 0
24
+ fi
25
+
26
+ # Exported only once the hook is enabled, so disabled runs keep the env clean.
27
+ export TOOLU_LIB_DIR="$HOOK_LIB"
28
+ TOOLU_CONFIG_DIR="$(toolu_config_root)"
29
+ export TOOLU_CONFIG_DIR
30
+
31
+ input=$(cat 2>/dev/null || echo "{}")
32
+ tool_name=$(jq -r '.tool_name // ""' <<<"$input" 2>/dev/null || echo "")
33
+
34
+ PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
35
+ export PATH="$PROJECT_ROOT/node_modules/.bin:$PATH"
36
+
37
+ export input tool_name PROJECT_ROOT
38
+
39
+ HOOK_DIR="$(cd "$(dirname "$0")" && pwd)/modules"
40
+
41
+ # Exits 2 if a module hard-blocks via exit code 2 (stderr forwarded).
42
+ toolu_dispatch_hook "$HOOK_DIR" "PostToolUse" "$(toolu_registry_event_dir PostToolUse)"
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env bash
2
+ # Post-tool check: Quality gate state tracking
3
+ # Persists global gate state to the host-native project state directory based on
4
+ # the exit code of recognized quality/test commands.
5
+ #
6
+ # Inputs (from parent dispatcher post-tools/mod.sh, via `export`):
7
+ # $tool_name - name of the tool being invoked
8
+ # $input - raw JSON payload on stdin
9
+ # $PROJECT_ROOT - repository root
10
+
11
+ : "${tool_name:=}"
12
+ : "${input:=}"
13
+ : "${PROJECT_ROOT:=$(pwd)}"
14
+
15
+ # Cursor Agent uses tool_name "Shell"; Claude Code uses "Bash".
16
+ [[ "$tool_name" != "Bash" && "$tool_name" != "Shell" ]] && exit 0
17
+
18
+ _toolu_lib="${TOOLU_LIB_DIR:-${BASH_SOURCE%/*}/../../lib}"
19
+ # shellcheck source=../../lib/host.sh
20
+ . "$_toolu_lib/host.sh"
21
+ GATE_DIR="$(toolu_project_state_root "$PROJECT_ROOT")"
22
+ GATE_FILE="$GATE_DIR/quality-gate-status.json"
23
+ mkdir -p "$GATE_DIR"
24
+
25
+ command=$(echo "$input" | jq -r '.tool_input.command // ""' 2>/dev/null || echo "")
26
+ exit_code=$(echo "$input" | jq -r '.tool_response.metadata.exit_code // .tool_response.exit_code // .tool_output.exit_code // empty' 2>/dev/null || echo "")
27
+ if [[ -z "$exit_code" || "$exit_code" == "null" ]]; then
28
+ tool_output_raw=$(echo "$input" | jq -r '.tool_output // empty' 2>/dev/null || echo "")
29
+ if [[ -n "$tool_output_raw" ]]; then
30
+ exit_code=$(echo "$tool_output_raw" | jq -r '.exitCode // .exit_code // empty' 2>/dev/null || echo "")
31
+ fi
32
+ fi
33
+
34
+ # A failing gate owned by a file-level quality hook (ts-quality-hook,
35
+ # rust-quality-hook, ...) is NOT clobbered by what follows: this module records
36
+ # and clears only its OWN command-channel slot (key "__global__", source
37
+ # "gate-status-hook") through gate-file.sh's ownership-checked helpers, so a file
38
+ # hook's entry — owned by a different source — survives until that file is fixed.
39
+ # This replaces an earlier top-level `source == *-quality-hook` guard that, while
40
+ # a file failure was live, dropped a failing command outright; fixing the file
41
+ # then cleared the only tracked entry and flipped the gate to passing even though
42
+ # the command was still failing.
43
+
44
+ # Quality / test commands that should toggle the global gate:
45
+ # * Project tool wrappers: tools/<name>/{check,test,format}.sh (any project basename)
46
+ # * TS/JS: bun run script aliases, bun test, vitest, jest, tsc, ./scripts/ts-check.sh
47
+ # * Rust: cargo clippy/test/build/nextest
48
+ # The wrapper-path regex is project-agnostic; per-project naming lives in the
49
+ # wrapper script, not here.
50
+ #
51
+ # Anchor every alternative at a COMMAND boundary so substring false positives
52
+ # (`cat tsconfig.json` → `tsc`, `vitests-helper` → `vitest`) no longer flip the
53
+ # gate. Unlike quality-gate.sh (which only anchors at start-of-line, inspecting
54
+ # the leading command of a push), gate-status must still detect a quality
55
+ # command that appears AFTER a shell operator in a compound command
56
+ # (`cd crate && cargo test`), so the prefix also matches after a separator.
57
+ # The trailing `|` (single pipe) in this alternation is BY DESIGN: a quality
58
+ # command after a pipe (`find . | cargo test`) still ran, so it must trigger
59
+ # gate registration. Do not drop it. (Locked in by gate-status.bats.)
60
+ GATE_TRIGGER_PREFIX='(^|[[:space:]]|&&|\|\||;|\|)[[:space:]]*'
61
+ GATE_TRIGGER_SUFFIX='([[:space:]]|$|&&|\|\||;|\|)'
62
+ GATE_TRIGGER_ALTERNATION='tools/[A-Za-z0-9_.-]+/(check|test|format)\.sh|bun run (check|check:fix|check:duplication|ts:check|ts:check:fix|rust:check|rust:test|check-types|lint|lint:fix|format|format:check|format:fix|build|test)|bun test|vitest|jest|tsc|\./scripts/ts-check\.sh|cargo (clippy|test|build|nextest)'
63
+ if ! echo "$command" | grep -qE "${GATE_TRIGGER_PREFIX}(${GATE_TRIGGER_ALTERNATION})${GATE_TRIGGER_SUFFIX}"; then
64
+ exit 0
65
+ fi
66
+
67
+ # Multi-slot gate writer/clearer (entries keyed by file/channel) so a command
68
+ # failure no longer clobbers a file hook's failure and vice-versa. Soft if
69
+ # absent: the fallbacks keep legacy single-slot behavior when the toolu lib
70
+ # predates gate-file.sh.
71
+ # shellcheck source=../../lib/gate-file.sh
72
+ [ -f "$_toolu_lib/gate-file.sh" ] && . "$_toolu_lib/gate-file.sh"
73
+ command -v gate_record_failure >/dev/null 2>&1 || gate_record_failure() {
74
+ jq -n --arg reason "$4" --arg source "$3" --arg file "$2" --arg violations "$5" \
75
+ --arg updatedAt "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
76
+ '{status: "failing", reason: $reason, source: $source, file: $file,
77
+ violations: $violations, updatedAt: $updatedAt}' > "$1"
78
+ }
79
+ command -v gate_clear_file >/dev/null 2>&1 || gate_clear_file() {
80
+ [ -f "$1" ] || return 0
81
+ local _src _file
82
+ _src=$(jq -r '.source // ""' "$1" 2>/dev/null || echo "")
83
+ _file=$(jq -r '.file // ""' "$1" 2>/dev/null || echo "")
84
+ if [ "$_src" = "$3" ] && [ "$_file" = "$2" ]; then
85
+ jq -n --arg source "$3" --arg updatedAt "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
86
+ '{status: "passing", source: $source, updatedAt: $updatedAt}' > "$1"
87
+ fi
88
+ }
89
+
90
+ # The quality-command gate slot. "__global__" is the same key gate-file.sh
91
+ # assigns a no-`file` command failure when it seeds a legacy record, so a record
92
+ # written here and a promoted legacy command failure occupy one slot.
93
+ GATE_COMMAND_KEY="__global__"
94
+
95
+ if [[ "$exit_code" =~ ^[0-9]+$ && "$exit_code" -ne 0 ]]; then
96
+ # Record the command failure as its OWN slot. When a file hook's failure is
97
+ # also live this coexists with it (different source/key); fixing the file later
98
+ # promotes this slot instead of flipping the gate to passing while the command
99
+ # is still broken.
100
+ gate_record_failure "$GATE_FILE" "$GATE_COMMAND_KEY" "gate-status-hook" \
101
+ "Quality command failed: $command (exit $exit_code)" ""
102
+
103
+ jq -n --arg ctx "Global quality gate failing. Fix all errors/warnings/tests before new tasks.\nFailed: $command (exit $exit_code)" '{
104
+ "hookSpecificOutput": {
105
+ "hookEventName": "PostToolUse",
106
+ "additionalContext": $ctx
107
+ }
108
+ }'
109
+ exit 0
110
+ fi
111
+
112
+ if [[ "$exit_code" == "0" ]]; then
113
+ # Clear ONLY this command-channel slot. gate_clear_file is ownership-checked: a
114
+ # live *-quality-hook file entry is owned by a different source, so it is left
115
+ # intact and the gate stays failing until that file is fixed too.
116
+ gate_clear_file "$GATE_FILE" "$GATE_COMMAND_KEY" "gate-status-hook"
117
+
118
+ # Assert an affirmative passing record ONLY when no gate file exists yet — the
119
+ # first-ever passing command, with nothing tracked. In every other case
120
+ # gate_clear_file already did the right thing: it flips the gate to passing when
121
+ # our slot was the last entry, and leaves a live file-hook failure (owned by a
122
+ # different source) untouched. Gating on `! -f` alone means we never re-write a
123
+ # file the helper just wrote (no hot-path double-write) and never clobber a
124
+ # foreign failure (the file is present, so we skip).
125
+ # CONCURRENCY: this write shares gate-file.sh's single-writer assumption — safe
126
+ # only while PostToolUse hooks fire serially; see the CONCURRENCY note there.
127
+ if [ ! -f "$GATE_FILE" ]; then
128
+ jq -n \
129
+ --arg status "passing" \
130
+ --arg source "$command" \
131
+ --arg updatedAt "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
132
+ '{
133
+ status: $status,
134
+ source: $source,
135
+ updatedAt: $updatedAt
136
+ }' > "$GATE_FILE"
137
+ fi
138
+ fi
139
+
140
+ exit 0