codesift-mcp 0.3.0 → 0.5.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 (543) hide show
  1. package/README.md +215 -23
  2. package/dist/cache/hono-cache.d.ts +50 -0
  3. package/dist/cache/hono-cache.d.ts.map +1 -0
  4. package/dist/cache/hono-cache.js +132 -0
  5. package/dist/cache/hono-cache.js.map +1 -0
  6. package/dist/cli/help.d.ts.map +1 -1
  7. package/dist/cli/help.js +8 -6
  8. package/dist/cli/help.js.map +1 -1
  9. package/dist/cli/platform.d.ts.map +1 -1
  10. package/dist/cli/platform.js +12 -14
  11. package/dist/cli/platform.js.map +1 -1
  12. package/dist/cli/setup.d.ts +1 -1
  13. package/dist/cli/setup.d.ts.map +1 -1
  14. package/dist/cli/setup.js +27 -3
  15. package/dist/cli/setup.js.map +1 -1
  16. package/dist/formatters-shortening.d.ts +13 -0
  17. package/dist/formatters-shortening.d.ts.map +1 -1
  18. package/dist/formatters-shortening.js +131 -0
  19. package/dist/formatters-shortening.js.map +1 -1
  20. package/dist/formatters.d.ts +38 -0
  21. package/dist/formatters.d.ts.map +1 -1
  22. package/dist/formatters.js +521 -0
  23. package/dist/formatters.js.map +1 -1
  24. package/dist/instructions.d.ts +1 -1
  25. package/dist/instructions.d.ts.map +1 -1
  26. package/dist/instructions.js +39 -38
  27. package/dist/instructions.js.map +1 -1
  28. package/dist/lsp/lsp-servers.d.ts.map +1 -1
  29. package/dist/lsp/lsp-servers.js +5 -0
  30. package/dist/lsp/lsp-servers.js.map +1 -1
  31. package/dist/lsp/lsp-tools.d.ts.map +1 -1
  32. package/dist/lsp/lsp-tools.js +1 -0
  33. package/dist/lsp/lsp-tools.js.map +1 -1
  34. package/dist/parser/astro-template.d.ts +47 -0
  35. package/dist/parser/astro-template.d.ts.map +1 -0
  36. package/dist/parser/astro-template.js +171 -0
  37. package/dist/parser/astro-template.js.map +1 -0
  38. package/dist/parser/extractors/_shared.d.ts +4 -0
  39. package/dist/parser/extractors/_shared.d.ts.map +1 -1
  40. package/dist/parser/extractors/_shared.js +8 -0
  41. package/dist/parser/extractors/_shared.js.map +1 -1
  42. package/dist/parser/extractors/astro.d.ts +4 -5
  43. package/dist/parser/extractors/astro.d.ts.map +1 -1
  44. package/dist/parser/extractors/astro.js +102 -26
  45. package/dist/parser/extractors/astro.js.map +1 -1
  46. package/dist/parser/extractors/gradle-kts.d.ts +4 -0
  47. package/dist/parser/extractors/gradle-kts.d.ts.map +1 -0
  48. package/dist/parser/extractors/gradle-kts.js +246 -0
  49. package/dist/parser/extractors/gradle-kts.js.map +1 -0
  50. package/dist/parser/extractors/hono-inline-analyzer.d.ts +34 -0
  51. package/dist/parser/extractors/hono-inline-analyzer.d.ts.map +1 -0
  52. package/dist/parser/extractors/hono-inline-analyzer.js +465 -0
  53. package/dist/parser/extractors/hono-inline-analyzer.js.map +1 -0
  54. package/dist/parser/extractors/hono-model.d.ts +196 -0
  55. package/dist/parser/extractors/hono-model.d.ts.map +1 -0
  56. package/dist/parser/extractors/hono-model.js +10 -0
  57. package/dist/parser/extractors/hono-model.js.map +1 -0
  58. package/dist/parser/extractors/hono.d.ts +118 -0
  59. package/dist/parser/extractors/hono.d.ts.map +1 -0
  60. package/dist/parser/extractors/hono.js +1527 -0
  61. package/dist/parser/extractors/hono.js.map +1 -0
  62. package/dist/parser/extractors/kotlin.d.ts +4 -0
  63. package/dist/parser/extractors/kotlin.d.ts.map +1 -0
  64. package/dist/parser/extractors/kotlin.js +521 -0
  65. package/dist/parser/extractors/kotlin.js.map +1 -0
  66. package/dist/parser/extractors/php.d.ts +22 -0
  67. package/dist/parser/extractors/php.d.ts.map +1 -0
  68. package/dist/parser/extractors/php.js +334 -0
  69. package/dist/parser/extractors/php.js.map +1 -0
  70. package/dist/parser/extractors/python.d.ts.map +1 -1
  71. package/dist/parser/extractors/python.js +234 -11
  72. package/dist/parser/extractors/python.js.map +1 -1
  73. package/dist/parser/extractors/sql.d.ts +33 -0
  74. package/dist/parser/extractors/sql.d.ts.map +1 -0
  75. package/dist/parser/extractors/sql.js +506 -0
  76. package/dist/parser/extractors/sql.js.map +1 -0
  77. package/dist/parser/extractors/typescript.d.ts.map +1 -1
  78. package/dist/parser/extractors/typescript.js +209 -3
  79. package/dist/parser/extractors/typescript.js.map +1 -1
  80. package/dist/parser/languages/tree-sitter-javascript.wasm +0 -0
  81. package/dist/parser/languages/tree-sitter-kotlin.wasm +0 -0
  82. package/dist/parser/languages/tree-sitter-php.wasm +0 -0
  83. package/dist/parser/languages/tree-sitter-php_only.wasm +0 -0
  84. package/dist/parser/languages/tree-sitter-python.wasm +0 -0
  85. package/dist/parser/parse-cache.d.ts +39 -0
  86. package/dist/parser/parse-cache.d.ts.map +1 -0
  87. package/dist/parser/parse-cache.js +87 -0
  88. package/dist/parser/parse-cache.js.map +1 -0
  89. package/dist/parser/parser-manager.d.ts +32 -0
  90. package/dist/parser/parser-manager.d.ts.map +1 -1
  91. package/dist/parser/parser-manager.js +93 -3
  92. package/dist/parser/parser-manager.js.map +1 -1
  93. package/dist/parser/symbol-extractor.d.ts.map +1 -1
  94. package/dist/parser/symbol-extractor.js +16 -0
  95. package/dist/parser/symbol-extractor.js.map +1 -1
  96. package/dist/register-tools.d.ts +38 -2
  97. package/dist/register-tools.d.ts.map +1 -1
  98. package/dist/register-tools.js +2444 -195
  99. package/dist/register-tools.js.map +1 -1
  100. package/dist/search/reranker.js +1 -1
  101. package/dist/search/reranker.js.map +1 -1
  102. package/dist/search/tool-ranker.d.ts +90 -0
  103. package/dist/search/tool-ranker.d.ts.map +1 -0
  104. package/dist/search/tool-ranker.js +420 -0
  105. package/dist/search/tool-ranker.js.map +1 -0
  106. package/dist/server-helpers.d.ts.map +1 -1
  107. package/dist/server-helpers.js +11 -0
  108. package/dist/server-helpers.js.map +1 -1
  109. package/dist/server.js +47 -14
  110. package/dist/server.js.map +1 -1
  111. package/dist/storage/index-store.d.ts +15 -1
  112. package/dist/storage/index-store.d.ts.map +1 -1
  113. package/dist/storage/index-store.js +27 -1
  114. package/dist/storage/index-store.js.map +1 -1
  115. package/dist/storage/session-state.d.ts +1 -1
  116. package/dist/storage/session-state.d.ts.map +1 -1
  117. package/dist/storage/session-state.js +6 -4
  118. package/dist/storage/session-state.js.map +1 -1
  119. package/dist/storage/usage-tracker.d.ts.map +1 -1
  120. package/dist/storage/usage-tracker.js +4 -1
  121. package/dist/storage/usage-tracker.js.map +1 -1
  122. package/dist/tools/agent-config-tools.d.ts +24 -0
  123. package/dist/tools/agent-config-tools.d.ts.map +1 -0
  124. package/dist/tools/agent-config-tools.js +119 -0
  125. package/dist/tools/agent-config-tools.js.map +1 -0
  126. package/dist/tools/architecture-tools.d.ts +23 -0
  127. package/dist/tools/architecture-tools.d.ts.map +1 -0
  128. package/dist/tools/architecture-tools.js +140 -0
  129. package/dist/tools/architecture-tools.js.map +1 -0
  130. package/dist/tools/astro-actions.d.ts +54 -0
  131. package/dist/tools/astro-actions.d.ts.map +1 -0
  132. package/dist/tools/astro-actions.js +561 -0
  133. package/dist/tools/astro-actions.js.map +1 -0
  134. package/dist/tools/astro-audit.d.ts +87 -0
  135. package/dist/tools/astro-audit.d.ts.map +1 -0
  136. package/dist/tools/astro-audit.js +345 -0
  137. package/dist/tools/astro-audit.js.map +1 -0
  138. package/dist/tools/astro-config.d.ts +33 -0
  139. package/dist/tools/astro-config.d.ts.map +1 -0
  140. package/dist/tools/astro-config.js +260 -0
  141. package/dist/tools/astro-config.js.map +1 -0
  142. package/dist/tools/astro-content-collections.d.ts +44 -0
  143. package/dist/tools/astro-content-collections.d.ts.map +1 -0
  144. package/dist/tools/astro-content-collections.js +630 -0
  145. package/dist/tools/astro-content-collections.js.map +1 -0
  146. package/dist/tools/astro-islands.d.ts +63 -0
  147. package/dist/tools/astro-islands.d.ts.map +1 -0
  148. package/dist/tools/astro-islands.js +255 -0
  149. package/dist/tools/astro-islands.js.map +1 -0
  150. package/dist/tools/astro-migration.d.ts +31 -0
  151. package/dist/tools/astro-migration.d.ts.map +1 -0
  152. package/dist/tools/astro-migration.js +378 -0
  153. package/dist/tools/astro-migration.js.map +1 -0
  154. package/dist/tools/astro-routes.d.ts +49 -0
  155. package/dist/tools/astro-routes.d.ts.map +1 -0
  156. package/dist/tools/astro-routes.js +119 -0
  157. package/dist/tools/astro-routes.js.map +1 -0
  158. package/dist/tools/async-correctness.d.ts +26 -0
  159. package/dist/tools/async-correctness.d.ts.map +1 -0
  160. package/dist/tools/async-correctness.js +166 -0
  161. package/dist/tools/async-correctness.js.map +1 -0
  162. package/dist/tools/audit-tools.d.ts +38 -0
  163. package/dist/tools/audit-tools.d.ts.map +1 -0
  164. package/dist/tools/audit-tools.js +248 -0
  165. package/dist/tools/audit-tools.js.map +1 -0
  166. package/dist/tools/celery-tools.d.ts +38 -0
  167. package/dist/tools/celery-tools.d.ts.map +1 -0
  168. package/dist/tools/celery-tools.js +154 -0
  169. package/dist/tools/celery-tools.js.map +1 -0
  170. package/dist/tools/clone-tools.js +1 -1
  171. package/dist/tools/clone-tools.js.map +1 -1
  172. package/dist/tools/complexity-tools.d.ts +4 -0
  173. package/dist/tools/complexity-tools.d.ts.map +1 -1
  174. package/dist/tools/complexity-tools.js +78 -4
  175. package/dist/tools/complexity-tools.js.map +1 -1
  176. package/dist/tools/compose-tools.d.ts +60 -0
  177. package/dist/tools/compose-tools.d.ts.map +1 -0
  178. package/dist/tools/compose-tools.js +203 -0
  179. package/dist/tools/compose-tools.js.map +1 -0
  180. package/dist/tools/coupling-tools.d.ts +50 -0
  181. package/dist/tools/coupling-tools.d.ts.map +1 -0
  182. package/dist/tools/coupling-tools.js +262 -0
  183. package/dist/tools/coupling-tools.js.map +1 -0
  184. package/dist/tools/dependency-audit-tools.d.ts +65 -0
  185. package/dist/tools/dependency-audit-tools.d.ts.map +1 -0
  186. package/dist/tools/dependency-audit-tools.js +553 -0
  187. package/dist/tools/dependency-audit-tools.js.map +1 -0
  188. package/dist/tools/django-settings.d.ts +22 -0
  189. package/dist/tools/django-settings.d.ts.map +1 -0
  190. package/dist/tools/django-settings.js +301 -0
  191. package/dist/tools/django-settings.js.map +1 -0
  192. package/dist/tools/django-view-security-tools.d.ts +32 -0
  193. package/dist/tools/django-view-security-tools.d.ts.map +1 -0
  194. package/dist/tools/django-view-security-tools.js +184 -0
  195. package/dist/tools/django-view-security-tools.js.map +1 -0
  196. package/dist/tools/fastapi-depends.d.ts +63 -0
  197. package/dist/tools/fastapi-depends.d.ts.map +1 -0
  198. package/dist/tools/fastapi-depends.js +191 -0
  199. package/dist/tools/fastapi-depends.js.map +1 -0
  200. package/dist/tools/frequency-tools.js +1 -1
  201. package/dist/tools/frequency-tools.js.map +1 -1
  202. package/dist/tools/graph-tools.d.ts +8 -2
  203. package/dist/tools/graph-tools.d.ts.map +1 -1
  204. package/dist/tools/graph-tools.js +44 -3
  205. package/dist/tools/graph-tools.js.map +1 -1
  206. package/dist/tools/hilt-tools.d.ts +55 -0
  207. package/dist/tools/hilt-tools.d.ts.map +1 -0
  208. package/dist/tools/hilt-tools.js +258 -0
  209. package/dist/tools/hilt-tools.js.map +1 -0
  210. package/dist/tools/hono-analyze-app.d.ts +48 -0
  211. package/dist/tools/hono-analyze-app.d.ts.map +1 -0
  212. package/dist/tools/hono-analyze-app.js +94 -0
  213. package/dist/tools/hono-analyze-app.js.map +1 -0
  214. package/dist/tools/hono-api-contract.d.ts +22 -0
  215. package/dist/tools/hono-api-contract.d.ts.map +1 -0
  216. package/dist/tools/hono-api-contract.js +112 -0
  217. package/dist/tools/hono-api-contract.js.map +1 -0
  218. package/dist/tools/hono-conditional-middleware.d.ts +27 -0
  219. package/dist/tools/hono-conditional-middleware.d.ts.map +1 -0
  220. package/dist/tools/hono-conditional-middleware.js +62 -0
  221. package/dist/tools/hono-conditional-middleware.js.map +1 -0
  222. package/dist/tools/hono-context-flow.d.ts +24 -0
  223. package/dist/tools/hono-context-flow.d.ts.map +1 -0
  224. package/dist/tools/hono-context-flow.js +70 -0
  225. package/dist/tools/hono-context-flow.js.map +1 -0
  226. package/dist/tools/hono-dead-routes.d.ts +26 -0
  227. package/dist/tools/hono-dead-routes.d.ts.map +1 -0
  228. package/dist/tools/hono-dead-routes.js +102 -0
  229. package/dist/tools/hono-dead-routes.js.map +1 -0
  230. package/dist/tools/hono-entry-resolver.d.ts +27 -0
  231. package/dist/tools/hono-entry-resolver.d.ts.map +1 -0
  232. package/dist/tools/hono-entry-resolver.js +31 -0
  233. package/dist/tools/hono-entry-resolver.js.map +1 -0
  234. package/dist/tools/hono-env-regression.d.ts +29 -0
  235. package/dist/tools/hono-env-regression.d.ts.map +1 -0
  236. package/dist/tools/hono-env-regression.js +157 -0
  237. package/dist/tools/hono-env-regression.js.map +1 -0
  238. package/dist/tools/hono-inline-analyze.d.ts +31 -0
  239. package/dist/tools/hono-inline-analyze.d.ts.map +1 -0
  240. package/dist/tools/hono-inline-analyze.js +59 -0
  241. package/dist/tools/hono-inline-analyze.js.map +1 -0
  242. package/dist/tools/hono-middleware-chain.d.ts +40 -0
  243. package/dist/tools/hono-middleware-chain.d.ts.map +1 -0
  244. package/dist/tools/hono-middleware-chain.js +121 -0
  245. package/dist/tools/hono-middleware-chain.js.map +1 -0
  246. package/dist/tools/hono-modules.d.ts +22 -0
  247. package/dist/tools/hono-modules.d.ts.map +1 -0
  248. package/dist/tools/hono-modules.js +118 -0
  249. package/dist/tools/hono-modules.js.map +1 -0
  250. package/dist/tools/hono-response-types.d.ts +37 -0
  251. package/dist/tools/hono-response-types.d.ts.map +1 -0
  252. package/dist/tools/hono-response-types.js +76 -0
  253. package/dist/tools/hono-response-types.js.map +1 -0
  254. package/dist/tools/hono-rpc-types.d.ts +21 -0
  255. package/dist/tools/hono-rpc-types.d.ts.map +1 -0
  256. package/dist/tools/hono-rpc-types.js +49 -0
  257. package/dist/tools/hono-rpc-types.js.map +1 -0
  258. package/dist/tools/hono-security.d.ts +31 -0
  259. package/dist/tools/hono-security.d.ts.map +1 -0
  260. package/dist/tools/hono-security.js +269 -0
  261. package/dist/tools/hono-security.js.map +1 -0
  262. package/dist/tools/hono-visualize.d.ts +13 -0
  263. package/dist/tools/hono-visualize.d.ts.map +1 -0
  264. package/dist/tools/hono-visualize.js +64 -0
  265. package/dist/tools/hono-visualize.js.map +1 -0
  266. package/dist/tools/hotspot-tools.d.ts.map +1 -1
  267. package/dist/tools/hotspot-tools.js +9 -7
  268. package/dist/tools/hotspot-tools.js.map +1 -1
  269. package/dist/tools/index-tools.d.ts +17 -0
  270. package/dist/tools/index-tools.d.ts.map +1 -1
  271. package/dist/tools/index-tools.js +210 -10
  272. package/dist/tools/index-tools.js.map +1 -1
  273. package/dist/tools/kotlin-tools.d.ts +142 -0
  274. package/dist/tools/kotlin-tools.d.ts.map +1 -0
  275. package/dist/tools/kotlin-tools.js +572 -0
  276. package/dist/tools/kotlin-tools.js.map +1 -0
  277. package/dist/tools/legacy-hono-conventions.d.ts +14 -0
  278. package/dist/tools/legacy-hono-conventions.d.ts.map +1 -0
  279. package/dist/tools/legacy-hono-conventions.js +152 -0
  280. package/dist/tools/legacy-hono-conventions.js.map +1 -0
  281. package/dist/tools/migration-lint-tools.d.ts +26 -0
  282. package/dist/tools/migration-lint-tools.d.ts.map +1 -0
  283. package/dist/tools/migration-lint-tools.js +247 -0
  284. package/dist/tools/migration-lint-tools.js.map +1 -0
  285. package/dist/tools/model-tools.d.ts +30 -0
  286. package/dist/tools/model-tools.d.ts.map +1 -0
  287. package/dist/tools/model-tools.js +145 -0
  288. package/dist/tools/model-tools.js.map +1 -0
  289. package/dist/tools/nest-ext-tools.d.ts +207 -0
  290. package/dist/tools/nest-ext-tools.d.ts.map +1 -0
  291. package/dist/tools/nest-ext-tools.js +752 -0
  292. package/dist/tools/nest-ext-tools.js.map +1 -0
  293. package/dist/tools/nest-tools.d.ts +198 -0
  294. package/dist/tools/nest-tools.d.ts.map +1 -0
  295. package/dist/tools/nest-tools.js +1142 -0
  296. package/dist/tools/nest-tools.js.map +1 -0
  297. package/dist/tools/nextjs-api-contract-readers.d.ts +14 -0
  298. package/dist/tools/nextjs-api-contract-readers.d.ts.map +1 -0
  299. package/dist/tools/nextjs-api-contract-readers.js +204 -0
  300. package/dist/tools/nextjs-api-contract-readers.js.map +1 -0
  301. package/dist/tools/nextjs-api-contract-tools.d.ts +57 -0
  302. package/dist/tools/nextjs-api-contract-tools.d.ts.map +1 -0
  303. package/dist/tools/nextjs-api-contract-tools.js +144 -0
  304. package/dist/tools/nextjs-api-contract-tools.js.map +1 -0
  305. package/dist/tools/nextjs-boundary-tools.d.ts +39 -0
  306. package/dist/tools/nextjs-boundary-tools.d.ts.map +1 -0
  307. package/dist/tools/nextjs-boundary-tools.js +152 -0
  308. package/dist/tools/nextjs-boundary-tools.js.map +1 -0
  309. package/dist/tools/nextjs-component-readers.d.ts +101 -0
  310. package/dist/tools/nextjs-component-readers.d.ts.map +1 -0
  311. package/dist/tools/nextjs-component-readers.js +287 -0
  312. package/dist/tools/nextjs-component-readers.js.map +1 -0
  313. package/dist/tools/nextjs-component-tools.d.ts +51 -0
  314. package/dist/tools/nextjs-component-tools.d.ts.map +1 -0
  315. package/dist/tools/nextjs-component-tools.js +212 -0
  316. package/dist/tools/nextjs-component-tools.js.map +1 -0
  317. package/dist/tools/nextjs-data-flow-tools.d.ts +42 -0
  318. package/dist/tools/nextjs-data-flow-tools.d.ts.map +1 -0
  319. package/dist/tools/nextjs-data-flow-tools.js +158 -0
  320. package/dist/tools/nextjs-data-flow-tools.js.map +1 -0
  321. package/dist/tools/nextjs-framework-audit-tools.d.ts +60 -0
  322. package/dist/tools/nextjs-framework-audit-tools.d.ts.map +1 -0
  323. package/dist/tools/nextjs-framework-audit-tools.js +394 -0
  324. package/dist/tools/nextjs-framework-audit-tools.js.map +1 -0
  325. package/dist/tools/nextjs-link-tools.d.ts +41 -0
  326. package/dist/tools/nextjs-link-tools.d.ts.map +1 -0
  327. package/dist/tools/nextjs-link-tools.js +157 -0
  328. package/dist/tools/nextjs-link-tools.js.map +1 -0
  329. package/dist/tools/nextjs-metadata-tools.d.ts +74 -0
  330. package/dist/tools/nextjs-metadata-tools.d.ts.map +1 -0
  331. package/dist/tools/nextjs-metadata-tools.js +252 -0
  332. package/dist/tools/nextjs-metadata-tools.js.map +1 -0
  333. package/dist/tools/nextjs-middleware-coverage-tools.d.ts +41 -0
  334. package/dist/tools/nextjs-middleware-coverage-tools.d.ts.map +1 -0
  335. package/dist/tools/nextjs-middleware-coverage-tools.js +88 -0
  336. package/dist/tools/nextjs-middleware-coverage-tools.js.map +1 -0
  337. package/dist/tools/nextjs-route-readers.d.ts +81 -0
  338. package/dist/tools/nextjs-route-readers.d.ts.map +1 -0
  339. package/dist/tools/nextjs-route-readers.js +340 -0
  340. package/dist/tools/nextjs-route-readers.js.map +1 -0
  341. package/dist/tools/nextjs-route-tools.d.ts +36 -0
  342. package/dist/tools/nextjs-route-tools.d.ts.map +1 -0
  343. package/dist/tools/nextjs-route-tools.js +175 -0
  344. package/dist/tools/nextjs-route-tools.js.map +1 -0
  345. package/dist/tools/nextjs-security-readers.d.ts +22 -0
  346. package/dist/tools/nextjs-security-readers.d.ts.map +1 -0
  347. package/dist/tools/nextjs-security-readers.js +318 -0
  348. package/dist/tools/nextjs-security-readers.js.map +1 -0
  349. package/dist/tools/nextjs-security-scoring.d.ts +15 -0
  350. package/dist/tools/nextjs-security-scoring.d.ts.map +1 -0
  351. package/dist/tools/nextjs-security-scoring.js +65 -0
  352. package/dist/tools/nextjs-security-scoring.js.map +1 -0
  353. package/dist/tools/nextjs-security-tools.d.ts +75 -0
  354. package/dist/tools/nextjs-security-tools.d.ts.map +1 -0
  355. package/dist/tools/nextjs-security-tools.js +153 -0
  356. package/dist/tools/nextjs-security-tools.js.map +1 -0
  357. package/dist/tools/nextjs-tools.d.ts +15 -0
  358. package/dist/tools/nextjs-tools.d.ts.map +1 -0
  359. package/dist/tools/nextjs-tools.js +15 -0
  360. package/dist/tools/nextjs-tools.js.map +1 -0
  361. package/dist/tools/outline-tools.d.ts.map +1 -1
  362. package/dist/tools/outline-tools.js +20 -0
  363. package/dist/tools/outline-tools.js.map +1 -1
  364. package/dist/tools/pattern-tools.d.ts +8 -0
  365. package/dist/tools/pattern-tools.d.ts.map +1 -1
  366. package/dist/tools/pattern-tools.js +651 -3
  367. package/dist/tools/pattern-tools.js.map +1 -1
  368. package/dist/tools/perf-tools.d.ts +32 -0
  369. package/dist/tools/perf-tools.d.ts.map +1 -0
  370. package/dist/tools/perf-tools.js +227 -0
  371. package/dist/tools/perf-tools.js.map +1 -0
  372. package/dist/tools/php-tools.d.ts +185 -0
  373. package/dist/tools/php-tools.d.ts.map +1 -0
  374. package/dist/tools/php-tools.js +645 -0
  375. package/dist/tools/php-tools.js.map +1 -0
  376. package/dist/tools/plan-turn-tools.d.ts +89 -0
  377. package/dist/tools/plan-turn-tools.d.ts.map +1 -0
  378. package/dist/tools/plan-turn-tools.js +508 -0
  379. package/dist/tools/plan-turn-tools.js.map +1 -0
  380. package/dist/tools/prisma-schema-tools.d.ts +44 -0
  381. package/dist/tools/prisma-schema-tools.d.ts.map +1 -0
  382. package/dist/tools/prisma-schema-tools.js +358 -0
  383. package/dist/tools/prisma-schema-tools.js.map +1 -0
  384. package/dist/tools/project-tools.d.ts +116 -7
  385. package/dist/tools/project-tools.d.ts.map +1 -1
  386. package/dist/tools/project-tools.js +595 -218
  387. package/dist/tools/project-tools.js.map +1 -1
  388. package/dist/tools/pydantic-models.d.ts +46 -0
  389. package/dist/tools/pydantic-models.d.ts.map +1 -0
  390. package/dist/tools/pydantic-models.js +249 -0
  391. package/dist/tools/pydantic-models.js.map +1 -0
  392. package/dist/tools/pyproject-tools.d.ts +23 -0
  393. package/dist/tools/pyproject-tools.d.ts.map +1 -0
  394. package/dist/tools/pyproject-tools.js +133 -0
  395. package/dist/tools/pyproject-tools.js.map +1 -0
  396. package/dist/tools/pytest-tools.d.ts +20 -0
  397. package/dist/tools/pytest-tools.d.ts.map +1 -0
  398. package/dist/tools/pytest-tools.js +106 -0
  399. package/dist/tools/pytest-tools.js.map +1 -0
  400. package/dist/tools/python-audit.d.ts +40 -0
  401. package/dist/tools/python-audit.d.ts.map +1 -0
  402. package/dist/tools/python-audit.js +244 -0
  403. package/dist/tools/python-audit.js.map +1 -0
  404. package/dist/tools/python-callers.d.ts +28 -0
  405. package/dist/tools/python-callers.d.ts.map +1 -0
  406. package/dist/tools/python-callers.js +110 -0
  407. package/dist/tools/python-callers.js.map +1 -0
  408. package/dist/tools/python-circular-imports.d.ts +19 -0
  409. package/dist/tools/python-circular-imports.d.ts.map +1 -0
  410. package/dist/tools/python-circular-imports.js +126 -0
  411. package/dist/tools/python-circular-imports.js.map +1 -0
  412. package/dist/tools/python-constants-tools.d.ts +44 -0
  413. package/dist/tools/python-constants-tools.d.ts.map +1 -0
  414. package/dist/tools/python-constants-tools.js +525 -0
  415. package/dist/tools/python-constants-tools.js.map +1 -0
  416. package/dist/tools/python-deps-analyzer.d.ts +46 -0
  417. package/dist/tools/python-deps-analyzer.d.ts.map +1 -0
  418. package/dist/tools/python-deps-analyzer.js +227 -0
  419. package/dist/tools/python-deps-analyzer.js.map +1 -0
  420. package/dist/tools/query-tools.d.ts +23 -0
  421. package/dist/tools/query-tools.d.ts.map +1 -0
  422. package/dist/tools/query-tools.js +256 -0
  423. package/dist/tools/query-tools.js.map +1 -0
  424. package/dist/tools/react-tools.d.ts +263 -0
  425. package/dist/tools/react-tools.d.ts.map +1 -0
  426. package/dist/tools/react-tools.js +839 -0
  427. package/dist/tools/react-tools.js.map +1 -0
  428. package/dist/tools/report-tools.js +47 -0
  429. package/dist/tools/report-tools.js.map +1 -1
  430. package/dist/tools/review-diff-tools.d.ts +5 -4
  431. package/dist/tools/review-diff-tools.d.ts.map +1 -1
  432. package/dist/tools/review-diff-tools.js +157 -66
  433. package/dist/tools/review-diff-tools.js.map +1 -1
  434. package/dist/tools/room-tools.d.ts +36 -0
  435. package/dist/tools/room-tools.d.ts.map +1 -0
  436. package/dist/tools/room-tools.js +147 -0
  437. package/dist/tools/room-tools.js.map +1 -0
  438. package/dist/tools/route-tools.d.ts +27 -1
  439. package/dist/tools/route-tools.d.ts.map +1 -1
  440. package/dist/tools/route-tools.js +744 -18
  441. package/dist/tools/route-tools.js.map +1 -1
  442. package/dist/tools/ruff-tools.d.ts +32 -0
  443. package/dist/tools/ruff-tools.d.ts.map +1 -0
  444. package/dist/tools/ruff-tools.js +114 -0
  445. package/dist/tools/ruff-tools.js.map +1 -0
  446. package/dist/tools/search-ranker.d.ts.map +1 -1
  447. package/dist/tools/search-ranker.js +7 -0
  448. package/dist/tools/search-ranker.js.map +1 -1
  449. package/dist/tools/search-tools.d.ts +3 -2
  450. package/dist/tools/search-tools.d.ts.map +1 -1
  451. package/dist/tools/search-tools.js +16 -3
  452. package/dist/tools/search-tools.js.map +1 -1
  453. package/dist/tools/serialization-tools.d.ts +24 -0
  454. package/dist/tools/serialization-tools.d.ts.map +1 -0
  455. package/dist/tools/serialization-tools.js +156 -0
  456. package/dist/tools/serialization-tools.js.map +1 -0
  457. package/dist/tools/sql-tools.d.ts +274 -0
  458. package/dist/tools/sql-tools.d.ts.map +1 -0
  459. package/dist/tools/sql-tools.js +1160 -0
  460. package/dist/tools/sql-tools.js.map +1 -0
  461. package/dist/tools/status-tools.d.ts +10 -0
  462. package/dist/tools/status-tools.d.ts.map +1 -0
  463. package/dist/tools/status-tools.js +32 -0
  464. package/dist/tools/status-tools.js.map +1 -0
  465. package/dist/tools/symbol-tools.d.ts +19 -0
  466. package/dist/tools/symbol-tools.d.ts.map +1 -1
  467. package/dist/tools/symbol-tools.js +75 -4
  468. package/dist/tools/symbol-tools.js.map +1 -1
  469. package/dist/tools/taint-tools.d.ts +43 -0
  470. package/dist/tools/taint-tools.d.ts.map +1 -0
  471. package/dist/tools/taint-tools.js +922 -0
  472. package/dist/tools/taint-tools.js.map +1 -0
  473. package/dist/tools/test-impact-tools.d.ts +29 -0
  474. package/dist/tools/test-impact-tools.d.ts.map +1 -0
  475. package/dist/tools/test-impact-tools.js +156 -0
  476. package/dist/tools/test-impact-tools.js.map +1 -0
  477. package/dist/tools/typecheck-tools.d.ts +39 -0
  478. package/dist/tools/typecheck-tools.d.ts.map +1 -0
  479. package/dist/tools/typecheck-tools.js +191 -0
  480. package/dist/tools/typecheck-tools.js.map +1 -0
  481. package/dist/tools/wiring-tools.d.ts +19 -0
  482. package/dist/tools/wiring-tools.d.ts.map +1 -0
  483. package/dist/tools/wiring-tools.js +147 -0
  484. package/dist/tools/wiring-tools.js.map +1 -0
  485. package/dist/types.d.ts +9 -1
  486. package/dist/types.d.ts.map +1 -1
  487. package/dist/utils/framework-detect.d.ts +18 -2
  488. package/dist/utils/framework-detect.d.ts.map +1 -1
  489. package/dist/utils/framework-detect.js +150 -3
  490. package/dist/utils/framework-detect.js.map +1 -1
  491. package/dist/utils/import-graph.d.ts +42 -0
  492. package/dist/utils/import-graph.d.ts.map +1 -1
  493. package/dist/utils/import-graph.js +248 -9
  494. package/dist/utils/import-graph.js.map +1 -1
  495. package/dist/utils/language-detect.d.ts +21 -0
  496. package/dist/utils/language-detect.d.ts.map +1 -0
  497. package/dist/utils/language-detect.js +183 -0
  498. package/dist/utils/language-detect.js.map +1 -0
  499. package/dist/utils/nextjs-ast-readers.d.ts +44 -0
  500. package/dist/utils/nextjs-ast-readers.d.ts.map +1 -0
  501. package/dist/utils/nextjs-ast-readers.js +341 -0
  502. package/dist/utils/nextjs-ast-readers.js.map +1 -0
  503. package/dist/utils/nextjs-audit-cache.d.ts +51 -0
  504. package/dist/utils/nextjs-audit-cache.d.ts.map +1 -0
  505. package/dist/utils/nextjs-audit-cache.js +116 -0
  506. package/dist/utils/nextjs-audit-cache.js.map +1 -0
  507. package/dist/utils/nextjs-metadata-readers.d.ts +65 -0
  508. package/dist/utils/nextjs-metadata-readers.d.ts.map +1 -0
  509. package/dist/utils/nextjs-metadata-readers.js +447 -0
  510. package/dist/utils/nextjs-metadata-readers.js.map +1 -0
  511. package/dist/utils/nextjs.d.ts +42 -0
  512. package/dist/utils/nextjs.d.ts.map +1 -0
  513. package/dist/utils/nextjs.js +284 -0
  514. package/dist/utils/nextjs.js.map +1 -0
  515. package/dist/utils/python-import-resolver.d.ts +42 -0
  516. package/dist/utils/python-import-resolver.d.ts.map +1 -0
  517. package/dist/utils/python-import-resolver.js +101 -0
  518. package/dist/utils/python-import-resolver.js.map +1 -0
  519. package/dist/utils/python-imports.d.ts +28 -0
  520. package/dist/utils/python-imports.d.ts.map +1 -0
  521. package/dist/utils/python-imports.js +117 -0
  522. package/dist/utils/python-imports.js.map +1 -0
  523. package/dist/utils/react-alias.d.ts +15 -0
  524. package/dist/utils/react-alias.d.ts.map +1 -0
  525. package/dist/utils/react-alias.js +31 -0
  526. package/dist/utils/react-alias.js.map +1 -0
  527. package/dist/utils/test-file.d.ts.map +1 -1
  528. package/dist/utils/test-file.js +7 -0
  529. package/dist/utils/test-file.js.map +1 -1
  530. package/dist/utils/walk.d.ts +22 -0
  531. package/dist/utils/walk.d.ts.map +1 -1
  532. package/dist/utils/walk.js +70 -2
  533. package/dist/utils/walk.js.map +1 -1
  534. package/package.json +4 -3
  535. package/rules/codesift.md +71 -5
  536. package/rules/codesift.mdc +71 -5
  537. package/rules/codex.md +71 -5
  538. package/rules/gemini.md +71 -5
  539. package/src/parser/languages/tree-sitter-javascript.wasm +0 -0
  540. package/src/parser/languages/tree-sitter-kotlin.wasm +0 -0
  541. package/src/parser/languages/tree-sitter-php.wasm +0 -0
  542. package/src/parser/languages/tree-sitter-php_only.wasm +0 -0
  543. package/src/parser/languages/tree-sitter-python.wasm +0 -0
@@ -2,11 +2,14 @@ import { z } from "zod";
2
2
  /** Boolean that also accepts "true"/"false" strings (LLMs often send strings instead of booleans) */
3
3
  const zBool = () => z.union([z.boolean(), z.string().transform((s) => s === "true")]).optional();
4
4
  import { wrapTool, registerShortener } from "./server-helpers.js";
5
- import { indexFolder, indexFile, indexRepo, listAllRepos, invalidateCache } from "./tools/index-tools.js";
5
+ import { detectProjectLanguagesSync } from "./utils/language-detect.js";
6
+ import { indexFolder, indexFile, indexRepo, listAllRepos, invalidateCache, getCodeIndex } from "./tools/index-tools.js";
7
+ import { STUB_LANGUAGES } from "./parser/parser-manager.js";
6
8
  import { searchSymbols, searchText, semanticSearch } from "./tools/search-tools.js";
7
9
  import { getFileTree, getFileOutline, getRepoOutline, suggestQueries } from "./tools/outline-tools.js";
8
10
  import { getSymbol, getSymbols, findAndShow, findReferences, findReferencesBatch, findDeadCode, getContextBundle, formatRefsCompact, formatSymbolCompact, formatSymbolsCompact, formatBundleCompact } from "./tools/symbol-tools.js";
9
11
  import { traceCallChain } from "./tools/graph-tools.js";
12
+ import { traceComponentTree, analyzeHooks, analyzeRenders, buildContextGraph, auditCompilerReadiness, reactQuickstart } from "./tools/react-tools.js";
10
13
  import { impactAnalysis } from "./tools/impact-tools.js";
11
14
  import { traceRoute } from "./tools/route-tools.js";
12
15
  import { detectCommunities } from "./tools/community-tools.js";
@@ -24,14 +27,60 @@ import { getUsageStats, formatUsageReport } from "./storage/usage-stats.js";
24
27
  import { goToDefinition, getTypeInfo, renameSymbol, getCallHierarchy } from "./lsp/lsp-tools.js";
25
28
  import { indexConversations, searchConversations, searchAllConversations, findConversationsForSymbol } from "./tools/conversation-tools.js";
26
29
  import { scanSecrets } from "./tools/secret-tools.js";
30
+ import { resolvePhpNamespace, tracePhpEvent, findPhpViews, resolvePhpService, phpSecurityScan, phpProjectAudit, } from "./tools/php-tools.js";
27
31
  import { consolidateMemories, readMemory } from "./tools/memory-tools.js";
28
32
  import { createAnalysisPlan, writeScratchpad, readScratchpad, listScratchpad, updateStepStatus, getPlan, listPlans } from "./tools/coordinator-tools.js";
29
33
  import { frequencyAnalysis } from "./tools/frequency-tools.js";
34
+ import { findExtensionFunctions, analyzeSealedHierarchy, traceSuspendChain, analyzeKmpDeclarations, traceFlowChain } from "./tools/kotlin-tools.js";
35
+ import { traceHiltGraph } from "./tools/hilt-tools.js";
36
+ import { traceComposeTree, analyzeComposeRecomposition } from "./tools/compose-tools.js";
37
+ import { traceRoomSchema } from "./tools/room-tools.js";
38
+ import { extractKotlinSerializationContract } from "./tools/serialization-tools.js";
39
+ import { astroAnalyzeIslands, astroHydrationAudit } from "./tools/astro-islands.js";
40
+ import { astroRouteMap } from "./tools/astro-routes.js";
41
+ import { astroActionsAudit } from "./tools/astro-actions.js";
42
+ import { astroAudit } from "./tools/astro-audit.js";
43
+ import { nextjsRouteMap } from "./tools/nextjs-route-tools.js";
44
+ import { nextjsMetadataAudit } from "./tools/nextjs-metadata-tools.js";
45
+ import { frameworkAudit } from "./tools/nextjs-framework-audit-tools.js";
46
+ import { astroConfigAnalyze } from "./tools/astro-config.js";
47
+ import { astroContentCollections } from "./tools/astro-content-collections.js";
30
48
  import { analyzeProject, getExtractorVersions } from "./tools/project-tools.js";
49
+ import { getModelGraph } from "./tools/model-tools.js";
50
+ import { getTestFixtures } from "./tools/pytest-tools.js";
51
+ import { findFrameworkWiring } from "./tools/wiring-tools.js";
52
+ import { runRuff } from "./tools/ruff-tools.js";
53
+ import { parsePyproject } from "./tools/pyproject-tools.js";
54
+ import { resolveConstantValue } from "./tools/python-constants-tools.js";
55
+ import { effectiveDjangoViewSecurity } from "./tools/django-view-security-tools.js";
56
+ import { findPythonCallers } from "./tools/python-callers.js";
57
+ import { taintTrace } from "./tools/taint-tools.js";
58
+ import { analyzeDjangoSettings } from "./tools/django-settings.js";
59
+ import { runMypy, runPyright } from "./tools/typecheck-tools.js";
60
+ import { analyzePythonDeps } from "./tools/python-deps-analyzer.js";
61
+ import { pythonAudit } from "./tools/python-audit.js";
62
+ import { traceFastAPIDepends } from "./tools/fastapi-depends.js";
63
+ import { analyzeAsyncCorrectness } from "./tools/async-correctness.js";
64
+ import { getPydanticModels } from "./tools/pydantic-models.js";
31
65
  import { reviewDiff } from "./tools/review-diff-tools.js";
66
+ import { auditScan } from "./tools/audit-tools.js";
67
+ import { indexStatus } from "./tools/status-tools.js";
68
+ import { auditAgentConfig } from "./tools/agent-config-tools.js";
69
+ import { testImpactAnalysis } from "./tools/test-impact-tools.js";
70
+ import { dependencyAudit } from "./tools/dependency-audit-tools.js";
71
+ import { migrationLint } from "./tools/migration-lint-tools.js";
72
+ import { planTurn, formatPlanTurnResult } from "./tools/plan-turn-tools.js";
73
+ import { astroMigrationCheck } from "./tools/astro-migration.js";
74
+ import { analyzePrismaSchema } from "./tools/prisma-schema-tools.js";
75
+ import { findPerfHotspots } from "./tools/perf-tools.js";
76
+ import { fanInFanOut, coChangeAnalysis } from "./tools/coupling-tools.js";
77
+ import { architectureSummary } from "./tools/architecture-tools.js";
78
+ import { nestAudit } from "./tools/nest-tools.js";
79
+ import { explainQuery } from "./tools/query-tools.js";
32
80
  import { formatSnapshot, getContext, getSessionState } from "./storage/session-state.js";
33
81
  import { formatComplexityCompact, formatComplexityCounts, formatClonesCompact, formatClonesCounts, formatHotspotsCompact, formatHotspotsCounts, formatTraceRouteCompact, formatTraceRouteCounts } from "./formatters-shortening.js";
34
- import { formatSearchSymbols, formatFileTree, formatFileOutline, formatSearchPatterns, formatDeadCode, formatComplexity, formatClones, formatHotspots, formatRepoOutline, formatSuggestQueries, formatSecrets, formatConversations, formatRoles, formatAssembleContext, formatCommunities, formatCallTree, formatTraceRoute, formatKnowledgeMap, formatImpactAnalysis, formatDiffOutline, formatChangedSymbols, formatReviewDiff } from "./formatters.js";
82
+ import { formatSearchSymbols, formatFileTree, formatFileOutline, formatSearchPatterns, formatDeadCode, formatComplexity, formatClones, formatHotspots, formatRepoOutline, formatSuggestQueries, formatSecrets, formatConversations, formatRoles, formatAssembleContext, formatCommunities, formatCallTree, formatTraceRoute, formatKnowledgeMap, formatImpactAnalysis, formatDiffOutline, formatChangedSymbols, formatReviewDiff, formatPerfHotspots, formatFanInFanOut, formatCoChange, formatArchitectureSummary, formatNextjsRouteMap, formatNextjsMetadataAudit, formatFrameworkAudit } from "./formatters.js";
83
+ import { formatNextjsRouteMapCompact, formatNextjsRouteMapCounts, formatNextjsMetadataAuditCompact, formatNextjsMetadataAuditCounts, formatFrameworkAuditCompact, formatFrameworkAuditCounts } from "./formatters-shortening.js";
35
84
  const zFiniteNumber = z.number().finite();
36
85
  /** Coerce string→number for numeric params while rejecting NaN/empty strings. */
37
86
  export const zNum = () => z.union([
@@ -43,6 +92,73 @@ export const zNum = () => z.union([
43
92
  .pipe(zFiniteNumber),
44
93
  ]).optional();
45
94
  // ---------------------------------------------------------------------------
95
+ // H11 — warn when symbol tools return empty for repos with text_stub languages
96
+ // ---------------------------------------------------------------------------
97
+ export const SYMBOL_TOOLS = new Set([
98
+ "search_symbols", "get_file_outline", "get_symbol", "get_symbols",
99
+ "find_references", "trace_call_chain", "find_dead_code", "analyze_complexity",
100
+ ]);
101
+ /**
102
+ * Build an H11 hint string from a list of FileEntry-like records. Returns
103
+ * null when no hint is needed. Separated from `checkTextStubHint` so the
104
+ * purely-deterministic core can be unit-tested without spinning up a real
105
+ * index.
106
+ *
107
+ * A file is counted as a "stub" when its language appears in STUB_LANGUAGES
108
+ * (queried dynamically). Languages like `kotlin` that have a real extractor
109
+ * are automatically excluded because they live outside STUB_LANGUAGES, so
110
+ * H11 no longer fires for Kotlin-heavy repos.
111
+ */
112
+ export function buildH11Hint(files) {
113
+ if (files.length === 0)
114
+ return null;
115
+ const stubFiles = files.filter((f) => STUB_LANGUAGES.has(f.language));
116
+ if (stubFiles.length === 0)
117
+ return null;
118
+ const stubPct = Math.round((stubFiles.length / files.length) * 100);
119
+ if (stubPct < 30)
120
+ return null;
121
+ const stubExts = [...new Set(stubFiles.map((f) => "." + f.path.split(".").pop()))].slice(0, 3).join(", ");
122
+ return `⚡H11 No parser for ${stubExts} files (${stubPct}% of repo). Symbol tools return empty.\n` +
123
+ ` → search_text(query) works on ALL files (uses ripgrep, not parser)\n` +
124
+ ` → get_file_tree shows file listing\n` +
125
+ ` → Only symbol-based tools (this one) need a parser to return results.\n`;
126
+ }
127
+ /**
128
+ * Check if a repo has stub-language files as a dominant portion. Returns a
129
+ * hint string to prepend to empty results, or null if no hint needed.
130
+ */
131
+ async function checkTextStubHint(repo, toolName, resultEmpty) {
132
+ if (!resultEmpty || !repo || !SYMBOL_TOOLS.has(toolName))
133
+ return null;
134
+ const index = await getCodeIndex(repo);
135
+ if (!index)
136
+ return null;
137
+ return buildH11Hint(index.files);
138
+ }
139
+ function formatAuditScan(result) {
140
+ const lines = [];
141
+ lines.push(`AUDIT SCAN: ${result.repo}`);
142
+ lines.push(`Gates checked: ${result.summary.gates_checked} | Findings: ${result.summary.total_findings} (${result.summary.critical} critical, ${result.summary.warning} warning)`);
143
+ lines.push("");
144
+ for (const gate of result.gates) {
145
+ const count = gate.findings.length;
146
+ const status = count === 0 ? "✓ PASS" : `✗ ${count} finding${count > 1 ? "s" : ""}`;
147
+ lines.push(`${gate.gate} ${status} — ${gate.description}`);
148
+ lines.push(` tool: ${gate.tool_used}`);
149
+ for (const f of gate.findings.slice(0, 10)) {
150
+ const loc = f.line ? `:${f.line}` : "";
151
+ const sev = f.severity === "critical" ? "🔴" : "🟡";
152
+ lines.push(` ${sev} ${f.file}${loc} — ${f.detail}`);
153
+ }
154
+ if (gate.findings.length > 10) {
155
+ lines.push(` ... +${gate.findings.length - 10} more`);
156
+ }
157
+ lines.push("");
158
+ }
159
+ return lines.join("\n");
160
+ }
161
+ // ---------------------------------------------------------------------------
46
162
  // Registered tool handles — populated by registerTools(), used by describe_tools reveal
47
163
  // ---------------------------------------------------------------------------
48
164
  const toolHandles = new Map();
@@ -50,6 +166,213 @@ const toolHandles = new Map();
50
166
  export function getToolHandle(name) {
51
167
  return toolHandles.get(name);
52
168
  }
169
+ /** Framework-specific tool bundles — auto-enabled when the framework is detected in an indexed repo */
170
+ const FRAMEWORK_TOOL_BUNDLES = {
171
+ nestjs: [
172
+ // All NestJS sub-tools absorbed into nest_audit
173
+ ],
174
+ };
175
+ /** Track which framework bundles have been auto-enabled this session (avoid repeat work) */
176
+ const enabledFrameworkBundles = new Set();
177
+ /**
178
+ * Enable framework-specific tool bundle — called after indexing when framework is detected.
179
+ * Idempotent: safe to call multiple times. Only enables tools that exist and are currently disabled.
180
+ */
181
+ export function enableFrameworkToolBundle(framework) {
182
+ if (enabledFrameworkBundles.has(framework))
183
+ return [];
184
+ const bundle = FRAMEWORK_TOOL_BUNDLES[framework];
185
+ if (!bundle)
186
+ return [];
187
+ const enabled = [];
188
+ for (const name of bundle) {
189
+ const handle = toolHandles.get(name);
190
+ if (handle && typeof handle.enable === "function") {
191
+ handle.enable();
192
+ enabled.push(name);
193
+ }
194
+ }
195
+ if (enabled.length > 0)
196
+ enabledFrameworkBundles.add(framework);
197
+ return enabled;
198
+ }
199
+ // ---------------------------------------------------------------------------
200
+ // Framework-specific tool auto-loading
201
+ // ---------------------------------------------------------------------------
202
+ /**
203
+ * Tool groups that should be auto-enabled when a matching project type is detected at CWD.
204
+ * Keys are detection signals (files at CWD root), values are tool names to enable.
205
+ */
206
+ const FRAMEWORK_TOOL_GROUPS = {
207
+ // PHP / Yii2 / Laravel — detected by composer.json
208
+ "composer.json": [
209
+ "resolve_php_namespace",
210
+ // analyze_activerecord, find_php_n_plus_one, find_php_god_model absorbed into php_project_audit
211
+ "trace_php_event",
212
+ "find_php_views",
213
+ "resolve_php_service",
214
+ "php_security_scan",
215
+ "php_project_audit",
216
+ ],
217
+ // Kotlin / Android / Gradle — detected by build.gradle.kts or settings.gradle.kts
218
+ "build.gradle.kts": [
219
+ "find_extension_functions",
220
+ "analyze_sealed_hierarchy",
221
+ "trace_hilt_graph",
222
+ "trace_suspend_chain",
223
+ "analyze_kmp_declarations",
224
+ "trace_compose_tree",
225
+ "analyze_compose_recomposition",
226
+ "trace_room_schema",
227
+ "extract_kotlin_serialization_contract",
228
+ "trace_flow_chain",
229
+ ],
230
+ "settings.gradle.kts": [
231
+ "find_extension_functions",
232
+ "analyze_sealed_hierarchy",
233
+ "trace_hilt_graph",
234
+ "trace_suspend_chain",
235
+ "analyze_kmp_declarations",
236
+ "trace_compose_tree",
237
+ "analyze_compose_recomposition",
238
+ "trace_room_schema",
239
+ "extract_kotlin_serialization_contract",
240
+ "trace_flow_chain",
241
+ ],
242
+ // Fallback — Android projects with Groovy gradle but Kotlin source
243
+ "build.gradle": [
244
+ "find_extension_functions",
245
+ "analyze_sealed_hierarchy",
246
+ "trace_hilt_graph",
247
+ "trace_suspend_chain",
248
+ "analyze_kmp_declarations",
249
+ "trace_compose_tree",
250
+ "analyze_compose_recomposition",
251
+ "trace_room_schema",
252
+ "extract_kotlin_serialization_contract",
253
+ "trace_flow_chain",
254
+ ],
255
+ };
256
+ /**
257
+ * React-specific tools — auto-enabled when a React project is detected.
258
+ * Detection requires BOTH a package.json with react dependency AND presence
259
+ * of .tsx/.jsx files (prevents false positives on non-UI projects that happen
260
+ * to have react as a transitive dep).
261
+ */
262
+ const REACT_TOOLS = [
263
+ "trace_component_tree",
264
+ "analyze_hooks",
265
+ "analyze_renders",
266
+ "analyze_context_graph",
267
+ "audit_compiler_readiness",
268
+ "react_quickstart",
269
+ ];
270
+ /**
271
+ * Hono-specific tools — auto-enabled when a Hono project is detected.
272
+ * Core tools (trace_middleware_chain, analyze_hono_app) are already in
273
+ * CORE_TOOL_NAMES. This list covers the 5 hidden tools that agents need
274
+ * to discover via describe_tools/discover_tools otherwise.
275
+ *
276
+ * Detection: package.json with "hono" OR "@hono/zod-openapi" dep.
277
+ * Content-based (not filename), so lives outside FRAMEWORK_TOOL_GROUPS.
278
+ */
279
+ const HONO_TOOLS = [
280
+ "trace_context_flow",
281
+ "extract_api_contract",
282
+ "trace_rpc_types",
283
+ "audit_hono_security",
284
+ "visualize_hono_routes",
285
+ // Phase 2 additions — closes blog-API demo gaps + GitHub issues #3587/#4121/#4270
286
+ "analyze_inline_handler",
287
+ "extract_response_types",
288
+ "detect_hono_modules",
289
+ "find_dead_hono_routes",
290
+ ];
291
+ /**
292
+ * Detect project type at CWD and return list of tools that should be auto-enabled.
293
+ * Returns empty array if no framework-specific tools apply.
294
+ * Exported for unit testing.
295
+ */
296
+ export async function detectAutoLoadTools(cwd) {
297
+ const { existsSync, readFileSync, readdirSync } = await import("node:fs");
298
+ const { join } = await import("node:path");
299
+ const toEnable = [];
300
+ for (const [signalFile, tools] of Object.entries(FRAMEWORK_TOOL_GROUPS)) {
301
+ if (existsSync(join(cwd, signalFile))) {
302
+ toEnable.push(...tools);
303
+ }
304
+ }
305
+ // React + Hono detection: both need to read package.json for dep signals.
306
+ const pkgPath = join(cwd, "package.json");
307
+ if (existsSync(pkgPath)) {
308
+ try {
309
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
310
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
311
+ // React: dep + .tsx/.jsx files
312
+ const hasReact = !!(allDeps["react"] || allDeps["next"] || allDeps["@remix-run/react"]);
313
+ if (hasReact && hasJsxFilesShallow(cwd, readdirSync)) {
314
+ toEnable.push(...REACT_TOOLS);
315
+ }
316
+ // Hono: any hono package is enough — the framework is only pulled in
317
+ // when used, and all 5 hidden tools degrade gracefully on non-Hono repos
318
+ // so false positives are harmless (return "no Hono detected" errors).
319
+ const hasHono = !!(allDeps["hono"] ||
320
+ allDeps["@hono/zod-openapi"] ||
321
+ allDeps["@hono/node-server"] ||
322
+ allDeps["hono-openapi"] ||
323
+ allDeps["chanfana"]);
324
+ if (hasHono) {
325
+ toEnable.push(...HONO_TOOLS);
326
+ }
327
+ }
328
+ catch { /* malformed package.json */ }
329
+ }
330
+ return toEnable;
331
+ }
332
+ /**
333
+ * Quick recursive scan for .tsx/.jsx files in common source dirs.
334
+ * Limits depth to 3 and stops on first match to stay fast (<10ms on typical repos).
335
+ * Skips node_modules, dist, build, .next, .astro, .git.
336
+ */
337
+ function hasJsxFilesShallow(cwd, readdirSyncFn) {
338
+ const { join } = require("node:path");
339
+ const IGNORE = new Set([
340
+ "node_modules", "dist", "build", ".next", ".astro", ".git",
341
+ "out", "coverage", ".turbo", ".vercel", ".cache",
342
+ ]);
343
+ const ROOTS = ["src", "app", "pages", "components", "."];
344
+ function scan(dir, depth) {
345
+ if (depth > 3)
346
+ return false;
347
+ let entries;
348
+ try {
349
+ entries = readdirSyncFn(dir, { withFileTypes: true });
350
+ }
351
+ catch {
352
+ return false;
353
+ }
354
+ for (const e of entries) {
355
+ if (e.isFile() && /\.(tsx|jsx)$/.test(e.name))
356
+ return true;
357
+ }
358
+ for (const e of entries) {
359
+ if (e.isDirectory() && !IGNORE.has(e.name) && !e.name.startsWith(".")) {
360
+ if (scan(join(dir, e.name), depth + 1))
361
+ return true;
362
+ }
363
+ }
364
+ return false;
365
+ }
366
+ for (const root of ROOTS) {
367
+ const dir = root === "." ? cwd : join(cwd, root);
368
+ try {
369
+ if (scan(dir, 0))
370
+ return true;
371
+ }
372
+ catch { /* skip */ }
373
+ }
374
+ return false;
375
+ }
53
376
  // ---------------------------------------------------------------------------
54
377
  // Output schemas — typed results for structured validation & documentation
55
378
  // ---------------------------------------------------------------------------
@@ -109,23 +432,67 @@ export const OutputSchemas = {
109
432
  /** list_repos */
110
433
  repoList: z.union([z.array(z.string()), z.array(z.object({ name: z.string() }).passthrough())]),
111
434
  };
112
- /** Tools always registered with full schema top 10 by usage (91% of calls) + essentials */
113
- const CORE_TOOL_NAMES = new Set([
114
- "search_text", // #1: 1536 calls, 36%
115
- "codebase_retrieval", // #2: 510 calls, 12%
116
- "get_file_outline", // #3: 342 calls, 8%
117
- "search_symbols", // #4: 321 calls, 8%
118
- "list_repos", // #5: 223 calls, 5%
119
- "get_file_tree", // #6: 218 calls, 5%
120
- "index_file", // #7: 163 calls, 4% — lightweight schema
121
- "get_symbol", // #8: 135 calls, 3%
122
- "index_conversations", // #9: 125 calls, 3% — lightweight schema
123
- "search_patterns", // #10: 122 calls, 3%
124
- "index_folder", // essential: repo onboarding
125
- "discover_tools", // meta: discovers deferred tools
126
- "get_session_snapshot", // session: compaction survival (core always visible)
127
- "analyze_project", // project profile: stack, conventions, file classifications
128
- "get_extractor_versions", // cache invalidation for project profile
435
+ /** Tools visible in ListTools core (high usage) + direct-use (agents call without discovery) */
436
+ export const CORE_TOOL_NAMES = new Set([
437
+ // --- Top 10 by usage (91% of calls) ---
438
+ "search_text", // #1: 1841 calls
439
+ "codebase_retrieval", // #2: 574 calls
440
+ "get_file_outline", // #3: 351 calls
441
+ "search_symbols", // #4: 332 calls
442
+ "list_repos", // #5: 292 calls
443
+ "get_file_tree", // #6: 268 calls
444
+ "index_file", // #7: 209 calls
445
+ "get_symbol", // #8: 138 calls
446
+ "search_patterns", // #9: 135 calls
447
+ "index_conversations", // #10: 127 calls
448
+ // --- Direct-use: agents call these without discovery ---
449
+ "assemble_context", // 64 calls, 21 sessions, 100% direct
450
+ "get_symbols", // 69 calls batch symbol reads
451
+ "find_references", // 39 calls symbol usage
452
+ "find_and_show", // 55 calls — symbol + refs
453
+ "search_conversations", // 37 calls, 100% direct
454
+ "get_context_bundle", // 36 calls, 19 sessions, 100% direct
455
+ "analyze_complexity", // 33 calls, 28 sessions
456
+ "detect_communities", // 32 calls, 24 sessions
457
+ "search_all_conversations", // 27 calls, 100% direct
458
+ "analyze_hotspots", // 22 calls, 18 sessions
459
+ "trace_call_chain", // 15 calls, 100% direct
460
+ "suggest_queries", // 13 calls, 13 sessions
461
+ "usage_stats", // 11 calls, 100% direct
462
+ "get_knowledge_map", // 10 calls, 100% direct
463
+ "get_repo_outline", // 9 calls, 100% direct
464
+ "trace_route", // 9 calls, 100% direct
465
+ "get_type_info", // 8 calls, 100% direct
466
+ "impact_analysis", // 4 calls, 100% direct
467
+ "go_to_definition", // 4 calls, 100% direct
468
+ // --- Composite tools ---
469
+ "audit_scan", // one-call audit: CQ8+CQ11+CQ13+CQ14+CQ17
470
+ "nest_audit", // one-call NestJS analysis: modules+DI+guards+routes+lifecycle
471
+ // --- Essential infrastructure ---
472
+ "index_folder", // repo onboarding
473
+ "discover_tools", // meta: discovers remaining hidden tools
474
+ "describe_tools", // meta: full schema for hidden tools
475
+ "plan_turn", // meta: route query to best tools/symbols/files
476
+ "get_session_snapshot", // session: compaction survival
477
+ "analyze_project", // project profile
478
+ "get_extractor_versions", // cache invalidation
479
+ "index_status", // meta: check if repo is indexed
480
+ // --- Astro tools (7 core) ---
481
+ "astro_analyze_islands",
482
+ // astro_hydration_audit: discoverable — use astro_audit for full check or call directly
483
+ "astro_route_map",
484
+ "astro_config_analyze",
485
+ "astro_actions_audit",
486
+ "astro_migration_check",
487
+ "astro_content_collections",
488
+ "astro_audit",
489
+ // --- Hono tools (Task 23) ---
490
+ "trace_middleware_chain", // core: top Hono pain point (Discussion #4255)
491
+ "analyze_hono_app", // core: meta-tool, first call for any Hono project
492
+ // --- Next.js tools ---
493
+ "nextjs_route_map",
494
+ "nextjs_metadata_audit",
495
+ "framework_audit",
129
496
  ]);
130
497
  /** Get all tool definitions (exported for testing) */
131
498
  export function getToolDefinitions() {
@@ -174,8 +541,16 @@ const TOOL_DEFINITIONS = [
174
541
  description: "List indexed repos. Only needed for multi-repo discovery — single-repo tools auto-resolve from CWD. Set compact=false for full metadata.",
175
542
  schema: {
176
543
  compact: zBool().describe("true=names only (default), false=full metadata"),
544
+ name_contains: z.string().optional().describe("Filter repos by name substring (case-insensitive). E.g. 'tgm' matches 'local/tgm-panel'"),
545
+ },
546
+ handler: (args) => {
547
+ const opts = {
548
+ compact: args.compact ?? true,
549
+ };
550
+ if (args.name_contains)
551
+ opts.name_contains = args.name_contains;
552
+ return listAllRepos(opts);
177
553
  },
178
- handler: (args) => listAllRepos({ compact: args.compact ?? true }),
179
554
  },
180
555
  {
181
556
  name: "invalidate_cache",
@@ -203,12 +578,13 @@ const TOOL_DEFINITIONS = [
203
578
  category: "search",
204
579
  searchHint: "search find symbols functions classes types methods by name signature",
205
580
  outputSchema: OutputSchemas.searchResults,
206
- description: "Search symbols by name/signature. detail_level: compact (~15 tok), standard (default), full.",
581
+ description: "Search symbols by name/signature. Supports kind, file, and decorator filters. detail_level: compact (~15 tok), standard (default), full.",
207
582
  schema: {
208
583
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
209
584
  query: z.string().describe("Search query string"),
210
585
  kind: z.string().optional().describe("Filter by symbol kind (function, class, etc.)"),
211
586
  file_pattern: z.string().optional().describe("Glob pattern to filter files"),
587
+ decorator: z.string().optional().describe("Filter by decorator metadata, e.g. login_required, @dataclass, router.get"),
212
588
  include_source: zBool().describe("Include full source code of each symbol"),
213
589
  top_k: zNum().describe("Maximum number of results to return (default 50)"),
214
590
  source_chars: zNum().describe("Truncate each symbol's source to N characters (reduces output size)"),
@@ -220,6 +596,7 @@ const TOOL_DEFINITIONS = [
220
596
  const results = await searchSymbols(args.repo, args.query, {
221
597
  kind: args.kind,
222
598
  file_pattern: args.file_pattern,
599
+ decorator: args.decorator,
223
600
  include_source: args.include_source,
224
601
  top_k: args.top_k,
225
602
  source_chars: args.source_chars,
@@ -227,18 +604,20 @@ const TOOL_DEFINITIONS = [
227
604
  token_budget: args.token_budget,
228
605
  rerank: args.rerank,
229
606
  });
230
- return formatSearchSymbols(results);
607
+ const output = formatSearchSymbols(results);
608
+ const hint = await checkTextStubHint(args.repo, "search_symbols", results.length === 0);
609
+ return hint ? hint + output : output;
231
610
  },
232
611
  },
233
612
  {
234
613
  name: "ast_query",
235
614
  category: "search",
236
- searchHint: "AST tree-sitter query structural pattern matching code shape",
237
- description: "Search AST patterns via tree-sitter S-expressions. Finds code by structural shape.",
615
+ searchHint: "AST tree-sitter query structural pattern matching code shape jsx react",
616
+ description: "Search AST patterns via tree-sitter S-expressions. Finds code by structural shape. React examples (language='tsx'): `(jsx_element open_tag: (jsx_opening_element name: (identifier) @tag))` finds all JSX component usage; `(call_expression function: (identifier) @fn (#match? @fn \"^use[A-Z]\"))` finds all hook calls.",
238
617
  schema: {
239
618
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
240
- query: z.string().describe("Tree-sitter query in S-expression syntax"),
241
- language: z.string().describe("Tree-sitter grammar: typescript, javascript, python, go, rust, java, ruby, php"),
619
+ query: z.string().describe("Tree-sitter query in S-expression syntax. For JSX/React use language='tsx'."),
620
+ language: z.string().describe("Tree-sitter grammar: typescript, tsx, javascript, python, go, rust, java, ruby, php"),
242
621
  file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
243
622
  max_matches: zNum().describe("Maximum matches to return (default: 50)"),
244
623
  },
@@ -341,7 +720,10 @@ const TOOL_DEFINITIONS = [
341
720
  },
342
721
  handler: async (args) => {
343
722
  const result = await getFileOutline(args.repo, args.file_path);
344
- return formatFileOutline(result);
723
+ const output = formatFileOutline(result);
724
+ const isEmpty = !result || (Array.isArray(result) && result.length === 0);
725
+ const hint = await checkTextStubHint(args.repo, "get_file_outline", isEmpty);
726
+ return hint ? hint + output : output;
345
727
  },
346
728
  },
347
729
  {
@@ -387,8 +769,10 @@ const TOOL_DEFINITIONS = [
387
769
  if (args.include_related != null)
388
770
  opts.include_related = args.include_related;
389
771
  const result = await getSymbol(args.repo, args.symbol_id, opts);
390
- if (!result)
391
- return null;
772
+ if (!result) {
773
+ const hint = await checkTextStubHint(args.repo, "get_symbol", true);
774
+ return hint ?? null;
775
+ }
392
776
  let text = formatSymbolCompact(result.symbol);
393
777
  if (result.related && result.related.length > 0) {
394
778
  text += "\n\n--- children ---\n" + result.related.map((s) => `${s.kind} ${s.name}${s.signature ? s.signature : ""} [${s.file}:${s.start_line}]`).join("\n");
@@ -410,7 +794,9 @@ const TOOL_DEFINITIONS = [
410
794
  },
411
795
  handler: async (args) => {
412
796
  const syms = await getSymbols(args.repo, args.symbol_ids);
413
- return formatSymbolsCompact(syms);
797
+ const output = formatSymbolsCompact(syms);
798
+ const hint = await checkTextStubHint(args.repo, "get_symbols", syms.length === 0);
799
+ return hint ? hint + output : output;
414
800
  },
415
801
  },
416
802
  {
@@ -470,16 +856,17 @@ const TOOL_DEFINITIONS = [
470
856
  return findReferencesBatch(args.repo, names, args.file_pattern);
471
857
  }
472
858
  const refs = await findReferences(args.repo, args.symbol_name, args.file_pattern);
473
- // Compact format: drop col, use file:line: context (matches grep output)
474
- return formatRefsCompact(refs);
859
+ const output = formatRefsCompact(refs);
860
+ const hint = await checkTextStubHint(args.repo, "find_references", refs.length === 0);
861
+ return hint ? hint + output : output;
475
862
  },
476
863
  },
477
864
  {
478
865
  name: "trace_call_chain",
479
866
  category: "graph",
480
- searchHint: "trace call chain callers callees dependency graph mermaid",
867
+ searchHint: "trace call chain callers callees dependency graph mermaid react hooks",
481
868
  outputSchema: OutputSchemas.callTree,
482
- description: "Trace call chain: callers or callees. output_format='mermaid' for diagram.",
869
+ description: "Trace call chain: callers or callees. output_format='mermaid' for diagram. filter_react_hooks=true skips useState/useEffect etc. for cleaner React graphs.",
483
870
  schema: {
484
871
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
485
872
  symbol_name: z.string().describe("Name of the symbol to trace"),
@@ -488,6 +875,7 @@ const TOOL_DEFINITIONS = [
488
875
  include_source: zBool().describe("Include full source code of each symbol (default: false)"),
489
876
  include_tests: zBool().describe("Include test files in trace results (default: false)"),
490
877
  output_format: z.enum(["json", "mermaid"]).optional().describe("Output format: 'json' (default) or 'mermaid' (flowchart diagram)"),
878
+ filter_react_hooks: zBool().describe("Skip edges to React stdlib hooks (useState, useEffect, etc.) to reduce call graph noise in React codebases (default: false)"),
491
879
  },
492
880
  handler: async (args) => {
493
881
  const result = await traceCallChain(args.repo, args.symbol_name, args.direction, {
@@ -495,8 +883,12 @@ const TOOL_DEFINITIONS = [
495
883
  include_source: args.include_source,
496
884
  include_tests: args.include_tests,
497
885
  output_format: args.output_format,
886
+ filter_react_hooks: args.filter_react_hooks,
498
887
  });
499
- return formatCallTree(result);
888
+ const output = formatCallTree(result);
889
+ const isEmpty = typeof result === "object" && result != null && "children" in result && Array.isArray(result.children) && result.children.length === 0;
890
+ const hint = await checkTextStubHint(args.repo, "trace_call_chain", isEmpty);
891
+ return hint ? hint + output : output;
500
892
  },
501
893
  },
502
894
  {
@@ -521,6 +913,120 @@ const TOOL_DEFINITIONS = [
521
913
  return formatImpactAnalysis(result);
522
914
  },
523
915
  },
916
+ {
917
+ name: "trace_component_tree",
918
+ category: "graph",
919
+ searchHint: "react component tree composition render jsx parent child hierarchy",
920
+ description: "Trace React component composition tree from a root component. Shows which components render which via JSX. React equivalent of trace_call_chain. output_format='mermaid' for diagram.",
921
+ schema: {
922
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
923
+ component_name: z.string().describe("Root component name (must have kind 'component' in index)"),
924
+ depth: zNum().describe("Maximum depth of composition tree (default: 3)"),
925
+ include_source: zBool().describe("Include full source of each component (default: false)"),
926
+ include_tests: zBool().describe("Include test files (default: false)"),
927
+ output_format: z.enum(["json", "mermaid"]).optional().describe("Output format: 'json' (default) or 'mermaid'"),
928
+ },
929
+ handler: async (args) => {
930
+ const result = await traceComponentTree(args.repo, args.component_name, {
931
+ depth: args.depth,
932
+ include_source: args.include_source,
933
+ include_tests: args.include_tests,
934
+ output_format: args.output_format,
935
+ });
936
+ return JSON.stringify(result, null, 2);
937
+ },
938
+ },
939
+ {
940
+ name: "analyze_hooks",
941
+ category: "analysis",
942
+ searchHint: "react hooks analyze inventory rule of hooks violations usestate useeffect custom",
943
+ description: "Analyze React hooks: inventory per component, Rule of Hooks violations (hook inside if/loop, hook after early return), custom hook composition, codebase-wide hook usage summary.",
944
+ schema: {
945
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
946
+ component_name: z.string().optional().describe("Filter to single component/hook (default: all)"),
947
+ file_pattern: z.string().optional().describe("Filter by file path substring"),
948
+ include_tests: zBool().describe("Include test files (default: false)"),
949
+ max_entries: zNum().describe("Max entries to return (default: 100)"),
950
+ },
951
+ handler: async (args) => {
952
+ const result = await analyzeHooks(args.repo, {
953
+ component_name: args.component_name,
954
+ file_pattern: args.file_pattern,
955
+ include_tests: args.include_tests,
956
+ max_entries: args.max_entries,
957
+ });
958
+ return JSON.stringify(result, null, 2);
959
+ },
960
+ },
961
+ {
962
+ name: "analyze_renders",
963
+ category: "analysis",
964
+ searchHint: "react render performance inline props memo useCallback useMemo re-render risk optimization",
965
+ description: "Static re-render risk analysis for React components. Detects inline object/array/function props in JSX (new reference every render), unstable default values (= [] or = {}), and components missing React.memo that render children. Returns per-component risk level (low/medium/high) with actionable suggestions.",
966
+ schema: {
967
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
968
+ component_name: z.string().optional().describe("Filter to single component (default: all)"),
969
+ file_pattern: z.string().optional().describe("Filter by file path substring"),
970
+ include_tests: zBool().describe("Include test files (default: false)"),
971
+ max_entries: zNum().describe("Max entries to return (default: 100)"),
972
+ },
973
+ handler: async (args) => {
974
+ const result = await analyzeRenders(args.repo, {
975
+ component_name: args.component_name,
976
+ file_pattern: args.file_pattern,
977
+ include_tests: args.include_tests,
978
+ max_entries: args.max_entries,
979
+ });
980
+ return JSON.stringify(result, null, 2);
981
+ },
982
+ },
983
+ {
984
+ name: "analyze_context_graph",
985
+ category: "analysis",
986
+ searchHint: "react context createContext provider useContext consumer re-render propagation",
987
+ description: "Map React context flows: createContext → Provider → useContext consumers. Shows which components consume each context and which provide values. Helps identify unnecessary re-renders from context value changes.",
988
+ schema: {
989
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
990
+ },
991
+ handler: async (args) => {
992
+ const index = await getCodeIndex(args.repo);
993
+ if (!index)
994
+ throw new Error(`Repository not found: ${args.repo}`);
995
+ const result = buildContextGraph(index.symbols);
996
+ return JSON.stringify(result, null, 2);
997
+ },
998
+ },
999
+ {
1000
+ name: "audit_compiler_readiness",
1001
+ category: "analysis",
1002
+ searchHint: "react compiler forget memoization bailout readiness migration adoption auto-memo",
1003
+ description: "Audit React Compiler (v1.0) adoption readiness. Scans all components for patterns that cause silent bailout (side effects in render, ref reads, prop/state mutation, try/catch). Returns readiness score (0-100), prioritized fix list, and count of redundant manual memoization safe to remove post-adoption. No competitor offers codebase-wide compiler readiness analysis.",
1004
+ schema: {
1005
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1006
+ file_pattern: z.string().optional().describe("Filter by file path substring"),
1007
+ include_tests: zBool().describe("Include test files (default: false)"),
1008
+ },
1009
+ handler: async (args) => {
1010
+ const result = await auditCompilerReadiness(args.repo, {
1011
+ file_pattern: args.file_pattern,
1012
+ include_tests: args.include_tests,
1013
+ });
1014
+ return JSON.stringify(result, null, 2);
1015
+ },
1016
+ },
1017
+ {
1018
+ name: "react_quickstart",
1019
+ category: "analysis",
1020
+ searchHint: "react onboarding day-1 overview stack inventory components hooks critical issues",
1021
+ description: "Day-1 onboarding composite for React projects. Single call returns: component/hook inventory, stack detection (state mgmt, routing, UI lib, form lib, build tool), critical pattern scan (XSS, Rule of Hooks, memory leaks), top hook usage, and suggested next queries. Replaces 5-6 manual tool calls. First tool to run on an unfamiliar React codebase.",
1022
+ schema: {
1023
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1024
+ },
1025
+ handler: async (args) => {
1026
+ const result = await reactQuickstart(args.repo);
1027
+ return JSON.stringify(result, null, 2);
1028
+ },
1029
+ },
524
1030
  {
525
1031
  name: "trace_route",
526
1032
  category: "graph",
@@ -842,7 +1348,10 @@ const TOOL_DEFINITIONS = [
842
1348
  file_pattern: args.file_pattern,
843
1349
  include_tests: args.include_tests,
844
1350
  });
845
- return formatDeadCode(result);
1351
+ const output = formatDeadCode(result);
1352
+ const isEmpty = !result || (result.candidates?.length ?? 0) === 0;
1353
+ const hint = await checkTextStubHint(args.repo, "find_dead_code", isEmpty);
1354
+ return hint ? hint + output : output;
846
1355
  },
847
1356
  },
848
1357
  {
@@ -893,7 +1402,10 @@ const TOOL_DEFINITIONS = [
893
1402
  min_complexity: args.min_complexity,
894
1403
  include_tests: args.include_tests,
895
1404
  });
896
- return formatComplexity(result);
1405
+ const output = formatComplexity(result);
1406
+ const isEmpty = !result || (result.functions?.length ?? 0) === 0;
1407
+ const hint = await checkTextStubHint(args.repo, "analyze_complexity", isEmpty);
1408
+ return hint ? hint + output : output;
897
1409
  },
898
1410
  },
899
1411
  {
@@ -1118,247 +1630,1936 @@ const TOOL_DEFINITIONS = [
1118
1630
  return formatSecrets(result);
1119
1631
  },
1120
1632
  },
1121
- // --- Memory consolidation ---
1633
+ // --- Kotlin tools (discoverable via discover_tools(query="kotlin")) ---
1122
1634
  {
1123
- name: "consolidate_memories",
1124
- category: "conversations",
1125
- searchHint: "consolidate memories dream knowledge MEMORY.md decisions solutions patterns",
1126
- description: "Consolidate conversations into MEMORY.md decisions, solutions, patterns.",
1635
+ name: "find_extension_functions",
1636
+ category: "analysis",
1637
+ requiresLanguage: "kotlin",
1638
+ searchHint: "kotlin extension function receiver type method discovery",
1639
+ description: "Find all Kotlin extension functions for a given receiver type. Scans indexed symbols for signatures matching 'ReceiverType.' prefix.",
1127
1640
  schema: {
1128
- project_path: z.string().optional().describe("Project path (auto-detects from cwd if omitted)"),
1129
- output_path: z.string().optional().describe("Custom output file path (default: MEMORY.md in project root)"),
1130
- min_confidence: z.enum(["high", "medium", "low"]).optional().describe("Minimum confidence level for extracted memories (default: low)"),
1641
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1642
+ receiver_type: z.string().describe("Receiver type name, e.g. 'String', 'List', 'User'"),
1643
+ file_pattern: z.string().optional().describe("Filter by file path substring"),
1131
1644
  },
1132
1645
  handler: async (args) => {
1133
1646
  const opts = {};
1134
- if (typeof args.output_path === "string")
1135
- opts.output_path = args.output_path;
1136
- if (typeof args.min_confidence === "string")
1137
- opts.min_confidence = args.min_confidence;
1138
- const result = await consolidateMemories(args.project_path, opts);
1139
- return result;
1647
+ if (typeof args.file_pattern === "string")
1648
+ opts.file_pattern = args.file_pattern;
1649
+ return await findExtensionFunctions(args.repo, args.receiver_type, opts);
1140
1650
  },
1141
1651
  },
1142
1652
  {
1143
- name: "read_memory",
1144
- category: "conversations",
1145
- searchHint: "read memory MEMORY.md institutional knowledge past decisions",
1146
- description: "Read MEMORY.md knowledge file with past decisions and patterns.",
1653
+ name: "analyze_sealed_hierarchy",
1654
+ category: "analysis",
1655
+ requiresLanguage: "kotlin",
1656
+ searchHint: "kotlin sealed class interface subtype when exhaustive branch missing hierarchy",
1657
+ description: "Analyze a Kotlin sealed class/interface: find all subtypes and check when() blocks for exhaustiveness (missing branches).",
1147
1658
  schema: {
1148
- project_path: z.string().optional().describe("Project path (default: current directory)"),
1659
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1660
+ sealed_class: z.string().describe("Name of the sealed class or interface to analyze"),
1149
1661
  },
1150
1662
  handler: async (args) => {
1151
- const result = await readMemory(args.project_path);
1152
- if (!result)
1153
- return { error: "No MEMORY.md found. Run consolidate_memories first." };
1154
- return result.content;
1663
+ return await analyzeSealedHierarchy(args.repo, args.sealed_class);
1155
1664
  },
1156
1665
  },
1157
- // --- Coordinator ---
1158
1666
  {
1159
- name: "create_analysis_plan",
1160
- category: "meta",
1161
- searchHint: "create plan multi-step analysis workflow coordinator scratchpad",
1162
- description: "Create multi-step analysis plan with shared scratchpad and dependencies.",
1667
+ name: "trace_hilt_graph",
1668
+ category: "analysis",
1669
+ searchHint: "hilt dagger DI dependency injection viewmodel inject module provides binds android kotlin graph",
1670
+ description: "Trace a Hilt DI dependency tree rooted at a class annotated with @HiltViewModel / @AndroidEntryPoint / @HiltAndroidApp. Returns constructor dependencies with matching @Provides/@Binds providers and their module. Unresolved deps are flagged.",
1163
1671
  schema: {
1164
- title: z.string().describe("Plan title describing the analysis goal"),
1165
- steps: z.union([
1166
- z.array(z.object({
1167
- description: z.string(),
1168
- tool: z.string(),
1169
- args: z.record(z.string(), z.unknown()),
1170
- result_key: z.string().optional(),
1171
- depends_on: z.array(z.string()).optional(),
1172
- })),
1173
- z.string().transform((s) => JSON.parse(s)),
1174
- ]).describe("Steps array: {description, tool, args, result_key?, depends_on?}. JSON string OK."),
1672
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1673
+ class_name: z.string().describe("Name of the Hilt-annotated class (e.g. 'UserViewModel')"),
1674
+ depth: z.number().optional().describe("Max traversal depth (default: 1)"),
1175
1675
  },
1176
1676
  handler: async (args) => {
1177
- const result = await createAnalysisPlan(args.title, args.steps);
1178
- return result;
1677
+ const opts = {};
1678
+ if (typeof args.depth === "number")
1679
+ opts.depth = args.depth;
1680
+ return await traceHiltGraph(args.repo, args.class_name, opts);
1179
1681
  },
1180
1682
  },
1181
1683
  {
1182
- name: "scratchpad_write",
1183
- category: "meta",
1184
- searchHint: "scratchpad write store knowledge cross-step data persist",
1185
- description: "Write key-value to plan scratchpad for cross-step knowledge sharing.",
1684
+ name: "trace_suspend_chain",
1685
+ category: "analysis",
1686
+ searchHint: "kotlin coroutine suspend dispatcher withContext runBlocking Thread.sleep blocking chain trace anti-pattern",
1687
+ description: "Trace the call chain of a Kotlin suspend function, emitting dispatcher transitions (withContext(Dispatchers.X)) and warnings for coroutine anti-patterns: runBlocking inside suspend, Thread.sleep, non-cancellable while(true) loops. Lexical walk — follows callee names found in the source, filtered to suspend-only functions.",
1186
1688
  schema: {
1187
- plan_id: z.string().describe("Analysis plan identifier"),
1188
- key: z.string().describe("Key name for the entry"),
1189
- value: z.string().describe("Value to store"),
1689
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1690
+ function_name: z.string().describe("Name of the suspend function to trace"),
1691
+ depth: z.number().optional().describe("Max chain depth (default: 3)"),
1692
+ },
1693
+ handler: async (args) => {
1694
+ const opts = {};
1695
+ if (typeof args.depth === "number")
1696
+ opts.depth = args.depth;
1697
+ return await traceSuspendChain(args.repo, args.function_name, opts);
1190
1698
  },
1191
- handler: async (args) => writeScratchpad(args.plan_id, args.key, args.value),
1192
1699
  },
1193
1700
  {
1194
- name: "scratchpad_read",
1195
- category: "meta",
1196
- searchHint: "scratchpad read retrieve knowledge entry",
1197
- description: "Read a key from a plan's scratchpad. Returns the stored value or null if not found.",
1701
+ name: "analyze_kmp_declarations",
1702
+ category: "analysis",
1703
+ searchHint: "kotlin multiplatform kmp expect actual source set common main android ios jvm js missing orphan",
1704
+ description: "Validate Kotlin Multiplatform expect/actual declarations across source sets. For each `expect` in commonMain, check every platform source set (androidMain/iosMain/jvmMain/jsMain/etc. discovered from the repo layout) for a matching `actual`. Reports fully matched pairs, expects missing on a platform, and orphan actuals with no corresponding expect.",
1198
1705
  schema: {
1199
- plan_id: z.string().describe("Analysis plan identifier"),
1200
- key: z.string().describe("Key name to read"),
1706
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1201
1707
  },
1202
1708
  handler: async (args) => {
1203
- const result = await readScratchpad(args.plan_id, args.key);
1204
- return result ?? { error: "Key not found in scratchpad" };
1709
+ return await analyzeKmpDeclarations(args.repo);
1205
1710
  },
1206
1711
  },
1712
+ // --- Kotlin Wave 3 tools ---
1207
1713
  {
1208
- name: "scratchpad_list",
1209
- category: "meta",
1210
- searchHint: "scratchpad list entries keys",
1211
- description: "List all entries in a plan's scratchpad with their sizes.",
1714
+ name: "trace_compose_tree",
1715
+ category: "analysis",
1716
+ searchHint: "kotlin compose composable component tree hierarchy ui call graph jetpack preview",
1717
+ description: "Build a Jetpack Compose component hierarchy rooted at a @Composable function. Traces PascalCase calls matching indexed composables, excludes @Preview. Reports tree depth, leaf components, and total component count.",
1212
1718
  schema: {
1213
- plan_id: z.string().describe("Analysis plan identifier"),
1719
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1720
+ root_name: z.string().describe("Name of the root @Composable function (e.g. 'HomeScreen')"),
1721
+ depth: z.number().optional().describe("Max tree depth (default: 10)"),
1722
+ },
1723
+ handler: async (args) => {
1724
+ const opts = {};
1725
+ if (typeof args.depth === "number")
1726
+ opts.depth = args.depth;
1727
+ return await traceComposeTree(args.repo, args.root_name, opts);
1214
1728
  },
1215
- handler: (args) => listScratchpad(args.plan_id),
1216
1729
  },
1217
1730
  {
1218
- name: "update_step_status",
1219
- category: "meta",
1220
- searchHint: "update step status plan progress completed failed",
1221
- description: "Update step status in plan. Auto-updates plan status on completion.",
1731
+ name: "analyze_compose_recomposition",
1732
+ category: "analysis",
1733
+ searchHint: "kotlin compose recomposition unstable remember mutableStateOf performance skip lambda collection",
1734
+ description: "Detect recomposition hazards in @Composable functions: mutableStateOf without remember (critical), unstable collection parameters (List/Map/Set), excessive function-type params. Scans all indexed composables, skipping @Preview.",
1222
1735
  schema: {
1223
- plan_id: z.string().describe("Analysis plan identifier"),
1224
- step_id: z.string().describe("Step identifier (e.g. step_1)"),
1225
- status: z.enum(["pending", "in_progress", "completed", "failed", "skipped"]).describe("New status for the step"),
1226
- error: z.string().optional().describe("Error message if status is 'failed'"),
1736
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1737
+ file_pattern: z.string().optional().describe("Filter by file path substring"),
1227
1738
  },
1228
1739
  handler: async (args) => {
1229
- const result = await updateStepStatus(args.plan_id, args.step_id, args.status, args.error);
1230
- return result;
1740
+ const opts = {};
1741
+ if (typeof args.file_pattern === "string")
1742
+ opts.file_pattern = args.file_pattern;
1743
+ return await analyzeComposeRecomposition(args.repo, opts);
1231
1744
  },
1232
1745
  },
1233
1746
  {
1234
- name: "get_analysis_plan",
1235
- category: "meta",
1236
- searchHint: "get plan status steps progress",
1237
- description: "Get the current state of an analysis plan including all step statuses.",
1747
+ name: "trace_room_schema",
1748
+ category: "analysis",
1749
+ searchHint: "kotlin room database entity dao query insert update delete schema sqlite persistence android",
1750
+ description: "Build a Room persistence schema graph: @Entity classes (with table names, primary keys), @Dao interfaces (with @Query SQL extraction), @Database declarations (with entity refs and version). Index-only.",
1238
1751
  schema: {
1239
- plan_id: z.string().describe("Analysis plan identifier"),
1752
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1240
1753
  },
1241
1754
  handler: async (args) => {
1242
- const plan = getPlan(args.plan_id);
1243
- return plan ?? { error: "Plan not found" };
1755
+ return await traceRoomSchema(args.repo);
1244
1756
  },
1245
1757
  },
1246
1758
  {
1247
- name: "list_analysis_plans",
1248
- category: "meta",
1249
- searchHint: "list plans active analysis workflows",
1250
- description: "List all active analysis plans with their completion status.",
1251
- schema: {},
1252
- handler: async () => listPlans(),
1759
+ name: "extract_kotlin_serialization_contract",
1760
+ category: "analysis",
1761
+ searchHint: "kotlin serialization serializable json schema serialname field type api contract data class",
1762
+ description: "Derive JSON field schema from @Serializable data classes. Extracts field names, types, @SerialName remapping, nullable flags, and defaults.",
1763
+ schema: {
1764
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1765
+ file_pattern: z.string().optional().describe("Filter by file path substring"),
1766
+ class_name: z.string().optional().describe("Filter to a single class by name"),
1767
+ },
1768
+ handler: async (args) => {
1769
+ const opts = {};
1770
+ if (typeof args.file_pattern === "string")
1771
+ opts.file_pattern = args.file_pattern;
1772
+ if (typeof args.class_name === "string")
1773
+ opts.class_name = args.class_name;
1774
+ return await extractKotlinSerializationContract(args.repo, opts);
1775
+ },
1253
1776
  },
1254
- // --- Review diff ---
1255
1777
  {
1256
- name: "review_diff",
1257
- category: "diff",
1258
- searchHint: "review diff static analysis git changes secrets breaking-changes complexity dead-code blast-radius",
1259
- description: "Run 9 parallel static analysis checks on a git diff: secrets, breaking changes, coupling gaps, complexity, dead-code, blast-radius, bug-patterns, test-gaps, hotspots. Returns a scored verdict (pass/warn/fail) with tiered findings.",
1778
+ name: "trace_flow_chain",
1779
+ category: "analysis",
1780
+ searchHint: "kotlin flow coroutine operator map filter collect stateIn shareIn catch chain pipeline reactive",
1781
+ description: "Analyze a Kotlin Flow<T> operator chain: detects 50+ operators, reports ordered list, warns about .collect without .catch and .stateIn without lifecycle scope.",
1260
1782
  schema: {
1261
1783
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1262
- since: z.string().optional().describe("Base git ref (default: HEAD~1)"),
1263
- until: z.string().optional().describe("Target ref. Default: HEAD. Special: WORKING, STAGED"),
1264
- checks: z.string().optional().describe("Comma-separated check names (default: all)"),
1265
- exclude_patterns: z.string().optional().describe("Comma-separated globs to exclude"),
1266
- token_budget: zNum().describe("Max tokens (default: 15000)"),
1267
- max_files: zNum().describe("Warn above N files (default: 50)"),
1268
- check_timeout_ms: zNum().describe("Per-check timeout ms (default: 8000)"),
1784
+ symbol_name: z.string().describe("Name of the function or property containing the Flow chain"),
1269
1785
  },
1270
1786
  handler: async (args) => {
1271
- const checksArr = args.checks
1787
+ return await traceFlowChain(args.repo, args.symbol_name);
1788
+ },
1789
+ },
1790
+ // --- Python tools (all discoverable via discover_tools(query="python")) ---
1791
+ {
1792
+ name: "get_model_graph",
1793
+ category: "analysis",
1794
+ requiresLanguage: "python",
1795
+ searchHint: "python django sqlalchemy orm model relationship foreignkey manytomany entity graph mermaid",
1796
+ description: "Extract ORM model relationships (Django ForeignKey/M2M/O2O, SQLAlchemy relationship). JSON or mermaid erDiagram.",
1797
+ schema: {
1798
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1799
+ file_pattern: z.string().optional().describe("Filter by file path substring"),
1800
+ output_format: z.enum(["json", "mermaid"]).optional().describe("Output as structured JSON or mermaid erDiagram"),
1801
+ },
1802
+ handler: async (args) => {
1803
+ const opts = {};
1804
+ if (args.file_pattern != null)
1805
+ opts.file_pattern = args.file_pattern;
1806
+ if (args.output_format != null)
1807
+ opts.output_format = args.output_format;
1808
+ return await getModelGraph(args.repo, opts);
1809
+ },
1810
+ },
1811
+ {
1812
+ name: "get_test_fixtures",
1813
+ category: "analysis",
1814
+ requiresLanguage: "python",
1815
+ searchHint: "python pytest fixture conftest scope autouse dependency graph session function",
1816
+ description: "Extract pytest fixture dependency graph: conftest hierarchy, scope, autouse, fixture-to-fixture deps.",
1817
+ schema: {
1818
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1819
+ file_pattern: z.string().optional().describe("Filter by file path substring"),
1820
+ },
1821
+ handler: async (args) => {
1822
+ const opts = {};
1823
+ if (args.file_pattern != null)
1824
+ opts.file_pattern = args.file_pattern;
1825
+ return await getTestFixtures(args.repo, opts);
1826
+ },
1827
+ },
1828
+ {
1829
+ name: "find_framework_wiring",
1830
+ category: "analysis",
1831
+ requiresLanguage: "python",
1832
+ searchHint: "python django signal receiver celery task middleware management command flask fastapi event wiring",
1833
+ description: "Discover implicit control flow: Django signals, Celery tasks/.delay() calls, middleware, management commands, Flask init_app, FastAPI events.",
1834
+ schema: {
1835
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1836
+ file_pattern: z.string().optional().describe("Filter by file path substring"),
1837
+ },
1838
+ handler: async (args) => {
1839
+ const opts = {};
1840
+ if (args.file_pattern != null)
1841
+ opts.file_pattern = args.file_pattern;
1842
+ return await findFrameworkWiring(args.repo, opts);
1843
+ },
1844
+ },
1845
+ {
1846
+ name: "run_ruff",
1847
+ category: "analysis",
1848
+ requiresLanguage: "python",
1849
+ searchHint: "python ruff lint check bugbear performance simplify security async unused argument",
1850
+ description: "Run ruff linter with symbol graph correlation. Configurable rule categories (B, PERF, SIM, UP, S, ASYNC, RET, ARG).",
1851
+ schema: {
1852
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1853
+ categories: z.array(z.string()).optional().describe("Rule categories to enable (default: B,PERF,SIM,UP,S,ASYNC,RET,ARG)"),
1854
+ file_pattern: z.string().optional().describe("Filter by file path substring"),
1855
+ max_results: zFiniteNumber.optional().describe("Max findings to return (default: 100)"),
1856
+ },
1857
+ handler: async (args) => {
1858
+ const opts = {};
1859
+ if (args.categories != null)
1860
+ opts.categories = args.categories;
1861
+ if (args.file_pattern != null)
1862
+ opts.file_pattern = args.file_pattern;
1863
+ if (args.max_results != null)
1864
+ opts.max_results = args.max_results;
1865
+ return await runRuff(args.repo, opts);
1866
+ },
1867
+ },
1868
+ {
1869
+ name: "parse_pyproject",
1870
+ category: "analysis",
1871
+ requiresLanguage: "python",
1872
+ searchHint: "python pyproject toml dependencies version build system entry points scripts tools ruff pytest mypy",
1873
+ description: "Parse pyproject.toml: name, version, Python version, build system, dependencies, optional groups, entry points, configured tools.",
1874
+ schema: { repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)") },
1875
+ handler: async (args) => { return await parsePyproject(args.repo); },
1876
+ },
1877
+ {
1878
+ name: "resolve_constant_value",
1879
+ category: "analysis",
1880
+ searchHint: "python typescript nestjs resolve constant value literal alias import default parameter propagation",
1881
+ description: "Resolve Python or TypeScript constants and function default values through simple aliases and import chains. Returns literals or explicit unresolved reasons.",
1882
+ schema: {
1883
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1884
+ symbol_name: z.string().describe("Constant, function, or method name to resolve"),
1885
+ file_pattern: z.string().optional().describe("Filter candidate symbols by file path substring"),
1886
+ language: z.enum(["python", "typescript"]).optional().describe("Force resolver language instead of auto-inference"),
1887
+ max_depth: zFiniteNumber.optional().describe("Maximum alias/import resolution depth (default: 8)"),
1888
+ },
1889
+ handler: async (args) => {
1890
+ const opts = {};
1891
+ if (args.file_pattern != null)
1892
+ opts.file_pattern = args.file_pattern;
1893
+ if (args.language != null)
1894
+ opts.language = args.language;
1895
+ if (args.max_depth != null)
1896
+ opts.max_depth = args.max_depth;
1897
+ return await resolveConstantValue(args.repo, args.symbol_name, opts);
1898
+ },
1899
+ },
1900
+ {
1901
+ name: "effective_django_view_security",
1902
+ category: "security",
1903
+ requiresLanguage: "python",
1904
+ searchHint: "python django view auth csrf login_required middleware mixin route security posture",
1905
+ description: "Assess effective Django view security from decorators, mixins, settings middleware, and optional route resolution.",
1906
+ schema: {
1907
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1908
+ path: z.string().optional().describe("Django route path to resolve first, e.g. /settings/"),
1909
+ symbol_name: z.string().optional().describe("View function/class/method name when you already know the symbol"),
1910
+ file_pattern: z.string().optional().describe("Filter candidate symbols by file path substring"),
1911
+ settings_file: z.string().optional().describe("Explicit Django settings file path (auto-detects if omitted)"),
1912
+ },
1913
+ handler: async (args) => {
1914
+ const opts = {};
1915
+ if (args.path != null)
1916
+ opts.path = args.path;
1917
+ if (args.symbol_name != null)
1918
+ opts.symbol_name = args.symbol_name;
1919
+ if (args.file_pattern != null)
1920
+ opts.file_pattern = args.file_pattern;
1921
+ if (args.settings_file != null)
1922
+ opts.settings_file = args.settings_file;
1923
+ return await effectiveDjangoViewSecurity(args.repo, opts);
1924
+ },
1925
+ },
1926
+ {
1927
+ name: "taint_trace",
1928
+ category: "security",
1929
+ requiresLanguage: "python",
1930
+ searchHint: "python django taint data flow source sink request get post redirect mark_safe cursor execute subprocess session trace",
1931
+ description: "Trace Python/Django user-controlled data from request sources to security sinks like redirect, mark_safe, cursor.execute, subprocess, requests/httpx, open, or session writes.",
1932
+ schema: {
1933
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1934
+ framework: z.enum(["python-django"]).optional().describe("Currently only python-django is implemented"),
1935
+ file_pattern: z.string().optional().describe("Restrict analysis to matching Python files"),
1936
+ source_patterns: z.array(z.string()).optional().describe("Optional source pattern allowlist (defaults to request.* presets)"),
1937
+ sink_patterns: z.array(z.string()).optional().describe("Optional sink pattern allowlist (defaults to built-in security sinks)"),
1938
+ max_depth: zFiniteNumber.optional().describe("Maximum interprocedural helper depth (default: 4)"),
1939
+ max_traces: zFiniteNumber.optional().describe("Maximum traces to return before truncation (default: 50)"),
1940
+ },
1941
+ handler: async (args) => {
1942
+ const opts = {};
1943
+ if (args.framework != null)
1944
+ opts.framework = args.framework;
1945
+ if (args.file_pattern != null)
1946
+ opts.file_pattern = args.file_pattern;
1947
+ if (args.source_patterns != null)
1948
+ opts.source_patterns = args.source_patterns;
1949
+ if (args.sink_patterns != null)
1950
+ opts.sink_patterns = args.sink_patterns;
1951
+ if (args.max_depth != null)
1952
+ opts.max_depth = args.max_depth;
1953
+ if (args.max_traces != null)
1954
+ opts.max_traces = args.max_traces;
1955
+ return await taintTrace(args.repo, opts);
1956
+ },
1957
+ },
1958
+ {
1959
+ name: "find_python_callers",
1960
+ category: "analysis",
1961
+ requiresLanguage: "python",
1962
+ searchHint: "python callers call site usage trace cross module import delay apply_async constructor",
1963
+ description: "Find all call sites of a Python symbol: direct calls, method calls, Celery .delay()/.apply_async(), constructor, references.",
1964
+ schema: {
1965
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1966
+ target_name: z.string().describe("Name of the target function/class/method"),
1967
+ target_file: z.string().optional().describe("Disambiguate target by file path substring"),
1968
+ file_pattern: z.string().optional().describe("Restrict caller search scope"),
1969
+ max_results: zFiniteNumber.optional().describe("Max callers to return (default: 100)"),
1970
+ },
1971
+ handler: async (args) => {
1972
+ const opts = {};
1973
+ if (args.target_file != null)
1974
+ opts.target_file = args.target_file;
1975
+ if (args.file_pattern != null)
1976
+ opts.file_pattern = args.file_pattern;
1977
+ if (args.max_results != null)
1978
+ opts.max_results = args.max_results;
1979
+ return await findPythonCallers(args.repo, args.target_name, opts);
1980
+ },
1981
+ },
1982
+ {
1983
+ name: "analyze_django_settings",
1984
+ category: "security",
1985
+ requiresLanguage: "python",
1986
+ searchHint: "python django settings security debug secret key allowed hosts csrf middleware cookie hsts cors",
1987
+ description: "Audit Django settings.py: 15 security/config checks (DEBUG, SECRET_KEY, CSRF, CORS, HSTS, cookies, sqlite, middleware).",
1988
+ schema: {
1989
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1990
+ settings_file: z.string().optional().describe("Explicit settings file path (auto-detects if omitted)"),
1991
+ },
1992
+ handler: async (args) => {
1993
+ const opts = {};
1994
+ if (args.settings_file != null)
1995
+ opts.settings_file = args.settings_file;
1996
+ return await analyzeDjangoSettings(args.repo, opts);
1997
+ },
1998
+ },
1999
+ {
2000
+ name: "run_mypy",
2001
+ category: "analysis",
2002
+ requiresLanguage: "python",
2003
+ searchHint: "python mypy type check error strict return incompatible argument missing",
2004
+ description: "Run mypy type checker with symbol correlation. Parses error codes, maps to containing symbols.",
2005
+ schema: {
2006
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2007
+ file_pattern: z.string().optional().describe("Filter by file path substring"),
2008
+ strict: zBool().describe("Enable mypy --strict mode"),
2009
+ max_results: zFiniteNumber.optional().describe("Max findings (default: 100)"),
2010
+ },
2011
+ handler: async (args) => {
2012
+ const opts = {};
2013
+ if (args.file_pattern != null)
2014
+ opts.file_pattern = args.file_pattern;
2015
+ if (args.strict != null)
2016
+ opts.strict = args.strict;
2017
+ if (args.max_results != null)
2018
+ opts.max_results = args.max_results;
2019
+ return await runMypy(args.repo, opts);
2020
+ },
2021
+ },
2022
+ {
2023
+ name: "run_pyright",
2024
+ category: "analysis",
2025
+ requiresLanguage: "python",
2026
+ searchHint: "python pyright type check reportMissingImports reportGeneralTypeIssues",
2027
+ description: "Run pyright type checker with symbol correlation. Parses JSON diagnostics, maps to containing symbols.",
2028
+ schema: {
2029
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2030
+ file_pattern: z.string().optional().describe("Filter by file path substring"),
2031
+ strict: zBool().describe("Enable strict level"),
2032
+ max_results: zFiniteNumber.optional().describe("Max findings (default: 100)"),
2033
+ },
2034
+ handler: async (args) => {
2035
+ const opts = {};
2036
+ if (args.file_pattern != null)
2037
+ opts.file_pattern = args.file_pattern;
2038
+ if (args.strict != null)
2039
+ opts.strict = args.strict;
2040
+ if (args.max_results != null)
2041
+ opts.max_results = args.max_results;
2042
+ return await runPyright(args.repo, opts);
2043
+ },
2044
+ },
2045
+ {
2046
+ name: "analyze_python_deps",
2047
+ category: "analysis",
2048
+ requiresLanguage: "python",
2049
+ searchHint: "python dependency version outdated vulnerable CVE pypi osv requirements pyproject",
2050
+ description: "Python dependency analysis: parse pyproject.toml/requirements.txt, detect unpinned deps, optional PyPI freshness, optional OSV.dev CVE scan.",
2051
+ schema: {
2052
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2053
+ check_pypi: zBool().describe("Check PyPI for latest versions (network, opt-in)"),
2054
+ check_vulns: zBool().describe("Check OSV.dev for CVEs (network, opt-in)"),
2055
+ },
2056
+ handler: async (args) => {
2057
+ const opts = {};
2058
+ if (args.check_pypi != null)
2059
+ opts.check_pypi = args.check_pypi;
2060
+ if (args.check_vulns != null)
2061
+ opts.check_vulns = args.check_vulns;
2062
+ return await analyzePythonDeps(args.repo, opts);
2063
+ },
2064
+ },
2065
+ {
2066
+ name: "trace_fastapi_depends",
2067
+ category: "analysis",
2068
+ requiresLanguage: "python",
2069
+ searchHint: "python fastapi depends dependency injection security scopes oauth2 authentication auth endpoint",
2070
+ description: "Trace FastAPI Depends()/Security() dependency injection chains recursively from route handlers. Detects yield deps (resource cleanup), Security() with scopes, shared deps across endpoints, endpoints without auth.",
2071
+ schema: {
2072
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2073
+ file_pattern: z.string().optional().describe("Filter by file path substring"),
2074
+ endpoint: z.string().optional().describe("Focus on a specific endpoint function name"),
2075
+ max_depth: zFiniteNumber.optional().describe("Max dependency tree depth (default: 5)"),
2076
+ },
2077
+ handler: async (args) => {
2078
+ const opts = {};
2079
+ if (args.file_pattern != null)
2080
+ opts.file_pattern = args.file_pattern;
2081
+ if (args.endpoint != null)
2082
+ opts.endpoint = args.endpoint;
2083
+ if (args.max_depth != null)
2084
+ opts.max_depth = args.max_depth;
2085
+ return await traceFastAPIDepends(args.repo, opts);
2086
+ },
2087
+ },
2088
+ {
2089
+ name: "analyze_async_correctness",
2090
+ category: "analysis",
2091
+ requiresLanguage: "python",
2092
+ searchHint: "python async await asyncio blocking sync requests sleep subprocess django sqlalchemy ORM coroutine fastapi",
2093
+ description: "Detect 8 asyncio pitfalls in async def: blocking requests/sleep/IO/subprocess, sync SQLAlchemy/Django ORM in async views, async without await, asyncio.create_task without ref storage.",
2094
+ schema: {
2095
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2096
+ file_pattern: z.string().optional().describe("Filter by file path substring"),
2097
+ rules: z.array(z.string()).optional().describe("Subset of rules to run"),
2098
+ max_results: zFiniteNumber.optional().describe("Max findings (default: 200)"),
2099
+ },
2100
+ handler: async (args) => {
2101
+ const opts = {};
2102
+ if (args.file_pattern != null)
2103
+ opts.file_pattern = args.file_pattern;
2104
+ if (args.rules != null)
2105
+ opts.rules = args.rules;
2106
+ if (args.max_results != null)
2107
+ opts.max_results = args.max_results;
2108
+ return await analyzeAsyncCorrectness(args.repo, opts);
2109
+ },
2110
+ },
2111
+ {
2112
+ name: "get_pydantic_models",
2113
+ category: "analysis",
2114
+ requiresLanguage: "python",
2115
+ searchHint: "python pydantic basemodel fastapi schema request response contract validator field constraint type classdiagram",
2116
+ description: "Extract Pydantic models: fields with types, validators, Field() constraints, model_config, cross-model references (list[X], Optional[Y]), inheritance. JSON or mermaid classDiagram.",
2117
+ schema: {
2118
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2119
+ file_pattern: z.string().optional().describe("Filter by file path substring"),
2120
+ output_format: z.enum(["json", "mermaid"]).optional().describe("Output as structured JSON or mermaid classDiagram"),
2121
+ },
2122
+ handler: async (args) => {
2123
+ const opts = {};
2124
+ if (args.file_pattern != null)
2125
+ opts.file_pattern = args.file_pattern;
2126
+ if (args.output_format != null)
2127
+ opts.output_format = args.output_format;
2128
+ return await getPydanticModels(args.repo, opts);
2129
+ },
2130
+ },
2131
+ {
2132
+ name: "python_audit",
2133
+ category: "analysis",
2134
+ requiresLanguage: "python",
2135
+ searchHint: "python audit health score compound project review django security circular patterns celery dependencies dead code task shared_task delay apply_async chain group chord canvas retry orphan queue import cycle ImportError TYPE_CHECKING DFS",
2136
+ description: "Compound Python project health audit: circular imports + Django settings + anti-patterns (17) + framework wiring + Celery orphans + pytest fixtures + deps + dead code. Runs in parallel, returns unified health score (0-100) + severity counts + prioritized top_risks list.",
2137
+ schema: {
2138
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2139
+ file_pattern: z.string().optional().describe("Filter by file path substring"),
2140
+ checks: z.array(z.string()).optional().describe("Subset of checks: circular_imports, django_settings, anti_patterns, framework_wiring, celery, pytest_fixtures, dependencies, dead_code"),
2141
+ },
2142
+ handler: async (args) => {
2143
+ const opts = {};
2144
+ if (args.file_pattern != null)
2145
+ opts.file_pattern = args.file_pattern;
2146
+ if (args.checks != null)
2147
+ opts.checks = args.checks;
2148
+ return await pythonAudit(args.repo, opts);
2149
+ },
2150
+ },
2151
+ // --- PHP / Yii2 tools (all discoverable via discover_tools(query="php")) ---
2152
+ {
2153
+ name: "resolve_php_namespace",
2154
+ category: "analysis",
2155
+ requiresLanguage: "php",
2156
+ searchHint: "php namespace resolve PSR-4 autoload composer class file path yii2 laravel symfony",
2157
+ description: "Resolve a PHP FQCN to file path via composer.json PSR-4 autoload mapping.",
2158
+ schema: {
2159
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2160
+ class_name: z.string().describe("Fully-qualified class name, e.g. 'App\\\\Models\\\\User'"),
2161
+ },
2162
+ handler: async (args) => {
2163
+ return await resolvePhpNamespace(args.repo, args.class_name);
2164
+ },
2165
+ },
2166
+ {
2167
+ name: "trace_php_event",
2168
+ category: "analysis",
2169
+ requiresLanguage: "php",
2170
+ searchHint: "php event listener trigger handler chain yii2 laravel observer dispatch",
2171
+ description: "Trace PHP event → listener chains: find trigger() calls and matching on() handlers.",
2172
+ schema: {
2173
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2174
+ event_name: z.string().optional().describe("Filter by specific event name"),
2175
+ },
2176
+ handler: async (args) => {
2177
+ const opts = {};
2178
+ if (typeof args.event_name === "string")
2179
+ opts.event_name = args.event_name;
2180
+ return await tracePhpEvent(args.repo, opts);
2181
+ },
2182
+ },
2183
+ {
2184
+ name: "find_php_views",
2185
+ category: "analysis",
2186
+ requiresLanguage: "php",
2187
+ searchHint: "php view render template controller widget yii2 laravel blade",
2188
+ description: "Map PHP controller render() calls to view files. Yii2/Laravel convention-aware.",
2189
+ schema: {
2190
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2191
+ controller: z.string().optional().describe("Filter by controller class name"),
2192
+ },
2193
+ handler: async (args) => {
2194
+ const opts = {};
2195
+ if (typeof args.controller === "string")
2196
+ opts.controller = args.controller;
2197
+ return await findPhpViews(args.repo, opts);
2198
+ },
2199
+ },
2200
+ {
2201
+ name: "resolve_php_service",
2202
+ category: "analysis",
2203
+ requiresLanguage: "php",
2204
+ searchHint: "php service locator DI container component resolve yii2 laravel facade provider",
2205
+ description: "Resolve PHP service locator references (Yii::$app->X, Laravel facades) to concrete classes via config parsing.",
2206
+ schema: {
2207
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2208
+ service_name: z.string().optional().describe("Filter by specific service name (e.g. 'db', 'user', 'cache')"),
2209
+ },
2210
+ handler: async (args) => {
2211
+ const opts = {};
2212
+ if (typeof args.service_name === "string")
2213
+ opts.service_name = args.service_name;
2214
+ return await resolvePhpService(args.repo, opts);
2215
+ },
2216
+ },
2217
+ {
2218
+ name: "php_security_scan",
2219
+ category: "security",
2220
+ requiresLanguage: "php",
2221
+ searchHint: "php security scan audit vulnerability injection XSS CSRF SQL eval exec unserialize",
2222
+ description: "Scan PHP code for security vulnerabilities: SQL injection, XSS, eval, exec, unserialize, file inclusion. Parallel pattern checks.",
2223
+ schema: {
2224
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2225
+ file_pattern: z.string().optional().describe("Glob pattern to filter scanned files (default: '*.php')"),
2226
+ checks: z.array(z.string()).optional().describe("Subset of checks to run: sql-injection-php, xss-php, eval-php, exec-php, unserialize-php, file-include-var, unescaped-yii-view, raw-query-yii"),
2227
+ },
2228
+ handler: async (args) => {
2229
+ const opts = {};
2230
+ if (typeof args.file_pattern === "string")
2231
+ opts.file_pattern = args.file_pattern;
2232
+ if (Array.isArray(args.checks))
2233
+ opts.checks = args.checks;
2234
+ return await phpSecurityScan(args.repo, opts);
2235
+ },
2236
+ },
2237
+ {
2238
+ name: "php_project_audit",
2239
+ category: "analysis",
2240
+ requiresLanguage: "php",
2241
+ searchHint: "php project audit health quality technical debt code review comprehensive yii2 laravel activerecord eloquent model schema relations rules behaviors table orm n+1 query foreach eager loading relation god class anti-pattern too many methods oversized",
2242
+ description: "Compound PHP project audit: security scan + ActiveRecord analysis + N+1 detection + god model detection + health score. Runs checks in parallel.",
2243
+ schema: {
2244
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2245
+ file_pattern: z.string().optional().describe("Glob pattern to filter analyzed files"),
2246
+ checks: z.string().optional().describe("Comma-separated checks: n_plus_one, god_model, activerecord, security, events, views, services, namespace. Default: all"),
2247
+ },
2248
+ handler: async (args) => {
2249
+ const opts = {};
2250
+ if (typeof args.file_pattern === "string")
2251
+ opts.file_pattern = args.file_pattern;
2252
+ if (typeof args.checks === "string" && args.checks.trim()) {
2253
+ opts.checks = args.checks.split(",").map((c) => c.trim()).filter(Boolean);
2254
+ }
2255
+ return await phpProjectAudit(args.repo, opts);
2256
+ },
2257
+ },
2258
+ // --- Memory consolidation ---
2259
+ {
2260
+ name: "consolidate_memories",
2261
+ category: "conversations",
2262
+ searchHint: "consolidate memories dream knowledge MEMORY.md decisions solutions patterns",
2263
+ description: "Consolidate conversations into MEMORY.md — decisions, solutions, patterns.",
2264
+ schema: {
2265
+ project_path: z.string().optional().describe("Project path (auto-detects from cwd if omitted)"),
2266
+ output_path: z.string().optional().describe("Custom output file path (default: MEMORY.md in project root)"),
2267
+ min_confidence: z.enum(["high", "medium", "low"]).optional().describe("Minimum confidence level for extracted memories (default: low)"),
2268
+ },
2269
+ handler: async (args) => {
2270
+ const opts = {};
2271
+ if (typeof args.output_path === "string")
2272
+ opts.output_path = args.output_path;
2273
+ if (typeof args.min_confidence === "string")
2274
+ opts.min_confidence = args.min_confidence;
2275
+ const result = await consolidateMemories(args.project_path, opts);
2276
+ return result;
2277
+ },
2278
+ },
2279
+ {
2280
+ name: "read_memory",
2281
+ category: "conversations",
2282
+ searchHint: "read memory MEMORY.md institutional knowledge past decisions",
2283
+ description: "Read MEMORY.md knowledge file with past decisions and patterns.",
2284
+ schema: {
2285
+ project_path: z.string().optional().describe("Project path (default: current directory)"),
2286
+ },
2287
+ handler: async (args) => {
2288
+ const result = await readMemory(args.project_path);
2289
+ if (!result)
2290
+ return { error: "No MEMORY.md found. Run consolidate_memories first." };
2291
+ return result.content;
2292
+ },
2293
+ },
2294
+ // --- Coordinator ---
2295
+ {
2296
+ name: "create_analysis_plan",
2297
+ category: "meta",
2298
+ searchHint: "create plan multi-step analysis workflow coordinator scratchpad",
2299
+ description: "Create multi-step analysis plan with shared scratchpad and dependencies.",
2300
+ schema: {
2301
+ title: z.string().describe("Plan title describing the analysis goal"),
2302
+ steps: z.union([
2303
+ z.array(z.object({
2304
+ description: z.string(),
2305
+ tool: z.string(),
2306
+ args: z.record(z.string(), z.unknown()),
2307
+ result_key: z.string().optional(),
2308
+ depends_on: z.array(z.string()).optional(),
2309
+ })),
2310
+ z.string().transform((s) => JSON.parse(s)),
2311
+ ]).describe("Steps array: {description, tool, args, result_key?, depends_on?}. JSON string OK."),
2312
+ },
2313
+ handler: async (args) => {
2314
+ const result = await createAnalysisPlan(args.title, args.steps);
2315
+ return result;
2316
+ },
2317
+ },
2318
+ {
2319
+ name: "scratchpad_write",
2320
+ category: "meta",
2321
+ searchHint: "scratchpad write store knowledge cross-step data persist",
2322
+ description: "Write key-value to plan scratchpad for cross-step knowledge sharing.",
2323
+ schema: {
2324
+ plan_id: z.string().describe("Analysis plan identifier"),
2325
+ key: z.string().describe("Key name for the entry"),
2326
+ value: z.string().describe("Value to store"),
2327
+ },
2328
+ handler: async (args) => writeScratchpad(args.plan_id, args.key, args.value),
2329
+ },
2330
+ {
2331
+ name: "scratchpad_read",
2332
+ category: "meta",
2333
+ searchHint: "scratchpad read retrieve knowledge entry",
2334
+ description: "Read a key from a plan's scratchpad. Returns the stored value or null if not found.",
2335
+ schema: {
2336
+ plan_id: z.string().describe("Analysis plan identifier"),
2337
+ key: z.string().describe("Key name to read"),
2338
+ },
2339
+ handler: async (args) => {
2340
+ const result = await readScratchpad(args.plan_id, args.key);
2341
+ return result ?? { error: "Key not found in scratchpad" };
2342
+ },
2343
+ },
2344
+ {
2345
+ name: "scratchpad_list",
2346
+ category: "meta",
2347
+ searchHint: "scratchpad list entries keys",
2348
+ description: "List all entries in a plan's scratchpad with their sizes.",
2349
+ schema: {
2350
+ plan_id: z.string().describe("Analysis plan identifier"),
2351
+ },
2352
+ handler: (args) => listScratchpad(args.plan_id),
2353
+ },
2354
+ {
2355
+ name: "update_step_status",
2356
+ category: "meta",
2357
+ searchHint: "update step status plan progress completed failed",
2358
+ description: "Update step status in plan. Auto-updates plan status on completion.",
2359
+ schema: {
2360
+ plan_id: z.string().describe("Analysis plan identifier"),
2361
+ step_id: z.string().describe("Step identifier (e.g. step_1)"),
2362
+ status: z.enum(["pending", "in_progress", "completed", "failed", "skipped"]).describe("New status for the step"),
2363
+ error: z.string().optional().describe("Error message if status is 'failed'"),
2364
+ },
2365
+ handler: async (args) => {
2366
+ const result = await updateStepStatus(args.plan_id, args.step_id, args.status, args.error);
2367
+ return result;
2368
+ },
2369
+ },
2370
+ {
2371
+ name: "get_analysis_plan",
2372
+ category: "meta",
2373
+ searchHint: "get plan status steps progress",
2374
+ description: "Get the current state of an analysis plan including all step statuses.",
2375
+ schema: {
2376
+ plan_id: z.string().describe("Analysis plan identifier"),
2377
+ },
2378
+ handler: async (args) => {
2379
+ const plan = getPlan(args.plan_id);
2380
+ return plan ?? { error: "Plan not found" };
2381
+ },
2382
+ },
2383
+ {
2384
+ name: "list_analysis_plans",
2385
+ category: "meta",
2386
+ searchHint: "list plans active analysis workflows",
2387
+ description: "List all active analysis plans with their completion status.",
2388
+ schema: {},
2389
+ handler: async () => listPlans(),
2390
+ },
2391
+ // --- Review diff ---
2392
+ {
2393
+ name: "review_diff",
2394
+ category: "diff",
2395
+ searchHint: "review diff static analysis git changes secrets breaking-changes complexity dead-code blast-radius",
2396
+ description: "Run 9 parallel static analysis checks on a git diff: secrets, breaking changes, coupling gaps, complexity, dead-code, blast-radius, bug-patterns, test-gaps, hotspots. Returns a scored verdict (pass/warn/fail) with tiered findings.",
2397
+ schema: {
2398
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2399
+ since: z.string().optional().describe("Base git ref (default: HEAD~1)"),
2400
+ until: z.string().optional().describe("Target ref. Default: HEAD. Special: WORKING, STAGED"),
2401
+ checks: z.string().optional().describe("Comma-separated check names (default: all)"),
2402
+ exclude_patterns: z.string().optional().describe("Comma-separated globs to exclude"),
2403
+ token_budget: zNum().describe("Max tokens (default: 15000)"),
2404
+ max_files: zNum().describe("Warn above N files (default: 50)"),
2405
+ check_timeout_ms: zNum().describe("Per-check timeout ms (default: 8000)"),
2406
+ },
2407
+ handler: async (args) => {
2408
+ const checksArr = args.checks
1272
2409
  ? args.checks.split(",").map((c) => c.trim()).filter(Boolean)
1273
2410
  : undefined;
1274
2411
  const excludeArr = args.exclude_patterns
1275
2412
  ? args.exclude_patterns.split(",").map((p) => p.trim()).filter(Boolean)
1276
2413
  : undefined;
1277
2414
  const opts = {
1278
- repo: args.repo,
2415
+ repo: args.repo,
2416
+ };
2417
+ if (args.since != null)
2418
+ opts.since = args.since;
2419
+ if (args.until != null)
2420
+ opts.until = args.until;
2421
+ if (checksArr != null)
2422
+ opts.checks = checksArr.join(",");
2423
+ if (excludeArr != null)
2424
+ opts.exclude_patterns = excludeArr;
2425
+ if (args.token_budget != null)
2426
+ opts.token_budget = args.token_budget;
2427
+ if (args.max_files != null)
2428
+ opts.max_files = args.max_files;
2429
+ if (args.check_timeout_ms != null)
2430
+ opts.check_timeout_ms = args.check_timeout_ms;
2431
+ const result = await reviewDiff(args.repo, opts);
2432
+ return formatReviewDiff(result);
2433
+ },
2434
+ },
2435
+ // --- Stats ---
2436
+ {
2437
+ name: "usage_stats",
2438
+ category: "meta",
2439
+ searchHint: "usage statistics tool calls tokens timing metrics",
2440
+ outputSchema: OutputSchemas.usageStats,
2441
+ description: "Show usage statistics for all CodeSift tool calls (call counts, tokens, timing, repos)",
2442
+ schema: {},
2443
+ handler: async () => {
2444
+ const stats = await getUsageStats();
2445
+ return { report: formatUsageReport(stats) };
2446
+ },
2447
+ },
2448
+ // ── Session context tools ───────────────────────────────────────────────
2449
+ {
2450
+ name: "get_session_snapshot",
2451
+ category: "session",
2452
+ searchHint: "session context snapshot compaction summary explored symbols files queries",
2453
+ description: "Get a compact ~200 token snapshot of what was explored in this session. Designed to survive context compaction. Call proactively before long tasks.",
2454
+ schema: {
2455
+ repo: z.string().optional().describe("Filter to specific repo. Default: most recent repo."),
2456
+ },
2457
+ handler: async (args) => {
2458
+ return formatSnapshot(getSessionState(), args.repo);
2459
+ },
2460
+ },
2461
+ {
2462
+ name: "get_session_context",
2463
+ category: "session",
2464
+ searchHint: "session context full explored symbols files queries negative evidence",
2465
+ description: "Get full session context: explored symbols, files, queries, and negative evidence (searched but not found). Use get_session_snapshot for a compact version.",
2466
+ schema: {
2467
+ repo: z.string().optional().describe("Filter to specific repo"),
2468
+ include_stale: zBool().describe("Include stale negative evidence entries (default: false)"),
2469
+ },
2470
+ handler: async (args) => {
2471
+ const includeStale = args.include_stale === true || args.include_stale === "true";
2472
+ return getContext(args.repo, includeStale);
2473
+ },
2474
+ },
2475
+ // --- Project Analysis ---
2476
+ {
2477
+ name: "analyze_project",
2478
+ category: "analysis",
2479
+ searchHint: "project profile stack conventions middleware routes rate-limits auth detection",
2480
+ description: "Analyze a repository to extract stack, file classifications, and framework-specific conventions. Returns a structured project profile (schema v1.0) with file:line evidence for convention-level facts.",
2481
+ schema: {
2482
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2483
+ force: zBool().describe("Ignore cached results and re-analyze"),
2484
+ },
2485
+ handler: async (args) => {
2486
+ const result = await analyzeProject(args.repo, {
2487
+ force: args.force,
2488
+ });
2489
+ return result;
2490
+ },
2491
+ },
2492
+ {
2493
+ name: "get_extractor_versions",
2494
+ category: "meta",
2495
+ searchHint: "extractor version cache invalidation profile parser languages",
2496
+ description: "Return parser_languages (tree-sitter symbol extractors) and profile_frameworks (analyze_project detectors). Text tools (search_text, get_file_tree) work on ALL files regardless — use this only for cache invalidation or to check symbol support for a specific language.",
2497
+ schema: {},
2498
+ handler: async () => getExtractorVersions(),
2499
+ },
2500
+ // --- Composite tools ---
2501
+ {
2502
+ name: "audit_scan",
2503
+ category: "analysis",
2504
+ searchHint: "audit scan code quality CQ gates dead code clones complexity patterns",
2505
+ description: "Run 5 analysis tools in parallel, return findings keyed by CQ gate. One call replaces sequential find_dead_code + search_patterns + find_clones + analyze_complexity + analyze_hotspots. Returns: CQ8 (empty catch), CQ11 (complexity), CQ13 (dead code), CQ14 (clones), CQ17 (perf anti-patterns).",
2506
+ schema: {
2507
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2508
+ file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
2509
+ include_tests: zBool().describe("Include test files (default: false)"),
2510
+ checks: z.string().optional().describe("Comma-separated CQ gates to check (default: all). E.g. 'CQ8,CQ11,CQ14'"),
2511
+ },
2512
+ handler: async (args) => {
2513
+ const checks = args.checks ? args.checks.split(",").map(s => s.trim()) : undefined;
2514
+ const opts = {};
2515
+ if (args.file_pattern)
2516
+ opts.file_pattern = args.file_pattern;
2517
+ if (args.include_tests)
2518
+ opts.include_tests = args.include_tests;
2519
+ if (checks)
2520
+ opts.checks = checks;
2521
+ const result = await auditScan(args.repo, opts);
2522
+ return formatAuditScan(result);
2523
+ },
2524
+ },
2525
+ // --- New tools (agent-requested) ---
2526
+ {
2527
+ name: "index_status",
2528
+ category: "meta",
2529
+ searchHint: "index status indexed repo check files symbols languages",
2530
+ description: "Check whether a repository is indexed and return index metadata: file count, symbol count, language breakdown, text_stub languages (no parser). Use this before calling symbol-based tools on unfamiliar repos.",
2531
+ schema: {
2532
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2533
+ },
2534
+ handler: async (args) => {
2535
+ const result = await indexStatus(args.repo);
2536
+ if (!result.indexed)
2537
+ return "index_status: NOT INDEXED — run index_folder first";
2538
+ const langs = Object.entries(result.language_breakdown ?? {})
2539
+ .sort(([, a], [, b]) => b - a)
2540
+ .map(([lang, count]) => `${lang}(${count})`)
2541
+ .join(", ");
2542
+ const parts = [
2543
+ `index_status: indexed=true`,
2544
+ `files: ${result.file_count} | symbols: ${result.symbol_count} | last_indexed: ${result.last_indexed}`,
2545
+ `languages: ${langs}`,
2546
+ ];
2547
+ if (result.text_stub_languages) {
2548
+ parts.push(`text_stub (no parser): ${result.text_stub_languages.join(", ")}`);
2549
+ }
2550
+ return parts.join("\n");
2551
+ },
2552
+ },
2553
+ {
2554
+ name: "find_perf_hotspots",
2555
+ category: "analysis",
2556
+ searchHint: "performance perf hotspot N+1 unbounded query sync handler pagination findMany pLimit",
2557
+ description: "Scan for 6 performance anti-patterns: unbounded DB queries, sync I/O in handlers, N+1 loops, unbounded Promise.all, missing pagination, expensive recompute. Returns findings grouped by severity (high/medium/low) with fix hints.",
2558
+ schema: {
2559
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2560
+ patterns: z.string().optional().describe("Comma-separated pattern names to check (default: all). Options: unbounded-query, sync-in-handler, n-plus-one, unbounded-parallel, missing-pagination, expensive-recompute"),
2561
+ file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
2562
+ include_tests: zBool().describe("Include test files (default: false)"),
2563
+ max_results: zNum().describe("Max findings to return (default: 50)"),
2564
+ },
2565
+ handler: async (args) => {
2566
+ const patterns = args.patterns
2567
+ ? args.patterns.split(",").map((s) => s.trim()).filter(Boolean)
2568
+ : undefined;
2569
+ const opts = {};
2570
+ if (patterns)
2571
+ opts.patterns = patterns;
2572
+ if (args.file_pattern != null)
2573
+ opts.file_pattern = args.file_pattern;
2574
+ if (args.include_tests != null)
2575
+ opts.include_tests = args.include_tests;
2576
+ if (args.max_results != null)
2577
+ opts.max_results = args.max_results;
2578
+ const result = await findPerfHotspots(args.repo, opts);
2579
+ return formatPerfHotspots(result);
2580
+ },
2581
+ },
2582
+ {
2583
+ name: "fan_in_fan_out",
2584
+ category: "architecture",
2585
+ searchHint: "fan-in fan-out coupling dependencies imports hub afferent efferent instability threshold",
2586
+ description: "Analyze import graph to find most-imported files (fan-in), most-dependent files (fan-out), and hub files (high both — instability risk). Returns coupling score 0-100. Use min_fan_in/min_fan_out for threshold-based audits ('all files with fan_in > 50') instead of top_n cap.",
2587
+ schema: {
2588
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2589
+ path: z.string().optional().describe("Focus on files in this directory"),
2590
+ top_n: zNum().describe("How many entries per list (default: 20)"),
2591
+ min_fan_in: zNum().describe("Only return files with fan_in >= this value (default: 0). Use for audits."),
2592
+ min_fan_out: zNum().describe("Only return files with fan_out >= this value (default: 0). Use for audits."),
2593
+ },
2594
+ handler: async (args) => {
2595
+ const opts = {};
2596
+ if (args.path != null)
2597
+ opts.path = args.path;
2598
+ if (args.top_n != null)
2599
+ opts.top_n = args.top_n;
2600
+ if (args.min_fan_in != null)
2601
+ opts.min_fan_in = args.min_fan_in;
2602
+ if (args.min_fan_out != null)
2603
+ opts.min_fan_out = args.min_fan_out;
2604
+ const result = await fanInFanOut(args.repo, opts);
2605
+ return formatFanInFanOut(result);
2606
+ },
2607
+ },
2608
+ {
2609
+ name: "co_change_analysis",
2610
+ category: "architecture",
2611
+ searchHint: "co-change temporal coupling git history Jaccard co-commit correlation cluster",
2612
+ description: "Analyze git history to find files that frequently change together (temporal coupling). Returns file pairs ranked by Jaccard similarity, plus clusters of always-co-changed files. Useful for detecting hidden dependencies.",
2613
+ schema: {
2614
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2615
+ since_days: zNum().describe("Analyze last N days of history (default: 180)"),
2616
+ min_support: zNum().describe("Minimum co-commits to include a pair (default: 3)"),
2617
+ min_jaccard: zNum().describe("Minimum Jaccard similarity threshold (default: 0.3)"),
2618
+ path: z.string().optional().describe("Focus on files in this directory"),
2619
+ top_n: zNum().describe("Max pairs to return (default: 30)"),
2620
+ },
2621
+ handler: async (args) => {
2622
+ const opts = {};
2623
+ if (args.since_days != null)
2624
+ opts.since_days = args.since_days;
2625
+ if (args.min_support != null)
2626
+ opts.min_support = args.min_support;
2627
+ if (args.min_jaccard != null)
2628
+ opts.min_jaccard = args.min_jaccard;
2629
+ if (args.path != null)
2630
+ opts.path = args.path;
2631
+ if (args.top_n != null)
2632
+ opts.top_n = args.top_n;
2633
+ const result = await coChangeAnalysis(args.repo, opts);
2634
+ return formatCoChange(result);
2635
+ },
2636
+ },
2637
+ {
2638
+ name: "architecture_summary",
2639
+ category: "architecture",
2640
+ searchHint: "architecture summary overview structure stack framework communities coupling circular dependencies entry points",
2641
+ description: "One-call architecture profile: stack detection, module communities, coupling hotspots, circular dependencies, LOC distribution, and entry points. Runs 5 analyses in parallel. Supports Mermaid diagram output.",
2642
+ schema: {
2643
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2644
+ focus: z.string().optional().describe("Focus on this directory path"),
2645
+ output_format: z.enum(["text", "mermaid"]).optional().describe("Output format (default: text)"),
2646
+ token_budget: zNum().describe("Max tokens for output"),
2647
+ },
2648
+ handler: async (args) => {
2649
+ const opts = {};
2650
+ if (args.focus != null)
2651
+ opts.focus = args.focus;
2652
+ if (args.output_format != null)
2653
+ opts.output_format = args.output_format;
2654
+ if (args.token_budget != null)
2655
+ opts.token_budget = args.token_budget;
2656
+ const result = await architectureSummary(args.repo, opts);
2657
+ return formatArchitectureSummary(result);
2658
+ },
2659
+ },
2660
+ {
2661
+ name: "explain_query",
2662
+ category: "analysis",
2663
+ searchHint: "explain query SQL Prisma ORM database performance EXPLAIN ANALYZE findMany pagination index",
2664
+ description: "Parse a Prisma call and generate approximate SQL with EXPLAIN ANALYZE. Detects: unbounded queries, N+1 risks from includes, missing indexes. MVP: Prisma only. Supports postgresql/mysql/sqlite dialects.",
2665
+ schema: {
2666
+ code: z.string().describe("Prisma code snippet (e.g. prisma.user.findMany({...}))"),
2667
+ dialect: z.enum(["postgresql", "mysql", "sqlite"]).optional().describe("SQL dialect (default: postgresql)"),
2668
+ },
2669
+ handler: async (args) => {
2670
+ const eqOpts = {};
2671
+ if (args.dialect != null)
2672
+ eqOpts.dialect = args.dialect;
2673
+ const result = explainQuery(args.code, eqOpts);
2674
+ const parts = [
2675
+ `explain_query: prisma.${result.parsed.model}.${result.parsed.method}`,
2676
+ `─── Generated SQL (${args.dialect ?? "postgresql"}) ───`,
2677
+ ` ${result.sql}`,
2678
+ `─── EXPLAIN command ───`,
2679
+ ` ${result.explain_command}`,
2680
+ ];
2681
+ if (result.warnings.length > 0) {
2682
+ parts.push("─── Warnings ───");
2683
+ for (const w of result.warnings)
2684
+ parts.push(` ⚠ ${w}`);
2685
+ }
2686
+ if (result.optimization_hints.length > 0) {
2687
+ parts.push("─── Optimization hints ───");
2688
+ for (const h of result.optimization_hints)
2689
+ parts.push(` → ${h}`);
2690
+ }
2691
+ return parts.join("\n");
2692
+ },
2693
+ },
2694
+ // --- NestJS analysis tools (sub-tools absorbed into nest_audit) ---
2695
+ {
2696
+ name: "nest_audit",
2697
+ category: "nestjs",
2698
+ searchHint: "nestjs audit analysis comprehensive module di guard route lifecycle pattern graphql websocket schedule typeorm microservice hook onModuleInit onApplicationBootstrap shutdown dependency graph circular import boundary injection provider constructor inject cycle interceptor pipe filter middleware chain security endpoint api map inventory list all params resolver query mutation subscription apollo gateway subscribemessage socketio realtime event cron interval timeout scheduled job task onevent listener entity relation onetomany manytoone database schema messagepattern eventpattern kafka rabbitmq nats transport request pipeline handler execution flow visualization bull bullmq queue processor process background worker scope transient singleton performance escalation swagger openapi documentation apiproperty apioperation apiresponse contract extract",
2699
+ description: "One-call NestJS architecture audit: modules, DI, guards, routes, lifecycle, patterns, GraphQL, WebSocket, schedule, TypeORM, microservices.",
2700
+ schema: {
2701
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2702
+ checks: z.string().optional().describe("Comma-separated checks (default: all). Options: modules,routes,di,guards,lifecycle,patterns,graphql,websocket,schedule,typeorm,microservice"),
2703
+ },
2704
+ handler: async (args) => {
2705
+ const checks = args.checks?.split(",").map((s) => s.trim()).filter(Boolean);
2706
+ return nestAudit(args.repo ?? "", checks ? { checks } : undefined);
2707
+ },
2708
+ },
2709
+ // --- Agent config audit ---
2710
+ {
2711
+ name: "audit_agent_config",
2712
+ category: "meta",
2713
+ searchHint: "audit agent config CLAUDE.md cursorrules stale symbols dead paths token waste redundancy",
2714
+ description: "Scan a config file (CLAUDE.md, .cursorrules) for stale symbol references, dead file paths, token cost, and redundancy. Cross-references against the CodeSift index. Optionally compares two config files for redundant content blocks.",
2715
+ schema: {
2716
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2717
+ config_path: z.string().optional().describe("Path to config file (default: CLAUDE.md in repo root)"),
2718
+ compare_with: z.string().optional().describe("Path to second config file for redundancy detection"),
2719
+ },
2720
+ handler: async (args) => {
2721
+ const opts = {};
2722
+ if (args.config_path != null)
2723
+ opts.config_path = args.config_path;
2724
+ if (args.compare_with != null)
2725
+ opts.compare_with = args.compare_with;
2726
+ const result = await auditAgentConfig(args.repo, opts);
2727
+ const parts = [`audit_agent_config: ${result.config_path}`, `token_cost: ~${result.token_cost} tokens`];
2728
+ if (result.stale_symbols.length > 0) {
2729
+ parts.push(`\n─── Stale Symbols (${result.stale_symbols.length}) ───`);
2730
+ for (const s of result.stale_symbols)
2731
+ parts.push(` line ${s.line}: \`${s.symbol}\` — not found in index`);
2732
+ }
2733
+ if (result.dead_paths.length > 0) {
2734
+ parts.push(`\n─── Dead Paths (${result.dead_paths.length}) ───`);
2735
+ for (const p of result.dead_paths)
2736
+ parts.push(` line ${p.line}: ${p.path} — file not in index`);
2737
+ }
2738
+ if (result.redundant_blocks.length > 0) {
2739
+ parts.push(`\n─── Redundant Blocks (${result.redundant_blocks.length}) ───`);
2740
+ for (const b of result.redundant_blocks)
2741
+ parts.push(` "${b.text.slice(0, 60)}..." found in: ${b.found_in.join(", ")}`);
2742
+ }
2743
+ if (result.findings.length > 0) {
2744
+ parts.push(`\n─── Findings ───`);
2745
+ for (const f of result.findings)
2746
+ parts.push(` ${f}`);
2747
+ }
2748
+ if (result.stale_symbols.length === 0 && result.dead_paths.length === 0 && result.redundant_blocks.length === 0) {
2749
+ parts.push("\nAll references valid. No issues found.");
2750
+ }
2751
+ return parts.join("\n");
2752
+ },
2753
+ },
2754
+ // --- Test impact analysis ---
2755
+ {
2756
+ name: "test_impact_analysis",
2757
+ category: "analysis",
2758
+ searchHint: "test impact analysis affected tests changed files CI confidence which tests to run",
2759
+ description: "Determine which tests to run based on changed files. Uses impact analysis, co-change correlation, and naming convention matching. Returns prioritized test list with confidence scores and a suggested test command.",
2760
+ schema: {
2761
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2762
+ since: z.string().optional().describe("Git ref to compare from (default: HEAD~1)"),
2763
+ until: z.string().optional().describe("Git ref to compare to (default: HEAD)"),
2764
+ },
2765
+ handler: async (args) => {
2766
+ const opts = {};
2767
+ if (args.since != null)
2768
+ opts.since = args.since;
2769
+ if (args.until != null)
2770
+ opts.until = args.until;
2771
+ const result = await testImpactAnalysis(args.repo, opts);
2772
+ const parts = [`test_impact: ${result.affected_tests.length} tests affected | ${result.changed_files.length} files changed`];
2773
+ if (result.suggested_command)
2774
+ parts.push(`\nRun: ${result.suggested_command}`);
2775
+ if (result.affected_tests.length > 0) {
2776
+ parts.push("\n─── Affected Tests ───");
2777
+ for (const t of result.affected_tests) {
2778
+ parts.push(` ${t.test_file} (confidence: ${t.confidence.toFixed(2)}) — ${t.reasons.join(", ")}`);
2779
+ }
2780
+ }
2781
+ else {
2782
+ parts.push("\nNo affected tests found.");
2783
+ }
2784
+ return parts.join("\n");
2785
+ },
2786
+ },
2787
+ // --- Dependency audit (composite) ---
2788
+ {
2789
+ name: "dependency_audit",
2790
+ category: "analysis",
2791
+ searchHint: "dependency audit npm vulnerabilities CVE licenses outdated freshness lockfile drift supply chain",
2792
+ description: "Composite dependency health check: vulnerabilities (npm/pnpm/yarn audit), licenses (problematic copyleft detection), freshness (outdated count + major gaps), lockfile integrity (drift, duplicates). Runs 4 sub-checks in parallel. Replaces ~40 manual bash calls for D1-D5 audit dimensions.",
2793
+ schema: {
2794
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2795
+ workspace_path: z.string().optional().describe("Workspace path (default: index root)"),
2796
+ skip_licenses: zBool().describe("Skip license check (faster, default: false)"),
2797
+ min_severity: z.enum(["low", "moderate", "high", "critical"]).optional().describe("Filter vulnerabilities by minimum severity"),
2798
+ },
2799
+ handler: async (args) => {
2800
+ const opts = {};
2801
+ if (args.workspace_path != null)
2802
+ opts.workspace_path = args.workspace_path;
2803
+ if (args.skip_licenses != null)
2804
+ opts.skip_licenses = args.skip_licenses;
2805
+ if (args.min_severity != null)
2806
+ opts.min_severity = args.min_severity;
2807
+ const result = await dependencyAudit(args.repo, opts);
2808
+ const parts = [
2809
+ `dependency_audit: ${result.workspace} (${result.package_manager}) — ${result.duration_ms}ms`,
2810
+ `\n─── Vulnerabilities (${result.vulnerabilities.total}) ───`,
2811
+ ` critical: ${result.vulnerabilities.by_severity.critical} | high: ${result.vulnerabilities.by_severity.high} | moderate: ${result.vulnerabilities.by_severity.moderate} | low: ${result.vulnerabilities.by_severity.low}`,
2812
+ ];
2813
+ for (const v of result.vulnerabilities.findings.slice(0, 10)) {
2814
+ parts.push(` [${v.severity}] ${v.package}${v.fix_available ? " (fix available)" : ""}`);
2815
+ }
2816
+ parts.push(`\n─── Licenses (${result.licenses.total}) ───`);
2817
+ if (result.licenses.problematic.length > 0) {
2818
+ parts.push(` ⚠ Problematic: ${result.licenses.problematic.length}`);
2819
+ for (const l of result.licenses.problematic.slice(0, 10))
2820
+ parts.push(` ${l.package}: ${l.license}`);
2821
+ }
2822
+ parts.push(`\n─── Freshness (${result.freshness.outdated_count} outdated) ───`);
2823
+ for (const o of result.freshness.major_gaps.slice(0, 10)) {
2824
+ parts.push(` ${o.package}: ${o.current} → ${o.latest} (${o.major_gap} major)`);
2825
+ }
2826
+ parts.push(`\n─── Lockfile ───`);
2827
+ parts.push(` present: ${result.lockfile.present} | issues: ${result.lockfile.issues.length}`);
2828
+ for (const i of result.lockfile.issues.slice(0, 5))
2829
+ parts.push(` ${i.type}: ${i.message}`);
2830
+ if (result.errors.length > 0) {
2831
+ parts.push(`\n─── Sub-check errors (${result.errors.length}) ───`);
2832
+ for (const e of result.errors)
2833
+ parts.push(` ${e}`);
2834
+ }
2835
+ return parts.join("\n");
2836
+ },
2837
+ },
2838
+ // --- Migration safety linter (squawk wrapper) ---
2839
+ {
2840
+ name: "migration_lint",
2841
+ category: "analysis",
2842
+ searchHint: "migration lint squawk SQL postgresql safety linter unsafe-migration not-null drop-column alter-column-type concurrently",
2843
+ description: "PostgreSQL migration safety linter via squawk wrapper. Detects 30+ anti-patterns: NOT NULL without default, DROP COLUMN, ALTER COLUMN TYPE, CREATE INDEX without CONCURRENTLY, etc. Requires squawk CLI installed (brew install squawk OR cargo install squawk-cli). Auto-discovers prisma/migrations, migrations/, db/migrate, drizzle/.",
2844
+ schema: {
2845
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2846
+ migration_glob: z.string().optional().describe("Custom migration file glob pattern"),
2847
+ excluded_rules: z.union([z.array(z.string()), z.string().transform((s) => s.split(",").map((x) => x.trim()))]).optional().describe("Squawk rules to exclude (comma-sep or array)"),
2848
+ pg_version: z.string().optional().describe("PostgreSQL version for version-aware rules (e.g. '13')"),
2849
+ },
2850
+ handler: async (args) => {
2851
+ const opts = {};
2852
+ if (args.migration_glob != null)
2853
+ opts.migration_glob = args.migration_glob;
2854
+ if (args.excluded_rules != null)
2855
+ opts.excluded_rules = args.excluded_rules;
2856
+ if (args.pg_version != null)
2857
+ opts.pg_version = args.pg_version;
2858
+ const result = await migrationLint(args.repo, opts);
2859
+ if (!result.squawk_installed) {
2860
+ return `migration_lint: squawk not installed.\n${result.install_hint}\n${result.files_checked} migration files would be checked.`;
2861
+ }
2862
+ const parts = [
2863
+ `migration_lint: squawk ${result.squawk_version ?? "unknown"} — ${result.files_checked} files checked`,
2864
+ `errors: ${result.by_severity.error} | warnings: ${result.by_severity.warning}`,
2865
+ ];
2866
+ if (result.findings.length > 0) {
2867
+ parts.push("\n─── Findings ───");
2868
+ for (const f of result.findings.slice(0, 30)) {
2869
+ parts.push(` [${f.level}] ${f.file}:${f.line} ${f.rule} — ${f.message}`);
2870
+ }
2871
+ }
2872
+ else {
2873
+ parts.push("\nNo issues found.");
2874
+ }
2875
+ return parts.join("\n");
2876
+ },
2877
+ },
2878
+ // --- Prisma schema analyzer ---
2879
+ {
2880
+ name: "analyze_prisma_schema",
2881
+ category: "analysis",
2882
+ searchHint: "prisma schema analyze ast model field index foreign-key relation soft-delete enum coverage",
2883
+ description: "Parse schema.prisma into structured AST. Returns model coverage: fields, indexes, FKs, relations, soft-delete detection, FK index coverage %, unindexed FKs (audit warning), status-as-String suggestions. Uses @mrleebo/prisma-ast for proper AST parsing (vs regex-only extractor).",
2884
+ schema: {
2885
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2886
+ schema_path: z.string().optional().describe("Path to schema.prisma (default: auto-detected)"),
2887
+ },
2888
+ handler: async (args) => {
2889
+ const opts = {};
2890
+ if (args.schema_path != null)
2891
+ opts.schema_path = args.schema_path;
2892
+ const result = await analyzePrismaSchema(args.repo, opts);
2893
+ const parts = [
2894
+ `analyze_prisma_schema: ${result.schema_path}`,
2895
+ `models: ${result.model_count} | enums: ${result.enum_count}`,
2896
+ `\n─── FK Index Coverage ───`,
2897
+ ` ${result.totals.fk_with_index}/${result.totals.fk_columns} FKs indexed (${result.totals.fk_index_coverage_pct.toFixed(1)}%)`,
2898
+ ` unindexed FKs: ${result.totals.fk_without_index}`,
2899
+ ` soft-delete models: ${result.totals.soft_delete_models}`,
2900
+ ` composite indexes: ${result.totals.composite_indexes} | single indexes: ${result.totals.single_indexes}`,
2901
+ ];
2902
+ if (result.warnings.length > 0) {
2903
+ parts.push(`\n─── Warnings (${result.warnings.length}) ───`);
2904
+ for (const w of result.warnings.slice(0, 20))
2905
+ parts.push(` ⚠ ${w}`);
2906
+ }
2907
+ // List models with audit issues
2908
+ const auditModels = result.models.filter((m) => m.fk_columns_without_index.length > 0 || m.status_like_string_fields.length > 0);
2909
+ if (auditModels.length > 0) {
2910
+ parts.push(`\n─── Models with issues (${auditModels.length}) ───`);
2911
+ for (const m of auditModels.slice(0, 15)) {
2912
+ const issues = [];
2913
+ if (m.fk_columns_without_index.length > 0)
2914
+ issues.push(`unindexed FKs: ${m.fk_columns_without_index.join(",")}`);
2915
+ if (m.status_like_string_fields.length > 0)
2916
+ issues.push(`status-as-String: ${m.status_like_string_fields.join(",")}`);
2917
+ parts.push(` ${m.name} — ${issues.join(" | ")}`);
2918
+ }
2919
+ }
2920
+ return parts.join("\n");
2921
+ },
2922
+ },
2923
+ // --- Astro tools ---
2924
+ {
2925
+ name: "astro_analyze_islands",
2926
+ category: "analysis",
2927
+ searchHint: "astro islands client hydration directives framework",
2928
+ description: "Analyze Astro islands (client:* directives) in a repo. Finds all interactive components with hydration directives, lists server islands with fallback status, and optionally generates optimization recommendations.",
2929
+ schema: {
2930
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2931
+ path_prefix: z.string().optional().describe("Only scan files under this path prefix"),
2932
+ include_recommendations: z.boolean().default(true).describe("Include optimization recommendations (default: true)"),
2933
+ },
2934
+ handler: async (args) => {
2935
+ const opts = {};
2936
+ if (args.repo != null)
2937
+ opts.repo = args.repo;
2938
+ if (args.path_prefix != null)
2939
+ opts.path_prefix = args.path_prefix;
2940
+ if (args.include_recommendations != null)
2941
+ opts.include_recommendations = args.include_recommendations;
2942
+ return await astroAnalyzeIslands(opts);
2943
+ },
2944
+ },
2945
+ {
2946
+ name: "astro_hydration_audit",
2947
+ category: "analysis",
2948
+ searchHint: "astro hydration audit anti-patterns client load",
2949
+ description: "Audit Astro hydration usage for anti-patterns such as client:load on heavy components, missing client directives, or suboptimal hydration strategies. Returns issues grouped by severity with a letter grade.",
2950
+ schema: {
2951
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2952
+ severity: z.enum(["all", "warnings", "errors"]).default("all").describe("Filter issues by severity (default: all)"),
2953
+ path_prefix: z.string().optional().describe("Only scan files under this path prefix"),
2954
+ fail_on: z.enum(["error", "warning", "info"]).optional().describe("Set exit_code gate: 'error' exits 1 on any errors; 'warning' exits 2 on warnings; 'info' exits 2 on info or warnings"),
2955
+ },
2956
+ handler: async (args) => {
2957
+ const opts = {};
2958
+ if (args.repo != null)
2959
+ opts.repo = args.repo;
2960
+ if (args.severity != null)
2961
+ opts.severity = args.severity;
2962
+ if (args.path_prefix != null)
2963
+ opts.path_prefix = args.path_prefix;
2964
+ if (args.fail_on != null)
2965
+ opts.fail_on = args.fail_on;
2966
+ return await astroHydrationAudit(opts);
2967
+ },
2968
+ },
2969
+ {
2970
+ name: "astro_route_map",
2971
+ category: "navigation",
2972
+ searchHint: "astro routes pages endpoints file-based routing",
2973
+ description: "Map all Astro routes (pages + API endpoints) discovered from the file-based routing structure. Returns routes with type, dynamic params, and handler symbols. Supports json/tree/table output formats.",
2974
+ schema: {
2975
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2976
+ include_endpoints: z.boolean().default(true).describe("Include API endpoint routes (default: true)"),
2977
+ output_format: z.enum(["json", "tree", "table"]).default("json").describe("Output format: json | tree | table (default: json)"),
2978
+ },
2979
+ handler: async (args) => {
2980
+ const opts = {};
2981
+ if (args.repo != null)
2982
+ opts.repo = args.repo;
2983
+ if (args.include_endpoints != null)
2984
+ opts.include_endpoints = args.include_endpoints;
2985
+ if (args.output_format != null)
2986
+ opts.output_format = args.output_format;
2987
+ return await astroRouteMap(opts);
2988
+ },
2989
+ },
2990
+ {
2991
+ name: "astro_config_analyze",
2992
+ category: "analysis",
2993
+ searchHint: "astro config integrations adapter output mode",
2994
+ description: "Analyze an Astro project's configuration file (astro.config.mjs/ts/js). Extracts output mode (static/server/hybrid), adapter, integrations, site URL, and base path. Identifies dynamic/unresolved config.",
2995
+ schema: {
2996
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
2997
+ },
2998
+ handler: async (args) => {
2999
+ const index = await getCodeIndex(args.repo ?? "");
3000
+ if (!index)
3001
+ throw new Error("Repository not found — run index_folder first");
3002
+ return await astroConfigAnalyze({ project_root: index.root });
3003
+ },
3004
+ },
3005
+ {
3006
+ name: "astro_actions_audit",
3007
+ category: "analysis",
3008
+ searchHint: "astro actions defineAction zod refine passthrough multipart file enctype audit",
3009
+ description: "Audit Astro Actions (src/actions/index.ts) for 6 known anti-patterns (AA01-AA06): missing handler return, top-level .refine() (Astro issue #11641), .passthrough() usage (issue #11693), File schema without multipart form, server-side invocation via actions.xxx(), and client calls to unknown actions. Returns issues grouped by severity with an A/B/C/D score.",
3010
+ schema: {
3011
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3012
+ severity: z.enum(["all", "warnings", "errors"]).default("all").describe("Filter issues by severity (default: all)"),
3013
+ },
3014
+ handler: async (args) => {
3015
+ const opts = {};
3016
+ if (args.repo != null)
3017
+ opts.repo = args.repo;
3018
+ if (args.severity != null)
3019
+ opts.severity = args.severity;
3020
+ return await astroActionsAudit(opts);
3021
+ },
3022
+ },
3023
+ {
3024
+ name: "astro_content_collections",
3025
+ category: "analysis",
3026
+ searchHint: "astro content collections defineCollection zod schema reference glob loader frontmatter",
3027
+ description: "Parse an Astro content collections config (src/content.config.ts or legacy src/content/config.ts), extract each collection's loader + Zod schema fields, build a reference() graph, and optionally validate entry frontmatter against required fields.",
3028
+ schema: {
3029
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3030
+ validate_entries: z.boolean().default(true).describe("Validate entry frontmatter against required schema fields (default: true)"),
3031
+ },
3032
+ handler: async (args) => {
3033
+ const index = await getCodeIndex(args.repo ?? "");
3034
+ if (!index)
3035
+ throw new Error("Repository not found — run index_folder first");
3036
+ const opts = { project_root: index.root };
3037
+ if (args.validate_entries != null)
3038
+ opts.validate_entries = args.validate_entries;
3039
+ return await astroContentCollections(opts);
3040
+ },
3041
+ },
3042
+ {
3043
+ name: "astro_audit",
3044
+ category: "analysis",
3045
+ searchHint: "astro meta audit full health check score gates recommendations islands hydration routes config actions content migration patterns",
3046
+ description: "One-call Astro project health check: runs all 7 Astro tools + 13 Astro patterns in parallel, returns unified {score, gates, sections, recommendations}. Mirrors react_quickstart pattern.",
3047
+ schema: {
3048
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3049
+ skip: z.array(z.string()).optional().describe("Sections to skip: config, hydration, routes, actions, content, migration, patterns"),
3050
+ },
3051
+ handler: async (args) => {
3052
+ const opts = {};
3053
+ if (args.repo != null)
3054
+ opts.repo = args.repo;
3055
+ if (args.skip != null)
3056
+ opts.skip = args.skip;
3057
+ return await astroAudit(opts);
3058
+ },
3059
+ },
3060
+ // --- Hono framework tools (Task 23) ---
3061
+ {
3062
+ name: "trace_middleware_chain",
3063
+ category: "graph",
3064
+ searchHint: "hono middleware chain trace order scope auth use conditional applied_when if method header path basicAuth gated",
3065
+ description: "Hono middleware introspection. Three query modes: (1) route mode — pass path (+optional method) to get the chain effective for that route; (2) scope mode — pass scope literal (e.g. '/posts/*') to get that specific app.use chain; (3) app-wide mode — omit path and scope to get every chain flattened. Any mode supports only_conditional=true to filter to entries with applied_when populated, so the blog-API pattern (basicAuth wrapped in `if (method !== 'GET')`) is surfaced as gated rather than missed. Absorbs the former trace_conditional_middleware tool.",
3066
+ schema: {
3067
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3068
+ path: z.string().optional().describe("Route path to look up (e.g. '/api/users/:id'). Omit for scope or app-wide query."),
3069
+ method: z.string().optional().describe("HTTP method filter (GET, POST, etc.). Only used in route mode."),
3070
+ scope: z.string().optional().describe("Exact middleware scope literal (e.g. '/posts/*'). Mutually exclusive with path."),
3071
+ only_conditional: z.boolean().optional().describe("Filter entries to those whose applied_when field is populated (conditional middleware)."),
3072
+ },
3073
+ handler: async (args) => {
3074
+ const { traceMiddlewareChain } = await import("./tools/hono-middleware-chain.js");
3075
+ const opts = {};
3076
+ if (args.scope !== undefined)
3077
+ opts.scope = args.scope;
3078
+ if (args.only_conditional !== undefined)
3079
+ opts.only_conditional = args.only_conditional;
3080
+ return await traceMiddlewareChain(args.repo, args.path, args.method, Object.keys(opts).length > 0 ? opts : undefined);
3081
+ },
3082
+ },
3083
+ {
3084
+ name: "analyze_hono_app",
3085
+ category: "analysis",
3086
+ searchHint: "hono overview analyze app routes middleware runtime env bindings rpc",
3087
+ description: "Complete Hono application overview: routes grouped by method/scope, middleware map, context vars, OpenAPI status, RPC exports (flags Issue #3869 slow pattern), runtime, env bindings. One call for full project analysis.",
3088
+ schema: {
3089
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3090
+ entry_file: z.string().optional().describe("Hono entry file (auto-detected if omitted)"),
3091
+ force_refresh: z.boolean().optional().describe("Clear cache and rebuild"),
3092
+ },
3093
+ handler: async (args) => {
3094
+ const { analyzeHonoApp } = await import("./tools/hono-analyze-app.js");
3095
+ return await analyzeHonoApp(args.repo, args.entry_file, args.force_refresh);
3096
+ },
3097
+ },
3098
+ {
3099
+ name: "trace_context_flow",
3100
+ category: "analysis",
3101
+ searchHint: "hono context flow c.set c.get c.var c.env middleware variable unguarded",
3102
+ description: "Trace Hono context variable flow (c.set/c.get/c.var/c.env). Detects MISSING_CONTEXT_VARIABLE findings where routes access variables that no middleware in their scope sets.",
3103
+ schema: {
3104
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3105
+ variable: z.string().optional().describe("Specific variable name to trace (default: all)"),
3106
+ },
3107
+ handler: async (args) => {
3108
+ const { traceContextFlow } = await import("./tools/hono-context-flow.js");
3109
+ return await traceContextFlow(args.repo, args.variable);
3110
+ },
3111
+ },
3112
+ {
3113
+ name: "extract_api_contract",
3114
+ category: "analysis",
3115
+ searchHint: "hono openapi contract api schema createRoute zValidator",
3116
+ description: "Extract OpenAPI-style API contract from a Hono app. Uses explicit createRoute() definitions when available, infers from regular routes otherwise. Format: 'openapi' (paths object) or 'summary' (table).",
3117
+ schema: {
3118
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3119
+ entry_file: z.string().optional().describe("Hono entry file (auto-detected if omitted)"),
3120
+ format: z.enum(["openapi", "summary"]).optional().describe("Output format (default: openapi)"),
3121
+ },
3122
+ handler: async (args) => {
3123
+ const { extractApiContract } = await import("./tools/hono-api-contract.js");
3124
+ return await extractApiContract(args.repo, args.entry_file, args.format);
3125
+ },
3126
+ },
3127
+ {
3128
+ name: "trace_rpc_types",
3129
+ category: "analysis",
3130
+ searchHint: "hono rpc client type export typeof slow pattern Issue 3869 compile time",
3131
+ description: "Analyze Hono RPC type exports. Detects the slow `export type X = typeof app` pattern from Issue #3869 (8-min CI compile time) and recommends splitting into per-route-group types.",
3132
+ schema: {
3133
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3134
+ },
3135
+ handler: async (args) => {
3136
+ const { traceRpcTypes } = await import("./tools/hono-rpc-types.js");
3137
+ return await traceRpcTypes(args.repo);
3138
+ },
3139
+ },
3140
+ {
3141
+ name: "audit_hono_security",
3142
+ category: "security",
3143
+ searchHint: "hono security audit rate limit secure headers auth order csrf env regression createMiddleware BlankEnv Issue 3587",
3144
+ description: "Security + type-safety audit of a Hono app. Rules: missing-secure-headers (global), missing-rate-limit + missing-auth (mutation routes, conditional-middleware aware via applied_when), auth-ordering (auth after non-auth in chain), env-regression (plain createMiddleware in 3+ chains — Hono Issue #3587, absorbed from the former detect_middleware_env_regression tool). Returns prioritized findings plus heuristic disclaimers via `notes` field for best-effort rules.",
3145
+ schema: {
3146
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3147
+ },
3148
+ handler: async (args) => {
3149
+ const { auditHonoSecurity } = await import("./tools/hono-security.js");
3150
+ return await auditHonoSecurity(args.repo);
3151
+ },
3152
+ },
3153
+ {
3154
+ name: "visualize_hono_routes",
3155
+ category: "reporting",
3156
+ searchHint: "hono routes visualize mermaid tree diagram documentation",
3157
+ description: "Produce a visualization of Hono routing topology. Supports 'mermaid' (diagram) and 'tree' (ASCII) formats.",
3158
+ schema: {
3159
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3160
+ format: z.enum(["mermaid", "tree"]).optional().describe("Output format (default: tree)"),
3161
+ },
3162
+ handler: async (args) => {
3163
+ const { visualizeHonoRoutes } = await import("./tools/hono-visualize.js");
3164
+ return await visualizeHonoRoutes(args.repo, args.format);
3165
+ },
3166
+ },
3167
+ // --- Hono Phase 2 tools (T13) ---
3168
+ {
3169
+ name: "analyze_inline_handler",
3170
+ category: "analysis",
3171
+ searchHint: "hono inline handler analyze c.json c.text status response error db fetch context",
3172
+ description: "Structured body analysis for each Hono inline handler: responses (c.json/text/html/redirect/newResponse with status + shape_hint), errors (throw new HTTPException/Error), db calls (prisma/db/knex/drizzle/mongoose/supabase), fetch calls, c.set/get/var/env access, inline validators, has_try_catch. Optional method + path filter. Named-handler routes return empty.",
3173
+ schema: {
3174
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3175
+ method: z.string().optional().describe("HTTP method filter (case-insensitive)"),
3176
+ path: z.string().optional().describe("Route path filter (exact match, e.g. '/users/:id')"),
3177
+ },
3178
+ handler: async (args) => {
3179
+ const { analyzeInlineHandler } = await import("./tools/hono-inline-analyze.js");
3180
+ return await analyzeInlineHandler(args.repo, args.method, args.path);
3181
+ },
3182
+ },
3183
+ {
3184
+ name: "extract_response_types",
3185
+ category: "analysis",
3186
+ searchHint: "hono response types status codes error paths RPC client InferResponseType Issue 4270",
3187
+ description: "Aggregate statically-knowable response types per route: c.json/text/html/body/redirect/newResponse emissions + throw new HTTPException/Error entries with status codes. Closes Hono Issue #4270 — RPC clients can generate types that include error paths. Returns routes[] plus total_statuses across the app.",
3188
+ schema: {
3189
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3190
+ },
3191
+ handler: async (args) => {
3192
+ const { extractResponseTypes } = await import("./tools/hono-response-types.js");
3193
+ return await extractResponseTypes(args.repo);
3194
+ },
3195
+ },
3196
+ {
3197
+ name: "detect_hono_modules",
3198
+ category: "analysis",
3199
+ searchHint: "hono modules architecture cluster path prefix middleware bindings enterprise Issue 4121",
3200
+ description: "Cluster Hono routes into logical modules by 2-segment path prefix, rolling up middleware chains, env bindings (from inline_analysis context_access), and source files per module. Closes Hono Issue #4121 — surfaces the implicit module structure for architecture review of enterprise apps. No new AST walking; post-processes the existing HonoAppModel.",
3201
+ schema: {
3202
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3203
+ },
3204
+ handler: async (args) => {
3205
+ const { detectHonoModules } = await import("./tools/hono-modules.js");
3206
+ return await detectHonoModules(args.repo);
3207
+ },
3208
+ },
3209
+ {
3210
+ name: "find_dead_hono_routes",
3211
+ category: "analysis",
3212
+ searchHint: "hono dead routes unused RPC client caller refactor monorepo cleanup",
3213
+ description: "Heuristically flag Hono server routes whose path segments do not appear in any non-server .ts/.tsx/.js/.jsx source file in the repo. Useful in monorepos to identify server endpoints that no Hono RPC client calls after refactors. Fully-dynamic routes (`/:id` only) are skipped. Documented as best-effort via the result note field.",
3214
+ schema: {
3215
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3216
+ },
3217
+ handler: async (args) => {
3218
+ const { findDeadHonoRoutes } = await import("./tools/hono-dead-routes.js");
3219
+ return await findDeadHonoRoutes(args.repo);
3220
+ },
3221
+ },
3222
+ // --- Next.js framework tools ---
3223
+ {
3224
+ name: "nextjs_route_map",
3225
+ category: "analysis",
3226
+ searchHint: "nextjs next.js route map app router pages router rendering strategy SSG SSR ISR edge middleware",
3227
+ description: "Complete Next.js route map with rendering strategy per route. Enumerates App Router and Pages Router conventions, reads route segment config exports (dynamic/revalidate/runtime), classifies each route as static/ssr/isr/edge/client, detects metadata exports, computes layout chain, and flags hybrid conflicts where the same URL is served by both routers.",
3228
+ schema: {
3229
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3230
+ workspace: z.string().optional().describe("Monorepo workspace path, e.g. 'apps/web'"),
3231
+ router: z.enum(["app", "pages", "both"]).optional().describe("Which routers to scan (default 'both')"),
3232
+ include_metadata: z.boolean().optional().describe("Include metadata export detection (default true)"),
3233
+ max_routes: z.number().int().positive().optional().describe("Max routes to process (default 1000)"),
3234
+ },
3235
+ handler: async (args) => {
3236
+ const opts = {};
3237
+ if (args.workspace != null)
3238
+ opts.workspace = args.workspace;
3239
+ if (args.router != null)
3240
+ opts.router = args.router;
3241
+ if (args.include_metadata != null)
3242
+ opts.include_metadata = args.include_metadata;
3243
+ if (args.max_routes != null)
3244
+ opts.max_routes = args.max_routes;
3245
+ const result = await nextjsRouteMap(args.repo ?? "", opts);
3246
+ return formatNextjsRouteMap(result);
3247
+ },
3248
+ },
3249
+ {
3250
+ name: "nextjs_metadata_audit",
3251
+ category: "analysis",
3252
+ searchHint: "nextjs seo metadata title description og image audit canonical twitter json-ld",
3253
+ description: "Audit Next.js page metadata for SEO completeness with per-route scoring. Walks app/page.tsx files, extracts title/description/openGraph/canonical/twitter/JSON-LD via tree-sitter, scores each route 0-100 with a weighted formula, and aggregates a per-grade distribution + top issue list.",
3254
+ schema: {
3255
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3256
+ workspace: z.string().optional().describe("Monorepo workspace path, e.g. 'apps/web'"),
3257
+ max_routes: z.number().int().positive().optional().describe("Max routes to process (default 1000)"),
3258
+ },
3259
+ handler: async (args) => {
3260
+ const opts = {};
3261
+ if (args.workspace != null)
3262
+ opts.workspace = args.workspace;
3263
+ if (args.max_routes != null)
3264
+ opts.max_routes = args.max_routes;
3265
+ const result = await nextjsMetadataAudit(args.repo ?? "", opts);
3266
+ return formatNextjsMetadataAudit(result);
3267
+ },
3268
+ },
3269
+ {
3270
+ name: "framework_audit",
3271
+ category: "analysis",
3272
+ searchHint: "nextjs next.js framework audit meta-tool overall score security metadata routes components classifier use client use server hooks server actions auth validation rate limit zod api contract route handler openapi method body schema response client boundary bundle imports loc link integrity broken navigation href router push 404 data flow fetch waterfall cache cookies headers ssr revalidate middleware coverage protected admin matcher",
3273
+ description: "Run all Next.js sub-audits (components, routes, metadata, security, api_contract, boundary, links, data_flow, middleware_coverage) and aggregate into a unified weighted overall score with grade. Use as a single first-call for any Next.js project.",
3274
+ schema: {
3275
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3276
+ workspace: z.string().optional().describe("Monorepo workspace path, e.g. 'apps/web'"),
3277
+ tools: z.array(z.string()).optional().describe("Subset of tools to run (default: all 9). Names: components, routes, metadata, security, api_contract, boundary, links, data_flow, middleware_coverage"),
3278
+ mode: z.enum(["full", "priority"]).optional().describe("Output mode: 'full' returns per-tool results + aggregated summary; 'priority' returns a single unified top-N actionable findings list sorted by severity × cross-tool occurrences"),
3279
+ priority_limit: z.number().int().positive().optional().describe("Max findings in priority mode (default: 20)"),
3280
+ },
3281
+ handler: async (args) => {
3282
+ const opts = {};
3283
+ if (args.workspace != null)
3284
+ opts.workspace = args.workspace;
3285
+ if (args.tools != null)
3286
+ opts.tools = args.tools;
3287
+ if (args.mode != null)
3288
+ opts.mode = args.mode;
3289
+ if (args.priority_limit != null)
3290
+ opts.priority_limit = args.priority_limit;
3291
+ const result = await frameworkAudit(args.repo ?? "", opts);
3292
+ return formatFrameworkAudit(result);
3293
+ },
3294
+ },
3295
+ // ── SQL analysis tools (hidden/discoverable) ─────────────
3296
+ {
3297
+ name: "analyze_schema",
3298
+ category: "analysis",
3299
+ searchHint: "SQL schema ERD entity relationship tables views columns foreign key database migration",
3300
+ description: "Analyze SQL schema: tables, views, columns, foreign keys, relationships. Output as JSON or Mermaid ERD.",
3301
+ schema: {
3302
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3303
+ file_pattern: z.string().optional().describe("Filter SQL files by pattern (e.g. 'migrations/')"),
3304
+ output_format: z.enum(["json", "mermaid"]).optional().describe("Output format (default: json)"),
3305
+ include_columns: zBool().describe("Include column details in output (default: true)"),
3306
+ },
3307
+ handler: async (args) => {
3308
+ const { analyzeSchema } = await import("./tools/sql-tools.js");
3309
+ const opts = {};
3310
+ if (args.file_pattern != null)
3311
+ opts.file_pattern = args.file_pattern;
3312
+ if (args.output_format != null)
3313
+ opts.output_format = args.output_format;
3314
+ if (args.include_columns != null)
3315
+ opts.include_columns = args.include_columns;
3316
+ const result = await analyzeSchema(args.repo, opts);
3317
+ const parts = [];
3318
+ parts.push(`Tables: ${result.tables.length} | Views: ${result.views.length} | Relationships: ${result.relationships.length}`);
3319
+ if (result.warnings.length > 0)
3320
+ parts.push(`Warnings: ${result.warnings.join("; ")}`);
3321
+ if (result.mermaid) {
3322
+ parts.push("");
3323
+ parts.push(result.mermaid);
3324
+ }
3325
+ else {
3326
+ for (const t of result.tables) {
3327
+ const cols = t.columns.map((c) => `${c.name} ${c.type}`).join(", ");
3328
+ parts.push(` ${t.name} (${t.file}:${t.line}) — ${cols || "(no columns)"}`);
3329
+ }
3330
+ for (const v of result.views) {
3331
+ parts.push(` VIEW ${v.name} (${v.file}:${v.line})`);
3332
+ }
3333
+ if (result.relationships.length > 0) {
3334
+ parts.push("Relationships:");
3335
+ for (const r of result.relationships) {
3336
+ parts.push(` ${r.from_table}.${r.from_column} → ${r.to_table}.${r.to_column} [${r.type}]`);
3337
+ }
3338
+ }
3339
+ }
3340
+ return parts.join("\n");
3341
+ },
3342
+ },
3343
+ {
3344
+ name: "trace_query",
3345
+ category: "analysis",
3346
+ searchHint: "SQL table query trace references cross-language ORM Prisma Drizzle migration",
3347
+ description: "Trace SQL table references across the codebase: DDL, DML, FK, and ORM models (Prisma, Drizzle).",
3348
+ schema: {
3349
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3350
+ table: z.string().describe("Table name to trace (required)"),
3351
+ include_orm: zBool().describe("Check Prisma/Drizzle ORM models (default: true)"),
3352
+ file_pattern: z.string().optional().describe("Scope search to files matching pattern"),
3353
+ max_references: zNum().describe("Maximum references to return (default: 500)"),
3354
+ },
3355
+ handler: async (args) => {
3356
+ const { traceQuery } = await import("./tools/sql-tools.js");
3357
+ const opts = {
3358
+ table: args.table,
1279
3359
  };
1280
- if (args.since != null)
1281
- opts.since = args.since;
1282
- if (args.until != null)
1283
- opts.until = args.until;
1284
- if (checksArr != null)
1285
- opts.checks = checksArr.join(",");
1286
- if (excludeArr != null)
1287
- opts.exclude_patterns = excludeArr;
1288
- if (args.token_budget != null)
1289
- opts.token_budget = args.token_budget;
1290
- if (args.max_files != null)
1291
- opts.max_files = args.max_files;
1292
- if (args.check_timeout_ms != null)
1293
- opts.check_timeout_ms = args.check_timeout_ms;
1294
- const result = await reviewDiff(args.repo, opts);
1295
- return formatReviewDiff(result);
3360
+ if (args.include_orm != null)
3361
+ opts.include_orm = args.include_orm;
3362
+ if (args.file_pattern != null)
3363
+ opts.file_pattern = args.file_pattern;
3364
+ if (args.max_references != null)
3365
+ opts.max_references = args.max_references;
3366
+ const result = await traceQuery(args.repo, opts);
3367
+ const parts = [];
3368
+ if (result.table_definition) {
3369
+ parts.push(`Definition: ${result.table_definition.file}:${result.table_definition.line} [${result.table_definition.kind}]`);
3370
+ }
3371
+ else {
3372
+ parts.push(`Definition: not found in index`);
3373
+ }
3374
+ parts.push(`SQL references: ${result.sql_references.length}${result.truncated ? " (truncated)" : ""}`);
3375
+ for (const ref of result.sql_references.slice(0, 50)) {
3376
+ parts.push(` ${ref.file}:${ref.line} [${ref.type}] ${ref.context}`);
3377
+ }
3378
+ if (result.orm_references.length > 0) {
3379
+ parts.push(`ORM references: ${result.orm_references.length}`);
3380
+ for (const ref of result.orm_references) {
3381
+ parts.push(` ${ref.file}:${ref.line} [${ref.orm}] model ${ref.model_name}`);
3382
+ }
3383
+ }
3384
+ if (result.warnings.length > 0) {
3385
+ parts.push(`Warnings: ${result.warnings.join("; ")}`);
3386
+ }
3387
+ return parts.join("\n");
1296
3388
  },
1297
3389
  },
1298
- // --- Stats ---
1299
3390
  {
1300
- name: "usage_stats",
1301
- category: "meta",
1302
- searchHint: "usage statistics tool calls tokens timing metrics",
1303
- outputSchema: OutputSchemas.usageStats,
1304
- description: "Show usage statistics for all CodeSift tool calls (call counts, tokens, timing, repos)",
1305
- schema: {},
1306
- handler: async () => {
1307
- const stats = await getUsageStats();
1308
- return { report: formatUsageReport(stats) };
3391
+ name: "sql_audit",
3392
+ category: "analysis",
3393
+ searchHint: "SQL audit composite drift orphan lint DML safety complexity god table schema diagnostic",
3394
+ description: "Composite SQL audit — runs 5 diagnostic gates (drift, orphan, lint, dml, complexity) in one call. Use this instead of calling the individual gate functions separately.",
3395
+ schema: {
3396
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3397
+ checks: z.array(z.enum(["drift", "orphan", "lint", "dml", "complexity"])).optional().describe("Subset of gates to run (default: all 5)"),
3398
+ file_pattern: z.string().optional().describe("Scope to files matching pattern"),
3399
+ max_results: zNum().describe("Max DML findings per pattern (default: 200)"),
3400
+ },
3401
+ handler: async (args) => {
3402
+ const { sqlAudit } = await import("./tools/sql-tools.js");
3403
+ const opts = {};
3404
+ if (args.checks != null)
3405
+ opts.checks = args.checks;
3406
+ if (args.file_pattern != null)
3407
+ opts.file_pattern = args.file_pattern;
3408
+ if (args.max_results != null)
3409
+ opts.max_results = args.max_results;
3410
+ const result = await sqlAudit(args.repo, opts);
3411
+ const parts = [];
3412
+ parts.push(`SQL audit: ${result.summary.gates_run} gates run, ${result.summary.gates_passed} passed, ${result.summary.gates_failed} failed`);
3413
+ parts.push(` Total findings: ${result.summary.total_findings}`);
3414
+ parts.push(` Critical findings: ${result.summary.critical_findings}`);
3415
+ parts.push("");
3416
+ for (const g of result.gates) {
3417
+ const icon = g.pass ? "✓" : (g.critical ? "✗ CRITICAL" : "⚠");
3418
+ parts.push(`${icon} ${g.check}: ${g.summary}`);
3419
+ }
3420
+ if (result.warnings.length > 0) {
3421
+ parts.push("");
3422
+ parts.push("─── Warnings ───");
3423
+ for (const w of result.warnings)
3424
+ parts.push(` ⚠ ${w}`);
3425
+ }
3426
+ return parts.join("\n");
1309
3427
  },
1310
3428
  },
1311
- // ── Session context tools ───────────────────────────────────────────────
1312
3429
  {
1313
- name: "get_session_snapshot",
1314
- category: "session",
1315
- searchHint: "session context snapshot compaction summary explored symbols files queries",
1316
- description: "Get a compact ~200 token snapshot of what was explored in this session. Designed to survive context compaction. Call proactively before long tasks.",
3430
+ name: "diff_migrations",
3431
+ category: "analysis",
3432
+ searchHint: "migration diff SQL destructive DROP ALTER ADD schema change deploy risk",
3433
+ description: "Scan SQL migration files and classify operations as additive (CREATE TABLE), modifying (ALTER ADD), or destructive (DROP TABLE, DROP COLUMN, TRUNCATE). Flags deploy risks.",
1317
3434
  schema: {
1318
- repo: z.string().optional().describe("Filter to specific repo. Default: most recent repo."),
3435
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3436
+ file_pattern: z.string().optional().describe("Scope to migration files matching pattern"),
1319
3437
  },
1320
3438
  handler: async (args) => {
1321
- return formatSnapshot(getSessionState(), args.repo);
3439
+ const { diffMigrations } = await import("./tools/sql-tools.js");
3440
+ const opts = {};
3441
+ if (args.file_pattern != null)
3442
+ opts.file_pattern = args.file_pattern;
3443
+ const result = await diffMigrations(args.repo, opts);
3444
+ const parts = [];
3445
+ parts.push(`Migration ops: ${result.summary.additive + result.summary.modifying + result.summary.destructive} across ${result.summary.total_files} files`);
3446
+ parts.push(` additive: ${result.summary.additive}`);
3447
+ parts.push(` modifying: ${result.summary.modifying}`);
3448
+ parts.push(` destructive: ${result.summary.destructive}`);
3449
+ if (result.destructive.length > 0) {
3450
+ parts.push("\n⚠ DESTRUCTIVE:");
3451
+ for (const d of result.destructive) {
3452
+ parts.push(` [${d.severity.toUpperCase()}] ${d.operation} ${d.target} (${d.file}:${d.line})`);
3453
+ }
3454
+ }
3455
+ if (result.modifying.length > 0) {
3456
+ parts.push("\nModifying:");
3457
+ for (const m of result.modifying.slice(0, 20)) {
3458
+ parts.push(` ${m.operation} ${m.target} (${m.file}:${m.line})`);
3459
+ }
3460
+ }
3461
+ return parts.join("\n");
1322
3462
  },
1323
3463
  },
1324
3464
  {
1325
- name: "get_session_context",
1326
- category: "session",
1327
- searchHint: "session context full explored symbols files queries negative evidence",
1328
- description: "Get full session context: explored symbols, files, queries, and negative evidence (searched but not found). Use get_session_snapshot for a compact version.",
3465
+ name: "search_columns",
3466
+ category: "search",
3467
+ searchHint: "search column SQL table field name type database schema find",
3468
+ description: "Search SQL columns across all tables by name (substring), type (int/string/float/...), or parent table. Returns column name, type, table, file, and line. Like search_symbols but scoped to SQL fields.",
1329
3469
  schema: {
1330
- repo: z.string().optional().describe("Filter to specific repo"),
1331
- include_stale: zBool().describe("Include stale negative evidence entries (default: false)"),
3470
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3471
+ query: z.string().describe("Column name substring to match (case-insensitive). Empty = no name filter."),
3472
+ type: z.string().optional().describe("Filter by normalized type: int, string, float, bool, datetime, json, uuid, bytes"),
3473
+ table: z.string().optional().describe("Filter by table name substring"),
3474
+ file_pattern: z.string().optional().describe("Scope to files matching pattern"),
3475
+ max_results: zNum().describe("Max columns to return (default: 100)"),
1332
3476
  },
1333
3477
  handler: async (args) => {
1334
- const includeStale = args.include_stale === true || args.include_stale === "true";
1335
- return getContext(args.repo, includeStale);
3478
+ const { searchColumns } = await import("./tools/sql-tools.js");
3479
+ const opts = {
3480
+ query: args.query ?? "",
3481
+ };
3482
+ if (args.type != null)
3483
+ opts.type = args.type;
3484
+ if (args.table != null)
3485
+ opts.table = args.table;
3486
+ if (args.file_pattern != null)
3487
+ opts.file_pattern = args.file_pattern;
3488
+ if (args.max_results != null)
3489
+ opts.max_results = args.max_results;
3490
+ const result = await searchColumns(args.repo, opts);
3491
+ const parts = [];
3492
+ parts.push(`Columns: ${result.columns.length}${result.truncated ? `/${result.total} (truncated)` : ""}`);
3493
+ for (const c of result.columns) {
3494
+ parts.push(` ${c.table}.${c.name.padEnd(24)} ${c.normalized_type.padEnd(10)} ${c.file}:${c.line}`);
3495
+ }
3496
+ return parts.join("\n");
1336
3497
  },
1337
3498
  },
1338
- // --- Project Analysis ---
3499
+ // --- Astro v6 migration check ---
1339
3500
  {
1340
- name: "analyze_project",
3501
+ name: "astro_migration_check",
1341
3502
  category: "analysis",
1342
- searchHint: "project profile stack conventions middleware routes rate-limits auth detection",
1343
- description: "Analyze a repository to extract stack, file classifications, and framework-specific conventions. Returns a structured project profile (schema v1.0) with file:line evidence for convention-level facts.",
3503
+ searchHint: "astro v6 migration upgrade breaking changes compatibility check AM01 AM10 content collections ViewTransitions",
3504
+ description: "Scan an Astro project for v5→v6 breaking changes. Detects 10 issues (AM01–AM10): removed APIs (Astro.glob, emitESMImage), component renames (ViewTransitions→ClientRouter), content collection config changes, Node.js version requirements, Zod 4 deprecations, hybrid output mode, and removed integrations (@astrojs/lit). Returns a migration report with per-issue effort estimates.",
1344
3505
  schema: {
1345
3506
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1346
- force: zBool().describe("Ignore cached results and re-analyze"),
3507
+ target_version: z.enum(["6"]).optional().describe("Target Astro version (default: '6')"),
1347
3508
  },
1348
3509
  handler: async (args) => {
1349
- const result = await analyzeProject(args.repo, {
1350
- force: args.force,
1351
- });
1352
- return result;
3510
+ const mcArgs = {};
3511
+ if (args.repo != null)
3512
+ mcArgs.repo = args.repo;
3513
+ if (args.target_version != null)
3514
+ mcArgs.target_version = args.target_version;
3515
+ const result = await astroMigrationCheck(mcArgs);
3516
+ const lines = [];
3517
+ lines.push(`ASTRO MIGRATION CHECK: v${result.current_version ?? "unknown"} → v${result.target_version}`);
3518
+ lines.push(`Issues: ${result.summary.total_issues} | Estimated: ${result.summary.estimated_migration_hours}`);
3519
+ if (Object.keys(result.summary.by_effort).length > 0) {
3520
+ const effortStr = Object.entries(result.summary.by_effort)
3521
+ .map(([k, v]) => `${v}×${k}`)
3522
+ .join(", ");
3523
+ lines.push(`Effort: ${effortStr}`);
3524
+ }
3525
+ if (result.breaking_changes.length === 0) {
3526
+ lines.push("\n✓ No v6 breaking changes detected.");
3527
+ }
3528
+ else {
3529
+ lines.push("");
3530
+ for (const issue of result.breaking_changes) {
3531
+ const sev = issue.severity === "error" ? "✗" : issue.severity === "warning" ? "⚠" : "ℹ";
3532
+ lines.push(`${sev} ${issue.code} [${issue.category}] — ${issue.message}`);
3533
+ lines.push(` effort: ${issue.effort} | files: ${issue.files.slice(0, 3).join(", ")}${issue.files.length > 3 ? ` +${issue.files.length - 3} more` : ""}`);
3534
+ if (issue.migration_guide)
3535
+ lines.push(` guide: ${issue.migration_guide}`);
3536
+ }
3537
+ }
3538
+ return lines.join("\n");
1353
3539
  },
1354
3540
  },
3541
+ // --- Discovery / concierge ---
1355
3542
  {
1356
- name: "get_extractor_versions",
1357
- category: "meta",
1358
- searchHint: "extractor version cache invalidation profile",
1359
- description: "Return current extractor versions without triggering analysis. Used for cache invalidation.",
1360
- schema: {},
1361
- handler: async () => getExtractorVersions(),
3543
+ name: "plan_turn",
3544
+ category: "discovery",
3545
+ searchHint: "plan turn routing recommend tools symbols files gap analysis session aware concierge",
3546
+ description: "Routes a natural-language query to the most relevant CodeSift tools, symbols, and files. Uses hybrid BM25+semantic ranking with session-aware dedup. Call at the start of a task to get a prioritized action list.",
3547
+ schema: {
3548
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3549
+ query: z.string().describe("Natural-language description of what you want to do"),
3550
+ max_results: z.number().optional().describe("Max tools to return (default 10)"),
3551
+ skip_session: z.boolean().optional().describe("Skip session state checks (default false)"),
3552
+ },
3553
+ handler: async (args) => {
3554
+ const { query, max_results, skip_session } = args;
3555
+ const opts = {};
3556
+ if (max_results !== undefined)
3557
+ opts.max_results = max_results;
3558
+ if (skip_session !== undefined)
3559
+ opts.skip_session = skip_session;
3560
+ const result = await planTurn(args.repo, query, opts);
3561
+ return formatPlanTurnResult(result);
3562
+ },
1362
3563
  },
1363
3564
  ];
1364
3565
  function buildToolSummaries() {
@@ -1465,6 +3666,21 @@ export function discoverTools(query, category) {
1465
3666
  // ---------------------------------------------------------------------------
1466
3667
  export function registerTools(server, options) {
1467
3668
  const deferNonCore = options?.deferNonCore ?? false;
3669
+ const projectRoot = options?.projectRoot ?? process.cwd();
3670
+ // Detect which languages the project actually uses — drives language-gated
3671
+ // tool registration. Tools with requiresLanguage="python" are only surfaced
3672
+ // when .py files exist, same for PHP and Kotlin.
3673
+ let languages;
3674
+ try {
3675
+ languages = detectProjectLanguagesSync(projectRoot);
3676
+ }
3677
+ catch {
3678
+ // On failure, enable everything — conservative fallback
3679
+ languages = {
3680
+ python: true, php: true, typescript: true, javascript: true,
3681
+ kotlin: true, go: true, rust: true, ruby: true,
3682
+ };
3683
+ }
1468
3684
  // Clear handles from any previous registration (e.g. tests calling registerTools multiple times)
1469
3685
  toolHandles.clear();
1470
3686
  // Register ALL tools with full schema; store returned handles
@@ -1472,6 +3688,18 @@ export function registerTools(server, options) {
1472
3688
  const handle = server.tool(tool.name, tool.description, tool.schema, async (args) => wrapTool(tool.name, args, () => tool.handler(args))());
1473
3689
  toolHandles.set(tool.name, handle);
1474
3690
  }
3691
+ // Language-gated disabling — tools requiring a language absent from the
3692
+ // project are disabled (still registered but hidden from ListTools).
3693
+ // Users can re-enable via describe_tools(reveal=true) if needed.
3694
+ for (const tool of TOOL_DEFINITIONS) {
3695
+ if (!tool.requiresLanguage)
3696
+ continue;
3697
+ if (languages[tool.requiresLanguage])
3698
+ continue;
3699
+ const handle = toolHandles.get(tool.name);
3700
+ if (handle)
3701
+ handle.disable();
3702
+ }
1475
3703
  // Always register discover_tools meta-tool
1476
3704
  const discoverHandle = server.tool("discover_tools", "Search tool catalog by keyword or category. Returns matching tools with descriptions.", {
1477
3705
  query: z.string().describe("Keywords to search for (e.g. 'dead code', 'complexity', 'rename', 'secrets')"),
@@ -1504,14 +3732,34 @@ export function registerTools(server, options) {
1504
3732
  handle.disable();
1505
3733
  }
1506
3734
  }
3735
+ // Auto-enable framework-specific tools when project type is detected at CWD.
3736
+ // E.g. composer.json → enable PHP/Yii2 tools automatically.
3737
+ detectAutoLoadTools(process.cwd())
3738
+ .then((toEnable) => {
3739
+ for (const name of toEnable) {
3740
+ const h = toolHandles.get(name);
3741
+ if (h)
3742
+ h.enable();
3743
+ }
3744
+ if (toEnable.length > 0) {
3745
+ console.error(`[codesift] Auto-loaded ${toEnable.length} framework tools for detected project type: ${toEnable.join(", ")}`);
3746
+ }
3747
+ })
3748
+ .catch(() => {
3749
+ // Silently ignore — auto-detection is best-effort
3750
+ });
1507
3751
  }
1508
3752
  // Register progressive shorteners for analysis tools with large outputs
1509
3753
  registerShortener("analyze_complexity", { compact: formatComplexityCompact, counts: formatComplexityCounts });
1510
3754
  registerShortener("find_clones", { compact: formatClonesCompact, counts: formatClonesCounts });
1511
3755
  registerShortener("analyze_hotspots", { compact: formatHotspotsCompact, counts: formatHotspotsCounts });
1512
3756
  registerShortener("trace_route", { compact: formatTraceRouteCompact, counts: formatTraceRouteCounts });
3757
+ registerShortener("nextjs_route_map", { compact: formatNextjsRouteMapCompact, counts: formatNextjsRouteMapCounts });
3758
+ registerShortener("nextjs_metadata_audit", { compact: formatNextjsMetadataAuditCompact, counts: formatNextjsMetadataAuditCounts });
3759
+ registerShortener("framework_audit", { compact: formatFrameworkAuditCompact, counts: formatFrameworkAuditCounts });
1513
3760
  registerShortener("get_session_context", {
1514
- compact: (text) => {
3761
+ compact: (raw) => {
3762
+ const text = typeof raw === "string" ? raw : JSON.stringify(raw);
1515
3763
  try {
1516
3764
  const data = JSON.parse(text);
1517
3765
  return `session:${data.session_id?.slice(0, 8)} calls:${data.call_count} files:${data.explored_files?.count} symbols:${data.explored_symbols?.count} queries:${data.queries?.count} neg:${data.negative_evidence?.count}`;
@@ -1520,7 +3768,8 @@ export function registerTools(server, options) {
1520
3768
  return text.slice(0, 500);
1521
3769
  }
1522
3770
  },
1523
- counts: (text) => {
3771
+ counts: (raw) => {
3772
+ const text = typeof raw === "string" ? raw : JSON.stringify(raw);
1524
3773
  try {
1525
3774
  const data = JSON.parse(text);
1526
3775
  return `files:${data.explored_files?.count} symbols:${data.explored_symbols?.count} queries:${data.queries?.count} neg:${data.negative_evidence?.count}`;