@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,24 @@
1
+ {
2
+ "name": "python-quality",
3
+ "version": "6.8.0",
4
+ "description": "Python PostToolUse quality checks registered into the toolu hook engine.",
5
+ "author": {
6
+ "name": "Falconiere Barbosa",
7
+ "email": "hello@falconiere.io"
8
+ },
9
+ "homepage": "https://github.com/falconiere/toolu",
10
+ "repository": "https://github.com/falconiere/toolu.git",
11
+ "license": "MIT",
12
+ "keywords": [
13
+ "claude-code",
14
+ "python",
15
+ "quality",
16
+ "hooks"
17
+ ],
18
+ "dependencies": [
19
+ {
20
+ "name": "toolu",
21
+ "marketplace": "toolu"
22
+ }
23
+ ]
24
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "python-quality",
3
+ "version": "6.8.0",
4
+ "description": "Python PostToolUse quality checks registered into the toolu hook engine.",
5
+ "hooks": "./hooks/hooks.json"
6
+ }
@@ -0,0 +1,23 @@
1
+ # python-quality
2
+
3
+ Python `PostToolUse` quality checks registered into the toolu hook engine.
4
+
5
+ ## Install
6
+
7
+ ```
8
+ /plugin install python-quality@toolu
9
+ ```
10
+
11
+ Requires the `toolu` plugin.
12
+
13
+ ## What it provides
14
+
15
+ Every Python file the agent edits is checked on the spot, contributing to toolu's quality gate. The checks (assembled from ordered `hooks/concerns/` fragments into one module at `SessionStart`) are static-only — the gate never invokes `ruff`, `pylint`, or any other linter:
16
+
17
+ - File / function line limits (config-driven).
18
+ - No suppression: bare `except:`, one-line `except ...: pass`, blanket `# noqa`, bare `# type: ignore`.
19
+ - Colocated `test_*.py` next to every module it tests.
20
+ - No mocks in tests (`unittest.mock`/`mock`/`pytest_mock` imports — covering `MagicMock` — plus `mocker`/`monkeypatch` fixtures).
21
+ - Docstring checks on public functions and classes.
22
+
23
+ The fragments register into the core toolu dispatcher and run only while this plugin is installed — uninstall it and the Python rules vanish, fail-closed.
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env bash
2
+ # Codex lacks a plugin-dependency manifest field. Warn when this dependent
3
+ # plugin is installed without the toolu core, using Codex's authoritative list.
4
+ set -u
5
+
6
+ [ "${TOOLU_HOST_OVERRIDE:-}" != claude ] || exit 0
7
+ [ -n "${PLUGIN_ROOT:-}" ] || exit 0
8
+ command -v codex >/dev/null 2>&1 || exit 0
9
+ command -v jq >/dev/null 2>&1 || exit 0
10
+
11
+ installed=$(codex plugin list --json 2>/dev/null) || exit 0
12
+ jq -e 'any(.installed[]?; .pluginId == "toolu@toolu" and
13
+ ((has("installed") | not) or (.installed == true)) and
14
+ ((has("enabled") | not) or (.enabled == true)))' \
15
+ <<<"$installed" >/dev/null 2>&1 && exit 0
16
+
17
+ jq -n '{hookSpecificOutput:{
18
+ hookEventName:"SessionStart",
19
+ additionalContext:"WARN: this plugin requires the toolu core. Install it first with: codex plugin add toolu@toolu"
20
+ }}'
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env bash
2
+ # Post-tool check: Python quality rules.
3
+ # Project-agnostic: no-op outside Python projects or when python3 is missing.
4
+ #
5
+ # Inputs (from parent dispatcher post-tools/mod.sh, via `export`):
6
+ # $tool_name - name of the tool being invoked
7
+ # $input - raw JSON payload on stdin
8
+ # $PROJECT_ROOT - repository root
9
+
10
+ : "${tool_name:=}"
11
+ : "${input:=}"
12
+ : "${PROJECT_ROOT:=$(pwd)}"
13
+
14
+ # Core lib comes from the toolu dispatcher via TOOLU_LIB_DIR (set by
15
+ # plugins/toolu/hooks/post-tools/mod.sh before registry dispatch). Outside
16
+ # that pipeline there is no relative path to it — fail SOFT: a quality check
17
+ # must never break a tool call by erroring.
18
+ [ -n "${TOOLU_LIB_DIR:-}" ] && [ -f "$TOOLU_LIB_DIR/detect.sh" ] || exit 0
19
+ # shellcheck source=../../../toolu/hooks/lib/detect.sh
20
+ . "$TOOLU_LIB_DIR/detect.sh"
21
+ # Threshold resolver (defaults + project/native overrides). Soft if absent.
22
+ # shellcheck source=../../../toolu/hooks/lib/quality-config.sh
23
+ [ -f "$TOOLU_LIB_DIR/quality-config.sh" ] && . "$TOOLU_LIB_DIR/quality-config.sh"
24
+ # Multi-slot gate writer (entries keyed by file — one hook's failure no longer
25
+ # clobbers another's). Soft if absent: fallbacks below keep the legacy
26
+ # single-slot behavior when the toolu lib predates gate-file.sh.
27
+ # shellcheck source=../../../toolu/hooks/lib/gate-file.sh
28
+ [ -f "$TOOLU_LIB_DIR/gate-file.sh" ] && . "$TOOLU_LIB_DIR/gate-file.sh"
29
+ command -v gate_record_failure >/dev/null 2>&1 || gate_record_failure() {
30
+ jq -n --arg reason "$4" --arg source "$3" --arg file "$2" --arg violations "$5" \
31
+ --arg updatedAt "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
32
+ '{status: "failing", reason: $reason, source: $source, file: $file,
33
+ violations: $violations, updatedAt: $updatedAt}' > "$1"
34
+ }
35
+ command -v gate_clear_file >/dev/null 2>&1 || gate_clear_file() {
36
+ [ -f "$1" ] || return 0
37
+ local _src _file
38
+ _src=$(jq -r '.source // ""' "$1" 2>/dev/null || echo "")
39
+ _file=$(jq -r '.file // ""' "$1" 2>/dev/null || echo "")
40
+ if [ "$_src" = "$3" ] && [ "$_file" = "$2" ]; then
41
+ jq -n --arg source "$3" --arg updatedAt "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
42
+ '{status: "passing", source: $source, updatedAt: $updatedAt}' > "$1"
43
+ fi
44
+ }
45
+ command -v python_max_file_lines >/dev/null 2>&1 || python_max_file_lines() { echo "${DEFAULT_PYTHON_MAX_FILE_LINES:-400}"; }
46
+ command -v python_max_fn_lines >/dev/null 2>&1 || python_max_fn_lines() { echo "${DEFAULT_PYTHON_MAX_FN_LINES:-50}"; }
47
+ # Boolean flag reader (e.g. noMocks) — falls back to the caller's default when
48
+ # quality-config.sh predates this reader.
49
+ command -v quality_flag >/dev/null 2>&1 || quality_flag() { printf '%s' "$3"; }
50
+ # count_python_code_lines comes from detect.sh (sourced above) — no fallback needed.
51
+
52
+ # Load the merged config ONCE so TOOLU_CFG_LOADED sticks for the threshold
53
+ # lookups below — each runs in a $(...) subshell that inherits it and skips
54
+ # re-merging (otherwise every wrapper re-spawns the jq merge).
55
+ command -v toolu_load_config >/dev/null 2>&1 && toolu_load_config 2>/dev/null || true
56
+
57
+ [ "$(detect_python)" = "python" ] || exit 0
58
+ command -v python3 >/dev/null 2>&1 || exit 0
59
+ command -v jq >/dev/null 2>&1 || exit 0
60
+
61
+ fp_from_input=""
62
+ if [[ "$tool_name" == "Write" || "$tool_name" == "Edit" || "$tool_name" == "MultiEdit" ]]; then
63
+ fp_from_input=$(echo "$input" | jq -r '.tool_input.path // .tool_input.file_path // .tool_input.target_file // empty' 2>/dev/null || echo "")
64
+ fi
65
+ FILE_PATH="${CLAUDE_FILE_PATHS:-$fp_from_input}"
66
+
67
+ # Deleted files and move sources cannot be linted after apply_patch completes,
68
+ # but their path-owned quality failure must not remain active forever.
69
+ EDIT_OPERATION="${TOOLU_EDIT_OPERATION:-$(echo "$input" | jq -r '.tool_input.toolu_edit_operation // ""' 2>/dev/null || echo "")}"
70
+ EDIT_MOVED_TO="${TOOLU_EDIT_MOVED_TO:-$(echo "$input" | jq -r '.tool_input.toolu_edit_moved_to // ""' 2>/dev/null || echo "")}"
71
+ if [[ "$EDIT_OPERATION" == delete || -n "$EDIT_MOVED_TO" ]]; then
72
+ if [[ -n "$FILE_PATH" && "$FILE_PATH" =~ \.py$ ]]; then
73
+ GATE_FILE="$(toolu_project_state_root "$PROJECT_ROOT")/quality-gate-status.json"
74
+ gate_clear_file "$GATE_FILE" "$FILE_PATH" "python-quality-hook"
75
+ fi
76
+ exit 0
77
+ fi
78
+
79
+ [[ -z "$FILE_PATH" || ! -f "$FILE_PATH" ]] && exit 0
80
+ [[ ! "$FILE_PATH" =~ \.py$ ]] && exit 0
81
+
82
+ MESSAGES=""
83
+ add_error() {
84
+ MESSAGES="${MESSAGES}${1}"$'\n'
85
+ }
@@ -0,0 +1,6 @@
1
+ PY_MAX_FILE=$(python_max_file_lines)
2
+ LINE_COUNT=$(count_python_code_lines "$FILE_PATH")
3
+ if [[ "$LINE_COUNT" -gt "$PY_MAX_FILE" ]]; then
4
+ add_error "File exceeds ${PY_MAX_FILE}-line limit: $FILE_PATH ($LINE_COUNT code lines, blanks/comments excluded) — split into submodules. Override via lang.python.maxFileLines."
5
+ fi
6
+
@@ -0,0 +1,37 @@
1
+ # Test placement: colocated `test_*.py` / `*_test.py` convention (module-sibling,
2
+ # not a separate tests/ tree — the TS __tests__/ colocation principle, applied to
3
+ # Python's own naming idiom). Two rules:
4
+ # (a) a test-bearing file (top-level `def test_...`/`async def test_...`, or a
5
+ # pytest/unittest import) whose basename does not follow the convention.
6
+ # (b) a `test_*.py` sitting in a directory with no non-test .py sibling — it
7
+ # has nothing to be "colocated" with.
8
+ # `conftest.py` (pytest's fixture-sharing file — imports pytest but is not
9
+ # itself a test) and `__init__.py` (package marker, never a test) are exempt
10
+ # from both rules.
11
+ _py_base="$(basename "$FILE_PATH")"
12
+ _is_python_test_file=0
13
+ case "$_py_base" in
14
+ test_*.py|*_test.py) _is_python_test_file=1 ;;
15
+ esac
16
+
17
+ case "$_py_base" in
18
+ conftest.py|__init__.py) ;;
19
+ *)
20
+ _has_test_marker=0
21
+ grep -qE '^(async[[:space:]]+)?def[[:space:]]+test_[A-Za-z0-9_]*[[:space:]]*\(' "$FILE_PATH" 2>/dev/null && _has_test_marker=1
22
+ grep -qE '^(import|from)[[:space:]]+(pytest|unittest)\b' "$FILE_PATH" 2>/dev/null && _has_test_marker=1
23
+ if [[ "$_has_test_marker" -eq 1 && "$_is_python_test_file" -eq 0 ]]; then
24
+ add_error "Test-bearing file not named test_*.py or *_test.py: $FILE_PATH — rename to follow the colocated test convention"
25
+ fi
26
+
27
+ if [[ "$_py_base" == test_*.py ]]; then
28
+ _py_dir="$(dirname "$FILE_PATH")"
29
+ if ! find "$_py_dir" -maxdepth 1 -type f -name '*.py' \
30
+ ! -name 'test_*.py' ! -name '*_test.py' ! -name 'conftest.py' ! -name '__init__.py' \
31
+ 2>/dev/null | grep -q .; then
32
+ add_error "Test not co-located with a module: $FILE_PATH — colocate test_*.py next to the module it tests"
33
+ fi
34
+ fi
35
+ ;;
36
+ esac
37
+
@@ -0,0 +1,33 @@
1
+
2
+ # Forbidden suppression markers — fix the underlying issue in code, never
3
+ # silence the tool. Four zero-false-positive forms, one awk pass (mirrors
4
+ # 40-unsafe.sh's single-process style). No \b anywhere — BSD awk (macOS) has
5
+ # none; explicit boundary characters are used instead:
6
+ # - bare `except:` (no exception type — swallows EVERYTHING,
7
+ # including KeyboardInterrupt/SystemExit)
8
+ # - one-line `except ...: pass` (catches and silently discards)
9
+ # - blanket `# noqa` (no `:CODE` suffix — suppresses every rule on
10
+ # the line, not a scoped `# noqa: E501`)
11
+ # - blanket `# type: ignore` (no `[code]` suffix — suppresses every mypy
12
+ # error on the line, not a scoped
13
+ # `# type: ignore[arg-type]`)
14
+ # Scoped forms are distinguished from blanket ones by a SECOND regex on the
15
+ # same line rather than a lookahead (POSIX/BSD awk has none).
16
+ SUPPRESSION_HITS=$(awk '
17
+ /^[[:space:]]*except[[:space:]]*:[[:space:]]*(#.*)?$/ {
18
+ print NR": bare except: (swallows everything)"; next
19
+ }
20
+ /^[[:space:]]*except[^:]*:[[:space:]]*pass[[:space:]]*(#.*)?$/ {
21
+ print NR": one-line except ...: pass (silently discarded)"; next
22
+ }
23
+ /#[ \t]*noqa/ && !/#[ \t]*noqa[ \t]*:[ \t]*[A-Za-z0-9]/ {
24
+ print NR": blanket # noqa (no :CODE suffix)"; next
25
+ }
26
+ /#[ \t]*type:[ \t]*ignore/ && !/#[ \t]*type:[ \t]*ignore\[/ {
27
+ print NR": blanket # type: ignore (no [code] suffix)"; next
28
+ }
29
+ ' "$FILE_PATH" 2>/dev/null | head -5)
30
+ if [[ -n "$SUPPRESSION_HITS" ]]; then
31
+ add_error "Forbidden suppression in $FILE_PATH — remove it and fix the underlying issue. Scoped forms (except SpecificError:, # noqa: E501, # type: ignore[arg-type]) are fine; blanket ones are not:"$'\n'"${SUPPRESSION_HITS}"
32
+ fi
33
+
@@ -0,0 +1,49 @@
1
+ PY_MAX_FN=$(python_max_fn_lines)
2
+ # Indent-based span (Python has no braces): a `def `/`async def ` line opens a
3
+ # span at its OWN indent; the span closes at the next nonblank line whose
4
+ # indent is <= the def's (decorators above are never counted — they sit at
5
+ # the same indent as the def and simply close the PRIOR span before the def
6
+ # line opens a new one). Only code lines count (blanks and full-line `#`
7
+ # comments are skipped), matching count_python_code_lines. Known limitation:
8
+ # a line continuation or multi-line string containing a dedented look-alike
9
+ # line is not tokenized here — same heuristic class as the rust brace counter.
10
+ LONG_PY_FUNCS=$(awk -v max="$PY_MAX_FN" '
11
+ function lead(s, i,c,n) {
12
+ n = 0
13
+ for (i = 1; i <= length(s); i++) {
14
+ c = substr(s, i, 1)
15
+ if (c == " " || c == "\t") n++
16
+ else break
17
+ }
18
+ return n
19
+ }
20
+ {
21
+ raw = $0
22
+ trimmed = raw
23
+ sub(/^[ \t]+/, "", trimmed)
24
+ blank = (length(trimmed) == 0)
25
+ if (infn && !blank) {
26
+ if (lead(raw) <= def_indent) {
27
+ if (count > max) printf "%s:%d (%d lines)\n", name, start, count
28
+ infn = 0
29
+ }
30
+ }
31
+ if (!infn && raw ~ /^[ \t]*(async[ \t]+)?def[ \t]/) {
32
+ infn = 1
33
+ def_indent = lead(raw)
34
+ start = NR
35
+ match(trimmed, /def[ \t]+[A-Za-z_][A-Za-z0-9_]*/)
36
+ name = substr(trimmed, RSTART, RLENGTH)
37
+ sub(/^def[ \t]+/, "", name)
38
+ count = 0
39
+ }
40
+ if (infn && !blank && substr(trimmed, 1, 1) != "#") count++
41
+ }
42
+ END {
43
+ if (infn && count > max) printf "%s:%d (%d lines)\n", name, start, count
44
+ }
45
+ ' "$FILE_PATH" 2>/dev/null)
46
+ if [[ -n "$LONG_PY_FUNCS" ]]; then
47
+ add_error "Function too long in $FILE_PATH (>${PY_MAX_FN} lines) — extract helpers."$'\n'"${LONG_PY_FUNCS}"
48
+ fi
49
+
@@ -0,0 +1,76 @@
1
+ # --- No-mocks (zero tolerance): forbid mock frameworks — real data/fixtures
2
+ # only. Test files ONLY (test_*.py / *_test.py / conftest.py, per the shared
3
+ # $_is_python_test_file set in 20-tests.sh) — a no-op elsewhere, mirroring
4
+ # rust's test-scope split in 70-no-mocks.sh. Same ast-grep-scan pattern: ONE
5
+ # process, severity warning (a matching rule and a clean run both exit 0 —
6
+ # only a genuine crash is distinguishable from a clean pass), jq flattens
7
+ # every hit to its def/import line, add_error on a match or on tool breakage.
8
+ #
9
+ # Opt-out: lang.python.noMocks (default true) via quality_flag.
10
+ if [[ "${_is_python_test_file:-0}" -eq 1 ]] \
11
+ && [[ "$(quality_flag python noMocks true)" == "true" ]] \
12
+ && command -v ast-grep >/dev/null 2>&1; then
13
+ NM_RULES=$(cat <<'PY_NOMOCK_RULES'
14
+ id: mock-import
15
+ language: python
16
+ severity: warning
17
+ message: 'mock import'
18
+ rule:
19
+ any:
20
+ - kind: import_statement
21
+ has: {kind: dotted_name, regex: '^(unittest\.mock|mock|pytest_mock)$'}
22
+ - kind: import_from_statement
23
+ has: {field: module_name, kind: dotted_name, regex: '^(unittest\.mock|mock)$'}
24
+ - kind: import_from_statement
25
+ all:
26
+ - has: {field: module_name, kind: dotted_name, regex: '^unittest$'}
27
+ - has: {field: name, kind: dotted_name, regex: '^mock$'}
28
+ ---
29
+ id: mocker-param
30
+ language: python
31
+ severity: warning
32
+ message: 'mocker/monkeypatch fixture parameter'
33
+ rule:
34
+ kind: function_definition
35
+ has:
36
+ field: parameters
37
+ has: {kind: identifier, regex: '^(mocker|monkeypatch)$'}
38
+ PY_NOMOCK_RULES
39
+ )
40
+
41
+ nm_err_file="$(mktemp)"
42
+ nm_json=$(ast-grep scan --inline-rules "$NM_RULES" --json "$FILE_PATH" 2>"$nm_err_file")
43
+ nm_rc=$?
44
+ nm_grep_failed=0
45
+ nm_fail_detail=""
46
+ nm_lines=""
47
+ if [[ "$nm_rc" -ne 0 || -s "$nm_err_file" ]]; then
48
+ nm_grep_failed=1
49
+ nm_stderr_first=$(head -n 1 "$nm_err_file" 2>/dev/null | cut -c1-200)
50
+ nm_fail_detail="ast-grep exit ${nm_rc}${nm_stderr_first:+: $nm_stderr_first}"
51
+ elif [[ -z "$(printf '%s' "$nm_json" | tr -d '[:space:]')" ]]; then
52
+ nm_grep_failed=1
53
+ nm_fail_detail="ast-grep exited 0 with empty output (expected at least the JSON array \"[]\")"
54
+ elif ! nm_lines=$(printf '%s' "$nm_json" | jq -r '
55
+ .[] | "\(.ruleId)\t\(.range.start.line + 1): \(.lines | split("\n")[0])"
56
+ ' 2>/dev/null); then
57
+ nm_grep_failed=1
58
+ nm_fail_detail="ast-grep exited 0 but its output did not parse as the documented JSON array"
59
+ nm_lines=""
60
+ fi
61
+ rm -f "$nm_err_file"
62
+
63
+ IMPORT_HITS=$(printf '%s\n' "$nm_lines" | awk -F'\t' '$1=="mock-import"{print $2}' | head -5)
64
+ if [[ -n "$IMPORT_HITS" ]]; then
65
+ add_error "no-mocks: mock import in $FILE_PATH — write against real data/fixtures instead"$'\n'"${IMPORT_HITS}"
66
+ fi
67
+ PARAM_HITS=$(printf '%s\n' "$nm_lines" | awk -F'\t' '$1=="mocker-param"{print $2}' | head -5)
68
+ if [[ -n "$PARAM_HITS" ]]; then
69
+ add_error "no-mocks: mocker/monkeypatch fixture parameter in $FILE_PATH — write against real data/fixtures instead"$'\n'"${PARAM_HITS}"
70
+ fi
71
+
72
+ if [[ "$nm_grep_failed" -ne 0 ]]; then
73
+ add_error "ast-grep failed while scanning $FILE_PATH — ${nm_fail_detail:-unknown error}; no-mocks rules could not be verified. Fix the tool/file and re-edit"
74
+ fi
75
+ fi
76
+
@@ -0,0 +1,52 @@
1
+ # --- Docs (soft advisory, never blocks) ---
2
+ # A top-level, non-underscore-prefixed `def`/`class` should open with a
3
+ # docstring. Advisory only — collected separately from MESSAGES so it never
4
+ # sets the failing gate. Column-0 (top-level) only: indented defs/methods are
5
+ # exempt (noise control — every private helper method would otherwise need
6
+ # one). The signature may span multiple lines; it is tracked forward to the
7
+ # line that actually ends with `:` before the docstring check runs. Known
8
+ # limitation: a `def`/`class`-shaped line quoted inside a module docstring at
9
+ # column 0 would be mis-read as a real definition — same class of heuristic
10
+ # limit as the other line-based scans in this plugin.
11
+ DOC_ADVISORY=""
12
+ _undoc=$(awk -v sq="'" '
13
+ function isblank(s, t) { t = s; gsub(/^[ \t]+|[ \t]+$/, "", t); return (length(t) == 0) }
14
+ function stripcomment(s, t) { t = s; sub(/[ \t]#.*$/, "", t); return t }
15
+ function endswithcolon(s, t) {
16
+ t = stripcomment(s); gsub(/[ \t]+$/, "", t)
17
+ return (length(t) > 0 && substr(t, length(t), 1) == ":")
18
+ }
19
+ function opens_docstring(s, t, p, body) {
20
+ t = s; gsub(/^[ \t]+/, "", t)
21
+ p = 0
22
+ if (t ~ /^[rRfF]/) p = 1
23
+ body = substr(t, p + 1, 3)
24
+ return (body == "\"\"\"" || body == sq sq sq)
25
+ }
26
+ {
27
+ line = $0
28
+ if (waitbody) {
29
+ if (isblank(line)) next
30
+ if (!opens_docstring(line)) printf "%d: %s\n", defline, defname
31
+ waitbody = 0; next
32
+ }
33
+ if (insig) {
34
+ if (endswithcolon(line)) { insig = 0; waitbody = 1 }
35
+ next
36
+ }
37
+ if (line ~ /^(async[ \t]+def|def|class)[ \t]+[A-Za-z_][A-Za-z0-9_]*/) {
38
+ tmp = line
39
+ sub(/^(async[ \t]+def|def|class)[ \t]+/, "", tmp)
40
+ match(tmp, /^[A-Za-z_][A-Za-z0-9_]*/)
41
+ name = substr(tmp, RSTART, RLENGTH)
42
+ if (substr(name, 1, 1) != "_") {
43
+ defline = NR; defname = name
44
+ if (endswithcolon(line)) { waitbody = 1 } else { insig = 1 }
45
+ }
46
+ }
47
+ }
48
+ ' "$FILE_PATH" 2>/dev/null | head -3)
49
+ if [[ -n "$_undoc" ]]; then
50
+ DOC_ADVISORY="Public def/class missing a docstring in $FILE_PATH — add a concise one:"$'\n'"${_undoc}"
51
+ fi
52
+
@@ -0,0 +1,36 @@
1
+ # --- Output ---
2
+
3
+ if [[ -n "$MESSAGES" ]]; then
4
+ # Record this file's violation in the gate status file (entry keyed by file
5
+ # path — does not clobber failures recorded for other files or hooks).
6
+ GATE_DIR="$(toolu_project_state_root "$PROJECT_ROOT")"
7
+ GATE_FILE="$GATE_DIR/quality-gate-status.json"
8
+ mkdir -p "$GATE_DIR"
9
+ gate_record_failure "$GATE_FILE" "$FILE_PATH" "python-quality-hook" \
10
+ "Post-edit Python quality violation(s) detected" "$MESSAGES"
11
+
12
+ jq -n --arg ctx "$MESSAGES" '{
13
+ "hookSpecificOutput": {
14
+ "hookEventName": "PostToolUse",
15
+ "additionalContext": ("QUALITY VIOLATION — fix before proceeding:\n" + $ctx)
16
+ }
17
+ }'
18
+ else
19
+ # Clear this file's entry now that it passes (only if this hook set it).
20
+ # Other files' failures stay recorded; the gate only flips to passing when
21
+ # no entry remains.
22
+ GATE_FILE="$(toolu_project_state_root "$PROJECT_ROOT")/quality-gate-status.json"
23
+ gate_clear_file "$GATE_FILE" "$FILE_PATH" "python-quality-hook"
24
+
25
+ # Docs advisory (non-blocking — no gate write).
26
+ if [[ -n "$DOC_ADVISORY" ]]; then
27
+ jq -n --arg ctx "$DOC_ADVISORY" '{
28
+ "hookSpecificOutput": {
29
+ "hookEventName": "PostToolUse",
30
+ "additionalContext": $ctx
31
+ }
32
+ }'
33
+ fi
34
+ fi
35
+
36
+ exit 0
@@ -0,0 +1,19 @@
1
+ {
2
+ "hooks": {
3
+ "SessionStart": [
4
+ {
5
+ "matcher": "startup|resume|clear|compact",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "${CLAUDE_PLUGIN_ROOT}/hooks/register.sh"
10
+ },
11
+ {
12
+ "type": "command",
13
+ "command": "${CLAUDE_PLUGIN_ROOT}/hooks/check-toolu.sh"
14
+ }
15
+ ]
16
+ }
17
+ ]
18
+ }
19
+ }
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env bash
2
+ # SessionStart registry sync for the python-quality plugin.
3
+ #
4
+ # Assembles hooks/concerns/[0-9][0-9]-*.sh (ordered preamble → concern partials
5
+ # → finalize) into ONE runtime module in the toolu registry
6
+ # (${TOOLU_CONFIG_DIR:-${CLAUDE_CONFIG_DIR:-$HOME/.claude}}/toolu/post-tools.d/) under the
7
+ # namespaced filename python-quality@toolu__python-quality.sh. The fragments are
8
+ # partials of a single script; concatenating them in numeric order yields the
9
+ # one module that runs as a single process doing one gate write (authored
10
+ # fragment-first — unlike rust/ts-quality there was never a pre-split monolith). Prunes any stale entry bearing OUR prefix, and
11
+ # clears our own crashed tmp residue. Other plugins' entries are never touched.
12
+ #
13
+ # Silent on success (SessionStart stdout becomes context); errors are
14
+ # non-fatal — a failed sync means the registry copy is stale, not broken.
15
+
16
+ SPEC="python-quality@toolu"
17
+ OUT="python-quality.sh"
18
+ SELF_DIR="$(cd "$(dirname "$0")" && pwd)"
19
+ SRC_DIR="$SELF_DIR/concerns"
20
+ if [ -n "${TOOLU_CONFIG_DIR:-}" ]; then
21
+ CONFIG_ROOT="$TOOLU_CONFIG_DIR"
22
+ elif [ "${TOOLU_HOST_OVERRIDE:-}" = codex ] || { [ -z "${TOOLU_HOST_OVERRIDE:-}" ] && [ -n "${PLUGIN_ROOT:-}" ]; }; then
23
+ CONFIG_ROOT="${CODEX_HOME:-$HOME/.codex}"
24
+ else
25
+ CONFIG_ROOT="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
26
+ fi
27
+ REG_DIR="$CONFIG_ROOT/toolu/post-tools.d"
28
+
29
+ # Consume stdin so Claude Code's hook IPC never stalls.
30
+ cat > /dev/null 2>&1 || true
31
+
32
+ [ -d "$SRC_DIR" ] || exit 0
33
+ mkdir -p "$REG_DIR" 2>/dev/null || exit 0
34
+
35
+ # Clear OUR orphaned atomic-write residue from prior crashed runs. Age-gated
36
+ # so a concurrent SessionStart's in-flight tmp (seconds old) is never clobbered.
37
+ find "$REG_DIR" -maxdepth 1 -name "${SPEC}__*.sh.tmp.*" -mmin +1 -delete 2>/dev/null
38
+
39
+ # Assemble: concatenate the numeric-prefixed fragments in lexical order into one
40
+ # module. A newline guard between fragments keeps a fragment that lacks a
41
+ # trailing newline from running into the next. __tests__/ and any non-prefixed
42
+ # .sh are skipped by the glob.
43
+ dst="$REG_DIR/${SPEC}__${OUT}"
44
+ tmp="${dst}.tmp.$$"
45
+ : > "$tmp"
46
+ for src in "$SRC_DIR"/[0-9][0-9]-*.sh; do
47
+ [ -f "$src" ] || continue
48
+ cat "$src" >> "$tmp" 2>/dev/null || { rm -f "$tmp"; exit 0; }
49
+ printf '\n' >> "$tmp" 2>/dev/null || { rm -f "$tmp"; exit 0; }
50
+ done
51
+ if [ -s "$tmp" ] && { [ ! -f "$dst" ] || ! cmp -s "$tmp" "$dst"; }; then
52
+ mv "$tmp" "$dst" 2>/dev/null || rm -f "$tmp"
53
+ else
54
+ rm -f "$tmp"
55
+ fi
56
+
57
+ # Prune: remove any entry bearing OUR prefix that is not the current assembled
58
+ # module (e.g. per-concern files left by an older version). Never glob outside
59
+ # our own spec prefix.
60
+ for old in "$REG_DIR/${SPEC}__"*.sh; do
61
+ [ -f "$old" ] || continue
62
+ [ "$(basename "$old")" = "${SPEC}__${OUT}" ] || rm -f "$old"
63
+ done
64
+
65
+ exit 0
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "rust-quality",
3
+ "version": "6.8.0",
4
+ "description": "Rust PostToolUse quality checks registered into the toolu hook engine.",
5
+ "author": {
6
+ "name": "Falconiere Barbosa",
7
+ "email": "hello@falconiere.io"
8
+ },
9
+ "homepage": "https://github.com/falconiere/toolu",
10
+ "repository": "https://github.com/falconiere/toolu.git",
11
+ "license": "MIT",
12
+ "keywords": [
13
+ "claude-code",
14
+ "rust",
15
+ "quality",
16
+ "hooks"
17
+ ],
18
+ "dependencies": [
19
+ {
20
+ "name": "toolu",
21
+ "marketplace": "toolu"
22
+ }
23
+ ]
24
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "rust-quality",
3
+ "version": "6.8.0",
4
+ "description": "Rust PostToolUse quality checks registered into the toolu hook engine.",
5
+ "hooks": "./hooks/hooks.json"
6
+ }
@@ -0,0 +1,24 @@
1
+ # rust-quality
2
+
3
+ Rust `PostToolUse` quality checks registered into the toolu hook engine.
4
+
5
+ ## Install
6
+
7
+ ```
8
+ /plugin install rust-quality@toolu
9
+ ```
10
+
11
+ Requires the `toolu` plugin.
12
+
13
+ ## What it provides
14
+
15
+ Every Rust file the agent edits is checked on the spot, contributing to toolu's quality gate. The checks (assembled from ordered `hooks/concerns/` fragments into one module at `SessionStart`):
16
+
17
+ - File / function / `impl` line limits (config-driven).
18
+ - No `.unwrap()` / `.expect()` — use `?` or `match`.
19
+ - No `unsafe` blocks.
20
+ - No `#[allow]` / `#[expect]` lint suppression — except a `tests/` file's file-level `#![allow(...)]` header, for the lints test code violates on purpose.
21
+ - Unit tests in a module-sibling `tests/` (wired by a bodyless `#[cfg(test)] mod tests;`, `#[path]`-attributed or not); crate-root `tests/` for integration. No inline test bodies in `src/`.
22
+ - Doc-comment checks on public items.
23
+
24
+ The fragments register into the core toolu dispatcher and run only while this plugin is installed — uninstall it and the Rust rules vanish, fail-closed.
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env bash
2
+ # Codex lacks a plugin-dependency manifest field. Warn when this dependent
3
+ # plugin is installed without the toolu core, using Codex's authoritative list.
4
+ set -u
5
+
6
+ [ "${TOOLU_HOST_OVERRIDE:-}" != claude ] || exit 0
7
+ [ -n "${PLUGIN_ROOT:-}" ] || exit 0
8
+ command -v codex >/dev/null 2>&1 || exit 0
9
+ command -v jq >/dev/null 2>&1 || exit 0
10
+
11
+ installed=$(codex plugin list --json 2>/dev/null) || exit 0
12
+ jq -e 'any(.installed[]?; .pluginId == "toolu@toolu" and
13
+ ((has("installed") | not) or (.installed == true)) and
14
+ ((has("enabled") | not) or (.enabled == true)))' \
15
+ <<<"$installed" >/dev/null 2>&1 && exit 0
16
+
17
+ jq -n '{hookSpecificOutput:{
18
+ hookEventName:"SessionStart",
19
+ additionalContext:"WARN: this plugin requires the toolu core. Install it first with: codex plugin add toolu@toolu"
20
+ }}'