@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,32 @@
1
+ #!/usr/bin/env bash
2
+ # Content-addressed branch-diff hash: the one formula push-review, plan-ledger
3
+ # (both the checker lib and the push-time gate), and docs-sync each computed as
4
+ # their own inline copy. Survives amend/rebase (content, not commit ids).
5
+ #
6
+ # Public API:
7
+ # toolu_diff_sha REPO_ROOT BASE_REF - print the hash of `git diff
8
+ # BASE_REF...HEAD` run in REPO_ROOT.
9
+ # Non-zero + empty stdout only when git
10
+ # fails or prints nothing; an empty
11
+ # diff returns 0 with the well-known
12
+ # empty-blob sha — callers own the
13
+ # sentinel.
14
+ #
15
+ # NO sentinel mapping here (e.g. push-review's well-known-empty-blob-sha ->
16
+ # "empty-diff" substitution) — an empty diff prints the real (empty-blob) sha
17
+ # like any other; each caller decides what an empty diff means to it.
18
+ #
19
+ # Source via: . "${TOOLU_LIB_DIR:-${BASH_SOURCE%/*}}/diff-sha.sh"
20
+
21
+ # pipefail so a failing `git diff` isn't masked by `git hash-object` succeeding
22
+ # on the resulting empty stream (which hashes to the well-known empty-blob sha).
23
+ set -o pipefail
24
+
25
+ # toolu_diff_sha REPO_ROOT BASE_REF
26
+ toolu_diff_sha() {
27
+ local repo_root="$1" base_ref="$2" sha
28
+ sha=$(git -C "$repo_root" diff --no-color "${base_ref}...HEAD" 2>/dev/null \
29
+ | git hash-object --stdin 2>/dev/null) || return 1
30
+ [ -n "$sha" ] || return 1
31
+ printf '%s\n' "$sha"
32
+ }
@@ -0,0 +1,365 @@
1
+ #!/usr/bin/env bash
2
+ # Shared module dispatcher for the pre-tools / post-tools hook entrypoints.
3
+ # Source via: . "${BASH_SOURCE%/*}/../lib/dispatch.sh"
4
+ #
5
+ # Public API:
6
+ # toolu_dispatch_modules MODULES_DIR EVENT_NAME [REGISTRY_DIR...]
7
+ # Runs every MODULES_DIR/*.sh in lexical order, then every *.sh in each
8
+ # REGISTRY_DIR (built-in modules first), feeding each the exported `input`
9
+ # variable (raw hook JSON) on stdin. EVENT_NAME is "PreToolUse" or
10
+ # "PostToolUse" and selects the decision semantics.
11
+ # Registry modules (anything outside MODULES_DIR) MUST be namespaced
12
+ # "<plugin-spec>__<name>.sh" (specs must not contain "__"; "__" instead of
13
+ # "." so specs like "name@git.example.com" parse unambiguously) and run
14
+ # only when `toolu_plugin_active <plugin-spec>` succeeds. A registry
15
+ # file that is not namespaced, or dispatched without that helper sourced,
16
+ # is SKIPPED — fail closed, so a partial-sourcing bug degrades to "do
17
+ # less" instead of running ungated modules. Built-in MODULES_DIR scripts
18
+ # are never gated. The plugin-active lookup is memoized per spec within
19
+ # one dispatch call.
20
+ #
21
+ # Output discipline:
22
+ # - PreToolUse: a module emitting `hookSpecificOutput.permissionDecision ==
23
+ # "deny"` is authoritative — its output is emitted immediately and
24
+ # dispatch stops (security wins; a deny must not be suppressed by an
25
+ # advisory).
26
+ # - PreToolUse: a module emitting `permissionDecision == "ask"` is HELD, not
27
+ # emitted immediately, and dispatch continues — a later module must still
28
+ # be able to deny, and a prompt the user could answer "yes" to must never
29
+ # outrank a hard block. The first ask wins if several fire; it is emitted
30
+ # at the end with any collected advisories appended to its reason, so an
31
+ # advisory is never lost behind a prompt.
32
+ # - PostToolUse: a module emitting top-level `decision == "block"` is
33
+ # authoritative — its output is emitted immediately and dispatch stops.
34
+ # (PostToolUse has no permissionDecision; that is PreToolUse-only.)
35
+ # - Otherwise every module's advisory `hookSpecificOutput.additionalContext`
36
+ # (and top-level `systemMessage`) is collected and merged into ONE final
37
+ # JSON object emitted at the end.
38
+ #
39
+ # Module exit-code semantics (deliberate — the old `echo "$input" | bash`
40
+ # pipeline masked these entirely):
41
+ # - 0: stdout handled per the discipline above; stderr discarded.
42
+ # - 2: the Claude Code "block via exit code" convention — the module's
43
+ # stderr is forwarded and the dispatcher returns 2, so the entrypoint
44
+ # exits 2 and Claude Code sees the block plus the stderr feedback.
45
+ # Remaining modules are skipped.
46
+ # - any other non-zero: treated as a module failure — one warning line on
47
+ # stderr (visible in claude --debug), the module's possibly-partial
48
+ # stdout is DISCARDED, and dispatch continues with the next module.
49
+
50
+ _TOOLU_DISPATCH_LIB_DIR="$(cd "${BASH_SOURCE%/*}" && pwd)"
51
+ # shellcheck source=edit-records.sh
52
+ . "$_TOOLU_DISPATCH_LIB_DIR/edit-records.sh"
53
+
54
+ toolu_dispatch_modules() {
55
+ # With <2 args `shift 2` would not shift at all, leaving $1 to be re-globbed
56
+ # as a registry dir and every built-in module run twice.
57
+ [[ $# -lt 2 ]] && return 0
58
+ local modules_dir="$1" event="$2"; shift 2
59
+ local registry_dirs=("$@")
60
+ local script result rc err_file decision ctx msg c base plugin
61
+ local contexts=() messages=()
62
+ # First `ask` seen this dispatch, held until every module has run.
63
+ local ask_result=""
64
+ # Per-dispatch memo of plugin-active lookups (space-delimited spec lists;
65
+ # plain strings, not associative arrays, for bash 3.2 compatibility).
66
+ local active_specs=" " inactive_specs=" "
67
+
68
+ # Constant for the whole dispatch; resolved once instead of per module.
69
+ local plugin_helper_ok=0
70
+ declare -F toolu_plugin_active >/dev/null 2>&1 && plugin_helper_ok=1
71
+
72
+ err_file=$(mktemp "${TMPDIR:-/tmp}/toolu-dispatch.XXXXXX") || return 0
73
+
74
+ # Ordered module list: built-in dir first, then each registry dir.
75
+ local scripts=()
76
+ for script in "$modules_dir"/*.sh; do
77
+ [[ -f "$script" ]] && scripts+=("$script")
78
+ done
79
+ local rdir
80
+ for rdir in "${registry_dirs[@]}"; do
81
+ [[ -d "$rdir" ]] || continue
82
+ for script in "$rdir"/*.sh; do
83
+ [[ -f "$script" ]] && scripts+=("$script")
84
+ done
85
+ done
86
+
87
+ for script in "${scripts[@]}"; do
88
+ [[ ! -f "$script" ]] && continue
89
+
90
+ # Anything outside MODULES_DIR is a registry module and MUST satisfy the
91
+ # gating contract; violations are skipped, never run. Built-ins are
92
+ # globbed directly from $modules_dir, so "registry" means the script's
93
+ # dirname is not EXACTLY $modules_dir — a prefix match would let a
94
+ # registry dir nested under the modules tree masquerade as built-in (and
95
+ # an empty modules_dir prefix would match every absolute path).
96
+ base=$(basename "$script")
97
+ if [[ -z "$modules_dir" || "${script%/*}" != "$modules_dir" ]]; then
98
+ # The spec must be non-empty ("__foo.sh") and whitespace-free: the
99
+ # memo lists are space-delimited, so a spec containing whitespace
100
+ # could substring-collide with another spec's memo entry.
101
+ plugin="${base%%__*}"
102
+ if [[ "$base" != *__*.sh || -z "$plugin" || "$plugin" == *[[:space:]]* ]]; then
103
+ printf 'toolu-dispatch: registry module %s lacks <plugin-spec>__<name>.sh namespace; skipped\n' \
104
+ "$base" >&2
105
+ continue
106
+ fi
107
+ if [[ $plugin_helper_ok -ne 1 ]]; then
108
+ printf 'toolu-dispatch: toolu_plugin_active not sourced; registry module %s skipped\n' \
109
+ "$base" >&2
110
+ continue
111
+ fi
112
+ # Resolve each distinct spec at most once per dispatch (hot path: one
113
+ # jq per plugin instead of one per module).
114
+ if [[ "$active_specs" != *" $plugin "* ]]; then
115
+ [[ "$inactive_specs" == *" $plugin "* ]] && continue
116
+ if ! toolu_plugin_active "$plugin"; then
117
+ inactive_specs="${inactive_specs}${plugin} "
118
+ continue
119
+ fi
120
+ active_specs="${active_specs}${plugin} "
121
+ fi
122
+ fi
123
+
124
+ rc=0
125
+ # Modules are always executed with `bash` regardless of their shebang. This
126
+ # is fine today because only *.sh files are globbed above; a future non-bash
127
+ # module would require this invocation to honor the script's interpreter.
128
+ # shellcheck disable=SC2154 # $input is exported by the sourcing entrypoint.
129
+ result=$(bash "$script" <<<"$input" 2>"$err_file") || rc=$?
130
+
131
+ if [[ $rc -eq 2 ]]; then
132
+ # Deliberate hard block: propagate stderr + exit code 2.
133
+ cat "$err_file" >&2
134
+ rm -f "$err_file"
135
+ return 2
136
+ fi
137
+ if [[ $rc -ne 0 ]]; then
138
+ # Module failure: make it visible, drop its partial output, keep going.
139
+ printf 'toolu-dispatch: module %s exited %d; output skipped\n' \
140
+ "$(basename "$script")" "$rc" >&2
141
+ continue
142
+ fi
143
+ [[ -z "$result" ]] && continue
144
+
145
+ case "$event" in
146
+ PreToolUse)
147
+ decision=$(jq -r '.hookSpecificOutput.permissionDecision // empty' <<<"$result" 2>/dev/null)
148
+ if [[ "$decision" == "deny" ]]; then
149
+ printf '%s\n' "$result"
150
+ rm -f "$err_file"
151
+ return 0
152
+ fi
153
+ if [[ "$decision" == "ask" ]]; then
154
+ # Hold the first ask and keep going: a later deny outranks it. The
155
+ # ask payload carries its text in permissionDecisionReason, not in
156
+ # additionalContext, so there is nothing to harvest below.
157
+ [[ -z "$ask_result" ]] && ask_result="$result"
158
+ continue
159
+ fi
160
+ ;;
161
+ PostToolUse)
162
+ decision=$(jq -r '.decision // empty' <<<"$result" 2>/dev/null)
163
+ if [[ "$decision" == "block" ]]; then
164
+ printf '%s\n' "$result"
165
+ rm -f "$err_file"
166
+ return 0
167
+ fi
168
+ ;;
169
+ esac
170
+
171
+ ctx=$(jq -r '.hookSpecificOutput.additionalContext // empty' <<<"$result" 2>/dev/null)
172
+ if [[ -n "$ctx" ]]; then
173
+ local seen=0 existing
174
+ for existing in "${contexts[@]}"; do [[ "$existing" == "$ctx" ]] && seen=1; done
175
+ [[ $seen -eq 0 ]] && contexts+=("$ctx")
176
+ fi
177
+ msg=$(jq -r '.systemMessage // empty' <<<"$result" 2>/dev/null)
178
+ if [[ -n "$msg" ]]; then
179
+ local seen_msg=0 existing_msg
180
+ for existing_msg in "${messages[@]}"; do [[ "$existing_msg" == "$msg" ]] && seen_msg=1; done
181
+ [[ $seen_msg -eq 0 ]] && messages+=("$msg")
182
+ fi
183
+ done
184
+ rm -f "$err_file"
185
+
186
+ local merged_ctx="" merged_msg=""
187
+ if [[ ${#contexts[@]} -gt 0 ]]; then
188
+ for c in "${contexts[@]}"; do
189
+ if [[ -z "$merged_ctx" ]]; then
190
+ merged_ctx="$c"
191
+ else
192
+ merged_ctx="${merged_ctx}"$'\n\n'"${c}"
193
+ fi
194
+ done
195
+ fi
196
+ if [[ ${#messages[@]} -gt 0 ]]; then
197
+ for c in "${messages[@]}"; do
198
+ if [[ -z "$merged_msg" ]]; then
199
+ merged_msg="$c"
200
+ else
201
+ merged_msg="${merged_msg}"$'\n\n'"${c}"
202
+ fi
203
+ done
204
+ fi
205
+
206
+ # A held ask is the decision, and it absorbs every advisory collected after
207
+ # it so nothing a later module wanted to say is dropped.
208
+ if [[ -n "$ask_result" ]]; then
209
+ # If the enrichment fails for any reason, emit the ask EXACTLY as the module
210
+ # wrote it. Losing an advisory is a cosmetic loss; losing the decision would
211
+ # turn a prompt into a silent allow.
212
+ jq -n --argjson ask "$ask_result" --arg ctx "$merged_ctx" --arg msg "$merged_msg" '
213
+ ($ask.hookSpecificOutput.permissionDecisionReason // "") as $reason
214
+ | $ask
215
+ | .hookSpecificOutput.permissionDecisionReason =
216
+ (if $ctx != "" then ($reason + "\n\n" + $ctx) else $reason end)
217
+ | (if $msg != "" then .systemMessage = ((.systemMessage // "") | if . == "" then $msg else . + "\n\n" + $msg end) else . end)
218
+ ' 2>/dev/null || printf '%s\n' "$ask_result"
219
+ return 0
220
+ fi
221
+
222
+ if [[ -n "$merged_ctx" || -n "$merged_msg" ]]; then
223
+ jq -n --arg ctx "$merged_ctx" --arg msg "$merged_msg" --arg ev "$event" '
224
+ {}
225
+ | (if $ctx != "" then .hookSpecificOutput = { hookEventName: $ev, additionalContext: $ctx } else . end)
226
+ | (if $msg != "" then .systemMessage = $msg else . end)
227
+ '
228
+ fi
229
+
230
+ return 0
231
+ }
232
+
233
+ # Host-aware dispatch wrapper. Non-edit tools preserve the single-dispatch
234
+ # path. Edit tools are normalized to one synthetic Edit payload per path; any
235
+ # per-path denial/block wins for the entire original patch, while exact
236
+ # duplicate advisories are emitted once.
237
+ toolu_dispatch_hook() {
238
+ [[ $# -lt 2 ]] && return 0
239
+ local modules_dir="$1" event="$2"; shift 2
240
+ local registry_dirs=("$@")
241
+ local original_input="${input:-}" original_tool="${tool_name:-}"
242
+ local records normalize_rc=0 record path operation from moved_to synthetic
243
+ local result rc decision ctx msg c existing seen
244
+ local contexts=() messages=()
245
+ # Same hold-the-ask discipline as toolu_dispatch_modules, across the
246
+ # synthetic per-path payloads of one apply_patch.
247
+ local ask_result=""
248
+
249
+ records=$(toolu_normalize_edit_records "$original_input" "$original_tool") || normalize_rc=$?
250
+ if [ "$normalize_rc" -eq 1 ] || [ "$normalize_rc" -eq 3 ]; then
251
+ toolu_dispatch_modules "$modules_dir" "$event" "${registry_dirs[@]}"
252
+ return $?
253
+ fi
254
+ if [ "$normalize_rc" -eq 2 ] || [ -z "$records" ]; then
255
+ case "$event" in
256
+ PreToolUse)
257
+ printf '%s\n' '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Unable to parse apply_patch file headers; patch blocked so protected-file and quality gates cannot be bypassed."}}'
258
+ ;;
259
+ PostToolUse)
260
+ printf '%s\n' '{"decision":"block","reason":"Unable to parse apply_patch file headers; per-file post-edit quality checks could not run."}'
261
+ ;;
262
+ esac
263
+ return 0
264
+ fi
265
+
266
+ while IFS= read -r record; do
267
+ [ -n "$record" ] || continue
268
+ path=$(jq -er '.path | strings' <<<"$record" 2>/dev/null) || continue
269
+ operation=$(jq -r '.operation // "update"' <<<"$record" 2>/dev/null)
270
+ from=$(jq -r '.from // ""' <<<"$record" 2>/dev/null)
271
+ moved_to=$(jq -r '.moved_to // ""' <<<"$record" 2>/dev/null)
272
+ synthetic=$(jq -c --arg path "$path" --arg operation "$operation" \
273
+ --arg from "$from" --arg moved_to "$moved_to" '
274
+ .tool_name = "Edit"
275
+ | .tool_input = ((.tool_input // {}) + {
276
+ file_path: $path,
277
+ path: $path,
278
+ toolu_edit_operation: $operation,
279
+ toolu_edit_from: $from,
280
+ toolu_edit_moved_to: $moved_to
281
+ })
282
+ ' <<<"$original_input" 2>/dev/null) || continue
283
+
284
+ input="$synthetic"
285
+ tool_name=Edit
286
+ TOOLU_EDIT_OPERATION="$operation"
287
+ TOOLU_EDIT_FROM="$from"
288
+ TOOLU_EDIT_MOVED_TO="$moved_to"
289
+ export input tool_name TOOLU_EDIT_OPERATION TOOLU_EDIT_FROM TOOLU_EDIT_MOVED_TO
290
+
291
+ rc=0
292
+ result=$(toolu_dispatch_modules "$modules_dir" "$event" "${registry_dirs[@]}") || rc=$?
293
+ if [ "$rc" -eq 2 ]; then
294
+ input="$original_input"; tool_name="$original_tool"; export input tool_name
295
+ unset TOOLU_EDIT_OPERATION TOOLU_EDIT_FROM TOOLU_EDIT_MOVED_TO
296
+ return 2
297
+ fi
298
+ [ -n "$result" ] || continue
299
+
300
+ case "$event" in
301
+ PreToolUse)
302
+ decision=$(jq -r '.hookSpecificOutput.permissionDecision // empty' <<<"$result" 2>/dev/null)
303
+ ;;
304
+ PostToolUse)
305
+ decision=$(jq -r '.decision // empty' <<<"$result" 2>/dev/null)
306
+ ;;
307
+ *) decision="" ;;
308
+ esac
309
+ if [ "$decision" = deny ] || [ "$decision" = block ]; then
310
+ input="$original_input"; tool_name="$original_tool"; export input tool_name
311
+ unset TOOLU_EDIT_OPERATION TOOLU_EDIT_FROM TOOLU_EDIT_MOVED_TO
312
+ printf '%s\n' "$result"
313
+ return 0
314
+ fi
315
+ if [ "$decision" = ask ]; then
316
+ # One path asking is enough to prompt for the whole patch, but a later
317
+ # path may still deny it outright — so hold and keep walking.
318
+ [ -z "$ask_result" ] && ask_result="$result"
319
+ continue
320
+ fi
321
+
322
+ ctx=$(jq -r '.hookSpecificOutput.additionalContext // empty' <<<"$result" 2>/dev/null)
323
+ if [ -n "$ctx" ]; then
324
+ seen=0
325
+ for existing in "${contexts[@]}"; do [ "$existing" = "$ctx" ] && seen=1; done
326
+ [ "$seen" -eq 0 ] && contexts+=("$ctx")
327
+ fi
328
+ msg=$(jq -r '.systemMessage // empty' <<<"$result" 2>/dev/null)
329
+ if [ -n "$msg" ]; then
330
+ seen=0
331
+ for existing in "${messages[@]}"; do [ "$existing" = "$msg" ] && seen=1; done
332
+ [ "$seen" -eq 0 ] && messages+=("$msg")
333
+ fi
334
+ done <<<"$records"
335
+
336
+ input="$original_input"; tool_name="$original_tool"; export input tool_name
337
+ unset TOOLU_EDIT_OPERATION TOOLU_EDIT_FROM TOOLU_EDIT_MOVED_TO
338
+
339
+ local merged_ctx="" merged_msg=""
340
+ for c in "${contexts[@]}"; do
341
+ if [ -z "$merged_ctx" ]; then merged_ctx="$c"; else merged_ctx="${merged_ctx}"$'\n\n'"${c}"; fi
342
+ done
343
+ for c in "${messages[@]}"; do
344
+ if [ -z "$merged_msg" ]; then merged_msg="$c"; else merged_msg="${merged_msg}"$'\n\n'"${c}"; fi
345
+ done
346
+ if [ -n "$ask_result" ]; then
347
+ # Same fail-closed fallback as toolu_dispatch_modules above.
348
+ jq -n --argjson ask "$ask_result" --arg ctx "$merged_ctx" --arg msg "$merged_msg" '
349
+ ($ask.hookSpecificOutput.permissionDecisionReason // "") as $reason
350
+ | $ask
351
+ | .hookSpecificOutput.permissionDecisionReason =
352
+ (if $ctx != "" then ($reason + "\n\n" + $ctx) else $reason end)
353
+ | (if $msg != "" then .systemMessage = ((.systemMessage // "") | if . == "" then $msg else . + "\n\n" + $msg end) else . end)
354
+ ' 2>/dev/null || printf '%s\n' "$ask_result"
355
+ return 0
356
+ fi
357
+ if [ -n "$merged_ctx" ] || [ -n "$merged_msg" ]; then
358
+ jq -n --arg ctx "$merged_ctx" --arg msg "$merged_msg" --arg ev "$event" '
359
+ {}
360
+ | (if $ctx != "" then .hookSpecificOutput = {hookEventName:$ev,additionalContext:$ctx} else . end)
361
+ | (if $msg != "" then .systemMessage = $msg else . end)
362
+ '
363
+ fi
364
+ return 0
365
+ }
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env bash
2
+ # Resolve the docs-sync glob sets for the docs-sync pre-tools module. Mirrors
3
+ # quality-config.sh: a project/user override from the merged toolu config
4
+ # (.claude/toolu.config.json `docsSync.*`) wins, else a built-in default.
5
+ # Never errors — missing jq or config falls through to the defaults.
6
+ #
7
+ # Public API (each echoes a newline-separated glob list):
8
+ # docs_sync_surfaces - doc files whose change SATISFIES the check
9
+ # docs_sync_surface_excludes - doc paths that DO NOT satisfy it (carved back
10
+ # out of `surfaces`; releases are per-release,
11
+ # not per-task, and `*` crosses `/` so the
12
+ # `docs/*.md` surface would otherwise swallow them)
13
+ # docs_sync_code_surfaces - code files whose change DEMANDS a doc touch
14
+ #
15
+ # Globs are matched by the module with bash `case` fnmatch, where a single `*`
16
+ # crosses `/` — so `docs/*.md` already covers nested paths and `*.ts` matches
17
+ # any depth. Author overrides with that semantics in mind.
18
+ #
19
+ # Source via: . "$TOOLU_LIB_DIR/docs-sync-config.sh"
20
+
21
+ _DS_LIB_DIR="${TOOLU_LIB_DIR:-${BASH_SOURCE%/*}}"
22
+ # shellcheck source=./config.sh
23
+ [ -f "$_DS_LIB_DIR/config.sh" ] && . "$_DS_LIB_DIR/config.sh"
24
+
25
+ # Built-in defaults — single source of truth, overridable from the environment
26
+ # (tests set these to assert fall-through without touching call sites).
27
+ : "${DEFAULT_DOCS_SYNC_SURFACES:=README.md
28
+ */README.md
29
+ docs/*.md
30
+ */SKILL.md
31
+ AGENTS.md
32
+ */AGENTS.md
33
+ CLAUDE.md
34
+ */CLAUDE.md
35
+ */workflows/*.md}"
36
+ : "${DEFAULT_DOCS_SYNC_SURFACE_EXCLUDES:=docs/releases/*
37
+ */docs/releases/*}"
38
+ : "${DEFAULT_DOCS_SYNC_CODE_SURFACES:=*.ts
39
+ *.rs
40
+ *.sh
41
+ */commands/*
42
+ *plugin.json
43
+ *.config.json}"
44
+
45
+ # _ds_override KEY -> newline-joined override globs, or "" when unset/empty.
46
+ # Reads docsSync.<key> as a JSON array of strings from the merged config.
47
+ _ds_override() {
48
+ local key="$1"
49
+ command -v toolu_load_config >/dev/null 2>&1 || return 0
50
+ toolu_load_config
51
+ [ "${_TOOLU_HAS_JQ:-0}" = "1" ] || return 0
52
+ [ -n "${TOOLU_CFG_JSON:-}" ] || return 0
53
+ jq -r --arg k "$key" '
54
+ (.docsSync? // {})[$k]?
55
+ | if type=="array" then .[] else empty end
56
+ ' <<< "$TOOLU_CFG_JSON" 2>/dev/null
57
+ }
58
+
59
+ # docs_sync_surfaces -> doc-surface globs (override -> default).
60
+ docs_sync_surfaces() {
61
+ local v
62
+ v=$(_ds_override surfaces)
63
+ [ -n "$v" ] && { printf '%s\n' "$v"; return 0; }
64
+ printf '%s\n' "$DEFAULT_DOCS_SYNC_SURFACES"
65
+ }
66
+
67
+ # docs_sync_surface_excludes -> doc paths carved back out (override -> default).
68
+ docs_sync_surface_excludes() {
69
+ local v
70
+ v=$(_ds_override surfaceExcludes)
71
+ [ -n "$v" ] && { printf '%s\n' "$v"; return 0; }
72
+ printf '%s\n' "$DEFAULT_DOCS_SYNC_SURFACE_EXCLUDES"
73
+ }
74
+
75
+ # docs_sync_code_surfaces -> code-surface globs (override -> default).
76
+ docs_sync_code_surfaces() {
77
+ local v
78
+ v=$(_ds_override codeSurfaces)
79
+ [ -n "$v" ] && { printf '%s\n' "$v"; return 0; }
80
+ printf '%s\n' "$DEFAULT_DOCS_SYNC_CODE_SURFACES"
81
+ }
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env bash
2
+ # Normalize host-specific edit payloads into one JSON record per affected path.
3
+ # Records are newline-delimited JSON objects with at least `path` and
4
+ # `operation`. A move produces one source record and one destination record so
5
+ # protected-file and post-edit concerns inspect both sides.
6
+
7
+ toolu_is_edit_tool() {
8
+ case "${1:-}" in
9
+ Edit|Write|MultiEdit|apply_patch) return 0 ;;
10
+ *) return 1 ;;
11
+ esac
12
+ }
13
+
14
+ _toolu_patch_path_valid() {
15
+ local path="${1:-}"
16
+ [ -n "$path" ] || return 1
17
+ case "$path" in
18
+ *$'\n'*|*$'\r'*|*$'\t'*) return 1 ;;
19
+ esac
20
+ return 0
21
+ }
22
+
23
+ _toolu_edit_record() {
24
+ local path="$1" operation="$2" relation_key="${3:-}" relation_value="${4:-}"
25
+ if [ -n "$relation_key" ]; then
26
+ jq -cn --arg path "$path" --arg operation "$operation" \
27
+ --arg key "$relation_key" --arg value "$relation_value" \
28
+ '{path:$path,operation:$operation} + {($key):$value}'
29
+ else
30
+ jq -cn --arg path "$path" --arg operation "$operation" \
31
+ '{path:$path,operation:$operation}'
32
+ fi
33
+ }
34
+
35
+ # Parse the apply_patch command carried by Codex. Return 2 with no output when
36
+ # framing or any file header is malformed; callers use that as a fail-closed
37
+ # PreToolUse signal.
38
+ _toolu_apply_patch_records() {
39
+ local patch="$1" line raw path pending_update="" target
40
+ local begun=0 ended=0 header_count=0 invalid=0
41
+ local records=()
42
+
43
+ while IFS= read -r line || [ -n "$line" ]; do
44
+ line=${line%$'\r'}
45
+ if [ "$ended" -eq 1 ]; then
46
+ [ -z "$line" ] || invalid=1
47
+ continue
48
+ fi
49
+ case "$line" in
50
+ '*** Begin Patch')
51
+ if [ "$begun" -ne 0 ]; then invalid=1; else begun=1; fi
52
+ ;;
53
+ '*** End Patch')
54
+ if [ "$begun" -ne 1 ]; then
55
+ invalid=1
56
+ else
57
+ if [ -n "$pending_update" ]; then
58
+ records+=("$(_toolu_edit_record "$pending_update" update)")
59
+ pending_update=""
60
+ fi
61
+ ended=1
62
+ fi
63
+ ;;
64
+ '*** Add File:'*)
65
+ [ "$begun" -eq 1 ] || { invalid=1; continue; }
66
+ if [ -n "$pending_update" ]; then
67
+ records+=("$(_toolu_edit_record "$pending_update" update)")
68
+ pending_update=""
69
+ fi
70
+ raw=${line#'*** Add File:'}
71
+ [[ "$raw" == ' '* ]] || { invalid=1; continue; }
72
+ path=${raw# }
73
+ _toolu_patch_path_valid "$path" || { invalid=1; continue; }
74
+ records+=("$(_toolu_edit_record "$path" add)")
75
+ header_count=$((header_count + 1))
76
+ ;;
77
+ '*** Update File:'*)
78
+ [ "$begun" -eq 1 ] || { invalid=1; continue; }
79
+ if [ -n "$pending_update" ]; then
80
+ records+=("$(_toolu_edit_record "$pending_update" update)")
81
+ fi
82
+ raw=${line#'*** Update File:'}
83
+ [[ "$raw" == ' '* ]] || { invalid=1; pending_update=""; continue; }
84
+ path=${raw# }
85
+ _toolu_patch_path_valid "$path" || { invalid=1; pending_update=""; continue; }
86
+ pending_update="$path"
87
+ header_count=$((header_count + 1))
88
+ ;;
89
+ '*** Delete File:'*)
90
+ [ "$begun" -eq 1 ] || { invalid=1; continue; }
91
+ if [ -n "$pending_update" ]; then
92
+ records+=("$(_toolu_edit_record "$pending_update" update)")
93
+ pending_update=""
94
+ fi
95
+ raw=${line#'*** Delete File:'}
96
+ [[ "$raw" == ' '* ]] || { invalid=1; continue; }
97
+ path=${raw# }
98
+ _toolu_patch_path_valid "$path" || { invalid=1; continue; }
99
+ records+=("$(_toolu_edit_record "$path" delete)")
100
+ header_count=$((header_count + 1))
101
+ ;;
102
+ '*** Move to:'*)
103
+ [ "$begun" -eq 1 ] && [ -n "$pending_update" ] || { invalid=1; continue; }
104
+ raw=${line#'*** Move to:'}
105
+ [[ "$raw" == ' '* ]] || { invalid=1; continue; }
106
+ target=${raw# }
107
+ _toolu_patch_path_valid "$target" || { invalid=1; continue; }
108
+ records+=("$(_toolu_edit_record "$pending_update" update moved_to "$target")")
109
+ records+=("$(_toolu_edit_record "$target" move from "$pending_update")")
110
+ pending_update=""
111
+ header_count=$((header_count + 1))
112
+ ;;
113
+ '*** End of File')
114
+ # Optional Codex hunk marker: it describes EOF placement inside the
115
+ # current file operation and does not introduce another affected path.
116
+ [ "$begun" -eq 1 ] && [ "$header_count" -gt 0 ] || invalid=1
117
+ ;;
118
+ '*** '*)
119
+ # Unknown apply_patch control header: never silently skip a path-shaped
120
+ # operation that a newer patch grammar may introduce.
121
+ invalid=1
122
+ ;;
123
+ *) ;;
124
+ esac
125
+ done <<<"$patch"
126
+
127
+ if [ "$invalid" -ne 0 ] || [ "$begun" -ne 1 ] || [ "$ended" -ne 1 ] || [ "$header_count" -eq 0 ]; then
128
+ return 2
129
+ fi
130
+ printf '%s\n' "${records[@]}"
131
+ }
132
+
133
+ # toolu_normalize_edit_records INPUT TOOL_NAME
134
+ # 0: emitted one or more records
135
+ # 1: TOOL_NAME is not an edit tool
136
+ # 2: edit payload is malformed
137
+ # 3: normalization dependency is unavailable
138
+ toolu_normalize_edit_records() {
139
+ local raw_input="$1" edit_tool="$2" path operation patch
140
+ toolu_is_edit_tool "$edit_tool" || return 1
141
+ command -v jq >/dev/null 2>&1 || return 3
142
+
143
+ if [ "$edit_tool" = apply_patch ]; then
144
+ patch=$(jq -er '.tool_input.command | strings' <<<"$raw_input" 2>/dev/null) || return 2
145
+ _toolu_apply_patch_records "$patch"
146
+ return $?
147
+ fi
148
+
149
+ path=$(jq -er '.tool_input.file_path // .tool_input.path // .tool_input.target_file | strings' \
150
+ <<<"$raw_input" 2>/dev/null) || return 2
151
+ _toolu_patch_path_valid "$path" || return 2
152
+ case "$edit_tool" in
153
+ Write) operation="write" ;;
154
+ *) operation="update" ;;
155
+ esac
156
+ _toolu_edit_record "$path" "$operation"
157
+ }