@toolu/opencode 6.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (331) hide show
  1. package/generated/GENERATED-NOTES.md +16 -0
  2. package/generated/agents/toolu--architect.md +52 -0
  3. package/generated/agents/toolu--deep-explore.md +65 -0
  4. package/generated/agents/toolu--implementer.md +50 -0
  5. package/generated/agents/toolu--quick-task.md +54 -0
  6. package/generated/agents/toolu--research-agent.md +101 -0
  7. package/generated/commands/toolu--commit.md +8 -0
  8. package/generated/commands/toolu--review-and-commit.md +8 -0
  9. package/generated/opencode.toolu.json +116 -0
  10. package/generated/skills/toolu--brainstorm/SKILL.md +57 -0
  11. package/generated/skills/toolu--brainstorm/references/design-questions.md +36 -0
  12. package/generated/skills/toolu--commit--skill/SKILL.md +11 -0
  13. package/generated/skills/toolu--debug/SKILL.md +54 -0
  14. package/generated/skills/toolu--deep-research/SKILL.md +51 -0
  15. package/generated/skills/toolu--execution/SKILL.md +77 -0
  16. package/generated/skills/toolu--orchestrator/SKILL.md +145 -0
  17. package/generated/skills/toolu--orchestrator/references/delegation-cost.md +104 -0
  18. package/generated/skills/toolu--orchestrator/references/model-routing.md +112 -0
  19. package/generated/skills/toolu--plan/SKILL.md +56 -0
  20. package/generated/skills/toolu--plan/references/ledger.md +18 -0
  21. package/generated/skills/toolu--plan-review/SKILL.md +48 -0
  22. package/generated/skills/toolu--review-and-commit--skill/SKILL.md +10 -0
  23. package/generated/skills/toolu--setup/SKILL.md +24 -0
  24. package/generated/skills/toolu--setup/scripts/setup.sh +206 -0
  25. package/generated/skills/toolu--spec/SKILL.md +67 -0
  26. package/generated/skills/toolu--spec-review/SKILL.md +50 -0
  27. package/generated/skills/toolu--test/SKILL.md +49 -0
  28. package/package.json +51 -0
  29. package/plugins/agent-browser/.claude-plugin/plugin.json +18 -0
  30. package/plugins/agent-browser/.codex-plugin/plugin.json +7 -0
  31. package/plugins/agent-browser/README.md +18 -0
  32. package/plugins/agent-browser/hooks/hooks.json +15 -0
  33. package/plugins/agent-browser/hooks/session-start.sh +42 -0
  34. package/plugins/agent-browser/skills/agent-browser/SKILL.md +77 -0
  35. package/plugins/agent-browser/skills/agent-browser/scripts/agent-browser.sh +80 -0
  36. package/plugins/ast-grep/.claude-plugin/plugin.json +18 -0
  37. package/plugins/ast-grep/.codex-plugin/plugin.json +7 -0
  38. package/plugins/ast-grep/README.md +21 -0
  39. package/plugins/ast-grep/hooks/hooks.json +15 -0
  40. package/plugins/ast-grep/hooks/post-tools.d/byte-savings.sh +67 -0
  41. package/plugins/ast-grep/hooks/pre-tools.d/search-nudge.sh +170 -0
  42. package/plugins/ast-grep/hooks/register.sh +68 -0
  43. package/plugins/ast-grep/scripts/byte-savings-report.sh +28 -0
  44. package/plugins/ast-grep/skills/ast-grep/SKILL.md +127 -0
  45. package/plugins/ast-grep/skills/ast-grep/references/ast-grep-advanced.md +44 -0
  46. package/plugins/ast-grep/skills/ast-grep/scripts/ast-grep.sh +103 -0
  47. package/plugins/context7/.claude-plugin/plugin.json +18 -0
  48. package/plugins/context7/.codex-plugin/plugin.json +7 -0
  49. package/plugins/context7/README.md +19 -0
  50. package/plugins/context7/hooks/hooks.json +15 -0
  51. package/plugins/context7/hooks/session-start.sh +42 -0
  52. package/plugins/context7/skills/context7/SKILL.md +64 -0
  53. package/plugins/context7/skills/context7/scripts/search.sh +151 -0
  54. package/plugins/exa-search/.claude-plugin/plugin.json +18 -0
  55. package/plugins/exa-search/.codex-plugin/plugin.json +7 -0
  56. package/plugins/exa-search/README.md +19 -0
  57. package/plugins/exa-search/hooks/hooks.json +15 -0
  58. package/plugins/exa-search/hooks/session-start.sh +34 -0
  59. package/plugins/exa-search/skills/exa-search/SKILL.md +91 -0
  60. package/plugins/exa-search/skills/exa-search/scripts/search.sh +221 -0
  61. package/plugins/jev/.claude-plugin/plugin.json +19 -0
  62. package/plugins/jev/.codex-plugin/plugin.json +7 -0
  63. package/plugins/jev/README.md +61 -0
  64. package/plugins/jev/hooks/hooks.json +25 -0
  65. package/plugins/jev/hooks/lib/common.sh +49 -0
  66. package/plugins/jev/hooks/session-start.sh +45 -0
  67. package/plugins/jev/hooks/user-prompt-submit.sh +41 -0
  68. package/plugins/jev/skills/jev/SKILL.md +81 -0
  69. package/plugins/jev/skills/jev/evals/README.md +98 -0
  70. package/plugins/jev/skills/jev/references/problem-solving.md +198 -0
  71. package/plugins/jev/skills/jev/scripts/jev.sh +405 -0
  72. package/plugins/jira/.claude-plugin/plugin.json +18 -0
  73. package/plugins/jira/.codex-plugin/plugin.json +7 -0
  74. package/plugins/jira/README.md +42 -0
  75. package/plugins/jira/hooks/hooks.json +15 -0
  76. package/plugins/jira/hooks/session-start.sh +34 -0
  77. package/plugins/jira/skills/jira/SKILL.md +123 -0
  78. package/plugins/jira/skills/jira/scripts/jira.sh +70 -0
  79. package/plugins/jira/skills/jira/scripts/lib/adf.sh +17 -0
  80. package/plugins/jira/skills/jira/scripts/lib/attachment.sh +94 -0
  81. package/plugins/jira/skills/jira/scripts/lib/board.sh +58 -0
  82. package/plugins/jira/skills/jira/scripts/lib/http.sh +184 -0
  83. package/plugins/jira/skills/jira/scripts/lib/issue.sh +159 -0
  84. package/plugins/jira/skills/jira/scripts/lib/paginate.sh +45 -0
  85. package/plugins/jira/skills/jira/scripts/lib/plan-parse.sh +70 -0
  86. package/plugins/jira/skills/jira/scripts/lib/plan-run.sh +115 -0
  87. package/plugins/jira/skills/jira/scripts/lib/plan-store.sh +168 -0
  88. package/plugins/jira/skills/jira/scripts/lib/plan.sh +108 -0
  89. package/plugins/jira/skills/jira/scripts/lib/project.sh +42 -0
  90. package/plugins/jira/skills/jira/scripts/lib/raw.sh +15 -0
  91. package/plugins/jira/skills/jira/scripts/lib/search.sh +41 -0
  92. package/plugins/jira/skills/jira/scripts/lib/sprint.sh +88 -0
  93. package/plugins/jira/skills/jira/scripts/lib/user.sh +45 -0
  94. package/plugins/jira/skills/jira/scripts/lib/worklog.sh +56 -0
  95. package/plugins/pr-babysit/.claude-plugin/plugin.json +25 -0
  96. package/plugins/pr-babysit/.codex-plugin/plugin.json +7 -0
  97. package/plugins/pr-babysit/README.md +38 -0
  98. package/plugins/pr-babysit/commands/babysit.md +13 -0
  99. package/plugins/pr-babysit/hooks/check-toolu.sh +20 -0
  100. package/plugins/pr-babysit/hooks/hooks.json +15 -0
  101. package/plugins/pr-babysit/scripts/babysit-tick.sh +133 -0
  102. package/plugins/pr-babysit/scripts/collect-pr.sh +224 -0
  103. package/plugins/pr-babysit/scripts/lib/common.sh +146 -0
  104. package/plugins/pr-babysit/scripts/lib/gh.sh +150 -0
  105. package/plugins/pr-babysit/scripts/lib/lock.sh +84 -0
  106. package/plugins/pr-babysit/scripts/lib/normalize.sh +98 -0
  107. package/plugins/pr-babysit/scripts/lib/state.sh +37 -0
  108. package/plugins/pr-babysit/scripts/parse-verdict.sh +171 -0
  109. package/plugins/pr-babysit/scripts/record.sh +69 -0
  110. package/plugins/pr-babysit/scripts/reduce-state.sh +245 -0
  111. package/plugins/pr-babysit/scripts/reply-thread.sh +90 -0
  112. package/plugins/pr-babysit/scripts/resolve-thread.sh +71 -0
  113. package/plugins/pr-babysit/skills/babysit/SKILL.md +31 -0
  114. package/plugins/pr-babysit/skills/babysit/references/helper.md +209 -0
  115. package/plugins/pr-babysit/workflows/babysit.md +722 -0
  116. package/plugins/python-quality/.claude-plugin/plugin.json +24 -0
  117. package/plugins/python-quality/.codex-plugin/plugin.json +6 -0
  118. package/plugins/python-quality/README.md +23 -0
  119. package/plugins/python-quality/hooks/check-toolu.sh +20 -0
  120. package/plugins/python-quality/hooks/concerns/00-preamble.sh +85 -0
  121. package/plugins/python-quality/hooks/concerns/10-size-file.sh +6 -0
  122. package/plugins/python-quality/hooks/concerns/20-tests.sh +37 -0
  123. package/plugins/python-quality/hooks/concerns/30-suppression.sh +33 -0
  124. package/plugins/python-quality/hooks/concerns/50-size-fn.sh +49 -0
  125. package/plugins/python-quality/hooks/concerns/70-no-mocks.sh +76 -0
  126. package/plugins/python-quality/hooks/concerns/90-docs.sh +52 -0
  127. package/plugins/python-quality/hooks/concerns/99-finalize.sh +36 -0
  128. package/plugins/python-quality/hooks/hooks.json +19 -0
  129. package/plugins/python-quality/hooks/register.sh +65 -0
  130. package/plugins/rust-quality/.claude-plugin/plugin.json +24 -0
  131. package/plugins/rust-quality/.codex-plugin/plugin.json +6 -0
  132. package/plugins/rust-quality/README.md +24 -0
  133. package/plugins/rust-quality/hooks/check-toolu.sh +20 -0
  134. package/plugins/rust-quality/hooks/concerns/00-preamble.sh +93 -0
  135. package/plugins/rust-quality/hooks/concerns/10-size-file.sh +10 -0
  136. package/plugins/rust-quality/hooks/concerns/20-tests.sh +84 -0
  137. package/plugins/rust-quality/hooks/concerns/30-suppression.sh +26 -0
  138. package/plugins/rust-quality/hooks/concerns/40-unsafe.sh +39 -0
  139. package/plugins/rust-quality/hooks/concerns/50-size-fn.sh +38 -0
  140. package/plugins/rust-quality/hooks/concerns/55-size-impl.sh +28 -0
  141. package/plugins/rust-quality/hooks/concerns/60-error-handling.sh +149 -0
  142. package/plugins/rust-quality/hooks/concerns/70-no-mocks.sh +139 -0
  143. package/plugins/rust-quality/hooks/concerns/90-docs.sh +32 -0
  144. package/plugins/rust-quality/hooks/concerns/99-finalize.sh +36 -0
  145. package/plugins/rust-quality/hooks/hooks.json +19 -0
  146. package/plugins/rust-quality/hooks/register.sh +65 -0
  147. package/plugins/statusline/.claude-plugin/plugin.json +19 -0
  148. package/plugins/statusline/.codex-plugin/plugin.json +7 -0
  149. package/plugins/statusline/README.md +108 -0
  150. package/plugins/statusline/commands/setup.md +30 -0
  151. package/plugins/statusline/hooks/hooks.json +15 -0
  152. package/plugins/statusline/hooks/session-start.sh +45 -0
  153. package/plugins/statusline/scripts/collect-status.sh +147 -0
  154. package/plugins/statusline/scripts/setup.sh +93 -0
  155. package/plugins/statusline/scripts/status.sh +55 -0
  156. package/plugins/statusline/skills/status/SKILL.md +13 -0
  157. package/plugins/statusline/statusline.sh +177 -0
  158. package/plugins/toolu/.claude-plugin/plugin.json +19 -0
  159. package/plugins/toolu/.codex-plugin/plugin.json +7 -0
  160. package/plugins/toolu/README.md +35 -0
  161. package/plugins/toolu/agents/.gitkeep +0 -0
  162. package/plugins/toolu/agents/architect.md +52 -0
  163. package/plugins/toolu/agents/deep-explore.md +65 -0
  164. package/plugins/toolu/agents/implementer.md +50 -0
  165. package/plugins/toolu/agents/quick-task.md +54 -0
  166. package/plugins/toolu/agents/research-agent.md +107 -0
  167. package/plugins/toolu/assets/agents/architect.toml +9 -0
  168. package/plugins/toolu/assets/agents/deep-explore.toml +9 -0
  169. package/plugins/toolu/assets/agents/implementer.toml +9 -0
  170. package/plugins/toolu/assets/agents/quick-task.toml +9 -0
  171. package/plugins/toolu/assets/agents/research-agent.toml +9 -0
  172. package/plugins/toolu/commands/.gitkeep +0 -0
  173. package/plugins/toolu/commands/commit.md +5 -0
  174. package/plugins/toolu/commands/review-and-commit.md +5 -0
  175. package/plugins/toolu/hooks/docs/gates.md +157 -0
  176. package/plugins/toolu/hooks/docs/model-routing.md +14 -0
  177. package/plugins/toolu/hooks/docs/post-compaction.md +3 -0
  178. package/plugins/toolu/hooks/docs/push-review.md +123 -0
  179. package/plugins/toolu/hooks/docs/session-start-python.md +3 -0
  180. package/plugins/toolu/hooks/docs/session-start-rust.md +4 -0
  181. package/plugins/toolu/hooks/docs/session-start-ts.md +3 -0
  182. package/plugins/toolu/hooks/docs/session-start.md +16 -0
  183. package/plugins/toolu/hooks/hooks.json +76 -0
  184. package/plugins/toolu/hooks/lib/config.sh +355 -0
  185. package/plugins/toolu/hooks/lib/detect.sh +929 -0
  186. package/plugins/toolu/hooks/lib/diff-sha.sh +32 -0
  187. package/plugins/toolu/hooks/lib/dispatch.sh +365 -0
  188. package/plugins/toolu/hooks/lib/docs-sync-config.sh +81 -0
  189. package/plugins/toolu/hooks/lib/edit-records.sh +157 -0
  190. package/plugins/toolu/hooks/lib/gate-file.sh +201 -0
  191. package/plugins/toolu/hooks/lib/gate-mode.sh +244 -0
  192. package/plugins/toolu/hooks/lib/host.sh +205 -0
  193. package/plugins/toolu/hooks/lib/permissions.sh +125 -0
  194. package/plugins/toolu/hooks/lib/plan-ledger-parse.sh +219 -0
  195. package/plugins/toolu/hooks/lib/plan-ledger-preflight.sh +137 -0
  196. package/plugins/toolu/hooks/lib/plan-ledger.sh +749 -0
  197. package/plugins/toolu/hooks/lib/push-waiver.sh +116 -0
  198. package/plugins/toolu/hooks/lib/quality-config.sh +184 -0
  199. package/plugins/toolu/hooks/lib/registry.sh +72 -0
  200. package/plugins/toolu/hooks/lib/state-sweeper.sh +194 -0
  201. package/plugins/toolu/hooks/lib/telemetry.sh +94 -0
  202. package/plugins/toolu/hooks/lib/verdict.sh +477 -0
  203. package/plugins/toolu/hooks/post-tools/mod.sh +42 -0
  204. package/plugins/toolu/hooks/post-tools/modules/gate-status.sh +140 -0
  205. package/plugins/toolu/hooks/post-tools/modules/push-waiver.sh +97 -0
  206. package/plugins/toolu/hooks/pre-compact.sh +18 -0
  207. package/plugins/toolu/hooks/pre-tools/agent-tier.sh +111 -0
  208. package/plugins/toolu/hooks/pre-tools/mod.sh +37 -0
  209. package/plugins/toolu/hooks/pre-tools/modules/bash-commands.sh +187 -0
  210. package/plugins/toolu/hooks/pre-tools/modules/code-edit-rules.sh +78 -0
  211. package/plugins/toolu/hooks/pre-tools/modules/commit-gate.sh +70 -0
  212. package/plugins/toolu/hooks/pre-tools/modules/docs-sync.sh +137 -0
  213. package/plugins/toolu/hooks/pre-tools/modules/mcp-blocker.sh +151 -0
  214. package/plugins/toolu/hooks/pre-tools/modules/plan-ledger.sh +201 -0
  215. package/plugins/toolu/hooks/pre-tools/modules/protected-files.sh +177 -0
  216. package/plugins/toolu/hooks/pre-tools/modules/push-review.sh +273 -0
  217. package/plugins/toolu/hooks/pre-tools/modules/quality-gate.sh +87 -0
  218. package/plugins/toolu/hooks/session-start.sh +379 -0
  219. package/plugins/toolu/hooks/user-prompt-submit.sh +197 -0
  220. package/plugins/toolu/scripts/context-budget.sh +111 -0
  221. package/plugins/toolu/scripts/debug-log.sh +98 -0
  222. package/plugins/toolu/scripts/debug-stack.sh +108 -0
  223. package/plugins/toolu/scripts/debug-testfail.sh +97 -0
  224. package/plugins/toolu/settings/.gitkeep +0 -0
  225. package/plugins/toolu/settings/README.md +127 -0
  226. package/plugins/toolu/settings/bash-allowlist.txt +12 -0
  227. package/plugins/toolu/settings/bash-denylist.txt +25 -0
  228. package/plugins/toolu/settings/code-edit-rules.json +67 -0
  229. package/plugins/toolu/settings/commit-prefixes.txt +12 -0
  230. package/plugins/toolu/settings/mcp-blocklist.txt +8 -0
  231. package/plugins/toolu/settings/permissions.fragment.json +36 -0
  232. package/plugins/toolu/settings/protected-files.txt +30 -0
  233. package/plugins/toolu/settings/rust-unsafe-exemptions.txt +5 -0
  234. package/plugins/toolu/settings/toolu.config.example.json +130 -0
  235. package/plugins/toolu/skills/.gitkeep +0 -0
  236. package/plugins/toolu/skills/brainstorm/SKILL.md +57 -0
  237. package/plugins/toolu/skills/brainstorm/references/design-questions.md +36 -0
  238. package/plugins/toolu/skills/commit/SKILL.md +11 -0
  239. package/plugins/toolu/skills/debug/SKILL.md +54 -0
  240. package/plugins/toolu/skills/debug/evals/README.md +17 -0
  241. package/plugins/toolu/skills/debug/evals/cases.json +24 -0
  242. package/plugins/toolu/skills/debug/evals/results.json +34 -0
  243. package/plugins/toolu/skills/deep-research/SKILL.md +51 -0
  244. package/plugins/toolu/skills/execution/SKILL.md +77 -0
  245. package/plugins/toolu/skills/orchestrator/SKILL.md +145 -0
  246. package/plugins/toolu/skills/orchestrator/references/delegation-cost.md +104 -0
  247. package/plugins/toolu/skills/orchestrator/references/model-routing.md +112 -0
  248. package/plugins/toolu/skills/plan/SKILL.md +56 -0
  249. package/plugins/toolu/skills/plan/references/ledger.md +18 -0
  250. package/plugins/toolu/skills/plan-review/SKILL.md +48 -0
  251. package/plugins/toolu/skills/review-and-commit/SKILL.md +10 -0
  252. package/plugins/toolu/skills/setup/SKILL.md +24 -0
  253. package/plugins/toolu/skills/setup/scripts/setup.sh +206 -0
  254. package/plugins/toolu/skills/spec/SKILL.md +67 -0
  255. package/plugins/toolu/skills/spec-review/SKILL.md +50 -0
  256. package/plugins/toolu/skills/test/SKILL.md +49 -0
  257. package/plugins/toolu/workflows/commit.md +21 -0
  258. package/plugins/toolu/workflows/host-mapping.md +22 -0
  259. package/plugins/toolu/workflows/review-and-commit.md +54 -0
  260. package/plugins/toolu/workflows/semantic-judgments.md +35 -0
  261. package/plugins/toolu-review/.claude-plugin/plugin.json +18 -0
  262. package/plugins/toolu-review/.codex-plugin/plugin.json +7 -0
  263. package/plugins/toolu-review/README.md +17 -0
  264. package/plugins/toolu-review/hooks/hooks.json +15 -0
  265. package/plugins/toolu-review/hooks/session-start.sh +34 -0
  266. package/plugins/toolu-review/skills/review/SKILL.md +90 -0
  267. package/plugins/toolu-review/skills/review/scripts/write-state.sh +189 -0
  268. package/plugins/ts-quality/.claude-plugin/plugin.json +24 -0
  269. package/plugins/ts-quality/.codex-plugin/plugin.json +6 -0
  270. package/plugins/ts-quality/README.md +24 -0
  271. package/plugins/ts-quality/hooks/check-toolu.sh +20 -0
  272. package/plugins/ts-quality/hooks/concerns/00-preamble.sh +123 -0
  273. package/plugins/ts-quality/hooks/concerns/10-imports.sh +33 -0
  274. package/plugins/ts-quality/hooks/concerns/15-type-as.sh +9 -0
  275. package/plugins/ts-quality/hooks/concerns/20-tests.sh +26 -0
  276. package/plugins/ts-quality/hooks/concerns/25-size-file.sh +40 -0
  277. package/plugins/ts-quality/hooks/concerns/30-size-fn.sh +53 -0
  278. package/plugins/ts-quality/hooks/concerns/35-react-hooks.sh +7 -0
  279. package/plugins/ts-quality/hooks/concerns/40-factory.sh +5 -0
  280. package/plugins/ts-quality/hooks/concerns/45-typeguard.sh +6 -0
  281. package/plugins/ts-quality/hooks/concerns/50-type-dup.sh +16 -0
  282. package/plugins/ts-quality/hooks/concerns/55-naming.sh +11 -0
  283. package/plugins/ts-quality/hooks/concerns/60-console.sh +6 -0
  284. package/plugins/ts-quality/hooks/concerns/65-suppression.sh +12 -0
  285. package/plugins/ts-quality/hooks/concerns/70-ui-confirm.sh +8 -0
  286. package/plugins/ts-quality/hooks/concerns/72-ui-radix.sh +12 -0
  287. package/plugins/ts-quality/hooks/concerns/74-react-props.sh +6 -0
  288. package/plugins/ts-quality/hooks/concerns/76-toast.sh +10 -0
  289. package/plugins/ts-quality/hooks/concerns/78-error-ast.sh +242 -0
  290. package/plugins/ts-quality/hooks/concerns/80-throw-literal.sh +23 -0
  291. package/plugins/ts-quality/hooks/concerns/85-no-mocks.sh +109 -0
  292. package/plugins/ts-quality/hooks/concerns/90-duplication.sh +30 -0
  293. package/plugins/ts-quality/hooks/concerns/92-docs.sh +36 -0
  294. package/plugins/ts-quality/hooks/concerns/94-handler.sh +15 -0
  295. package/plugins/ts-quality/hooks/concerns/99-finalize.sh +46 -0
  296. package/plugins/ts-quality/hooks/hooks.json +19 -0
  297. package/plugins/ts-quality/hooks/register.sh +65 -0
  298. package/src/adapter/__tests__/evaluate.test.ts +82 -0
  299. package/src/adapter/__tests__/permission-map.test.ts +91 -0
  300. package/src/adapter/evaluate.ts +54 -0
  301. package/src/adapter/permission-map.ts +177 -0
  302. package/src/bootstrap/__tests__/bootstrap.test.ts +148 -0
  303. package/src/bootstrap/entrypoint.ts +18 -0
  304. package/src/bootstrap/index.ts +5 -0
  305. package/src/bootstrap/readiness.ts +59 -0
  306. package/src/bootstrap/result.ts +21 -0
  307. package/src/bootstrap/runtime.ts +99 -0
  308. package/src/bootstrap/test-helpers.ts +25 -0
  309. package/src/host/__tests__/host.test.ts +37 -0
  310. package/src/host/detect.ts +46 -0
  311. package/src/host/index.ts +10 -0
  312. package/src/host/roots.ts +59 -0
  313. package/src/host/types.ts +14 -0
  314. package/src/inventory/__tests__/inventory.test.ts +69 -0
  315. package/src/inventory/index.ts +9 -0
  316. package/src/inventory/manifest.ts +46 -0
  317. package/src/inventory/scan.ts +66 -0
  318. package/src/inventory/selection.ts +92 -0
  319. package/src/inventory/types.ts +22 -0
  320. package/src/lifecycle/__tests__/lifecycle.test.ts +7 -0
  321. package/src/lifecycle/index.ts +2 -0
  322. package/src/lifecycle/table.ts +42 -0
  323. package/src/plugin/__tests__/plugin-stub.test.ts +6 -0
  324. package/src/plugin/plugin-stub.ts +7 -0
  325. package/src/plugin/toolu.ts +142 -0
  326. package/src/preflight/__tests__/preflight.test.ts +20 -0
  327. package/src/preflight/check.ts +75 -0
  328. package/src/preflight/index.ts +2 -0
  329. package/src/select/__tests__/select.test.ts +52 -0
  330. package/src/select/index.ts +2 -0
  331. package/src/select/resolve.ts +87 -0
@@ -0,0 +1,90 @@
1
+ ---
2
+ name: review
3
+ description: Pre-push review mirroring CI code-review@v8 (+ Jev) so first push stays clean. Reviews correctness, security, performance, test coverage, doc accuracy, and tight assertions, then records push-review state. Use before pushing a feature branch, when asked to review before push, or when pr-babysit needs a reviewer. Explicit — does NOT auto-fire on edits.
4
+ ---
5
+
6
+ # toolu-review:review
7
+
8
+ A pre-push reviewer tuned to what this repo's CI Toolu Code Review action
9
+ (`falconiere/toolu-ghactions/code-review@v8`, Jev-enabled; posts as
10
+ `github-actions[bot]`) flags — run it locally so the verdict is clean on the
11
+ first push instead of bouncing low/nit findings back as rework.
12
+
13
+ ## What it reviews
14
+
15
+ Review `git diff <base>...HEAD` against these dimensions. Every finding blocks
16
+ (the gate requires zero) — fix in code, do not suppress:
17
+
18
+ 1. **Correctness** — logic, edge cases, error handling (no swallowed errors, no
19
+ `@ts-ignore`/`eslint-disable`/`#[allow]` papering over a real problem).
20
+ 2. **Security** — input validation, injection, secrets, unsafe file/symlink ops.
21
+ 3. **Performance** — hot paths (e.g. per-render/per-hook work), needless spawns.
22
+ 4. **Test coverage for every NEW behavior** — a new code path without a colocated
23
+ real-data test is a finding. (The bot flagged a missing bats for an orphan
24
+ sweep on a prior PR — catch that class here.)
25
+ 5. **Doc/comment accuracy** — comments must match behavior; e.g. no "one-time" on
26
+ a block that runs every invocation; no stale paths after a move.
27
+ 6. **Tight test assertions** — assert the full identity, not a loose suffix
28
+ (`*/statusline/statusline.sh`, not `*/statusline.sh`).
29
+ 7. **In-session migration WARNs** — a breaking change (moved path, removed symlink)
30
+ must surface an actionable in-session hint, not a silent failure later.
31
+ 8. **Convention adherence** — follow `AGENTS.md` / related convention files the CI
32
+ action reads from the base ref (same bar as `code-review@v8`).
33
+
34
+ ## How to run
35
+
36
+ **Commit the fix first, then review.** The gate binds `git diff <base>...HEAD` —
37
+ the *committed* diff. State recorded while a fix is still uncommitted describes
38
+ the pre-fix tree, so committing staleifies it and the push denies.
39
+
40
+ 1. Resolve the diff: `git diff --no-color <base>...HEAD` (base = the push-review
41
+ gate's base; the helper below resolves it the same way).
42
+ 2. Review every changed hunk against the checklist. Read surrounding code and
43
+ grep for usage before claiming a finding — no speculative nits.
44
+ 3. Fix accepted findings in code, commit them, re-review until none remain.
45
+ 4. Record the clean state for the push-review gate:
46
+
47
+ ```bash
48
+ # Codex
49
+ TOOLU_HOST_OVERRIDE=codex bash \
50
+ "${TOOLU_CONFIG_DIR:-${CODEX_HOME:-$HOME/.codex}}/toolu-review/write-state.sh" \
51
+ --findings-count 0 --reviewers '["toolu-review:review"]'
52
+
53
+ # Claude Code
54
+ TOOLU_HOST_OVERRIDE=claude bash \
55
+ "${TOOLU_CONFIG_DIR:-${CLAUDE_CONFIG_DIR:-$HOME/.claude}}/toolu-review/write-state.sh" \
56
+ --findings-count 0 --reviewers '["toolu-review:review"]'
57
+ ```
58
+
59
+ Pass `--branch <name>` on a detached checkout — pr-babysit's
60
+ `git worktree add --detach` + `git push origin HEAD:<name>` contract — so
61
+ the state file is keyed to the branch the push targets; the push-review gate
62
+ resolves that same branch from the refspec. Omitting it there fails with
63
+ "not on a branch (detached HEAD?)".
64
+ Pass `--repo <path>` when the reviewed checkout is not the session's cwd —
65
+ a worktree, say. The gate reads the state file under the **pushed repo's own
66
+ root**, so a file written anywhere else is invisible to it. `--repo` defaults
67
+ to the cwd's repo root and the script fails with "not inside a git repo" when
68
+ the path given is not one. `$STATE_DIR`, when set, overrides the directory for
69
+ the writer and the gate alike.
70
+
71
+ `write-state.sh` is published below the active host's explicit config root,
72
+ as shown above, by the plugin's SessionStart hook. Always pass the matching
73
+ host override in the same command: plugin-root variables are lifecycle
74
+ context and are not reliable in ordinary shell calls.
75
+
76
+ It computes the gate's exact `diff_sha`/`base`/`slug`, sets `review_round`
77
+ (1 for a new `diff_sha`, +1 only when rewriting at the same one — the gate
78
+ caps at 5 rounds on an unchanged diff), and writes
79
+ the active host's `<repo root>/.claude/tmp/push-review/` or
80
+ `<repo root>/.codex/tmp/push-review/` path atomically as
81
+ schema `version: 2`, including `reviewed_files` — auto-computed from
82
+ `git diff --name-only <base>...HEAD` (sorted, unique); pass
83
+ `--reviewed-files a.ts,b.rs` only if the review genuinely covered a
84
+ different path set than the one auto-detected. The gate denies unless
85
+ `reviewed_files` matches the diff's changed paths exactly. Harmless no-op
86
+ when the toolu push-review gate is not installed (the file goes unread).
87
+
88
+ If findings remain that you cannot fix (e.g. needs a human decision), record them
89
+ with `--findings-count <n> --findings '<json>'` instead of 0 — the gate will then
90
+ keep blocking the push, which is correct: open findings are not done.
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env bash
2
+ # write-state.sh — write the push-review state file the toolu push-review
3
+ # gate validates (<host-dir>/tmp/push-review/<branch-slug>.json).
4
+ #
5
+ # The review JUDGMENT (which findings exist) belongs to the caller — the
6
+ # `toolu-review:review` skill. This script does only the deterministic
7
+ # bookkeeping: compute the gate's diff_sha/base/slug, bump review_round, and
8
+ # write the JSON atomically.
9
+ #
10
+ # The base / diff_sha / slug / reviewed_files recipes are MIRRORS of the gate
11
+ # in plugins/toolu/hooks/pre-tools/modules/push-review.sh — the cross-check in
12
+ # scripts/__tests__/state-writer.bats asserts they produce identical SHAs (and,
13
+ # for reviewed_files, an identical file list), so a drift in either recipe
14
+ # fails CI. This mirror is deliberate (cross-plugin sourcing is barred here —
15
+ # see spec Non-Goal 7): keep it local rather than sourcing toolu's diff-sha.sh.
16
+ # Harmless no-op when the toolu gate is not installed (the file is simply
17
+ # never read).
18
+ #
19
+ # Usage: write-state.sh --findings-count N [--reviewers JSON] [--findings JSON]
20
+ # [--repo PATH] [--reviewed-files a.ts,b.rs]
21
+ # --findings-count (required) integer; the gate allows push only when 0.
22
+ # --reviewers (default ["toolu-review:review"]) JSON array.
23
+ # --findings (default []) JSON array of {path,severity,text}.
24
+ # --repo (default: the cwd's repo root) the checkout being reviewed.
25
+ # Pass the worktree path when reviewing a worktree from a
26
+ # session rooted elsewhere — the gate reads the state file
27
+ # under the pushed repo's own root, so writing it anywhere
28
+ # else is invisible to the gate.
29
+ # --branch (default: the checked-out branch) the branch the push
30
+ # targets. Required on a detached checkout — pr-babysit's
31
+ # `git worktree add --detach` + `push origin HEAD:<branch>`
32
+ # contract — and must name an existing refs/heads or
33
+ # refs/remotes/origin ref. On an attached checkout it must
34
+ # equal the checked-out branch: the gate keys the state
35
+ # file by that branch and would never read a re-keyed one.
36
+ # --reviewed-files (default: auto-computed) comma-separated list of paths,
37
+ # overriding the auto-computed `git diff --name-only
38
+ # <base>...HEAD` file list. The gate (schema v2) requires
39
+ # `reviewed_files` (sorted, unique) to equal the diff's
40
+ # changed paths exactly, so only override this when the
41
+ # review genuinely covered a different path set than the
42
+ # one auto-detected here.
43
+ # Prints the state file path on success.
44
+ set -o pipefail
45
+
46
+ findings_count=""
47
+ reviewers='["toolu-review:review"]'
48
+ findings='[]'
49
+ repo=""
50
+ branch_arg=""
51
+ reviewed_files_arg=""
52
+ reviewed_files_set=0
53
+ while [ $# -gt 0 ]; do
54
+ case "$1" in
55
+ --findings-count) findings_count="$2"; shift 2 ;;
56
+ --reviewers) reviewers="$2"; shift 2 ;;
57
+ --findings) findings="$2"; shift 2 ;;
58
+ --repo) repo="$2"; shift 2 ;;
59
+ --branch) branch_arg="$2"; shift 2 ;;
60
+ --reviewed-files) reviewed_files_arg="$2"; reviewed_files_set=1; shift 2 ;;
61
+ *) echo "write-state.sh: unknown arg: $1" >&2; exit 2 ;;
62
+ esac
63
+ done
64
+
65
+ [ -n "$findings_count" ] || { echo "write-state.sh: --findings-count required" >&2; exit 2; }
66
+ [[ "$findings_count" =~ ^[0-9]+$ ]] || { echo "write-state.sh: --findings-count must be an integer" >&2; exit 2; }
67
+ command -v jq >/dev/null 2>&1 || { echo "write-state.sh: jq required" >&2; exit 2; }
68
+ command -v git >/dev/null 2>&1 || { echo "write-state.sh: git required" >&2; exit 2; }
69
+
70
+ # Repo root — MIRROR of push_target_root's fallback chain. The gate resolves
71
+ # the root from the push command (`git -C <path> push`), so the writer must be
72
+ # able to name the same checkout or the two disagree in a worktree.
73
+ repo_root=$(git -C "${repo:-.}" rev-parse --show-toplevel 2>/dev/null || echo "")
74
+ [ -n "$repo_root" ] || { echo "write-state.sh: ${repo:-$(pwd)} is not inside a git repo" >&2; exit 1; }
75
+
76
+ # Branch — MIRROR of push_target_branch (lib/detect.sh): the checked-out
77
+ # branch when attached; on a detached checkout the branch the push targets,
78
+ # which the caller names with --branch because the writer never sees the push
79
+ # command. Validated against real refs so a typo cannot key a state file the
80
+ # gate will never read.
81
+ branch=$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
82
+ if [ -z "$branch" ] || [ "$branch" = "HEAD" ]; then
83
+ [ -n "$branch_arg" ] || { echo "write-state.sh: not on a branch (detached HEAD?) — pass --branch <name> naming the branch the push targets (git push origin HEAD:<name>)" >&2; exit 1; }
84
+ git check-ref-format --branch "$branch_arg" >/dev/null 2>&1 \
85
+ || { echo "write-state.sh: --branch '$branch_arg' is not a valid branch name" >&2; exit 1; }
86
+ if ! git -C "$repo_root" show-ref --verify --quiet "refs/heads/$branch_arg" \
87
+ && ! git -C "$repo_root" show-ref --verify --quiet "refs/remotes/origin/$branch_arg"; then
88
+ echo "write-state.sh: unknown branch '$branch_arg' (no refs/heads/$branch_arg or refs/remotes/origin/$branch_arg)" >&2; exit 1
89
+ fi
90
+ branch="$branch_arg"
91
+ elif [ -n "$branch_arg" ] && [ "$branch_arg" != "$branch" ]; then
92
+ echo "write-state.sh: --branch '$branch_arg' does not match the checked-out branch '$branch'; the gate keys the state file by the checked-out branch" >&2; exit 1
93
+ fi
94
+
95
+ # Base branch — mirror of detect_base_branch's core; $PUSH_REVIEW_BASE matches
96
+ # the gate's own override.
97
+ base="${PUSH_REVIEW_BASE:-}"
98
+ if [ -z "$base" ]; then
99
+ base=$(git -C "$repo_root" symbolic-ref --quiet refs/remotes/origin/HEAD 2>/dev/null | sed 's#refs/remotes/origin/##')
100
+ [ -n "$base" ] || base="main"
101
+ fi
102
+
103
+ # diff_sha — MIRROR of push-review.sh's current_diff_sha (cross-checked by
104
+ # state-writer.bats).
105
+ diff_sha=$(git -C "$repo_root" diff --no-color "${base}...HEAD" 2>/dev/null | git -C "$repo_root" hash-object --stdin 2>/dev/null || echo "")
106
+ [ -n "$diff_sha" ] || { echo "write-state.sh: git diff ${base}...HEAD failed" >&2; exit 1; }
107
+
108
+ # Refuse the empty-blob SHA: an empty diff against base means nothing diverged
109
+ # (or a force-reset). The push-review gate treats this sentinel as never-matching
110
+ # and denies, so writing a state file with it just yields a confusing
111
+ # "review recorded" → "diff is empty" deny. Fail early with an actionable message.
112
+ EMPTY_BLOB_SHA="e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"
113
+ if [ "$diff_sha" = "$EMPTY_BLOB_SHA" ]; then
114
+ echo "write-state.sh: diff against ${base} is empty; nothing to review yet" >&2
115
+ exit 1
116
+ fi
117
+
118
+ # reviewed_files — MIRROR of the gate's `changed_sorted` (push-review.sh):
119
+ # auto-computed from the real diff's changed paths (sorted, unique) unless
120
+ # --reviewed-files overrides it. The gate denies unless this list equals its
121
+ # own `git diff --name-only` computation exactly, so both branches emit a
122
+ # sorted-unique JSON array of strings.
123
+ if [ "$reviewed_files_set" -eq 1 ]; then
124
+ reviewed_files_json=$(printf '%s' "$reviewed_files_arg" \
125
+ | jq -R 'split(",") | map(select(length > 0)) | unique')
126
+ else
127
+ reviewed_files_json=$(git -C "$repo_root" diff --no-color "${base}...HEAD" --name-only 2>/dev/null \
128
+ | sort -u | jq -R -s 'split("\n") | map(select(length > 0))')
129
+ fi
130
+ # jq always prints at least "[]" on success, so empty stdout here only
131
+ # happens on a real git/jq failure — surface it rather than writing a state
132
+ # file with a silently-wrong (and gate-denying) reviewed_files.
133
+ [ -n "$reviewed_files_json" ] || { echo "write-state.sh: failed to compute reviewed_files" >&2; exit 1; }
134
+
135
+ # slug — MIRROR of push-review.sh:_branch_slug.
136
+ slug=$(echo "$branch" | tr '/' '_' | tr -cd 'a-zA-Z0-9_-')
137
+ [ -n "$slug" ] || slug="_default"
138
+
139
+ # state_dir — MIRROR of the gate's resolution, including its $STATE_DIR override.
140
+ # The plugin is self-contained, so mirror the core adapter's project-dir choice.
141
+ project_dir="${TOOLU_PROJECT_CONFIG_DIRNAME:-}"
142
+ if [ -z "$project_dir" ]; then
143
+ if [ "${TOOLU_HOST_OVERRIDE:-}" = codex ] || { [ -z "${TOOLU_HOST_OVERRIDE:-}" ] && [ -n "${PLUGIN_ROOT:-}" ]; }; then
144
+ project_dir=.codex
145
+ else
146
+ project_dir=.claude
147
+ fi
148
+ fi
149
+ state_dir="${STATE_DIR:-$repo_root/$project_dir/tmp/push-review}"
150
+ state_file="$state_dir/${slug}.json"
151
+ mkdir -p "$state_dir" || { echo "write-state.sh: cannot create $state_dir" >&2; exit 1; }
152
+
153
+ # review_round counts rewrites AT THE SAME diff_sha, so the gate's MAX_ROUNDS
154
+ # cap means "reviewers keep finding issues in code that never changed". A
155
+ # changed diff restarts at 1: the previous rounds judged different code, and
156
+ # nothing ever resets the counter after a push, so carrying it forward made the
157
+ # cap terminal for any branch that lived long enough.
158
+ prev_round=0
159
+ if [ -f "$state_file" ]; then
160
+ prev_sha=$(jq -r '.diff_sha // ""' "$state_file" 2>/dev/null || echo "")
161
+ if [ "$prev_sha" = "$diff_sha" ]; then
162
+ prev_round=$(jq -r '.review_round // 0' "$state_file" 2>/dev/null || echo 0)
163
+ [[ "$prev_round" =~ ^[0-9]+$ ]] || prev_round=0
164
+ fi
165
+ fi
166
+ review_round=$((prev_round + 1))
167
+
168
+ reviewed_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
169
+
170
+ tmp="$state_file.tmp.$$"
171
+ if ! jq -n \
172
+ --arg branch "$branch" \
173
+ --arg diff_sha "$diff_sha" \
174
+ --arg base "$base" \
175
+ --arg reviewed_at "$reviewed_at" \
176
+ --argjson reviewers "$reviewers" \
177
+ --argjson findings_count "$findings_count" \
178
+ --argjson findings "$findings" \
179
+ --argjson review_round "$review_round" \
180
+ --argjson reviewed_files "$reviewed_files_json" \
181
+ '{version:2, branch:$branch, diff_sha:$diff_sha, base_branch:$base,
182
+ reviewed_at:$reviewed_at, reviewers:$reviewers,
183
+ findings_count:$findings_count, findings:$findings, review_round:$review_round,
184
+ reviewed_files:$reviewed_files}' \
185
+ > "$tmp"; then
186
+ rm -f "$tmp"; echo "write-state.sh: jq failed (bad --reviewers/--findings/--reviewed-files JSON?)" >&2; exit 1
187
+ fi
188
+ mv "$tmp" "$state_file" || { rm -f "$tmp"; echo "write-state.sh: atomic mv failed" >&2; exit 1; }
189
+ echo "$state_file"
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "ts-quality",
3
+ "version": "6.8.0",
4
+ "description": "TypeScript 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
+ "typescript",
15
+ "quality",
16
+ "hooks"
17
+ ],
18
+ "dependencies": [
19
+ {
20
+ "name": "toolu",
21
+ "marketplace": "toolu"
22
+ }
23
+ ]
24
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "ts-quality",
3
+ "version": "6.8.0",
4
+ "description": "TypeScript PostToolUse quality checks registered into the toolu hook engine.",
5
+ "hooks": "./hooks/hooks.json"
6
+ }
@@ -0,0 +1,24 @@
1
+ # ts-quality
2
+
3
+ TypeScript `PostToolUse` quality checks registered into the toolu hook engine.
4
+
5
+ ## Install
6
+
7
+ ```
8
+ /plugin install ts-quality@toolu
9
+ ```
10
+
11
+ Requires the `toolu` plugin.
12
+
13
+ ## What it provides
14
+
15
+ Every TypeScript 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 line limits (config-driven).
18
+ - No `../` relative imports — use the `@/` alias (only enforced when the project defines a `@/*` path alias in its tsconfig).
19
+ - No `as` type assertions and no hand-rolled type guards — use a type guard / Zod schema.
20
+ - Tests colocated in a flat `__tests__/`.
21
+ - Duplicate-type detection across the tree, plus "does too much" / too-many-factories heuristics.
22
+ - No `console` left in, no lint suppression, no thrown literals; React hooks/props, toast, and error-handling AST checks.
23
+
24
+ The fragments register into the core toolu dispatcher and run only while this plugin is installed — uninstall it and the TypeScript 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,123 @@
1
+ #!/usr/bin/env bash
2
+ # Post-tool check: TypeScript / TSX quality rules
3
+ # Lightweight file-level checks (fast, no external tool invocations).
4
+ # Project-agnostic: no-op outside TS projects; package-manager driven.
5
+ #
6
+ # Inputs (from parent dispatcher post-tools/mod.sh, via `export`):
7
+ # $tool_name - name of the tool being invoked
8
+ # $input - raw JSON payload on stdin
9
+ # $PROJECT_ROOT - repository root
10
+
11
+ : "${tool_name:=}"
12
+ : "${input:=}"
13
+ : "${PROJECT_ROOT:=$(pwd)}"
14
+
15
+ # Core lib comes from the toolu dispatcher via TOOLU_LIB_DIR (set by
16
+ # plugins/toolu/hooks/post-tools/mod.sh before registry dispatch). Outside
17
+ # that pipeline there is no relative path to it — fail SOFT: a quality check
18
+ # must never break a tool call by erroring.
19
+ [ -n "${TOOLU_LIB_DIR:-}" ] && [ -f "$TOOLU_LIB_DIR/detect.sh" ] || exit 0
20
+ # shellcheck source=../../../toolu/hooks/lib/detect.sh
21
+ . "$TOOLU_LIB_DIR/detect.sh"
22
+ # Threshold resolver (defaults + project/native overrides). Soft if absent.
23
+ # shellcheck source=../../../toolu/hooks/lib/quality-config.sh
24
+ [ -f "$TOOLU_LIB_DIR/quality-config.sh" ] && . "$TOOLU_LIB_DIR/quality-config.sh"
25
+ # Multi-slot gate writer (entries keyed by file — one hook's failure no longer
26
+ # clobbers another's). Soft if absent: fallbacks below keep the legacy
27
+ # single-slot behavior when the toolu lib predates gate-file.sh.
28
+ # shellcheck source=../../../toolu/hooks/lib/gate-file.sh
29
+ [ -f "$TOOLU_LIB_DIR/gate-file.sh" ] && . "$TOOLU_LIB_DIR/gate-file.sh"
30
+ command -v gate_record_failure >/dev/null 2>&1 || gate_record_failure() {
31
+ jq -n --arg reason "$4" --arg source "$3" --arg file "$2" --arg violations "$5" \
32
+ --arg updatedAt "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
33
+ '{status: "failing", reason: $reason, source: $source, file: $file,
34
+ violations: $violations, updatedAt: $updatedAt}' > "$1"
35
+ }
36
+ command -v gate_clear_file >/dev/null 2>&1 || gate_clear_file() {
37
+ [ -f "$1" ] || return 0
38
+ local _src _file
39
+ _src=$(jq -r '.source // ""' "$1" 2>/dev/null || echo "")
40
+ _file=$(jq -r '.file // ""' "$1" 2>/dev/null || echo "")
41
+ if [ "$_src" = "$3" ] && [ "$_file" = "$2" ]; then
42
+ jq -n --arg source "$3" --arg updatedAt "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
43
+ '{status: "passing", source: $source, updatedAt: $updatedAt}' > "$1"
44
+ fi
45
+ }
46
+ command -v ts_max_file_lines >/dev/null 2>&1 || ts_max_file_lines() { echo "${DEFAULT_TS_MAX_FILE_LINES:-300}"; }
47
+ command -v ts_max_fn_lines >/dev/null 2>&1 || ts_max_fn_lines() { echo "${DEFAULT_TS_MAX_FN_LINES:-60}"; }
48
+ command -v ts_max_file_lines_source >/dev/null 2>&1 || ts_max_file_lines_source() { printf 'default'; }
49
+ # Boolean flag reader (e.g. noMocks) — falls back to the caller's default when
50
+ # quality-config.sh predates this reader.
51
+ command -v quality_flag >/dev/null 2>&1 || quality_flag() { printf '%s' "$3"; }
52
+ # count_code_lines comes from detect.sh (sourced above) — no fallback needed.
53
+
54
+ # Load the merged config ONCE in this shell so TOOLU_CFG_LOADED sticks for
55
+ # the threshold lookups below — each runs in a $(...) subshell that inherits it
56
+ # and skips re-merging (otherwise every wrapper re-spawns the jq merge).
57
+ command -v toolu_load_config >/dev/null 2>&1 && toolu_load_config 2>/dev/null || true
58
+
59
+ # Exit early if this isn't a TypeScript project.
60
+ [ "$(detect_ts)" = "ts" ] || exit 0
61
+
62
+ # Exit if no package manager is detected — we cannot recommend a typecheck command.
63
+ pm="$(detect_node_pm)"
64
+ [ -n "$pm" ] || exit 0
65
+ command -v "$pm" >/dev/null 2>&1 || exit 0
66
+
67
+ # Resolve the project's typecheck command per package manager.
68
+ typecheck_cmd() {
69
+ case "$1" in
70
+ bun) echo "bun run typecheck" ;;
71
+ pnpm) echo "pnpm -w typecheck" ;;
72
+ yarn) echo "yarn typecheck" ;;
73
+ npm) echo "npm run typecheck" ;;
74
+ *) echo "$1 run typecheck" ;;
75
+ esac
76
+ }
77
+ TYPECHECK_CMD="$(typecheck_cmd "$pm")"
78
+
79
+ command -v jq >/dev/null 2>&1 || exit 0
80
+
81
+ fp_from_input=""
82
+ if [[ "$tool_name" == "Write" || "$tool_name" == "Edit" || "$tool_name" == "MultiEdit" ]]; then
83
+ fp_from_input=$(echo "$input" | jq -r '.tool_input.path // .tool_input.file_path // .tool_input.target_file // empty' 2>/dev/null || echo "")
84
+ fi
85
+ FILE_PATH="${CLAUDE_FILE_PATHS:-$fp_from_input}"
86
+
87
+ # A deletion (or the source side of a move) no longer exists to lint, but any
88
+ # gate entry previously owned by this path must be removed. The normalized
89
+ # dispatcher exports these fields and also embeds them in tool_input so the
90
+ # assembled module remains independently testable.
91
+ EDIT_OPERATION="${TOOLU_EDIT_OPERATION:-$(echo "$input" | jq -r '.tool_input.toolu_edit_operation // ""' 2>/dev/null || echo "")}"
92
+ EDIT_MOVED_TO="${TOOLU_EDIT_MOVED_TO:-$(echo "$input" | jq -r '.tool_input.toolu_edit_moved_to // ""' 2>/dev/null || echo "")}"
93
+ if [[ "$EDIT_OPERATION" == delete || -n "$EDIT_MOVED_TO" ]]; then
94
+ if [[ -n "$FILE_PATH" && "$FILE_PATH" =~ \.(ts|tsx)$ ]]; then
95
+ GATE_FILE="$(toolu_project_state_root "$PROJECT_ROOT")/quality-gate-status.json"
96
+ gate_clear_file "$GATE_FILE" "$FILE_PATH" "ts-quality-hook"
97
+ fi
98
+ exit 0
99
+ fi
100
+
101
+ [[ -z "$FILE_PATH" || ! -f "$FILE_PATH" ]] && exit 0
102
+ [[ ! "$FILE_PATH" =~ \.(ts|tsx)$ ]] && exit 0
103
+
104
+ # Skip files inside git linked worktrees — quality state is for the main checkout only.
105
+ # One git call, one dirname: --git-dir + --git-common-dir come back on two lines.
106
+ _file_dir="$(dirname "$FILE_PATH")"
107
+ _file_git_dir=""; _file_common_dir=""
108
+ { IFS= read -r _file_git_dir; IFS= read -r _file_common_dir; } < <(
109
+ git -C "$_file_dir" rev-parse --path-format=absolute --git-dir --git-common-dir 2>/dev/null
110
+ )
111
+ _file_git_dir="${_file_git_dir%/}"
112
+ _file_common_dir="${_file_common_dir%/}"
113
+ if [[ -n "$_file_git_dir" && -n "$_file_common_dir" && "$_file_git_dir" != "$_file_common_dir" ]]; then
114
+ exit 0
115
+ fi
116
+
117
+ MESSAGES=""
118
+ # Separator is a REAL newline. It used to be a literal backslash-n inside double
119
+ # quotes, which jq then encoded as "\\n" — so every multi-line violation reached
120
+ # the agent as one run-on line with a visible \n in it.
121
+ add_error() {
122
+ MESSAGES="${MESSAGES}${1}"$'\n'
123
+ }
@@ -0,0 +1,33 @@
1
+ # --- Existing checks ---
2
+
3
+ # The "../ import → @/ alias" guidance is only actionable when the project
4
+ # actually defines a `@/*` path alias. In a repo that uses plain NodeNext
5
+ # relative imports with no alias configured, the recommendation cannot be
6
+ # followed, so flagging `../` there is a false positive that blocks every edit
7
+ # to such a file. Gate the check on the alias existing: walk up from the edited
8
+ # file to PROJECT_ROOT for a tsconfig whose compilerOptions.paths declares a
9
+ # `@/`-prefixed key. Tolerant of a missing / JSONC / oddly-shaped tsconfig (jq
10
+ # failure → treat as "no alias", so we never emit advice the project can't act
11
+ # on).
12
+ _ts_has_at_alias() {
13
+ local dir root cfg
14
+ dir="$(cd "$(dirname "$FILE_PATH")" 2>/dev/null && pwd)" || return 1
15
+ root="$(cd "${PROJECT_ROOT:-/}" 2>/dev/null && pwd)" || root=""
16
+ while [ -n "$dir" ]; do
17
+ for cfg in "$dir/tsconfig.json" "$dir/tsconfig.base.json"; do
18
+ [ -f "$cfg" ] || continue
19
+ if jq -e '[(.compilerOptions.paths // {}) | keys[] | select(startswith("@/"))] | length > 0' "$cfg" >/dev/null 2>&1; then
20
+ return 0
21
+ fi
22
+ done
23
+ [ "$dir" = "$root" ] && break
24
+ [ "$dir" = "/" ] && break
25
+ dir="$(dirname "$dir")"
26
+ done
27
+ return 1
28
+ }
29
+
30
+ if grep -q 'from ["'"'"']\.\./' "$FILE_PATH" 2>/dev/null && _ts_has_at_alias; then
31
+ add_error "Forbidden ../ import in $FILE_PATH — use @/ alias"
32
+ fi
33
+
@@ -0,0 +1,9 @@
1
+ AS_LINES=$(grep -nE '\)[[:space:]]+as[[:space:]]+[a-zA-Z]|\bas[[:space:]]+any\b|\bas[[:space:]]+unknown\b|[a-zA-Z>][[:space:]]+as[[:space:]]+[A-Z]|[a-zA-Z>][[:space:]]+as[[:space:]]+(string|number|boolean|object|symbol|bigint|never|undefined|null|void)\b' "$FILE_PATH" 2>/dev/null \
2
+ | grep -vE '^[0-9]+:[[:space:]]*//' \
3
+ | grep -vE '\bas[[:space:]]+const\b' \
4
+ | grep -vE '\bimport\b|^[0-9]+:[[:space:]]*export[[:space:]]*(type[[:space:]]+)?\{' \
5
+ | head -5)
6
+ if [[ -n "$AS_LINES" ]]; then
7
+ add_error "Forbidden 'as' type assertion in $FILE_PATH — use type guards or Zod"$'\n'"${AS_LINES}"
8
+ fi
9
+
@@ -0,0 +1,26 @@
1
+ # E2E specs follow a framework layout (e2e/specs/<feature>/*.spec.ts), not the
2
+ # unit-test __tests__/ co-location model — exempt them from these rules.
3
+ if [[ "$FILE_PATH" =~ \.(test|spec)\.(ts|tsx)$ && "$FILE_PATH" != */e2e/* ]]; then
4
+ if [[ "$FILE_PATH" != */__tests__/* ]]; then
5
+ add_error "Test file outside __tests__/: $FILE_PATH — move to sibling __tests__/ directory"
6
+ else
7
+ _after_tests="${FILE_PATH##*__tests__/}"
8
+ if [[ "$_after_tests" == */* ]]; then
9
+ _subdir="${_after_tests%%/*}"
10
+ if [[ "$_subdir" != "fixtures" && "$_subdir" != "helpers" && "$_subdir" != "utils" ]]; then
11
+ add_error "Test nested in __tests__/ subdirectory: $FILE_PATH — keep __tests__/ flat (only fixtures/helpers/utils subdirs allowed; no mocks/ — tests must exercise real data)"
12
+ fi
13
+ fi
14
+ # `%` (shortest suffix) resolves to the NEAREST __tests__, matching the
15
+ # `##` (last __tests__) used for the nested-subdir check above; `%%` would
16
+ # pick the OUTERMOST __tests__ and mis-validate a doubly-nested path.
17
+ _tests_dir="${FILE_PATH%__tests__/*}__tests__"
18
+ _parent_dir=$(dirname "$_tests_dir")
19
+ if ! find "$_parent_dir" -maxdepth 1 -type f \( -name "*.ts" -o -name "*.tsx" \) \
20
+ ! -name "*.test.*" ! -name "*.spec.*" ! -name "*.d.ts" 2>/dev/null | grep -q . \
21
+ && ! find "$_parent_dir" -maxdepth 1 -type d ! -name "__tests__" ! -name "." 2>/dev/null | grep -q .; then
22
+ add_error "Test not co-located with source: $FILE_PATH — __tests__/ must be at the same level as the code it tests"
23
+ fi
24
+ fi
25
+ fi
26
+
@@ -0,0 +1,40 @@
1
+ # Resolve the limit AND where it came from in one pass (avoids running the
2
+ # override/native lookups twice). Contract: ts_max_file_lines_resolved prints
3
+ # exactly "<int> <source>" where <source> is ONE token (override|native|default)
4
+ # — the two-field `read` below relies on it; a multi-word source would spill
5
+ # into TS_MAX_SRC and break the case arms.
6
+ TS_MAX_FILE=""; TS_MAX_SRC="default"
7
+ if command -v ts_max_file_lines_resolved >/dev/null 2>&1; then
8
+ read -r TS_MAX_FILE TS_MAX_SRC <<<"$(ts_max_file_lines_resolved)"
9
+ else
10
+ TS_MAX_FILE=$(ts_max_file_lines)
11
+ fi
12
+ [ -n "$TS_MAX_FILE" ] || TS_MAX_FILE="${DEFAULT_TS_MAX_FILE_LINES:-300}"
13
+ [ -n "$TS_MAX_SRC" ] || TS_MAX_SRC="default" # empty read -> case must still pick a branch
14
+ TS_LINE_COUNT=$(count_code_lines "$FILE_PATH")
15
+ if [[ "$TS_LINE_COUNT" -gt "$TS_MAX_FILE" ]]; then
16
+ _split_hint="split into smaller modules"
17
+ _linter="$(detect_ts_linter)"
18
+ if [ -n "$_linter" ]; then
19
+ # Only claim the linter owns the limit when the limit truly came from its
20
+ # config; if a linter is present but its config isn't machine-readable
21
+ # (e.g. .eslintrc.cjs / eslint.config.js), say so instead of contradicting.
22
+ case "$TS_MAX_SRC" in
23
+ native) _split_hint="$_split_hint ($_linter enforces this max-lines limit)" ;;
24
+ default)
25
+ # Biome has no max-lines rule at all, so "unparsed config form" would be
26
+ # misleading for it — say so plainly. ESLint/oxc do have max-lines, so
27
+ # there the limit genuinely could have come from an unreadable config.
28
+ if [ "$_linter" = "biome" ]; then
29
+ _split_hint="$_split_hint (biome has no max-lines equivalent — gate uses the ${TS_MAX_FILE}-line default)"
30
+ else
31
+ _split_hint="$_split_hint ($_linter is present but the gate's limit didn't come from its config (unparsed config form or a per-glob override) — gate uses the ${TS_MAX_FILE}-line default; align them)"
32
+ fi
33
+ ;;
34
+ esac
35
+ fi
36
+ _approx=""
37
+ has_unterminated_block "$FILE_PATH" && _approx=" (size approximated — an unterminated /* or a string containing /* may be affecting the count)"
38
+ add_error "TS file exceeds ${TS_MAX_FILE}-line limit: $FILE_PATH ($TS_LINE_COUNT code lines, blanks/comments excluded)${_approx} — $_split_hint"
39
+ fi
40
+
@@ -0,0 +1,53 @@
1
+ TS_MAX_FN=$(ts_max_fn_lines)
2
+ # Brace-depth fn-length counter — mirrors rust-quality.sh so TS and Rust measure
3
+ # the same way. The end of a function is found by counting brace depth from the
4
+ # fn's own line (single-line "..."/'...'/`...` string and quote char-literal
5
+ # contents stripped first), so a method inside a class/object is measured to ITS
6
+ # own indented close, an inner if/for/switch close can't end the range early, and
7
+ # a column-0 `}` inside a template literal no longer cuts the range short — the
8
+ # bugs the old `^}` col-0 marker had. Three start forms: `function` declarations,
9
+ # `const NAME = (…) =>` / `= function` expressions (params may span lines), and
10
+ # class/object methods `name(…) {` (control-flow keywords excluded so their
11
+ # blocks aren't mistaken for methods). Known limitation (same as Rust): a brace
12
+ # inside a MULTI-LINE template/string still leaks into the count — an unbalanced
13
+ # one leaves the fn unmeasured (fail-open), never falsely flagged short.
14
+ LONG_FUNCS=$(awk -v max="$TS_MAX_FN" -v q="'" '
15
+ function strip(l) {
16
+ gsub(/\\"/, "", l) # escaped double quotes
17
+ gsub(/"[^"]*"/, "", l) # double-quoted string contents
18
+ gsub(q "[^" q "]*" q, "", l) # single-quoted string / char-literal contents
19
+ gsub(/`[^`]*`/, "", l) # single-line template-literal contents
20
+ return l
21
+ }
22
+ function fnstart() { infn=1; start=NR; name=$0; depth=0; opened=0 }
23
+ !infn {
24
+ s=strip($0)
25
+ if (s ~ /^[[:space:]]*(export[[:space:]]+)?(default[[:space:]]+)?(async[[:space:]]+)?function[ \t*]/) { fnstart() }
26
+ else if (s ~ /^[[:space:]]*(export[[:space:]]+)?(default[[:space:]]+)?const[[:space:]]+[A-Za-z_$][A-Za-z0-9_$]*[[:space:]]*=[[:space:]]*(async[[:space:]]+)?(\(|function[ \t(*])/) { fnstart() }
27
+ else if (s ~ /^[[:space:]]+(public[[:space:]]+|private[[:space:]]+|protected[[:space:]]+|static[[:space:]]+|async[[:space:]]+|override[[:space:]]+|readonly[[:space:]]+|get[[:space:]]+|set[[:space:]]+|\*[[:space:]]*)*[A-Za-z_$][A-Za-z0-9_$]*[[:space:]]*(<[^(){}]*>)?[[:space:]]*\(/ \
28
+ && s !~ /^[[:space:]]*(if|for|while|switch|catch|return|do|else|function|await|with|yield|throw|new|typeof|delete|void|in|of|case)[^A-Za-z0-9_$]/ \
29
+ && s !~ /=>/ \
30
+ && s !~ /;[[:space:]]*$/ \
31
+ && s ~ /\)([[:space:]]*:[^={]*)?[[:space:]]*\{[[:space:]]*$/) { fnstart() }
32
+ }
33
+ infn {
34
+ line=strip($0)
35
+ no=gsub(/\{/, "{", line); nc=gsub(/\}/, "}", line)
36
+ depth += no - nc
37
+ if (no > 0) opened=1
38
+ if (opened && depth <= 0) {
39
+ len=NR-start
40
+ if (len > max) printf "%s:%d (%d lines)\n", name, start, len
41
+ infn=0
42
+ }
43
+ # Release a brace-less single-line form (an expression-bodied arrow like
44
+ # `const sq = (x) => x * x;` or a parenthesized `const x = (a, b);`) on its
45
+ # terminating `;`. Mirrors rust-quality.sh: without this the start sticks and
46
+ # the NEXT real function is misattributed to this line — a bogus over-length.
47
+ if (!opened && $0 ~ /;[[:space:]]*$/) infn=0
48
+ }
49
+ ' "$FILE_PATH" 2>/dev/null)
50
+ if [[ -n "$LONG_FUNCS" ]]; then
51
+ add_error "Function too long in $FILE_PATH (>${TS_MAX_FN} lines) — simplify or split"
52
+ fi
53
+