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
@@ -0,0 +1,1160 @@
1
+ /**
2
+ * SQL analysis tools — analyze_schema and trace_query.
3
+ * Hidden/discoverable: not in CORE_TOOL_NAMES.
4
+ */
5
+ import { getCodeIndex } from "./index-tools.js";
6
+ import { searchText } from "./search-tools.js";
7
+ const FK_RE = /REFERENCES\s+(?:(?:"([^"]+)"|(\w+))\s*\.\s*)?(?:"([^"]+)"|(\w+))\s*\(\s*(?:"([^"]+)"|(\w+))\s*\)/gi;
8
+ export async function analyzeSchema(repo, options) {
9
+ const index = await getCodeIndex(repo);
10
+ if (!index) {
11
+ throw new Error(`Repository "${repo}" not found. Run index_folder first.`);
12
+ }
13
+ const includeColumns = options?.include_columns ?? true;
14
+ const filePattern = options?.file_pattern;
15
+ // Collect tables and views from index
16
+ const tables = [];
17
+ const views = [];
18
+ const warnings = [];
19
+ const tableSymbols = index.symbols.filter((s) => (s.kind === "table" || s.kind === "view") &&
20
+ (!filePattern || s.file.includes(filePattern)));
21
+ if (tableSymbols.length === 0) {
22
+ warnings.push("No SQL files indexed in this repository.");
23
+ return { tables, views, relationships: [], warnings };
24
+ }
25
+ // Check for duplicate table names
26
+ const nameCounts = new Map();
27
+ for (const sym of tableSymbols) {
28
+ nameCounts.set(sym.name, (nameCounts.get(sym.name) ?? 0) + 1);
29
+ }
30
+ for (const [name, count] of nameCounts) {
31
+ if (count > 1) {
32
+ warnings.push(`Duplicate table/view name "${name}" found in ${count} files.`);
33
+ }
34
+ }
35
+ for (const sym of tableSymbols) {
36
+ if (sym.kind === "view") {
37
+ views.push({ name: sym.name, file: sym.file, line: sym.start_line });
38
+ continue;
39
+ }
40
+ const columns = [];
41
+ if (includeColumns) {
42
+ const fields = index.symbols.filter((f) => f.kind === "field" && f.parent === sym.id);
43
+ for (const f of fields) {
44
+ columns.push({ name: f.name, type: f.signature ?? "unknown" });
45
+ }
46
+ }
47
+ tables.push({
48
+ name: sym.name,
49
+ file: sym.file,
50
+ line: sym.start_line,
51
+ columns,
52
+ });
53
+ }
54
+ // Extract FK relationships from column signatures + table-level constraints
55
+ const relationships = [];
56
+ for (const table of tables) {
57
+ // Column-level REFERENCES
58
+ for (const col of table.columns) {
59
+ FK_RE.lastIndex = 0;
60
+ const m = FK_RE.exec(col.type);
61
+ if (m) {
62
+ const toTable = m[3] ?? m[4] ?? m[1] ?? m[2] ?? "";
63
+ const toCol = m[5] ?? m[6] ?? "id";
64
+ let relType = "fk";
65
+ if (toTable === table.name) {
66
+ relType = "self_reference";
67
+ }
68
+ relationships.push({
69
+ from_table: table.name,
70
+ from_column: col.name,
71
+ to_table: toTable,
72
+ to_column: toCol,
73
+ type: relType,
74
+ });
75
+ }
76
+ }
77
+ // Table-level FOREIGN KEY constraints: scan full table source
78
+ const tableSym = index.symbols.find((s) => s.kind === "table" && s.name === table.name);
79
+ if (tableSym?.source) {
80
+ const tableFkRe = /FOREIGN\s+KEY\s*\(\s*(?:"([^"]+)"|(\w+))\s*\)\s*REFERENCES\s+(?:(?:"[^"]+"|(\w+))\s*\.\s*)?(?:"([^"]+)"|(\w+))\s*\(\s*(?:"([^"]+)"|(\w+))\s*\)/gi;
81
+ let fkm;
82
+ while ((fkm = tableFkRe.exec(tableSym.source)) !== null) {
83
+ const fromCol = fkm[1] ?? fkm[2] ?? "";
84
+ const toTable = fkm[4] ?? fkm[5] ?? fkm[3] ?? "";
85
+ const toCol = fkm[6] ?? fkm[7] ?? "id";
86
+ // Avoid duplicates (column-level already caught this FK)
87
+ if (relationships.some((r) => r.from_table === table.name && r.from_column === fromCol && r.to_table === toTable))
88
+ continue;
89
+ relationships.push({
90
+ from_table: table.name,
91
+ from_column: fromCol,
92
+ to_table: toTable,
93
+ to_column: toCol,
94
+ type: toTable === table.name ? "self_reference" : "fk",
95
+ });
96
+ }
97
+ }
98
+ }
99
+ // Detect circular references
100
+ detectCircularRefs(relationships, warnings);
101
+ const result = { tables, views, relationships, warnings };
102
+ if (options?.output_format === "mermaid") {
103
+ result.mermaid = generateMermaid(tables, relationships);
104
+ }
105
+ return result;
106
+ }
107
+ function detectCircularRefs(relationships, warnings) {
108
+ // Build adjacency map
109
+ const adj = new Map();
110
+ for (const rel of relationships) {
111
+ if (rel.type === "self_reference")
112
+ continue;
113
+ if (!adj.has(rel.from_table))
114
+ adj.set(rel.from_table, new Set());
115
+ adj.get(rel.from_table).add(rel.to_table);
116
+ }
117
+ // DFS cycle detection with visited set
118
+ const visited = new Set();
119
+ const inStack = new Set();
120
+ function dfs(node) {
121
+ if (inStack.has(node)) {
122
+ warnings.push(`Circular FK reference detected involving "${node}".`);
123
+ return;
124
+ }
125
+ if (visited.has(node))
126
+ return;
127
+ visited.add(node);
128
+ inStack.add(node);
129
+ for (const neighbor of adj.get(node) ?? []) {
130
+ dfs(neighbor);
131
+ }
132
+ inStack.delete(node);
133
+ }
134
+ for (const node of adj.keys()) {
135
+ dfs(node);
136
+ }
137
+ }
138
+ /**
139
+ * Sanitize a name for Mermaid erDiagram entities.
140
+ * Strips leading non-word chars (e.g. Joomla's `#__`), keeps the rest readable.
141
+ * If the name becomes empty, returns "anon".
142
+ */
143
+ function mermaidSafeName(name) {
144
+ // Strip leading non-word chars, replace remaining non-word with _
145
+ const cleaned = name
146
+ .replace(/^[^a-zA-Z0-9]+/, "") // strip leading #, $, @, etc.
147
+ .replace(/[^a-zA-Z0-9_]/g, "_");
148
+ return cleaned || "anon";
149
+ }
150
+ /** Sanitize a column type for Mermaid — keep the base type name only (drop size/precision). */
151
+ function mermaidSafeType(type) {
152
+ // "int(10) unsigned" → "int", "varchar(255)" → "varchar", "DECIMAL(10,2)" → "decimal"
153
+ const m = /^[a-zA-Z][a-zA-Z0-9_]*/.exec(type);
154
+ return m ? m[0].toLowerCase() : "unknown";
155
+ }
156
+ function generateMermaid(tables, relationships) {
157
+ const lines = ["erDiagram"];
158
+ for (const table of tables) {
159
+ const safeName = mermaidSafeName(table.name);
160
+ lines.push(` ${safeName} {`);
161
+ for (const col of table.columns) {
162
+ const typeName = mermaidSafeType(col.type);
163
+ lines.push(` ${typeName} ${mermaidSafeName(col.name)}`);
164
+ }
165
+ lines.push(" }");
166
+ }
167
+ for (const rel of relationships) {
168
+ const arrow = rel.type === "self_reference" ? "||--o|" : "||--o{";
169
+ lines.push(` ${mermaidSafeName(rel.from_table)} ${arrow} ${mermaidSafeName(rel.to_table)} : "${mermaidSafeName(rel.from_column)}"`);
170
+ }
171
+ return lines.join("\n");
172
+ }
173
+ export async function traceQuery(repo, options) {
174
+ if (!options.table?.trim()) {
175
+ throw new Error("table parameter is required");
176
+ }
177
+ const index = await getCodeIndex(repo);
178
+ if (!index) {
179
+ throw new Error(`Repository "${repo}" not found. Run index_folder first.`);
180
+ }
181
+ const tableName = options.table.trim();
182
+ const maxRefs = options.max_references ?? 500;
183
+ const includeOrm = options.include_orm ?? true;
184
+ // Find table definition
185
+ const tableSym = index.symbols.find((s) => (s.kind === "table" || s.kind === "view") && s.name === tableName);
186
+ const table_definition = tableSym
187
+ ? { file: tableSym.file, line: tableSym.start_line, kind: tableSym.kind }
188
+ : null;
189
+ // Delegate to ripgrep-backed searchText for the fast literal scan, then
190
+ // post-filter in JS with an identifier-char boundary regex. Ripgrep doesn't
191
+ // support JS-style lookbehind, and \b fails on names starting with # or $.
192
+ const IDENT_CHAR = "[a-zA-Z0-9_#$@]";
193
+ const escaped = escapeRegex(tableName);
194
+ const boundaryRegex = new RegExp(`(?<!${IDENT_CHAR})${escaped}(?!${IDENT_CHAR})`, "i");
195
+ // Fetch a wider window so post-filter losses don't silently cap results.
196
+ const rgMatches = await searchText(repo, tableName, {
197
+ regex: false,
198
+ max_results: Math.max(maxRefs * 3, 500),
199
+ file_pattern: options.file_pattern,
200
+ context_lines: 0,
201
+ });
202
+ const sql_references = [];
203
+ let kept = 0;
204
+ let truncated = false;
205
+ for (const m of rgMatches) {
206
+ const text = m.content ?? "";
207
+ if (!boundaryRegex.test(text))
208
+ continue;
209
+ // Skip the definition line itself
210
+ if (tableSym && m.file === tableSym.file && m.line === tableSym.start_line)
211
+ continue;
212
+ sql_references.push({
213
+ file: m.file,
214
+ line: m.line,
215
+ context: text.trim().slice(0, 120),
216
+ type: classifyReference(text),
217
+ });
218
+ kept++;
219
+ if (kept >= maxRefs) {
220
+ truncated = true;
221
+ break;
222
+ }
223
+ }
224
+ const warnings = [];
225
+ if (truncated) {
226
+ warnings.push(`Results truncated at max_references=${maxRefs}. Pass file_pattern or increase max_references to see more.`);
227
+ }
228
+ // ORM detection
229
+ const orm_references = [];
230
+ if (includeOrm) {
231
+ // Prisma detection
232
+ const prismaFiles = index.files.filter((f) => f.path.endsWith(".prisma"));
233
+ for (const pf of prismaFiles) {
234
+ const prismaSymbols = index.symbols.filter((s) => s.file === pf.path);
235
+ for (const sym of prismaSymbols) {
236
+ // Check @@map("tableName") in source
237
+ if (sym.source?.includes(`@@map("${tableName}")`)) {
238
+ orm_references.push({
239
+ file: sym.file,
240
+ line: sym.start_line,
241
+ orm: "prisma",
242
+ model_name: sym.name,
243
+ });
244
+ }
245
+ // Check model name matching table name (case-insensitive)
246
+ if (sym.kind === "class" && sym.name.toLowerCase() === tableName.toLowerCase()) {
247
+ // Avoid duplicates
248
+ if (!orm_references.some((r) => r.file === sym.file && r.model_name === sym.name)) {
249
+ orm_references.push({
250
+ file: sym.file,
251
+ line: sym.start_line,
252
+ orm: "prisma",
253
+ model_name: sym.name,
254
+ });
255
+ }
256
+ }
257
+ }
258
+ }
259
+ // Drizzle detection
260
+ const tsFiles = index.files.filter((f) => f.path.endsWith(".ts") || f.path.endsWith(".js"));
261
+ for (const tf of tsFiles) {
262
+ const tsSymbols = index.symbols.filter((s) => s.file === tf.path && s.source);
263
+ for (const sym of tsSymbols) {
264
+ if (sym.source?.includes(`pgTable("${tableName}"`) ||
265
+ sym.source?.includes(`mysqlTable("${tableName}"`) ||
266
+ sym.source?.includes(`sqliteTable("${tableName}"`)) {
267
+ orm_references.push({
268
+ file: sym.file,
269
+ line: sym.start_line,
270
+ orm: "drizzle",
271
+ model_name: sym.name,
272
+ });
273
+ }
274
+ }
275
+ }
276
+ // Warn if ORM detected but no references found
277
+ if (prismaFiles.length > 0 && orm_references.length === 0) {
278
+ warnings.push(`ORM detected (Prisma) but no model found for table "${tableName}".`);
279
+ }
280
+ }
281
+ if (!table_definition && sql_references.length === 0) {
282
+ warnings.push(`Table "${tableName}" not found in indexed SQL files. If SQL support was disabled, this is expected.`);
283
+ }
284
+ return { table_definition, sql_references, orm_references, warnings, truncated };
285
+ }
286
+ function classifyReference(line) {
287
+ const upper = line.toUpperCase().trim();
288
+ if (/^\s*ALTER\s+TABLE/i.test(upper))
289
+ return "ddl";
290
+ if (/^\s*CREATE\s+(?:OR\s+REPLACE\s+)?(?:MATERIALIZED\s+)?VIEW/i.test(upper))
291
+ return "view";
292
+ if (/REFERENCES/i.test(upper))
293
+ return "fk";
294
+ if (/^\s*(?:SELECT|INSERT|UPDATE|DELETE)\b/i.test(upper))
295
+ return "dml";
296
+ return "dml";
297
+ }
298
+ function escapeRegex(str) {
299
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
300
+ }
301
+ export async function searchColumns(repo, options) {
302
+ const index = await getCodeIndex(repo);
303
+ if (!index) {
304
+ throw new Error(`Repository "${repo}" not found. Run index_folder first.`);
305
+ }
306
+ const queryLower = (options.query ?? "").toLowerCase();
307
+ const typeFilter = options.type?.toLowerCase();
308
+ const tableFilter = options.table?.toLowerCase();
309
+ const filePattern = options.file_pattern;
310
+ const maxResults = options.max_results ?? 100;
311
+ // Build table-id → table-name lookup (only SQL tables, not Prisma models)
312
+ const tableIdToName = new Map();
313
+ for (const sym of index.symbols) {
314
+ if (sym.kind !== "table")
315
+ continue;
316
+ if (filePattern && !sym.file.includes(filePattern))
317
+ continue;
318
+ tableIdToName.set(sym.id, { name: sym.name, file: sym.file });
319
+ }
320
+ // Collect field symbols whose parent is a SQL table
321
+ const allHits = [];
322
+ for (const sym of index.symbols) {
323
+ if (sym.kind !== "field")
324
+ continue;
325
+ if (!sym.parent)
326
+ continue;
327
+ const parent = tableIdToName.get(sym.parent);
328
+ if (!parent)
329
+ continue;
330
+ const name = sym.name;
331
+ const type = sym.signature ?? "unknown";
332
+ const normalized = normalizeType(type);
333
+ // Apply filters
334
+ if (queryLower && !name.toLowerCase().includes(queryLower))
335
+ continue;
336
+ if (typeFilter && normalized !== typeFilter)
337
+ continue;
338
+ if (tableFilter && !parent.name.toLowerCase().includes(tableFilter))
339
+ continue;
340
+ allHits.push({
341
+ name,
342
+ type,
343
+ normalized_type: normalized,
344
+ table: parent.name,
345
+ file: parent.file,
346
+ line: sym.start_line,
347
+ });
348
+ }
349
+ const total = allHits.length;
350
+ const truncated = total > maxResults;
351
+ const columns = truncated ? allHits.slice(0, maxResults) : allHits;
352
+ return { columns, total, truncated };
353
+ }
354
+ /**
355
+ * Per-table complexity score: column count + FK count + index count.
356
+ * Identifies "god tables" that need refactoring. Sorted by score desc.
357
+ */
358
+ export async function analyzeSchemaComplexity(repo, options) {
359
+ const index = await getCodeIndex(repo);
360
+ if (!index) {
361
+ throw new Error(`Repository "${repo}" not found. Run index_folder first.`);
362
+ }
363
+ const filePattern = options?.file_pattern;
364
+ const topN = options?.top_n ?? 50;
365
+ const tables = index.symbols.filter((s) => {
366
+ if (s.kind !== "table")
367
+ return false;
368
+ if (filePattern && !s.file.includes(filePattern))
369
+ return false;
370
+ return true;
371
+ });
372
+ // Pre-compute: index count per table name
373
+ const indexCounts = new Map();
374
+ for (const sym of index.symbols) {
375
+ if (sym.kind !== "index")
376
+ continue;
377
+ // Index source typically contains "ON table_name(...)"
378
+ const onMatch = /\bON\s+(?:`([^`]+)`|"([^"]+)"|\[([^\]]+)\]|(\w+))/i.exec(sym.source ?? "");
379
+ if (onMatch) {
380
+ const tableName = (onMatch[1] ?? onMatch[2] ?? onMatch[3] ?? onMatch[4] ?? "").toLowerCase();
381
+ indexCounts.set(tableName, (indexCounts.get(tableName) ?? 0) + 1);
382
+ }
383
+ }
384
+ const results = [];
385
+ for (const table of tables) {
386
+ const columns = index.symbols.filter((s) => s.kind === "field" && s.parent === table.id);
387
+ const column_count = columns.length;
388
+ // Count FK references in columns
389
+ let fk_count = 0;
390
+ for (const col of columns) {
391
+ if (/REFERENCES/i.test(col.signature ?? ""))
392
+ fk_count++;
393
+ }
394
+ const index_count = indexCounts.get(table.name.toLowerCase()) ?? 0;
395
+ // Weighted score: columns dominate, FKs and indexes add coupling signal
396
+ const score = column_count * 1.0 + fk_count * 3.0 + index_count * 1.5;
397
+ results.push({
398
+ name: table.name,
399
+ file: table.file,
400
+ line: table.start_line,
401
+ column_count,
402
+ fk_count,
403
+ index_count,
404
+ score,
405
+ });
406
+ }
407
+ results.sort((a, b) => b.score - a.score);
408
+ return { tables: results.slice(0, topN) };
409
+ }
410
+ /**
411
+ * Scan codebase for unsafe DML patterns in SQL strings.
412
+ * Uses ripgrep to find DML statements, then classifies safety.
413
+ */
414
+ export async function scanDmlSafety(repo, options) {
415
+ const index = await getCodeIndex(repo);
416
+ if (!index) {
417
+ throw new Error(`Repository "${repo}" not found. Run index_folder first.`);
418
+ }
419
+ const filePattern = options?.file_pattern;
420
+ const maxResults = options?.max_results ?? 200;
421
+ const findings = [];
422
+ const filesScanned = new Set();
423
+ // Pattern 1: DELETE without WHERE
424
+ const delMatches = await searchText(repo, "DELETE FROM", {
425
+ regex: false,
426
+ max_results: maxResults,
427
+ file_pattern: filePattern,
428
+ context_lines: 0,
429
+ });
430
+ for (const m of delMatches) {
431
+ filesScanned.add(m.file);
432
+ const text = m.content ?? "";
433
+ // Check if WHERE exists after DELETE FROM on the same line or nearby
434
+ if (!/\bWHERE\b/i.test(text)) {
435
+ findings.push({
436
+ rule: "delete-without-where",
437
+ severity: "high",
438
+ file: m.file,
439
+ line: m.line,
440
+ context: text.trim().slice(0, 120),
441
+ detail: `DELETE FROM without WHERE clause — may delete all rows.`,
442
+ });
443
+ }
444
+ }
445
+ // Pattern 2: UPDATE without WHERE
446
+ const updMatches = await searchText(repo, "UPDATE", {
447
+ regex: false,
448
+ max_results: maxResults,
449
+ file_pattern: filePattern,
450
+ context_lines: 0,
451
+ });
452
+ for (const m of updMatches) {
453
+ filesScanned.add(m.file);
454
+ const text = m.content ?? "";
455
+ // Must contain SET (otherwise it's not a DML UPDATE)
456
+ if (!/\bSET\b/i.test(text))
457
+ continue;
458
+ if (!/\bWHERE\b/i.test(text)) {
459
+ findings.push({
460
+ rule: "update-without-where",
461
+ severity: "high",
462
+ file: m.file,
463
+ line: m.line,
464
+ context: text.trim().slice(0, 120),
465
+ detail: `UPDATE...SET without WHERE clause — may update all rows.`,
466
+ });
467
+ }
468
+ }
469
+ // Pattern 3: SELECT * (unbounded read)
470
+ const selMatches = await searchText(repo, "SELECT *", {
471
+ regex: false,
472
+ max_results: maxResults,
473
+ file_pattern: filePattern,
474
+ context_lines: 0,
475
+ });
476
+ for (const m of selMatches) {
477
+ filesScanned.add(m.file);
478
+ const text = m.content ?? "";
479
+ // Only flag if FROM is present (actual query, not comment/string fragment)
480
+ if (!/\bFROM\b/i.test(text))
481
+ continue;
482
+ findings.push({
483
+ rule: "select-star",
484
+ severity: "info",
485
+ file: m.file,
486
+ line: m.line,
487
+ context: text.trim().slice(0, 120),
488
+ detail: `SELECT * — fetches all columns. Consider listing specific fields.`,
489
+ });
490
+ }
491
+ // Deduplicate: same file:line + same rule
492
+ const seen = new Set();
493
+ const deduped = findings.filter((f) => {
494
+ const key = `${f.file}:${f.line}:${f.rule}`;
495
+ if (seen.has(key))
496
+ return false;
497
+ seen.add(key);
498
+ return true;
499
+ });
500
+ const by_rule = {};
501
+ for (const f of deduped) {
502
+ by_rule[f.rule] = (by_rule[f.rule] ?? 0) + 1;
503
+ }
504
+ return {
505
+ findings: deduped,
506
+ summary: { total: deduped.length, by_rule, files_scanned: filesScanned.size },
507
+ };
508
+ }
509
+ /**
510
+ * Lint SQL schema for common anti-patterns.
511
+ * Conservative ruleset with near-zero false positive rate:
512
+ * - no-primary-key: table without PRIMARY KEY (serious design smell)
513
+ * - wide-table: table with >20 columns (god table)
514
+ * - duplicate-index-name: same index name defined multiple times
515
+ */
516
+ export async function lintSchema(repo, options) {
517
+ const index = await getCodeIndex(repo);
518
+ if (!index) {
519
+ throw new Error(`Repository "${repo}" not found. Run index_folder first.`);
520
+ }
521
+ const filePattern = options?.file_pattern;
522
+ const findings = [];
523
+ const warnings = [];
524
+ const tables = index.symbols.filter((s) => {
525
+ if (s.kind !== "table")
526
+ return false;
527
+ if (filePattern && !s.file.includes(filePattern))
528
+ return false;
529
+ return true;
530
+ });
531
+ if (tables.length === 0) {
532
+ warnings.push("No SQL tables found in this repository.");
533
+ return { findings, summary: { total: 0, by_rule: {} }, warnings };
534
+ }
535
+ // Rule 1: no-primary-key — table with no PK field
536
+ for (const table of tables) {
537
+ const source = table.source ?? "";
538
+ const hasPK = /PRIMARY\s+KEY/i.test(source) || /\bSERIAL\b/i.test(source);
539
+ if (!hasPK) {
540
+ findings.push({
541
+ rule: "no-primary-key",
542
+ severity: "warning",
543
+ table: table.name,
544
+ detail: `Table "${table.name}" has no PRIMARY KEY or SERIAL column.`,
545
+ file: table.file,
546
+ line: table.start_line,
547
+ });
548
+ }
549
+ }
550
+ // Rule 2: wide-table — >20 columns
551
+ for (const table of tables) {
552
+ const fields = index.symbols.filter((s) => s.kind === "field" && s.parent === table.id);
553
+ if (fields.length > 20) {
554
+ findings.push({
555
+ rule: "wide-table",
556
+ severity: "warning",
557
+ table: table.name,
558
+ detail: `Table "${table.name}" has ${fields.length} columns (threshold: 20). Consider splitting.`,
559
+ file: table.file,
560
+ line: table.start_line,
561
+ });
562
+ }
563
+ }
564
+ // Rule 3: duplicate-index-name
565
+ const indexNames = new Map();
566
+ const indexes = index.symbols.filter((s) => {
567
+ if (s.kind !== "index")
568
+ return false;
569
+ if (filePattern && !s.file.includes(filePattern))
570
+ return false;
571
+ return true;
572
+ });
573
+ for (const idx of indexes) {
574
+ const key = idx.name.toLowerCase();
575
+ if (indexNames.has(key)) {
576
+ const prev = indexNames.get(key);
577
+ findings.push({
578
+ rule: "duplicate-index-name",
579
+ severity: "warning",
580
+ table: idx.name,
581
+ detail: `Index "${idx.name}" defined at ${idx.file}:${idx.start_line} duplicates index at ${prev.file}:${prev.line}.`,
582
+ file: idx.file,
583
+ line: idx.start_line,
584
+ });
585
+ }
586
+ else {
587
+ indexNames.set(key, { file: idx.file, line: idx.start_line });
588
+ }
589
+ }
590
+ // Build summary
591
+ const by_rule = {};
592
+ for (const f of findings) {
593
+ by_rule[f.rule] = (by_rule[f.rule] ?? 0) + 1;
594
+ }
595
+ return {
596
+ findings,
597
+ summary: { total: findings.length, by_rule },
598
+ warnings,
599
+ };
600
+ }
601
+ const MIGRATION_PATTERNS = [
602
+ // Destructive (high severity)
603
+ { regex: /DROP\s+TABLE\s+(?:IF\s+EXISTS\s+)?(?:`([^`]+)`|"([^"]+)"|\[([^\]]+)\]|(\w+))/i, operation: "DROP TABLE", category: "destructive", severity: "high", targetGroup: 1 },
604
+ { regex: /ALTER\s+TABLE\s+(?:`([^`]+)`|"([^"]+)"|\[([^\]]+)\]|(\w+))\s+DROP\s+COLUMN\s+(?:IF\s+EXISTS\s+)?(?:`([^`]+)`|"([^"]+)"|\[([^\]]+)\]|(\w+))/i, operation: "DROP COLUMN", category: "destructive", severity: "high", targetGroup: 1 },
605
+ { regex: /DROP\s+INDEX\s+(?:IF\s+EXISTS\s+)?(?:`([^`]+)`|"([^"]+)"|\[([^\]]+)\]|(\w+))/i, operation: "DROP INDEX", category: "destructive", severity: "medium", targetGroup: 1 },
606
+ { regex: /ALTER\s+TABLE\s+(?:`([^`]+)`|"([^"]+)"|\[([^\]]+)\]|(\w+))\s+DROP\s+CONSTRAINT/i, operation: "DROP CONSTRAINT", category: "destructive", severity: "medium", targetGroup: 1 },
607
+ { regex: /TRUNCATE\s+(?:TABLE\s+)?(?:`([^`]+)`|"([^"]+)"|\[([^\]]+)\]|(\w+))/i, operation: "TRUNCATE", category: "destructive", severity: "high", targetGroup: 1 },
608
+ // Modifying (medium severity)
609
+ { regex: /ALTER\s+TABLE\s+(?:`([^`]+)`|"([^"]+)"|\[([^\]]+)\]|(\w+))\s+ADD\s+COLUMN/i, operation: "ADD COLUMN", category: "modifying", severity: "low", targetGroup: 1 },
610
+ { regex: /ALTER\s+TABLE\s+(?:`([^`]+)`|"([^"]+)"|\[([^\]]+)\]|(\w+))\s+ADD\s+(?!COLUMN)/i, operation: "ALTER TABLE ADD", category: "modifying", severity: "low", targetGroup: 1 },
611
+ { regex: /ALTER\s+TABLE\s+(?:`([^`]+)`|"([^"]+)"|\[([^\]]+)\]|(\w+))\s+ALTER\s+COLUMN/i, operation: "ALTER COLUMN", category: "modifying", severity: "medium", targetGroup: 1 },
612
+ { regex: /ALTER\s+TABLE\s+(?:`([^`]+)`|"([^"]+)"|\[([^\]]+)\]|(\w+))\s+RENAME/i, operation: "RENAME", category: "modifying", severity: "medium", targetGroup: 1 },
613
+ // Additive (low severity) — these overlap with the extractor's DDL patterns
614
+ { regex: /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:`([^`]+)`|"([^"]+)"|\[([^\]]+)\]|(\w+))/i, operation: "CREATE TABLE", category: "additive", severity: "low", targetGroup: 1 },
615
+ { regex: /CREATE\s+(?:UNIQUE\s+)?INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:`([^`]+)`|"([^"]+)"|\[([^\]]+)\]|(\w+))/i, operation: "CREATE INDEX", category: "additive", severity: "low", targetGroup: 1 },
616
+ { regex: /CREATE\s+(?:OR\s+REPLACE\s+)?(?:MATERIALIZED\s+)?VIEW\s+(?:`([^`]+)`|"([^"]+)"|\[([^\]]+)\]|(\w+))/i, operation: "CREATE VIEW", category: "additive", severity: "low", targetGroup: 1 },
617
+ ];
618
+ function pickTarget(m, startGroup) {
619
+ for (let i = startGroup; i < m.length; i++) {
620
+ if (m[i])
621
+ return m[i];
622
+ }
623
+ return "(unknown)";
624
+ }
625
+ export async function diffMigrations(repo, options) {
626
+ const index = await getCodeIndex(repo);
627
+ if (!index) {
628
+ throw new Error(`Repository "${repo}" not found. Run index_folder first.`);
629
+ }
630
+ const filePattern = options?.file_pattern;
631
+ // Find .sql files, sorted by name (migration order heuristic)
632
+ const sqlFiles = index.files
633
+ .filter((f) => (f.language === "sql" || f.language === "sql-jinja"))
634
+ .filter((f) => !filePattern || f.path.includes(filePattern))
635
+ .sort((a, b) => a.path.localeCompare(b.path));
636
+ const additive = [];
637
+ const modifying = [];
638
+ const destructive = [];
639
+ for (const fileEntry of sqlFiles) {
640
+ // Read file source from symbols (each symbol has source)
641
+ // Or reconstruct from all symbols in this file
642
+ const fileSymbols = index.symbols.filter((s) => s.file === fileEntry.path);
643
+ // Collect all raw source lines we can access
644
+ const seenLines = new Set();
645
+ for (const sym of fileSymbols) {
646
+ if (!sym.source)
647
+ continue;
648
+ for (const line of sym.source.split("\n")) {
649
+ seenLines.add(line);
650
+ }
651
+ }
652
+ // Also scan the file directly for DML patterns not captured as symbols
653
+ // (ALTER, DROP, TRUNCATE aren't symbols — they're imperative ops)
654
+ let fullSource;
655
+ try {
656
+ const { readFileSync } = await import("node:fs");
657
+ const { join } = await import("node:path");
658
+ fullSource = readFileSync(join(index.root, fileEntry.path), "utf-8");
659
+ }
660
+ catch {
661
+ // File not accessible — use symbol sources only
662
+ }
663
+ const linesToScan = fullSource
664
+ ? fullSource.split("\n")
665
+ : [...seenLines];
666
+ for (let lineIdx = 0; lineIdx < linesToScan.length; lineIdx++) {
667
+ const line = linesToScan[lineIdx];
668
+ const trimmed = line.trim();
669
+ if (!trimmed || trimmed.startsWith("--"))
670
+ continue;
671
+ for (const pat of MIGRATION_PATTERNS) {
672
+ const m = pat.regex.exec(trimmed);
673
+ if (!m)
674
+ continue;
675
+ const target = pickTarget(m, pat.targetGroup);
676
+ // For DROP COLUMN, include table.column
677
+ let fullTarget = target;
678
+ if (pat.operation === "DROP COLUMN") {
679
+ const colName = pickTarget(m, 5); // groups 5-8 are the column name
680
+ fullTarget = `${target}.${colName}`;
681
+ }
682
+ const op = {
683
+ operation: pat.operation,
684
+ target: fullTarget,
685
+ severity: pat.severity,
686
+ file: fileEntry.path,
687
+ line: lineIdx + 1,
688
+ raw: trimmed.slice(0, 120),
689
+ };
690
+ switch (pat.category) {
691
+ case "additive":
692
+ additive.push(op);
693
+ break;
694
+ case "modifying":
695
+ modifying.push(op);
696
+ break;
697
+ case "destructive":
698
+ destructive.push(op);
699
+ break;
700
+ }
701
+ break; // first match wins per line
702
+ }
703
+ }
704
+ }
705
+ return {
706
+ additive,
707
+ modifying,
708
+ destructive,
709
+ summary: {
710
+ additive: additive.length,
711
+ modifying: modifying.length,
712
+ destructive: destructive.length,
713
+ total_files: sqlFiles.length,
714
+ },
715
+ };
716
+ }
717
+ /**
718
+ * Find SQL tables with zero references outside their own CREATE TABLE definition.
719
+ * Uses ripgrep-backed literal search per table for speed.
720
+ */
721
+ export async function findOrphanTables(repo, options) {
722
+ const index = await getCodeIndex(repo);
723
+ if (!index) {
724
+ throw new Error(`Repository "${repo}" not found. Run index_folder first.`);
725
+ }
726
+ const filePattern = options?.file_pattern;
727
+ // Collect all SQL tables
728
+ const tables = index.symbols.filter((s) => {
729
+ if (s.kind !== "table")
730
+ return false;
731
+ if (filePattern && !s.file.includes(filePattern))
732
+ return false;
733
+ return true;
734
+ });
735
+ const orphans = [];
736
+ for (const table of tables) {
737
+ // Search for references to this table name via ripgrep
738
+ let rgMatches = [];
739
+ try {
740
+ rgMatches = await searchText(repo, table.name, {
741
+ regex: false,
742
+ max_results: 20,
743
+ context_lines: 0,
744
+ });
745
+ }
746
+ catch {
747
+ rgMatches = [];
748
+ }
749
+ // Boundary filter + exclude the definition line itself
750
+ const IDENT_CHAR = "[a-zA-Z0-9_#$@]";
751
+ const escaped = escapeRegex(table.name);
752
+ const boundaryRegex = new RegExp(`(?<!${IDENT_CHAR})${escaped}(?!${IDENT_CHAR})`, "i");
753
+ const realRefs = rgMatches.filter((m) => {
754
+ const text = m.content ?? "";
755
+ if (!boundaryRegex.test(text))
756
+ return false;
757
+ // Exclude the CREATE TABLE definition line
758
+ if (m.file === table.file && m.line === table.start_line)
759
+ return false;
760
+ // Exclude lines within the CREATE TABLE body (column defs, constraints)
761
+ if (m.file === table.file && m.line > table.start_line && m.line <= table.end_line)
762
+ return false;
763
+ return true;
764
+ });
765
+ if (realRefs.length === 0) {
766
+ const columnCount = index.symbols.filter((s) => s.kind === "field" && s.parent === table.id).length;
767
+ orphans.push({
768
+ name: table.name,
769
+ file: table.file,
770
+ line: table.start_line,
771
+ column_count: columnCount,
772
+ });
773
+ }
774
+ }
775
+ return {
776
+ orphans,
777
+ total_tables: tables.length,
778
+ orphan_count: orphans.length,
779
+ };
780
+ }
781
+ /** Parse a Prisma model block source → fields + @@map table name */
782
+ function parsePrismaModel(sym) {
783
+ const source = sym.source ?? "";
784
+ const lines = source.split("\n");
785
+ const fields = [];
786
+ let tableName = camelToSnake(sym.name);
787
+ // @@map("table_name")
788
+ const mapMatch = /@@map\s*\(\s*"([^"]+)"\s*\)/.exec(source);
789
+ if (mapMatch)
790
+ tableName = mapMatch[1];
791
+ // Field line pattern: ` fieldName Type? @attr @attr`
792
+ const FIELD_RE = /^\s*(\w+)\s+(\w+)(\[\])?(\?)?\s*(.*)$/;
793
+ const RELATION_RE = /@relation/;
794
+ const MAP_ATTR_RE = /@map\s*\(\s*"([^"]+)"\s*\)/;
795
+ // Prisma scalar types — anything else with an uppercase first letter is a model relation
796
+ const SCALAR_TYPES = new Set([
797
+ "Int", "BigInt", "Float", "Decimal", "String", "Boolean", "DateTime", "Json", "Bytes", "Unsupported",
798
+ ]);
799
+ for (const line of lines) {
800
+ const trimmed = line.trim();
801
+ // Skip model header, closing brace, comments, attributes
802
+ if (trimmed.startsWith("model ") || trimmed === "" || trimmed === "}")
803
+ continue;
804
+ if (trimmed.startsWith("//") || trimmed.startsWith("@@"))
805
+ continue;
806
+ const m = FIELD_RE.exec(trimmed);
807
+ if (!m)
808
+ continue;
809
+ const name = m[1];
810
+ const type = m[2];
811
+ const isList = m[3] === "[]";
812
+ const optional = m[4] === "?";
813
+ const attrs = m[5] ?? "";
814
+ // Skip reserved keywords that aren't fields (e.g. "model", "enum")
815
+ if (/^(model|enum|type|view)$/.test(name))
816
+ continue;
817
+ const is_id = /@id\b/.test(attrs);
818
+ // A field is a relation if:
819
+ // 1. It has @relation attr, OR
820
+ // 2. It's a list (Type[]) — always a relation side, OR
821
+ // 3. Its type starts with uppercase AND isn't a built-in scalar (→ custom model)
822
+ const is_relation = RELATION_RE.test(attrs) ||
823
+ isList ||
824
+ (/^[A-Z]/.test(type) && !SCALAR_TYPES.has(type));
825
+ // Relations aren't real DB columns — skip for drift purposes
826
+ if (is_relation)
827
+ continue;
828
+ // @map("db_col") overrides the db name
829
+ const mapField = MAP_ATTR_RE.exec(attrs);
830
+ const db_name = mapField ? mapField[1] : camelToSnake(name);
831
+ fields.push({ name, db_name, type, optional, is_id, is_relation });
832
+ }
833
+ return {
834
+ name: sym.name,
835
+ table: tableName,
836
+ file: sym.file,
837
+ line: sym.start_line,
838
+ fields,
839
+ };
840
+ }
841
+ function camelToSnake(s) {
842
+ return s
843
+ .replace(/([A-Z])/g, (_, c) => "_" + c.toLowerCase())
844
+ .replace(/^_/, "");
845
+ }
846
+ /**
847
+ * Normalize a type name for cross-layer comparison.
848
+ * Prisma Int → int, SQL INTEGER → int, Float → float, etc.
849
+ *
850
+ * Handles signatures like "TEXT NOT NULL", "SERIAL PRIMARY KEY", "int(10) unsigned".
851
+ * Extracts the base type word, then groups by semantic equivalence.
852
+ */
853
+ function normalizeType(raw) {
854
+ // Take the first word only (strip modifiers, constraints, size).
855
+ // "TEXT NOT NULL" → "text", "int(10) unsigned" → "int", "DECIMAL(10,2)" → "decimal"
856
+ const firstWord = /[a-zA-Z]+/.exec(raw)?.[0]?.toLowerCase() ?? "";
857
+ // Group equivalents
858
+ if (/^(int|integer|smallint|bigint|serial|bigserial|smallserial|tinyint|mediumint)$/.test(firstWord))
859
+ return "int";
860
+ if (/^(float|real|double|decimal|numeric|money)$/.test(firstWord))
861
+ return "float";
862
+ if (/^(text|varchar|char|string|nvarchar|longtext|mediumtext|tinytext)$/.test(firstWord))
863
+ return "string";
864
+ if (/^(bool|boolean|bit)$/.test(firstWord))
865
+ return "bool";
866
+ if (/^(timestamp|timestamptz|datetime|date|time|timetz)$/.test(firstWord))
867
+ return "datetime";
868
+ if (/^(json|jsonb)$/.test(firstWord))
869
+ return "json";
870
+ if (/^(uuid)$/.test(firstWord))
871
+ return "uuid";
872
+ if (/^(bytea|blob|binary|longblob|mediumblob|tinyblob)$/.test(firstWord))
873
+ return "bytes";
874
+ return firstWord || "unknown";
875
+ }
876
+ export async function analyzeSchemaDrift(repo, options) {
877
+ const index = await getCodeIndex(repo);
878
+ if (!index) {
879
+ throw new Error(`Repository "${repo}" not found. Run index_folder first.`);
880
+ }
881
+ const filePattern = options?.file_pattern;
882
+ const drifts = [];
883
+ const warnings = [];
884
+ const orms_detected = [];
885
+ // Collect SQL tables and fields
886
+ const sqlTables = new Map();
887
+ for (const sym of index.symbols) {
888
+ if (sym.kind !== "table")
889
+ continue;
890
+ if (filePattern && !sym.file.includes(filePattern))
891
+ continue;
892
+ const columns = new Map();
893
+ const fields = index.symbols.filter((f) => f.kind === "field" && f.parent === sym.id);
894
+ for (const f of fields) {
895
+ columns.set(f.name.toLowerCase(), {
896
+ type: f.signature ?? "unknown",
897
+ file: f.file,
898
+ line: f.start_line,
899
+ });
900
+ }
901
+ sqlTables.set(sym.name.toLowerCase(), {
902
+ file: sym.file,
903
+ line: sym.start_line,
904
+ columns,
905
+ });
906
+ }
907
+ // Collect Prisma models (kind === "class" in prisma extractor)
908
+ const prismaModels = [];
909
+ for (const sym of index.symbols) {
910
+ if (sym.kind !== "class")
911
+ continue;
912
+ if (!sym.file.endsWith(".prisma"))
913
+ continue;
914
+ if (filePattern && !sym.file.includes(filePattern))
915
+ continue;
916
+ prismaModels.push(parsePrismaModel(sym));
917
+ }
918
+ if (prismaModels.length > 0)
919
+ orms_detected.push("prisma");
920
+ // TODO: Drizzle and TypeORM collection (v1.2)
921
+ if (orms_detected.length === 0) {
922
+ warnings.push("No ORM models found in repository. analyze_schema_drift requires at least one ORM (Prisma/Drizzle/TypeORM).");
923
+ return {
924
+ drifts: [],
925
+ summary: { extra_in_orm: 0, extra_in_sql: 0, type_mismatches: 0, total: 0 },
926
+ orms_detected,
927
+ warnings,
928
+ };
929
+ }
930
+ // Cross-reference: Prisma models vs SQL tables
931
+ const matchedSqlTables = new Set();
932
+ for (const model of prismaModels) {
933
+ const sqlTable = sqlTables.get(model.table.toLowerCase());
934
+ if (!sqlTable) {
935
+ // Prisma model has no matching SQL table → extra_in_orm
936
+ drifts.push({
937
+ kind: "extra_in_orm",
938
+ table: model.table,
939
+ orm: "prisma",
940
+ orm_file: model.file,
941
+ orm_line: model.line,
942
+ detail: `Prisma model "${model.name}" maps to table "${model.table}" which does not exist in SQL schema.`,
943
+ });
944
+ continue;
945
+ }
946
+ matchedSqlTables.add(model.table.toLowerCase());
947
+ // Compare fields
948
+ const sqlCols = sqlTable.columns;
949
+ const matchedSqlCols = new Set();
950
+ for (const field of model.fields) {
951
+ const sqlCol = sqlCols.get(field.db_name.toLowerCase());
952
+ if (!sqlCol) {
953
+ drifts.push({
954
+ kind: "extra_in_orm",
955
+ table: model.table,
956
+ column: field.name,
957
+ orm: "prisma",
958
+ orm_file: model.file,
959
+ orm_line: model.line,
960
+ orm_type: field.type,
961
+ detail: `Prisma field "${model.name}.${field.name}" (maps to "${field.db_name}") does not exist in SQL table "${model.table}".`,
962
+ });
963
+ continue;
964
+ }
965
+ matchedSqlCols.add(field.db_name.toLowerCase());
966
+ // Type compatibility check
967
+ const ormNorm = normalizeType(field.type);
968
+ const sqlNorm = normalizeType(sqlCol.type);
969
+ if (ormNorm !== sqlNorm && ormNorm !== "unknown" && sqlNorm !== "unknown") {
970
+ drifts.push({
971
+ kind: "type_mismatch",
972
+ table: model.table,
973
+ column: field.name,
974
+ orm: "prisma",
975
+ orm_file: model.file,
976
+ orm_line: model.line,
977
+ sql_file: sqlCol.file,
978
+ sql_line: sqlCol.line,
979
+ orm_type: field.type,
980
+ sql_type: sqlCol.type,
981
+ detail: `Type mismatch: Prisma "${model.name}.${field.name}" is "${field.type}" (${ormNorm}) but SQL "${model.table}.${field.db_name}" is "${sqlCol.type}" (${sqlNorm}).`,
982
+ });
983
+ }
984
+ }
985
+ // SQL columns not covered by any Prisma field → extra_in_sql (column-level)
986
+ for (const [colName, sqlCol] of sqlCols) {
987
+ if (matchedSqlCols.has(colName))
988
+ continue;
989
+ // Don't flag common auto-columns that Prisma often omits
990
+ if (/^(created_at|updated_at|deleted_at)$/i.test(colName))
991
+ continue;
992
+ drifts.push({
993
+ kind: "extra_in_sql",
994
+ table: model.table,
995
+ column: colName,
996
+ orm: "prisma",
997
+ sql_file: sqlCol.file,
998
+ sql_line: sqlCol.line,
999
+ sql_type: sqlCol.type,
1000
+ detail: `SQL column "${model.table}.${colName}" has no corresponding field in Prisma model "${model.name}".`,
1001
+ });
1002
+ }
1003
+ }
1004
+ // SQL tables with no Prisma model → extra_in_sql (table-level)
1005
+ for (const [tableName, sqlTable] of sqlTables) {
1006
+ if (matchedSqlTables.has(tableName))
1007
+ continue;
1008
+ drifts.push({
1009
+ kind: "extra_in_sql",
1010
+ table: tableName,
1011
+ orm: "prisma",
1012
+ sql_file: sqlTable.file,
1013
+ sql_line: sqlTable.line,
1014
+ detail: `SQL table "${tableName}" has no corresponding Prisma model.`,
1015
+ });
1016
+ }
1017
+ const summary = {
1018
+ extra_in_orm: drifts.filter((d) => d.kind === "extra_in_orm").length,
1019
+ extra_in_sql: drifts.filter((d) => d.kind === "extra_in_sql").length,
1020
+ type_mismatches: drifts.filter((d) => d.kind === "type_mismatch").length,
1021
+ total: drifts.length,
1022
+ };
1023
+ // Heuristic warning: if >50% of ORM tables have no SQL counterpart, this is
1024
+ // likely a Prisma-migrate style project where SQL files are incremental
1025
+ // migrations, not a full schema snapshot. In that case table-level drift
1026
+ // is noise — the real signal is field-level drift on matched tables.
1027
+ const totalOrmTables = prismaModels.length;
1028
+ const orphanOrmTables = drifts.filter((d) => d.kind === "extra_in_orm" && !d.column).length;
1029
+ if (totalOrmTables > 0 && orphanOrmTables / totalOrmTables > 0.5) {
1030
+ warnings.push(`${orphanOrmTables}/${totalOrmTables} Prisma models have no SQL counterpart. ` +
1031
+ `This likely means the project uses Prisma Migrate (incremental migrations) ` +
1032
+ `rather than a full schema.sql snapshot. Field-level drifts on matched tables ` +
1033
+ `are still meaningful; ignore table-level extra_in_orm drifts in this mode.`);
1034
+ }
1035
+ return { drifts, summary, orms_detected, warnings };
1036
+ }
1037
+ const DEFAULT_SQL_AUDIT_CHECKS = ["drift", "orphan", "lint", "dml", "complexity"];
1038
+ /**
1039
+ * Composite SQL audit — runs multiple diagnostic gates in a single call.
1040
+ * Mirrors framework_audit / nest_audit / audit_scan pattern.
1041
+ *
1042
+ * Individual gate functions (analyzeSchemaDrift, findOrphanTables, lintSchema,
1043
+ * scanDmlSafety, analyzeSchemaComplexity) remain exported for internal use
1044
+ * but are NOT registered as separate MCP tools. sql_audit is the single
1045
+ * discoverable entry point.
1046
+ */
1047
+ export async function sqlAudit(repo, options) {
1048
+ const checks = options?.checks ?? DEFAULT_SQL_AUDIT_CHECKS;
1049
+ const filePattern = options?.file_pattern;
1050
+ const gates = [];
1051
+ const warnings = [];
1052
+ // Build option objects with only defined fields (for exactOptionalPropertyTypes compat)
1053
+ const scopedOpts = {};
1054
+ if (filePattern !== undefined)
1055
+ scopedOpts.file_pattern = filePattern;
1056
+ // Gate 1: schema_drift (ORM ↔ SQL drift)
1057
+ if (checks.includes("drift")) {
1058
+ const drift = await analyzeSchemaDrift(repo, scopedOpts);
1059
+ const criticalCount = drift.summary.type_mismatches;
1060
+ gates.push({
1061
+ check: "drift",
1062
+ pass: drift.summary.total === 0,
1063
+ critical: criticalCount > 0,
1064
+ finding_count: drift.summary.total,
1065
+ data: drift,
1066
+ summary: drift.summary.total === 0
1067
+ ? "No schema drift detected"
1068
+ : `${drift.summary.total} drift${drift.summary.total === 1 ? "" : "s"}: ${drift.summary.extra_in_orm} extra in ORM, ${drift.summary.extra_in_sql} extra in SQL, ${drift.summary.type_mismatches} type mismatches`,
1069
+ });
1070
+ for (const w of drift.warnings)
1071
+ warnings.push(`drift: ${w}`);
1072
+ }
1073
+ // Gate 2: orphan_tables
1074
+ if (checks.includes("orphan")) {
1075
+ const orphan = await findOrphanTables(repo, scopedOpts);
1076
+ gates.push({
1077
+ check: "orphan",
1078
+ pass: orphan.orphan_count === 0,
1079
+ critical: false,
1080
+ finding_count: orphan.orphan_count,
1081
+ data: orphan,
1082
+ summary: orphan.orphan_count === 0
1083
+ ? `No orphan tables (${orphan.total_tables} tables scanned)`
1084
+ : `${orphan.orphan_count}/${orphan.total_tables} tables with zero references`,
1085
+ });
1086
+ }
1087
+ // Gate 3: lint_schema
1088
+ if (checks.includes("lint")) {
1089
+ const lint = await lintSchema(repo, scopedOpts);
1090
+ gates.push({
1091
+ check: "lint",
1092
+ pass: lint.summary.total === 0,
1093
+ critical: false,
1094
+ finding_count: lint.summary.total,
1095
+ data: lint,
1096
+ summary: lint.summary.total === 0
1097
+ ? "No schema lint violations"
1098
+ : `${lint.summary.total} lint violation${lint.summary.total === 1 ? "" : "s"}: ${Object.entries(lint.summary.by_rule).map(([r, n]) => `${r}=${n}`).join(", ")}`,
1099
+ });
1100
+ for (const w of lint.warnings)
1101
+ warnings.push(`lint: ${w}`);
1102
+ }
1103
+ // Gate 4: dml_safety
1104
+ if (checks.includes("dml")) {
1105
+ const dmlOpts = {};
1106
+ if (filePattern !== undefined)
1107
+ dmlOpts.file_pattern = filePattern;
1108
+ if (options?.max_results !== undefined)
1109
+ dmlOpts.max_results = options.max_results;
1110
+ const dml = await scanDmlSafety(repo, dmlOpts);
1111
+ const highSeverity = dml.findings.filter((f) => f.severity === "high").length;
1112
+ gates.push({
1113
+ check: "dml",
1114
+ pass: highSeverity === 0,
1115
+ critical: highSeverity > 0,
1116
+ finding_count: dml.summary.total,
1117
+ data: dml,
1118
+ summary: dml.summary.total === 0
1119
+ ? `No DML safety issues (${dml.summary.files_scanned} files scanned)`
1120
+ : `${dml.summary.total} DML issue${dml.summary.total === 1 ? "" : "s"} (${highSeverity} high risk): ${Object.entries(dml.summary.by_rule).map(([r, n]) => `${r}=${n}`).join(", ")}`,
1121
+ });
1122
+ }
1123
+ // Gate 5: schema_complexity (god tables)
1124
+ if (checks.includes("complexity")) {
1125
+ const complexityOpts = { top_n: 10 };
1126
+ if (filePattern !== undefined)
1127
+ complexityOpts.file_pattern = filePattern;
1128
+ const complexity = await analyzeSchemaComplexity(repo, complexityOpts);
1129
+ // Threshold: score >= 25 = "needs refactor" (20 cols + 1 FK + 1 idx → 24.5)
1130
+ const godTables = complexity.tables.filter((t) => t.score >= 25);
1131
+ gates.push({
1132
+ check: "complexity",
1133
+ pass: godTables.length === 0,
1134
+ critical: false,
1135
+ finding_count: godTables.length,
1136
+ data: complexity,
1137
+ summary: godTables.length === 0
1138
+ ? `No god tables detected (${complexity.tables.length} tables analyzed)`
1139
+ : `${godTables.length} god table${godTables.length === 1 ? "" : "s"}: ${godTables.slice(0, 3).map((t) => `${t.name}(${t.score.toFixed(0)})`).join(", ")}${godTables.length > 3 ? "..." : ""}`,
1140
+ });
1141
+ }
1142
+ const total_findings = gates.reduce((sum, g) => sum + g.finding_count, 0);
1143
+ const critical_findings = gates
1144
+ .filter((g) => g.critical)
1145
+ .reduce((sum, g) => sum + g.finding_count, 0);
1146
+ const gates_passed = gates.filter((g) => g.pass).length;
1147
+ const gates_failed = gates.filter((g) => !g.pass).length;
1148
+ return {
1149
+ gates,
1150
+ summary: {
1151
+ total_findings,
1152
+ critical_findings,
1153
+ gates_run: gates.length,
1154
+ gates_passed,
1155
+ gates_failed,
1156
+ },
1157
+ warnings,
1158
+ };
1159
+ }
1160
+ //# sourceMappingURL=sql-tools.js.map