@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,7 @@
1
+ if [[ "$FILE_PATH" =~ use-.*\.ts$ || "$FILE_PATH" =~ use[A-Z].*\.ts$ ]]; then
2
+ HOOK_COUNT=$(grep -cE '^[[:space:]]*(const \[|useRef\(|useEffect\()' "$FILE_PATH" 2>/dev/null || true)
3
+ if [[ "$HOOK_COUNT" -gt 3 ]]; then
4
+ add_error "Hook does too many things in $FILE_PATH ($HOOK_COUNT useState/useRef/useEffect) — split into focused hooks"
5
+ fi
6
+ fi
7
+
@@ -0,0 +1,5 @@
1
+ FACTORY_COUNT=$(grep -cE '^export (async )?function create' "$FILE_PATH" 2>/dev/null || true)
2
+ if [[ "$FACTORY_COUNT" -gt 2 ]]; then
3
+ add_error "Too many factory functions in $FILE_PATH ($FACTORY_COUNT) — simplify construction"
4
+ fi
5
+
@@ -0,0 +1,6 @@
1
+ if grep -qE 'function is[A-Z].*\): .* is [A-Z]' "$FILE_PATH" 2>/dev/null; then
2
+ if [[ -f "$PROJECT_ROOT/package.json" ]] && grep -q '"zod"' "$PROJECT_ROOT/package.json" 2>/dev/null; then
3
+ add_error "Manual type guard in $FILE_PATH — use Zod schema instead"
4
+ fi
5
+ fi
6
+
@@ -0,0 +1,16 @@
1
+ NEW_TYPES=$(grep -oE '^export (interface|type) [A-Z][a-zA-Z]+' "$FILE_PATH" 2>/dev/null | awk '{print $NF}')
2
+ if [[ -n "$NEW_TYPES" ]]; then
3
+ # git grep instead of grep -r: uses the index, skips .gitignore'd trees
4
+ # (node_modules, dist) — the recursive walk was a per-edit hot-path cost on
5
+ # large monorepos. --untracked keeps brand-new files visible.
6
+ _ts_rel_fp="${FILE_PATH#"$PROJECT_ROOT"/}"
7
+ for TYPE_NAME in $NEW_TYPES; do
8
+ DUPE_FILE=$(git -C "$PROJECT_ROOT" grep -l --untracked -E \
9
+ "^export (interface|type) ${TYPE_NAME}[ <{]" -- 'packages/*.ts' 'packages/*.tsx' 'apps/*.ts' 'apps/*.tsx' 2>/dev/null \
10
+ | grep -vFx "$_ts_rel_fp" | head -1)
11
+ if [[ -n "$DUPE_FILE" ]]; then
12
+ add_error "Type '$TYPE_NAME' in $FILE_PATH already defined in $DUPE_FILE — import instead of redefining"
13
+ fi
14
+ done
15
+ fi
16
+
@@ -0,0 +1,11 @@
1
+ # Check: forbidden sub-component file names (must match the exported function)
2
+ _basename="$(basename "$FILE_PATH" .tsx)"
3
+ _basename="${_basename%.ts}"
4
+ if [[ "$FILE_PATH" =~ \.(tsx)$ ]]; then
5
+ if echo "$_basename" | grep -qE '^(parts|components|helpers|items|sections|elements)$|-(parts|sections|items|elements)$'; then
6
+ add_error "Forbidden component filename '$_basename.tsx' in $FILE_PATH — name file after its exported function (e.g. api-key-create-button.tsx)"
7
+ fi
8
+ fi
9
+
10
+ # --- New checks ---
11
+
@@ -0,0 +1,6 @@
1
+ # Check: console.log — capture once, then test (not grep-twice).
2
+ CONSOLE_LINES=$(grep -nE '^[[:space:]]*console\.log\(' "$FILE_PATH" 2>/dev/null | grep -vE '^[0-9]+:[[:space:]]*//' | head -3)
3
+ if [[ -n "$CONSOLE_LINES" ]]; then
4
+ add_error "Forbidden console.log in $FILE_PATH — use console.error/warn/info"$'\n'"${CONSOLE_LINES}"
5
+ fi
6
+
@@ -0,0 +1,12 @@
1
+ # Check: suppression comments — fix the issue in code, never silence the tool.
2
+ # @ts-expect-error is exempt in test/spec files, where asserting a compile error
3
+ # is a legitimate test; banned everywhere else like the rest.
4
+ SUPPRESS_TOKENS='@ts-ignore|@ts-nocheck|eslint-disable|biome-ignore'
5
+ if [[ ! "$FILE_PATH" =~ \.(test|spec)\.(ts|tsx)$ ]]; then
6
+ SUPPRESS_TOKENS="${SUPPRESS_TOKENS}|@ts-expect-error"
7
+ fi
8
+ DISABLE_LINES=$(grep -nE "(//|/\*+)[[:space:]]*(${SUPPRESS_TOKENS})" "$FILE_PATH" 2>/dev/null | head -3)
9
+ if [[ -n "$DISABLE_LINES" ]]; then
10
+ add_error "Forbidden suppression comment in $FILE_PATH — fix the underlying issue in code, never silence it"$'\n'"${DISABLE_LINES}"
11
+ fi
12
+
@@ -0,0 +1,8 @@
1
+ # Check: confirm()/alert() in frontend files
2
+ if [[ "$FILE_PATH" == */components/* || "$FILE_PATH" == */routes/* ]]; then
3
+ CONFIRM_LINES=$(grep -nE '\b(confirm|alert)[[:space:]]*\(' "$FILE_PATH" 2>/dev/null | grep -vE '^[0-9]+:[[:space:]]*//' | grep -vE '(ConfirmDeleteAlert|AlertDialog|customAlert|customConfirm)' | head -3)
4
+ if [[ -n "$CONFIRM_LINES" ]]; then
5
+ add_error "Forbidden confirm()/alert() in $FILE_PATH — use AlertDialog component"$'\n'"${CONFIRM_LINES}"
6
+ fi
7
+ fi
8
+
@@ -0,0 +1,12 @@
1
+ # Check: raw radix AlertDialog/Dialog imports. Drop leading-`//` comment lines
2
+ # first (same filter as the AS_LINES check above) so a commented-out import does
3
+ # not false-positive. Like AS_LINES, only whole-line `//` comments are dropped —
4
+ # a radix path inside a trailing comment after real code still matches (rare:
5
+ # imports are standalone). Kept consistent with the sibling rules on purpose.
6
+ if grep -nE "from ['\"]@radix-ui/react-(alert-dialog|dialog)['\"]" "$FILE_PATH" 2>/dev/null \
7
+ | grep -qvE '^[0-9]+:[[:space:]]*//'; then
8
+ if [[ "$FILE_PATH" != */packages/ui/* ]]; then
9
+ add_error "Raw radix import in $FILE_PATH — use shared components from @/components/ui/"
10
+ fi
11
+ fi
12
+
@@ -0,0 +1,6 @@
1
+ # Check: mutable props (Props type param without Readonly)
2
+ PROPS_LINES=$(grep -nE '\((props|[a-z]+Props):[[:space:]]+[A-Z][a-zA-Z]+Props\)' "$FILE_PATH" 2>/dev/null | grep -v 'Readonly' | head -3)
3
+ if [[ -n "$PROPS_LINES" ]]; then
4
+ add_error "Mutable props in $FILE_PATH — wrap in Readonly<Props>"$'\n'"${PROPS_LINES}"
5
+ fi
6
+
@@ -0,0 +1,10 @@
1
+ # Check: manual try/catch+toast pattern in components
2
+ if [[ "$FILE_PATH" == */components/* || "$FILE_PATH" == */routes/* ]]; then
3
+ # [[:space:]] not \s: BSD awk (macOS) has no \s and reads it as a literal `s`,
4
+ # which silently killed this rule for the universal `catch (` spelling.
5
+ CATCH_TOAST=$(awk '/catch[[:space:]]*\(/{found=1} found && /toast\(/{print NR": "$0; found=0}' "$FILE_PATH" 2>/dev/null | head -3)
6
+ if [[ -n "$CATCH_TOAST" ]]; then
7
+ add_error "Manual try/catch+toast in $FILE_PATH — use shared error handling"$'\n'"${CATCH_TOAST}"
8
+ fi
9
+ fi
10
+
@@ -0,0 +1,242 @@
1
+ # shellcheck shell=bash
2
+ # --- Error-handling rules (zero tolerance) ---
3
+
4
+ if command -v ast-grep >/dev/null 2>&1; then
5
+ # ONE ast-grep process for ALL the rules below, not one per rule. The previous shape ran
6
+ # `ast-grep run -p <pattern>` once per pattern — 14 spawns, each re-parsing the
7
+ # same file — and measured ~170ms of the TS gate's ~300ms on every edit.
8
+ # `scan --inline-rules` parses once and returns every rule's hits as JSON
9
+ # (~13ms). The excerpt lines rebuilt from .file/.range/.lines below are
10
+ # byte-identical to what `run -p` printed for every pattern rule. The one
11
+ # exception is swallow-bare, which had to become a catch_clause match (see its
12
+ # comment): its excerpt is the catch clause rather than the whole try
13
+ # statement, which is narrower, not malformed.
14
+ #
15
+ # severity is `warning` on purpose: `scan` exits non-zero when an ERROR-level
16
+ # rule matches, which would be indistinguishable from a crashed tool. At
17
+ # warning level both a clean and a matching run exit 0, so a non-zero exit (or
18
+ # any stderr) still means the tool genuinely broke — preserving the fail-loud
19
+ # contract the old per-pattern ast_scan had.
20
+ ts_ast_err_file="$(mktemp)"
21
+ ts_ast_rules=$(cat <<'TS_AST_RULES'
22
+ id: empty-catch
23
+ language: ts
24
+ severity: warning
25
+ message: empty catch
26
+ rule:
27
+ pattern: 'try { $$$ } catch ($_) { }'
28
+ ---
29
+ id: empty-catch-noarg
30
+ language: ts
31
+ severity: warning
32
+ message: empty catch (no binding)
33
+ rule:
34
+ pattern: 'try { $$$ } catch { }'
35
+ ---
36
+ id: empty-catch-handler
37
+ language: ts
38
+ severity: warning
39
+ message: empty promise catch handler
40
+ rule:
41
+ pattern: '$_.catch(() => { })'
42
+ ---
43
+ id: null-catch-handler
44
+ language: ts
45
+ severity: warning
46
+ message: promise catch handler returning null
47
+ rule:
48
+ pattern: '$_.catch(() => null)'
49
+ ---
50
+ id: undef-catch-handler
51
+ language: ts
52
+ severity: warning
53
+ message: promise catch handler returning undefined
54
+ rule:
55
+ pattern: '$_.catch(() => undefined)'
56
+ ---
57
+ id: swallow-null-arg
58
+ language: ts
59
+ severity: warning
60
+ message: catch returns null
61
+ rule:
62
+ pattern: 'try { $$$ } catch ($_) { return null }'
63
+ ---
64
+ id: swallow-undef-arg
65
+ language: ts
66
+ severity: warning
67
+ message: catch returns undefined
68
+ rule:
69
+ pattern: 'try { $$$ } catch ($_) { return undefined }'
70
+ ---
71
+ id: swallow-null
72
+ language: ts
73
+ severity: warning
74
+ message: catch returns null (no binding)
75
+ rule:
76
+ pattern: 'try { $$$ } catch { return null }'
77
+ ---
78
+ id: swallow-undef
79
+ language: ts
80
+ severity: warning
81
+ message: catch returns undefined (no binding)
82
+ rule:
83
+ pattern: 'try { $$$ } catch { return undefined }'
84
+ ---
85
+ # A bare `return` in an ast-grep pattern acts as a WILDCARD over the optional
86
+ # argument: `catch { return }` also matched `return []`, `return 42` and
87
+ # `return null`, i.e. every non-nullish fallback got reported as "returns a
88
+ # nullish value". Match the catch clause itself, then require that its single
89
+ # return statement carries no argument at all.
90
+ id: swallow-bare
91
+ language: ts
92
+ severity: warning
93
+ message: catch returns nothing
94
+ rule:
95
+ all:
96
+ - any:
97
+ - pattern:
98
+ context: 'try {} catch { return }'
99
+ selector: catch_clause
100
+ - pattern:
101
+ context: 'try {} catch ($_) { return }'
102
+ selector: catch_clause
103
+ - not:
104
+ has:
105
+ stopBy: end
106
+ kind: return_statement
107
+ has:
108
+ stopBy: neighbor
109
+ pattern: $X
110
+ ---
111
+ id: throw-empty-error
112
+ language: ts
113
+ severity: warning
114
+ message: throw new Error() with no message
115
+ rule:
116
+ pattern: 'throw new Error()'
117
+ ---
118
+ id: throw-string
119
+ language: ts
120
+ severity: warning
121
+ message: throw of a string literal
122
+ rule:
123
+ pattern: 'throw "$S"'
124
+ ---
125
+ id: throw-template
126
+ language: ts
127
+ severity: warning
128
+ message: throw of a template literal
129
+ rule:
130
+ pattern: 'throw `$S`'
131
+ TS_AST_RULES
132
+ )
133
+
134
+ ts_ast_failed=0
135
+ ts_ast_fail_detail=""
136
+ ts_ast_lines=""
137
+ ts_ast_json=$(ast-grep scan --inline-rules "$ts_ast_rules" --json "$FILE_PATH" 2>"$ts_ast_err_file")
138
+ ts_ast_rc=$?
139
+ # Flatten every hit to "<ruleId>\t<file>:<line>:<source line>", one output line
140
+ # per matched source line — the exact shape `run -p` emitted, now tagged with
141
+ # the rule that produced it.
142
+ #
143
+ # Two distinct failure modes, reported distinctly: ast-grep itself broke, or it
144
+ # exited 0 and returned something that is not the documented JSON array. Both
145
+ # mean the rules could not be verified, but collapsing them into one message
146
+ # sends the reader to the wrong tool.
147
+ ts_ast_fail_stage=""
148
+ if [[ "$ts_ast_rc" -ne 0 || -s "$ts_ast_err_file" ]]; then
149
+ ts_ast_failed=1
150
+ ts_ast_fail_stage="ast-grep"
151
+ elif ! ts_ast_lines=$(printf '%s' "$ts_ast_json" | jq -r '
152
+ .[] | . as $m
153
+ | ((($m.lines // "") | split("\n")) | to_entries[])
154
+ | "\($m.ruleId)\t\($m.file):\($m.range.start.line + 1 + .key):\(.value)"
155
+ ' 2>/dev/null); then
156
+ ts_ast_failed=1
157
+ ts_ast_fail_stage="jq"
158
+ ts_ast_lines=""
159
+ fi
160
+ if [[ "$ts_ast_failed" -ne 0 ]]; then
161
+ # Exit code plus the first stderr line (capped) so the agent learns WHAT
162
+ # broke, not just THAT it did.
163
+ ts_ast_stderr_first=$(head -n 1 "$ts_ast_err_file" 2>/dev/null | cut -c1-200)
164
+ if [[ "$ts_ast_fail_stage" == "jq" ]]; then
165
+ ts_ast_fail_detail="ast-grep exited 0 but its output did not parse as the documented JSON array"
166
+ else
167
+ ts_ast_fail_detail="ast-grep exit ${ts_ast_rc}${ts_ast_stderr_first:+: $ts_ast_stderr_first}"
168
+ fi
169
+ fi
170
+
171
+ # ts_join_hits BLOCK... — concatenate non-empty hit blocks into $TS_JOINED with
172
+ # one newline between them. Each block comes from a $(...) capture, which strips
173
+ # the trailing newline, so plain concatenation glued one block's last line onto
174
+ # the next block's first line. Sets a variable instead of printing to avoid a
175
+ # fork on a path that runs after every edit.
176
+ ts_join_hits() {
177
+ local _part
178
+ TS_JOINED=""
179
+ for _part in "$@"; do
180
+ [ -z "$_part" ] && continue
181
+ [ -n "$TS_JOINED" ] && TS_JOINED="${TS_JOINED}"$'\n'
182
+ TS_JOINED="${TS_JOINED}${_part}"
183
+ done
184
+ }
185
+
186
+ # ts_ast_hits RULE_ID LIMIT — this rule's excerpt lines, capped like before.
187
+ ts_ast_hits() {
188
+ local _id="$1" _limit="$2" _row _rest
189
+ [ -n "$ts_ast_lines" ] || return 0
190
+ while IFS=$'\t' read -r _row _rest; do
191
+ [ "$_row" = "$_id" ] && printf '%s\n' "$_rest"
192
+ done <<< "$ts_ast_lines" | head -n "$_limit"
193
+ }
194
+
195
+ # Empty catch — swallowed error
196
+ EMPTY_CATCH=$(ts_ast_hits empty-catch 3)
197
+ EMPTY_CATCH_NOARG=$(ts_ast_hits empty-catch-noarg 3)
198
+ if [[ -n "$EMPTY_CATCH" || -n "$EMPTY_CATCH_NOARG" ]]; then
199
+ ts_join_hits "$EMPTY_CATCH" "$EMPTY_CATCH_NOARG"
200
+ add_error "Empty catch block in $FILE_PATH — handle the error or rethrow; do not swallow"$'\n'"${TS_JOINED}"
201
+ fi
202
+
203
+ # Silent promise rejection — .catch(() => {}) / .catch(() => null)
204
+ EMPTY_CATCH_HANDLER=$(ts_ast_hits empty-catch-handler 3)
205
+ NULL_CATCH_HANDLER=$(ts_ast_hits null-catch-handler 3)
206
+ UNDEF_CATCH_HANDLER=$(ts_ast_hits undef-catch-handler 3)
207
+ if [[ -n "$EMPTY_CATCH_HANDLER" || -n "$NULL_CATCH_HANDLER" || -n "$UNDEF_CATCH_HANDLER" ]]; then
208
+ ts_join_hits "$EMPTY_CATCH_HANDLER" "$NULL_CATCH_HANDLER" "$UNDEF_CATCH_HANDLER"
209
+ add_error "Silent promise rejection in $FILE_PATH — log or rethrow the error"$'\n'"${TS_JOINED}"
210
+ fi
211
+
212
+ # Swallow via a catch that just returns a nullish value — error vanishes.
213
+ SWALLOW_CATCH=""
214
+ for _rule in swallow-null-arg swallow-undef-arg \
215
+ swallow-null swallow-undef swallow-bare; do
216
+ _h=$(ts_ast_hits "$_rule" 2)
217
+ ts_join_hits "$SWALLOW_CATCH" "$_h"
218
+ SWALLOW_CATCH="$TS_JOINED"
219
+ done
220
+ if [[ -n "$SWALLOW_CATCH" ]]; then
221
+ add_error "Catch swallows the error by returning a nullish value in $FILE_PATH — handle, log, or rethrow it"$'\n'"${SWALLOW_CATCH}"
222
+ fi
223
+
224
+ # throw new Error() with no message
225
+ THROW_EMPTY_ERROR=$(ts_ast_hits throw-empty-error 3)
226
+ if [[ -n "$THROW_EMPTY_ERROR" ]]; then
227
+ add_error "throw new Error() with no message in $FILE_PATH — include a descriptive message"$'\n'"${THROW_EMPTY_ERROR}"
228
+ fi
229
+
230
+ # throw of string literal — breaks instanceof Error
231
+ THROW_STRING=$(ts_ast_hits throw-string 3)
232
+ THROW_TSTR=$(ts_ast_hits throw-template 3)
233
+ if [[ -n "$THROW_STRING" || -n "$THROW_TSTR" ]]; then
234
+ ts_join_hits "$THROW_STRING" "$THROW_TSTR"
235
+ add_error "throw of string literal in $FILE_PATH — throw an Error (or subclass) instead"$'\n'"${TS_JOINED}"
236
+ fi
237
+
238
+ rm -f "$ts_ast_err_file"
239
+ if [[ "$ts_ast_failed" -ne 0 ]]; then
240
+ add_error "ast-grep failed while scanning $FILE_PATH — ${ts_ast_fail_detail:-unknown error}; error-handling rules could not be verified. Fix the tool/file and re-edit"
241
+ fi
242
+ fi
@@ -0,0 +1,23 @@
1
+ # throw of numeric/boolean/null/undefined literal — match anywhere on line,
2
+ # strip line comments and inline /* */ blocks first so `// throw 5` style
3
+ # annotations and example code in comments don't trigger.
4
+ THROW_LITERAL=$(awk '
5
+ {
6
+ line = $0
7
+ # Strip /* ... */ blocks on a single line (heuristic; multi-line not handled).
8
+ # Greedy on purpose: a line with TWO inline blocks sandwiching real code
9
+ # (`/* a */ throw 5; /* b */`) erases the `throw 5` and misses it. Vanishingly
10
+ # rare; accepted over a count_code_lines-style state machine here.
11
+ gsub(/\/\*.*\*\//, "", line)
12
+ # Strip // line comments (heuristic — does not preserve `//` inside a string,
13
+ # which is rare in real code).
14
+ sub(/\/\/.*$/, "", line)
15
+ if (match(line, /(^|[^a-zA-Z_$])throw[ \t]+(-?[0-9]+(\.[0-9]+)?|null|undefined|true|false)([ \t]|;|}|$)/)) {
16
+ print NR ": " $0
17
+ }
18
+ }
19
+ ' "$FILE_PATH" 2>/dev/null | head -3)
20
+ if [[ -n "$THROW_LITERAL" ]]; then
21
+ add_error "throw of non-Error literal in $FILE_PATH — throw an Error (or subclass) instead"$'\n'"${THROW_LITERAL}"
22
+ fi
23
+
@@ -0,0 +1,109 @@
1
+ # Check: no-mocks — tests must exercise real data/services, not mocked stand-ins.
2
+ # Scope: test files only — *.test.ts(x) / *.spec.ts(x), or any file under a
3
+ # __tests__/ directory — excluding e2e specs, which follow the platform's own
4
+ # fixture conventions (same carve-out as 20-tests.sh / 65-suppression.sh).
5
+ # Opt-out: lang.ts.noMocks=false in the merged config (default true — blocking).
6
+ if [[ "$FILE_PATH" != */e2e/* ]] && { [[ "$FILE_PATH" =~ \.(test|spec)\.(ts|tsx)$ ]] || [[ "$FILE_PATH" == */__tests__/* ]]; } \
7
+ && [ "$(quality_flag ts noMocks true)" = "true" ]; then
8
+
9
+ NOMOCKS_LINES=""
10
+ if command -v ast-grep >/dev/null 2>&1; then
11
+ # ONE ast-grep process for all the mock-call rules below, same shape as
12
+ # 78-error-ast.sh: `scan --inline-rules` parses once and returns every
13
+ # rule's hits as JSON. severity is `warning` on purpose — `scan` exits
14
+ # non-zero on an ERROR-level match, which would be indistinguishable from a
15
+ # crashed tool; at `warning` a non-zero exit (or any stderr) always means
16
+ # the tool itself broke.
17
+ ts_nomocks_err_file="$(mktemp)"
18
+ ts_nomocks_rules=$(cat <<'TS_NOMOCKS_RULES'
19
+ id: jest-mock
20
+ language: ts
21
+ severity: warning
22
+ message: jest.mock() call
23
+ rule:
24
+ pattern: jest.mock($$$)
25
+ ---
26
+ id: vi-mock
27
+ language: ts
28
+ severity: warning
29
+ message: vi.mock() call
30
+ rule:
31
+ pattern: vi.mock($$$)
32
+ ---
33
+ id: jest-fn
34
+ language: ts
35
+ severity: warning
36
+ message: jest.fn() call
37
+ rule:
38
+ pattern: jest.fn($$$)
39
+ ---
40
+ id: vi-fn
41
+ language: ts
42
+ severity: warning
43
+ message: vi.fn() call
44
+ rule:
45
+ pattern: vi.fn($$$)
46
+ ---
47
+ id: sinon-method
48
+ language: ts
49
+ severity: warning
50
+ message: sinon mock method call
51
+ rule:
52
+ pattern: sinon.$M($$$)
53
+ TS_NOMOCKS_RULES
54
+ )
55
+
56
+ ts_nomocks_failed=0
57
+ ts_nomocks_fail_stage=""
58
+ ts_nomocks_json=$(ast-grep scan --inline-rules "$ts_nomocks_rules" --json "$FILE_PATH" 2>"$ts_nomocks_err_file")
59
+ ts_nomocks_rc=$?
60
+ if [[ "$ts_nomocks_rc" -ne 0 || -s "$ts_nomocks_err_file" ]]; then
61
+ ts_nomocks_failed=1
62
+ ts_nomocks_fail_stage="ast-grep"
63
+ elif [[ -z "$(printf '%s' "$ts_nomocks_json" | tr -d '[:space:]')" ]]; then
64
+ # ast-grep exited 0 with genuinely empty stdout — jq also exits 0 on
65
+ # empty input with empty output, so left unchecked this would be
66
+ # indistinguishable from a clean "[]" scan and silently swallow a real
67
+ # breakage as "no hits". A well-behaved scan always emits at least "[]".
68
+ ts_nomocks_failed=1
69
+ ts_nomocks_fail_stage="empty-output"
70
+ elif ! NOMOCKS_LINES=$(printf '%s' "$ts_nomocks_json" | jq -r '
71
+ .[] | . as $m
72
+ | ((($m.lines // "") | split("\n")) | to_entries[])
73
+ | "\($m.ruleId)\t\($m.file):\($m.range.start.line + 1 + .key):\(.value)"
74
+ ' 2>/dev/null); then
75
+ ts_nomocks_failed=1
76
+ ts_nomocks_fail_stage="jq"
77
+ NOMOCKS_LINES=""
78
+ fi
79
+
80
+ if [[ "$ts_nomocks_failed" -ne 0 ]]; then
81
+ ts_nomocks_stderr_first=$(head -n 1 "$ts_nomocks_err_file" 2>/dev/null | cut -c1-200)
82
+ if [[ "$ts_nomocks_fail_stage" == "jq" ]]; then
83
+ add_error "ast-grep failed while scanning $FILE_PATH for mocks — its output did not parse as the documented JSON array; no-mocks rule could not be verified. Fix the tool/file and re-edit"
84
+ elif [[ "$ts_nomocks_fail_stage" == "empty-output" ]]; then
85
+ add_error "ast-grep failed while scanning $FILE_PATH for mocks — exited 0 with empty output (expected at least the JSON array \"[]\"); no-mocks rule could not be verified. Fix the tool/file and re-edit"
86
+ else
87
+ add_error "ast-grep failed while scanning $FILE_PATH for mocks — exit ${ts_nomocks_rc}${ts_nomocks_stderr_first:+: $ts_nomocks_stderr_first}; no-mocks rule could not be verified. Fix the tool/file and re-edit"
88
+ fi
89
+ fi
90
+ rm -f "$ts_nomocks_err_file"
91
+
92
+ if [[ -n "$NOMOCKS_LINES" ]]; then
93
+ # Strip the leading "<ruleId>\t" — only the excerpt (file:line:source) is shown.
94
+ NOMOCKS_EXCERPT=$(printf '%s\n' "$NOMOCKS_LINES" | cut -f2- | head -5)
95
+ add_error "Mocked test double in $FILE_PATH — tests must exercise real data/services, not mocks/stubs (jest.mock/vi.mock/jest.fn/vi.fn/sinon)"$'\n'"${NOMOCKS_EXCERPT}"
96
+ fi
97
+ fi
98
+ # ast-grep absent: the mock-call rules above are skipped, same fail-soft
99
+ # posture as 78-error-ast.sh — but the ts-mockito import check below needs no
100
+ # external tool, so it still runs.
101
+
102
+ # ts-mockito is a whole mocking LIBRARY imported by name — a plain grep for
103
+ # the import specifier, same idiom as the `../` import check in 10-imports.sh
104
+ # (quote-agnostic via a bracket class).
105
+ if grep -qE "from[[:space:]]+[\"']ts-mockito[\"']" "$FILE_PATH" 2>/dev/null; then
106
+ add_error "Import from ts-mockito in $FILE_PATH — tests must exercise real data/services, not mocks/stubs"
107
+ fi
108
+ fi
109
+
@@ -0,0 +1,30 @@
1
+ # Check: code duplication (jscpd, scoped to package directory)
2
+ # Advisory only — warns but does NOT block (pre-existing duplication is not the editor's fault).
3
+ DUPLICATION_WARNING=""
4
+ if [[ -z "$MESSAGES" && ! "$FILE_PATH" =~ \.(test|spec)\. ]]; then
5
+ _pkg_rel="${FILE_PATH#"$PROJECT_ROOT"/}"
6
+ _pkg_dir=""
7
+ if [[ "$_pkg_rel" == apps/* || "$_pkg_rel" == packages/* ]]; then
8
+ _pkg_dir="$PROJECT_ROOT/$(echo "$_pkg_rel" | cut -d/ -f1-2)"
9
+ fi
10
+ # jscpd runner — use whatever package manager runner is available.
11
+ jscpd_runner=""
12
+ case "$pm" in
13
+ bun) command -v bunx >/dev/null 2>&1 && jscpd_runner="bunx jscpd" ;;
14
+ pnpm) command -v pnpm >/dev/null 2>&1 && jscpd_runner="pnpm dlx jscpd" ;;
15
+ yarn) command -v yarn >/dev/null 2>&1 && jscpd_runner="yarn dlx jscpd" ;;
16
+ npm) command -v npx >/dev/null 2>&1 && jscpd_runner="npx jscpd" ;;
17
+ esac
18
+ if [[ -n "$_pkg_dir" && -d "$_pkg_dir" && -f "$PROJECT_ROOT/.jscpd.json" && -n "$jscpd_runner" ]]; then
19
+ # shellcheck disable=SC2086 # $jscpd_runner is a multi-word command (e.g. "pnpm dlx jscpd") — intentional word split
20
+ _jscpd_out=$(timeout 10 $jscpd_runner "$_pkg_dir" \
21
+ --config "$PROJECT_ROOT/.jscpd.json" \
22
+ 2>&1 || true)
23
+ _file_base=$(basename "$FILE_PATH")
24
+ if echo "$_jscpd_out" | grep -qi "found.*clone\|duplicat" && \
25
+ echo "$_jscpd_out" | grep -q "$_file_base"; then
26
+ DUPLICATION_WARNING="Code duplication detected involving $FILE_PATH — deduplicate or run '$TYPECHECK_CMD' before commit"
27
+ fi
28
+ fi
29
+ fi
30
+
@@ -0,0 +1,36 @@
1
+ # --- Docs (soft advisory, never blocks) ---
2
+ # Exported API should carry a concise JSDoc. Advisory only — kept out of
3
+ # MESSAGES so it never sets the failing gate. Skips tests, barrels, declarations.
4
+ DOC_ADVISORY=""
5
+ _ts_base="$(basename "$FILE_PATH")"
6
+ if [[ ! "$FILE_PATH" =~ \.(test|spec)\.(ts|tsx)$ && ! "$FILE_PATH" =~ \.d\.ts$ \
7
+ && "$_ts_base" != "index.ts" && "$_ts_base" != "index.tsx" ]]; then
8
+ _undoc=$(awk '
9
+ /^[[:space:]]*$/ { next } # blanks do not reset the doc context
10
+ /^[[:space:]]*\/\// { next } # line comments / pragmas sit between doc and export
11
+ {
12
+ if ($0 ~ /^export (async )?function / || $0 ~ /^export (abstract )?class / \
13
+ || $0 ~ /^export default / || $0 ~ /^export (const|interface|type|enum) [A-Z]/ \
14
+ || $0 ~ /^export const [a-z_][A-Za-z0-9_]* = (async )?(\(|function)/) {
15
+ if (prev !~ /\*\/[[:space:]]*$/ && prev !~ /^[[:space:]]*\/\*\*/) printf "%d: %s\n", NR, $0
16
+ }
17
+ prev=$0
18
+ }
19
+ ' "$FILE_PATH" 2>/dev/null | head -3)
20
+ if [[ -n "$_undoc" ]]; then
21
+ DOC_ADVISORY="Exported API missing a JSDoc in $FILE_PATH — add a concise /** */ doc:"$'\n'"${_undoc}"
22
+ fi
23
+ _verbose_doc=$(awk '
24
+ !inb && /\/\*\*/ { inb=1; start=NR; cnt=0 } # !inb: a /** in prose must not reset the count mid-block
25
+ inb { cnt++ }
26
+ inb && /\*\// { if (cnt>12) printf "%d: JSDoc block is %d lines — trim to the essentials\n", start, cnt; inb=0 }
27
+ ' "$FILE_PATH" 2>/dev/null | head -2)
28
+ if [[ -n "$_verbose_doc" ]]; then
29
+ # Separator appended on its own line: ANSI-C quoting is NOT honored inside a
30
+ # ${var:+word} expansion, so the old one-liner would have emitted a literal
31
+ # $'\n' there.
32
+ [[ -n "$DOC_ADVISORY" ]] && DOC_ADVISORY="${DOC_ADVISORY}"$'\n'
33
+ DOC_ADVISORY="${DOC_ADVISORY}Verbose JSDoc in $FILE_PATH — docs must be present but concise:"$'\n'"${_verbose_doc}"
34
+ fi
35
+ fi
36
+
@@ -0,0 +1,15 @@
1
+ # Handler-presence advisory (non-blocking): the file awaits but has no try/catch
2
+ # and no .catch anywhere — rejections may be unhandled. Advisory, not a block,
3
+ # because the handler can legitimately live in every caller.
4
+ ERR_ADVISORY=""
5
+ # Comment-only lines are stripped first: a commented-out `await` must not raise
6
+ # the advisory, and a `try`/`.catch` that only appears in a comment must not
7
+ # suppress it. (`*`-prefixed lines are JSDoc/block-comment continuations; a
8
+ # CODE line starting with `*` — a multiplication continuation holding the only
9
+ # await — is stripped too, accepted: advisory-only, fails open.)
10
+ _ts_noncomment=$(grep -vE '^[[:space:]]*(//|/\*|\*)' "$FILE_PATH" 2>/dev/null)
11
+ if printf '%s\n' "$_ts_noncomment" | grep -qE '\bawait[[:space:]]' \
12
+ && ! printf '%s\n' "$_ts_noncomment" | grep -qE '\btry\b|\.catch\('; then
13
+ ERR_ADVISORY="Async code in $FILE_PATH uses await with no try/catch or .catch in the file — ensure rejections are handled here or by every caller."
14
+ fi
15
+
@@ -0,0 +1,46 @@
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" "ts-quality-hook" \
10
+ "Post-edit 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" "ts-quality-hook"
24
+
25
+ # Advisories (non-blocking — no gate write). Duplication + docs combined into
26
+ # a single additionalContext so the hook emits exactly one JSON object.
27
+ # Joined with REAL newlines. `${ADVISORY:+$ADVISORY\n}` appended a literal
28
+ # backslash-n (and ANSI-C quoting is not honored inside a :+ expansion either),
29
+ # so stacked advisories reached the agent as one run-on line.
30
+ ADVISORY="$DUPLICATION_WARNING"
31
+ for _adv in "$DOC_ADVISORY" "$ERR_ADVISORY"; do
32
+ [[ -z "$_adv" ]] && continue
33
+ [[ -n "$ADVISORY" ]] && ADVISORY="${ADVISORY}"$'\n'
34
+ ADVISORY="${ADVISORY}${_adv}"
35
+ done
36
+ if [[ -n "$ADVISORY" ]]; then
37
+ jq -n --arg ctx "$ADVISORY" '{
38
+ "hookSpecificOutput": {
39
+ "hookEventName": "PostToolUse",
40
+ "additionalContext": $ctx
41
+ }
42
+ }'
43
+ fi
44
+ fi
45
+
46
+ 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
+ }