@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,97 @@
1
+ #!/usr/bin/env bash
2
+ # Post-tool: turn "the user said yes" into a durable push-review waiver.
3
+ #
4
+ # push-review.sh (PreToolUse) can only ask; it never learns the answer. This
5
+ # module is the other half: PostToolUse runs only when the tool actually ran,
6
+ # so its presence IS the yes. It promotes the pending marker push-review left
7
+ # behind into a waiver for that exact diff.
8
+ #
9
+ # Two guards keep a waiver honest:
10
+ # * the diff SHA must still match what was asked about — a pending marker
11
+ # from older code is never cashed in by a push of different code;
12
+ # * the push must have SUCCEEDED. PostToolUse fires when a tool ran, not when
13
+ # it worked, and a rejected non-fast-forward push has not been reviewed by
14
+ # anybody. On failure the pending marker survives for the retry.
15
+ #
16
+ # Inputs (from the parent dispatcher post-tools/mod.sh, via `export`):
17
+ # $tool_name - name of the tool being invoked
18
+ # $input - raw JSON payload on stdin
19
+
20
+ # pipefail for parity with the other gate modules (push-review.sh,
21
+ # plan-ledger.sh, docs-sync.sh all use exactly this).
22
+ #
23
+ # Deliberately NOT -e and NOT -u, for the same underlying reason: both options
24
+ # apply to the shared libraries this module SOURCES, which are written for the
25
+ # shell they are sourced into rather than for a strict one.
26
+ #
27
+ # -e: this module's control flow uses non-zero returns as signal —
28
+ # push_waiver_promote returns 1 whenever there is nothing to promote,
29
+ # which is the normal case on most pushes.
30
+ # -u: the libraries use the guard-then-assign idiom, reading a variable
31
+ # before it is ever assigned. hooks/lib/detect.sh:307 is one of several:
32
+ # [ -n "$root" ] || root=$(git rev-parse --show-toplevel 2>/dev/null)
33
+ # Under nounset that aborts with "root: unbound variable" before this
34
+ # module reaches its own work, so a real push silently fails to record
35
+ # its waiver. Verified: adding -u fails
36
+ # __tests__/push-waiver.bats "the module never writes to stdout".
37
+ #
38
+ # Making these strict is a repo-wide change to how the shared libraries are
39
+ # written, not a per-module flag.
40
+ set -o pipefail
41
+
42
+ : "${tool_name:=}"
43
+ : "${input:=}"
44
+
45
+ # Cursor Agent uses tool_name "Shell"; Claude Code uses "Bash".
46
+ [[ "$tool_name" != "Bash" && "$tool_name" != "Shell" ]] && exit 0
47
+
48
+ command -v jq >/dev/null 2>&1 || exit 0
49
+ command -v git >/dev/null 2>&1 || exit 0
50
+
51
+ _toolu_lib="${TOOLU_LIB_DIR:-${BASH_SOURCE%/*}/../../lib}"
52
+ # shellcheck source=../../lib/detect.sh
53
+ . "$_toolu_lib/detect.sh"
54
+ # shellcheck source=../../lib/diff-sha.sh
55
+ . "$_toolu_lib/diff-sha.sh"
56
+ # shellcheck source=../../lib/push-waiver.sh
57
+ . "$_toolu_lib/push-waiver.sh"
58
+
59
+ command=$(echo "$input" | jq -r '.tool_input.command // ""' 2>/dev/null || echo "")
60
+ is_git_push "$command" || exit 0
61
+
62
+ # Exit-code resolution mirrors post-tools/modules/gate-status.sh so both
63
+ # modules read a host's response shape the same way.
64
+ exit_code=$(echo "$input" | jq -r '.tool_response.metadata.exit_code // .tool_response.exit_code // .tool_output.exit_code // empty' 2>/dev/null || echo "")
65
+ if [[ -z "$exit_code" || "$exit_code" == "null" ]]; then
66
+ tool_output_raw=$(echo "$input" | jq -r '.tool_output // empty' 2>/dev/null || echo "")
67
+ if [[ -n "$tool_output_raw" ]]; then
68
+ exit_code=$(echo "$tool_output_raw" | jq -r '.exitCode // .exit_code // empty' 2>/dev/null || echo "")
69
+ fi
70
+ fi
71
+
72
+ # A reported non-zero exit means the push did not land: leave the pending
73
+ # marker alone so the retry still asks.
74
+ if [[ -n "$exit_code" && "$exit_code" != "null" && "$exit_code" != "0" ]]; then
75
+ exit 0
76
+ fi
77
+
78
+ # An interrupted tool call did not complete either.
79
+ interrupted=$(echo "$input" | jq -r '.tool_response.interrupted // false' 2>/dev/null || echo "false")
80
+ [[ "$interrupted" == "true" ]] && exit 0
81
+
82
+ # Same target-repo resolution as the pre-tool gate: `git -C <worktree> push`
83
+ # is judged on the worktree's own branch and state.
84
+ repo_root=$(push_target_root "$command")
85
+ current_branch=$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
86
+ [[ -z "$current_branch" || "$current_branch" == "HEAD" ]] && exit 0
87
+ slug=$(branch_slug "$current_branch")
88
+
89
+ base_branch="${PUSH_REVIEW_BASE:-$(detect_base_branch "$repo_root")}"
90
+ current_diff_sha=$(toolu_diff_sha "$repo_root" "$base_branch")
91
+ [[ -z "$current_diff_sha" ]] && exit 0
92
+
93
+ # Nothing pending, or pending for other code: promote is a no-op and says so
94
+ # by returning non-zero. Either way this module has no output.
95
+ push_waiver_promote "$repo_root" "$slug" "$current_diff_sha" || exit 0
96
+
97
+ exit 0
@@ -0,0 +1,18 @@
1
+ #!/bin/bash
2
+ # PreCompact hook
3
+
4
+ HOOK_DIR="$(dirname "$0")"
5
+
6
+ # shellcheck source=lib/config.sh
7
+ . "$HOOK_DIR/lib/config.sh"
8
+
9
+ # Drain stdin with a shell builtin so Codex does not see EPIPE from a fast exit.
10
+ while IFS= read -r; do :; done
11
+
12
+ if ! toolu_enabled hooks pre-compact; then
13
+ exit 0
14
+ fi
15
+
16
+ # PreCompact has no shared context-output shape, so a successful no-op is silent.
17
+
18
+ exit 0
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env bash
2
+ # Agent/Task model-tier telemetry + advisory. Standalone PreToolUse hook — NOT
3
+ # under pre-tools/modules/ (mod.sh only dispatches that directory, so a
4
+ # sibling script here is invisible to it and, like mcp-blocker.sh, must read
5
+ # its own stdin instead of relying on the parent dispatcher's `export`s).
6
+ #
7
+ # Registered in hooks.json under matcher "spawn_agent|Agent|Task" — deliberately loose
8
+ # (it also matches TaskCreate/TaskUpdate/etc.); the real guard is the exact
9
+ # tool_name equality check below (resolves spec OQ-1).
10
+ #
11
+ # Behavior:
12
+ # - Anything but tool_name == "spawn_agent", "Agent", or "Task" -> no-op.
13
+ # - Always append a `delegation` telemetry event: {model, subagent_type,
14
+ # step_id, step_model}, all null when not applicable. The plan-ledger join
15
+ # (step_id/step_model) only happens when a ledger file for the current
16
+ # branch exists (cheap stat first): step_id is the running step's id, else
17
+ # the ledger's `next`; step_model is that step's declared `model` field.
18
+ # - Nudge/deny ONLY when the joined step names a non-null model AND the
19
+ # call supplied a `model` param AND they differ. A missing call `model`
20
+ # means tier-inherit and is always legitimate — never nudged, never
21
+ # denied. Controlled by `agentTier.mode`: advise (default, emits
22
+ # additionalContext) | block (deny) | off (silent).
23
+ # - All failure paths (no jq, unreadable/malformed ledger, no git repo)
24
+ # fail open (exit 0); a telemetry bug must never block a delegation.
25
+
26
+ set -o pipefail
27
+
28
+ _toolu_lib="${TOOLU_LIB_DIR:-${BASH_SOURCE%/*}/../lib}"
29
+ # shellcheck source=../lib/detect.sh
30
+ . "$_toolu_lib/detect.sh"
31
+ # shellcheck source=../lib/config.sh
32
+ . "$_toolu_lib/config.sh"
33
+ # shellcheck source=../lib/telemetry.sh
34
+ . "$_toolu_lib/telemetry.sh"
35
+ # shellcheck source=../lib/gate-mode.sh
36
+ . "$_toolu_lib/gate-mode.sh"
37
+
38
+ command -v jq >/dev/null 2>&1 || exit 0
39
+
40
+ _input=$(cat 2>/dev/null || true)
41
+
42
+ tool_name=$(jq -r '.tool_name // empty' <<<"$_input" 2>/dev/null || true)
43
+
44
+ # Exact-equality self-filter: the hooks.json matcher is deliberately loose
45
+ # (spawn_agent|Agent|Task), so everything else — including TaskCreate/TaskUpdate — must be
46
+ # silently ignored here.
47
+ case "$tool_name" in
48
+ Agent|Task|spawn_agent) ;;
49
+ *) exit 0 ;;
50
+ esac
51
+
52
+ command -v git >/dev/null 2>&1 || exit 0
53
+
54
+ root=$(detect_project_root)
55
+ [ -n "$root" ] || exit 0
56
+
57
+ call_model=$(jq -r '.tool_input.model // empty' <<<"$_input" 2>/dev/null || true)
58
+ subagent_type=$(jq -r '.tool_input.subagent_type // .tool_input.task_name // .tool_input.agent_type // empty' <<<"$_input" 2>/dev/null || true)
59
+ reasoning_effort=$(jq -r '.tool_input.reasoning_effort // .tool_input.reasoningEffort // empty' <<<"$_input" 2>/dev/null || true)
60
+
61
+ # Ledger join: cheap stat first, so a repo with no active plan pays no extra
62
+ # jq cost. LEDGER_DIR mirrors pre-tools/modules/plan-ledger.sh's override.
63
+ step_id="" step_model=""
64
+ branch=$(git -C "$root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
65
+ if [ -n "$branch" ] && [ "$branch" != "HEAD" ]; then
66
+ slug=$(branch_slug "$branch")
67
+ ledger_dir="${LEDGER_DIR:-$(toolu_project_state_dir plan-ledger "$root")}"
68
+ ledger_file="$ledger_dir/${slug}.json"
69
+ if [ -f "$ledger_file" ]; then
70
+ ledger_json=$(cat "$ledger_file" 2>/dev/null || true)
71
+ if [ -n "$ledger_json" ] && jq -e . >/dev/null 2>&1 <<<"$ledger_json"; then
72
+ # Running step's id if one is running, else the ledger's `next`
73
+ # (already null when every step is fresh-green).
74
+ step_id=$(jq -r '
75
+ (first(.steps[]? | select(.status == "running") | .id)) // (.next // empty)
76
+ ' <<<"$ledger_json" 2>/dev/null || true)
77
+ if [ -n "$step_id" ]; then
78
+ step_model=$(jq -r --arg id "$step_id" \
79
+ '(first(.steps[]? | select(.id == $id) | .model)) // empty' \
80
+ <<<"$ledger_json" 2>/dev/null || true)
81
+ fi
82
+ fi
83
+ fi
84
+ fi
85
+
86
+ # Always record the delegation — join or not, nudge or not.
87
+ extra=$(jq -n \
88
+ --arg model "$call_model" \
89
+ --arg subagent_type "$subagent_type" \
90
+ --arg reasoning_effort "$reasoning_effort" \
91
+ --arg step_id "$step_id" \
92
+ --arg step_model "$step_model" \
93
+ '{
94
+ model: (if $model == "" then null else $model end),
95
+ subagent_type: (if $subagent_type == "" then null else $subagent_type end),
96
+ reasoning_effort: (if $reasoning_effort == "" then null else $reasoning_effort end),
97
+ step_id: (if $step_id == "" then null else $step_id end),
98
+ step_model: (if $step_model == "" then null else $step_model end)
99
+ }')
100
+ telemetry_append "$root" delegation "$extra"
101
+
102
+ # Nudge/deny: only when the joined step declares a non-null model AND the
103
+ # call supplied one AND they differ. A missing call model is tier-inherit —
104
+ # always legitimate, never nudged or denied.
105
+ if [ -n "$step_model" ] && [ -n "$call_model" ] && [ "$step_model" != "$call_model" ]; then
106
+ mode=$(toolu_gate_mode agentTier)
107
+ reason="plan step \"$step_id\" expects model tier \"$step_model\" but this delegation used \"$call_model\""
108
+ toolu_gate_emit "$mode" "$reason"
109
+ fi
110
+
111
+ exit 0
@@ -0,0 +1,37 @@
1
+ #!/bin/bash
2
+ # Pre-tool-use hook dispatcher (entrypoint).
3
+ # Runs all scripts in .claude/hooks/pre-tools/modules/*.sh sequentially.
4
+ # Each script receives $input and $tool_name as env vars plus $input on stdin.
5
+ #
6
+ # Dispatch, decision short-circuit (permissionDecision:"deny"), advisory
7
+ # merging, and module exit-code semantics live in lib/dispatch.sh.
8
+
9
+ HOOK_LIB="$(cd "$(dirname "$0")/../lib" && pwd)"
10
+ # shellcheck source=../lib/config.sh
11
+ . "$HOOK_LIB/config.sh"
12
+ # shellcheck source=../lib/dispatch.sh
13
+ . "$HOOK_LIB/dispatch.sh"
14
+ # shellcheck source=../lib/detect.sh
15
+ . "$HOOK_LIB/detect.sh"
16
+ # shellcheck source=../lib/registry.sh
17
+ . "$HOOK_LIB/registry.sh"
18
+
19
+ if ! toolu_enabled hooks pre-tools; then
20
+ cat > /dev/null 2>&1 || true
21
+ exit 0
22
+ fi
23
+
24
+ # Exported only once the hook is enabled, so disabled runs keep the env clean.
25
+ export TOOLU_LIB_DIR="$HOOK_LIB"
26
+ TOOLU_CONFIG_DIR="$(toolu_config_root)"
27
+ export TOOLU_CONFIG_DIR
28
+
29
+ input=$(cat)
30
+ tool_name=$(jq -r '.tool_name // ""' <<<"$input" 2>/dev/null || echo "")
31
+
32
+ export input tool_name
33
+
34
+ HOOK_DIR="$(cd "$(dirname "$0")" && pwd)/modules"
35
+
36
+ # Exits 2 if a module hard-blocks via exit code 2 (stderr forwarded).
37
+ toolu_dispatch_hook "$HOOK_DIR" "PreToolUse" "$(toolu_registry_event_dir PreToolUse)"
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/env bash
2
+ # Pre-tool check: Bash/Shell command validation
3
+ # Data-driven: loads allow/deny rules from $TOOLU_SETTINGS_DIR/bash-{allow,deny}list.txt.
4
+ # Argv-aware: tokenizes the command and matches "<bin> <flag>" deny rules so
5
+ # substring evasion is harder (e.g. `node -e "evil"` is rejected even if `node`
6
+ # alone is allowed).
7
+ #
8
+ # Inputs (from parent dispatcher pre-tools/mod.sh, via `export`):
9
+ # $tool_name - name of the tool being invoked
10
+ # $input - raw JSON payload on stdin
11
+
12
+ : "${tool_name:=}"
13
+ : "${input:=}"
14
+
15
+ _toolu_lib="${TOOLU_LIB_DIR:-${BASH_SOURCE%/*}/../../lib}"
16
+ # shellcheck source=../../lib/detect.sh
17
+ . "$_toolu_lib/detect.sh"
18
+ # shellcheck source=../../lib/gate-mode.sh
19
+ . "$_toolu_lib/gate-mode.sh"
20
+
21
+ [[ "$tool_name" != "Bash" && "$tool_name" != "Shell" ]] && exit 0
22
+ command -v jq >/dev/null 2>&1 || exit 0
23
+
24
+ TOOLU_SETTINGS_DIR=$(toolu_settings_dir)
25
+
26
+ # read_list + strip_heredocs are sourced from lib/detect.sh.
27
+
28
+ # bash_tokenize <command>
29
+ # Tokenize a shell command into argv tokens, respecting single and double quotes.
30
+ # Echoes one token per line.
31
+ bash_tokenize() {
32
+ local cmd="$1"
33
+ python3 - "$cmd" <<'PY' 2>/dev/null || printf '%s\n' "$cmd"
34
+ import shlex, sys
35
+ try:
36
+ for tok in shlex.split(sys.argv[1], posix=True):
37
+ print(tok)
38
+ except ValueError:
39
+ # Unparseable (unbalanced quote etc) — print whole string so substring rules still fire.
40
+ print(sys.argv[1])
41
+ PY
42
+ }
43
+
44
+ # tokens_contain_rule <rule> <token...>
45
+ # Returns 0 if `tokens[0]` matches the first rule token AND every subsequent
46
+ # rule token appears as a distinct later argv token. Substring matching is
47
+ # never used here — that was the source of false-positives on commit messages
48
+ # like `git commit -m "fix cargo test failure"`.
49
+ tokens_contain_rule() {
50
+ local rule="$1"; shift
51
+ local -a tokens=("$@")
52
+ local -a rule_tokens
53
+ # shellcheck disable=SC2206 # intentional word-split on whitespace
54
+ rule_tokens=($rule)
55
+ [ ${#rule_tokens[@]} -eq 0 ] && return 1
56
+ [ "${tokens[0]:-}" = "${rule_tokens[0]}" ] || return 1
57
+ local i j want found
58
+ for ((i=1; i<${#rule_tokens[@]}; i++)); do
59
+ want="${rule_tokens[$i]}"
60
+ found=0
61
+ for ((j=1; j<${#tokens[@]}; j++)); do
62
+ if [ "${tokens[$j]}" = "$want" ]; then
63
+ found=1
64
+ break
65
+ fi
66
+ done
67
+ [ "$found" -eq 0 ] && return 1
68
+ done
69
+ return 0
70
+ }
71
+
72
+ # matches_deny <command> <denylist>
73
+ # Returns 0 (match) if any deny rule fires against the command, else 1.
74
+ # Rules containing whitespace (e.g. `node -e`, `cargo test`) use argv-aware
75
+ # multi-token matching only (no substring fallback); single-token rules
76
+ # (bare names like `biome`) substring-match anywhere on the command.
77
+ matches_deny() {
78
+ local cmd="$1"
79
+ local denylist="$2"
80
+ local rule tokens _tok
81
+ tokens=(); while IFS= read -r _tok; do tokens+=("$_tok"); done < <(bash_tokenize "$cmd")
82
+ # Tokenizer yielded nothing (empty/whitespace-only command): fall back to
83
+ # the raw command as a single token — same shape bash_tokenize itself emits
84
+ # on a parse failure. Substring rules below never consult tokens, so this
85
+ # cannot bypass them.
86
+ [ ${#tokens[@]} -eq 0 ] && tokens=("$cmd")
87
+ while IFS= read -r rule; do
88
+ [ -z "$rule" ] && continue
89
+ if [[ "$rule" == *" "* ]]; then
90
+ if tokens_contain_rule "$rule" "${tokens[@]}"; then
91
+ echo "$rule"
92
+ return 0
93
+ fi
94
+ else
95
+ # Single-token rule: substring match against the command. Bare-name
96
+ # rules (`biome`) land here; rules with internal whitespace
97
+ # (`cargo test`) take the multi-token argv branch above.
98
+ if [[ "$cmd" == *"$rule"* ]]; then
99
+ echo "$rule"
100
+ return 0
101
+ fi
102
+ fi
103
+ done <<< "$denylist"
104
+ return 1
105
+ }
106
+
107
+ # matches_allow <command> <allowlist>
108
+ # Returns 0 if any allow rule matches. Multi-token rules are argv-aware
109
+ # (same shape as deny); single-token rules substring-match.
110
+ matches_allow() {
111
+ local cmd="$1"
112
+ local allowlist="$2"
113
+ local rule tokens _tok
114
+ tokens=(); while IFS= read -r _tok; do tokens+=("$_tok"); done < <(bash_tokenize "$cmd")
115
+ # Same empty-tokenization fallback as matches_deny.
116
+ [ ${#tokens[@]} -eq 0 ] && tokens=("$cmd")
117
+ while IFS= read -r rule; do
118
+ [ -z "$rule" ] && continue
119
+ if [[ "$rule" == *" "* ]]; then
120
+ if tokens_contain_rule "$rule" "${tokens[@]}"; then
121
+ return 0
122
+ fi
123
+ else
124
+ if [[ "$cmd" == *"$rule"* ]]; then
125
+ return 0
126
+ fi
127
+ fi
128
+ done <<< "$allowlist"
129
+ return 1
130
+ }
131
+
132
+ # Public entry — invokable when the script is sourced (used by tests).
133
+ #
134
+ # Semantics: deny is evaluated FIRST so a deny result still carries the rule
135
+ # that fired. The allowlist is an explicit override on top of deny, intended
136
+ # for project-specific exemptions: deny + matching allow → allow; deny with
137
+ # no allow match → deny; no deny match → allow (default-open).
138
+ bash_commands_decide() {
139
+ local cmd="$1"
140
+ local allowlist denylist hit
141
+ allowlist=$(read_list "$TOOLU_SETTINGS_DIR/bash-allowlist.txt")
142
+ denylist=$(read_list "$TOOLU_SETTINGS_DIR/bash-denylist.txt")
143
+
144
+ # Strip heredoc bodies so we only check actual commands, not prose in commit messages.
145
+ cmd=$(printf '%s\n' "$cmd" | strip_heredocs)
146
+
147
+ if hit=$(matches_deny "$cmd" "$denylist"); then
148
+ if matches_allow "$cmd" "$allowlist"; then
149
+ echo "allow"
150
+ else
151
+ echo "deny:$hit"
152
+ fi
153
+ return 0
154
+ fi
155
+
156
+ echo "allow"
157
+ return 0
158
+ }
159
+
160
+ # When sourced (tests), return early.
161
+ (return 0 2>/dev/null) && return 0
162
+
163
+ command=$(echo "$input" | jq -r '.tool_input.command // ""')
164
+ decision=$(bash_commands_decide "$command")
165
+
166
+ case "$decision" in
167
+ deny:*)
168
+ rule="${decision#deny:}"
169
+ mode=$(toolu_gate_mode bashCommands)
170
+ case "$mode" in
171
+ ask)
172
+ reason=$(toolu_gate_guardrail_warning \
173
+ "Claude wants to run a command matching the deny rule \"$rule\"." \
174
+ "Rules in settings/bash-denylist.txt cover commands that execute arbitrary code from a string (node -e, bun -e) or that this project has ruled out. The command runs with your full shell privileges if you approve.")
175
+ ;;
176
+ advise)
177
+ reason="Command matches deny rule \"$rule\" (plugins/toolu/settings/bash-denylist.txt). The command was not stopped — gates.bashCommands.mode is 'advise'."
178
+ ;;
179
+ *)
180
+ reason="Command blocked by deny rule: $rule"
181
+ ;;
182
+ esac
183
+ toolu_gate_emit "$mode" "$reason"
184
+ ;;
185
+ esac
186
+
187
+ exit 0
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env bash
2
+ # Pre-tool check: Remind to read rules before editing code files.
3
+ # Data-driven: loads $TOOLU_SETTINGS_DIR/code-edit-rules.json. Empty/missing → no-op.
4
+ #
5
+ # Inputs (from parent dispatcher pre-tools/mod.sh, via `export`):
6
+ # $tool_name - name of the tool being invoked
7
+ # $input - raw JSON payload on stdin
8
+
9
+ : "${tool_name:=}"
10
+ : "${input:=}"
11
+
12
+ _toolu_lib="${TOOLU_LIB_DIR:-${BASH_SOURCE%/*}/../../lib}"
13
+ # shellcheck source=../../lib/detect.sh
14
+ . "$_toolu_lib/detect.sh"
15
+
16
+ # MultiEdit is in the PreToolUse matcher and carries .tool_input.file_path just
17
+ # like Edit/Write — include it so rule reminders are not dropped for MultiEdit.
18
+ [[ "$tool_name" != "Edit" && "$tool_name" != "Write" && "$tool_name" != "MultiEdit" ]] && exit 0
19
+ command -v jq >/dev/null 2>&1 || exit 0
20
+
21
+ TOOLU_SETTINGS_DIR=$(toolu_settings_dir)
22
+ RULES_FILE="$TOOLU_SETTINGS_DIR/code-edit-rules.json"
23
+
24
+ [ -f "$RULES_FILE" ] || exit 0
25
+
26
+ file_path=$(echo "$input" | jq -r '.tool_input.file_path // ""')
27
+ [ -z "$file_path" ] && exit 0
28
+
29
+ # Normalize abs → repo-relative so patterns in code-edit-rules.json match
30
+ # the same way the user wrote them.
31
+ file_path=$(to_relative_path "$file_path")
32
+
33
+ # glob_match <pattern> <path> — bash-native glob check
34
+ glob_match() {
35
+ local pattern="$1"
36
+ local path="$2"
37
+ shopt -s extglob globstar 2>/dev/null || true
38
+ # shellcheck disable=SC2053 # intentional glob match on RHS
39
+ [[ "$path" == $pattern ]]
40
+ }
41
+
42
+ # Iterate rules and accumulate docs whose match-glob fits.
43
+ docs=""
44
+ rule_count=$(jq '.rules | length' "$RULES_FILE" 2>/dev/null || echo 0)
45
+ [ "$rule_count" = "null" ] && rule_count=0
46
+
47
+ for ((i=0; i<rule_count; i++)); do
48
+ match=$(jq -r ".rules[$i].match // \"\"" "$RULES_FILE")
49
+ [ -z "$match" ] && continue
50
+ if glob_match "$match" "$file_path"; then
51
+ # Base docs
52
+ base=$(jq -r ".rules[$i].docs // [] | join(\" + \")" "$RULES_FILE")
53
+ [ -n "$base" ] && [ "$base" != "null" ] && docs="${docs}${docs:+ + }${base}"
54
+ # Conditional extra docs
55
+ extra_paths_count=$(jq -r ".rules[$i].when_path_matches // [] | length" "$RULES_FILE")
56
+ if [ "$extra_paths_count" -gt 0 ]; then
57
+ for ((j=0; j<extra_paths_count; j++)); do
58
+ cond=$(jq -r ".rules[$i].when_path_matches[$j]" "$RULES_FILE")
59
+ if glob_match "$cond" "$file_path"; then
60
+ extra=$(jq -r ".rules[$i].extra_docs // [] | join(\" + \")" "$RULES_FILE")
61
+ [ -n "$extra" ] && [ "$extra" != "null" ] && docs="${docs}${docs:+ + }${extra}"
62
+ break
63
+ fi
64
+ done
65
+ fi
66
+ break
67
+ fi
68
+ done
69
+
70
+ [ -z "$docs" ] && exit 0
71
+
72
+ jq -n --arg msg "Apply these rules: $docs" --arg file "$file_path" '{
73
+ "hookSpecificOutput": {
74
+ "hookEventName": "PreToolUse",
75
+ "additionalContext": ("File: " + $file + "\n" + $msg)
76
+ }
77
+ }'
78
+ exit 0
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env bash
2
+ # Pre-commit gate — validates Conventional Commits prefix and reminds about
3
+ # scope verification + memory save before commits.
4
+ # Project-agnostic: prefixes come from settings/commit-prefixes.txt; base
5
+ # branch is detected via detect_base_branch.
6
+
7
+ : "${tool_name:=}"
8
+ : "${input:=}"
9
+
10
+ _toolu_lib="${TOOLU_LIB_DIR:-${BASH_SOURCE%/*}/../../lib}"
11
+ # shellcheck source=../../lib/detect.sh
12
+ . "$_toolu_lib/detect.sh"
13
+ # shellcheck source=../../lib/gate-mode.sh
14
+ . "$_toolu_lib/gate-mode.sh"
15
+
16
+ [[ "$tool_name" != "Bash" ]] && exit 0
17
+ command -v jq >/dev/null 2>&1 || exit 0
18
+
19
+ command=$(echo "$input" | jq -r '.tool_input.command // ""' 2>/dev/null || echo "")
20
+
21
+ # Only match git commit commands. is_git_commit (lib/detect.sh) is used rather
22
+ # than a literal prefix match so `git -C <path> commit` and a commit reached
23
+ # through `&&` are seen too, and a heredoc body that merely mentions committing
24
+ # is not.
25
+ is_git_commit "$command" || exit 0
26
+
27
+ mode=$(toolu_gate_mode commitGate)
28
+ [ "$mode" = "off" ] && exit 0
29
+
30
+ TOOLU_SETTINGS_DIR=$(toolu_settings_dir)
31
+ PREFIX_FILE="$TOOLU_SETTINGS_DIR/commit-prefixes.txt"
32
+ BASE_BRANCH=$(detect_base_branch)
33
+
34
+ # read_list is sourced from lib/detect.sh.
35
+
36
+ # Extract the message from -m "..." if present; else allow (it's editor-driven).
37
+ # Double-quoted form honors backslash escapes (`-m "fix \"bug\""`) so an
38
+ # escaped quote no longer truncates the message; single-quoted form cannot
39
+ # contain escapes by shell rules. If neither form parses, msg stays empty and
40
+ # the gate falls through gracefully (no deny, context message only).
41
+ msg=""
42
+ if [[ "$command" == *" -m "* ]]; then
43
+ msg=$(printf '%s' "$command" | sed -nE 's/.* -m[[:space:]]+"((\\.|[^"\\])*)".*/\1/p')
44
+ if [ -z "$msg" ]; then
45
+ msg=$(printf '%s' "$command" | sed -nE "s/.* -m[[:space:]]+'([^']*)'.*/\\1/p")
46
+ else
47
+ # Unescape so the subject parser sees the literal message (\" -> ").
48
+ msg=$(printf '%s' "$msg" | sed -E 's/\\(.)/\1/g')
49
+ fi
50
+ fi
51
+
52
+ prefixes=$(read_list "$PREFIX_FILE")
53
+ if [ -n "$msg" ] && [ -n "$prefixes" ]; then
54
+ # Subject = first line up to first colon/space
55
+ subject_prefix=$(printf '%s' "$msg" | head -1 | sed -nE 's/^([a-z]+)(\(.*\))?:.*/\1/p')
56
+ if [ -n "$subject_prefix" ]; then
57
+ if ! echo "$prefixes" | grep -qFx "$subject_prefix"; then
58
+ toolu_gate_emit "$mode" "Unknown Conventional Commits prefix: \"$subject_prefix\". Allowed prefixes are in settings/commit-prefixes.txt. Base branch: $BASE_BRANCH"
59
+ exit 0
60
+ fi
61
+ fi
62
+ fi
63
+
64
+ jq -n --arg base "$BASE_BRANCH" '{
65
+ "hookSpecificOutput": {
66
+ "hookEventName": "PreToolUse",
67
+ "additionalContext": ("BEFORE COMMITTING:\n1. Verify diff covers only expected scope (git diff --stat against " + $base + ")\n2. Save memory of significant decisions before committing.\nSkip only if already done this task.")
68
+ }
69
+ }'
70
+ exit 0