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,645 @@
1
+ /**
2
+ * PHP/Yii2-specific code intelligence tools.
3
+ *
4
+ * Provides 9 hidden/discoverable tools that augment generic code intelligence
5
+ * with PHP framework awareness: PSR-4 namespace resolution, ActiveRecord schema
6
+ * extraction, event/listener tracing, view mapping, service locator resolution,
7
+ * security scanning, compound project audit (9-gate), N+1 query detection,
8
+ * and god-model detection.
9
+ */
10
+ import { readFile } from "node:fs/promises";
11
+ import { join } from "node:path";
12
+ import { getCodeIndex } from "./index-tools.js";
13
+ import { searchPatterns } from "./pattern-tools.js";
14
+ export async function resolvePhpNamespace(repo, className) {
15
+ const index = await getCodeIndex(repo);
16
+ if (!index)
17
+ throw new Error(`Repository "${repo}" not found.`);
18
+ const composer = await readJsonSafe(join(index.root, "composer.json"));
19
+ const psr4 = {
20
+ ...(composer?.autoload?.["psr-4"] ?? {}),
21
+ ...(composer?.["autoload-dev"]?.["psr-4"] ?? {}),
22
+ };
23
+ // Strip leading backslash
24
+ const normalized = className.replace(/^\\/, "");
25
+ const parts = normalized.split("\\");
26
+ const namespaceOnly = parts.slice(0, -1).join("\\");
27
+ const shortName = parts[parts.length - 1];
28
+ // Find matching PSR-4 prefix (longest match wins)
29
+ let bestPrefix = null;
30
+ let bestRoot = null;
31
+ for (const [prefix, roots] of Object.entries(psr4)) {
32
+ const normalizedPrefix = prefix.replace(/\\$/, "");
33
+ if (normalized.startsWith(normalizedPrefix + "\\") || normalized === normalizedPrefix) {
34
+ if (!bestPrefix || normalizedPrefix.length > bestPrefix.length) {
35
+ bestPrefix = normalizedPrefix;
36
+ bestRoot = Array.isArray(roots) ? roots[0] ?? null : roots;
37
+ }
38
+ }
39
+ }
40
+ if (!bestPrefix || !bestRoot) {
41
+ return {
42
+ class_name: shortName,
43
+ namespace: namespaceOnly,
44
+ file_path: null,
45
+ exists: false,
46
+ psr4_root: null,
47
+ psr4_prefix: null,
48
+ };
49
+ }
50
+ // Construct file path: strip prefix, replace \ with /, append .php
51
+ const remainder = normalized.slice(bestPrefix.length).replace(/^\\/, "");
52
+ const relativePath = remainder.replace(/\\/g, "/") + ".php";
53
+ const root = bestRoot.replace(/\/$/, "");
54
+ const filePath = root + "/" + relativePath;
55
+ // Check if file exists in index (strip leading ./ for comparison)
56
+ const normalizedFP = filePath.replace(/^\.\//, "");
57
+ const exists = index.files.some((f) => f.path === normalizedFP || f.path === filePath);
58
+ return {
59
+ class_name: shortName,
60
+ namespace: namespaceOnly,
61
+ file_path: filePath,
62
+ exists,
63
+ psr4_root: bestRoot,
64
+ psr4_prefix: bestPrefix,
65
+ };
66
+ }
67
+ export async function analyzeActiveRecord(repo, options) {
68
+ const index = await getCodeIndex(repo);
69
+ if (!index)
70
+ throw new Error(`Repository "${repo}" not found.`);
71
+ // Find PHP class symbols in model files
72
+ const classSymbols = index.symbols.filter((s) => {
73
+ if (s.kind !== "class")
74
+ return false;
75
+ if (!s.file.endsWith(".php"))
76
+ return false;
77
+ if (options?.model_name && s.name !== options.model_name)
78
+ return false;
79
+ if (options?.file_pattern && !s.file.includes(options.file_pattern))
80
+ return false;
81
+ return true;
82
+ });
83
+ const models = [];
84
+ for (const cls of classSymbols) {
85
+ // Heuristic: only models that have source containing ActiveRecord or extend Model
86
+ if (!cls.source)
87
+ continue;
88
+ const extendsAR = /extends\s+(?:ActiveRecord|Model|\\yii\\db\\ActiveRecord)/.test(cls.source);
89
+ if (!extendsAR)
90
+ continue;
91
+ const model = {
92
+ name: cls.name,
93
+ file: cls.file,
94
+ table_name: null,
95
+ relations: [],
96
+ rules: [],
97
+ behaviors: [],
98
+ methods: [],
99
+ };
100
+ // Extract tableName() return value
101
+ const tableMatch = /function\s+tableName\s*\([^)]*\)[^{]*\{[^}]*return\s+['"]([^'"]+)['"]/s.exec(cls.source);
102
+ if (tableMatch)
103
+ model.table_name = tableMatch[1];
104
+ // Find child method symbols
105
+ const methods = index.symbols.filter((s) => s.parent === cls.id && s.kind === "method");
106
+ model.methods = methods.map((m) => m.name);
107
+ // Extract relations from getX() methods that return hasOne/hasMany.
108
+ // Two-pass detection:
109
+ // Pass 1: find the primary `->hasOne(Target::class, ...)` or
110
+ // `->hasMany(Target::class, ...)` call.
111
+ // Pass 2: scan the rest of the source for modifiers:
112
+ // ->via('relation') (Yii2 2.0.13+ junction table via relation)
113
+ // ->viaTable('tbl', [...]) (direct junction table)
114
+ // ->inverseOf('relation') (bidirectional relation)
115
+ // The presence of `via` or `viaTable` upgrades the relation type
116
+ // to `manyMany`. `inverseOf` is decorative and doesn't change type.
117
+ for (const m of methods) {
118
+ if (!m.name.startsWith("get") || !m.source)
119
+ continue;
120
+ const relName = m.name.slice(3);
121
+ const primaryRe = /->(hasOne|hasMany)\s*\(\s*([\w\\]+)(?:::class)?/;
122
+ const primaryMatch = primaryRe.exec(m.source);
123
+ if (!primaryMatch)
124
+ continue;
125
+ const baseType = primaryMatch[1] === "hasOne" ? "hasOne" : "hasMany";
126
+ const targetClass = primaryMatch[2];
127
+ // Scan the method source for junction-table modifiers on the same chain.
128
+ // If found, the semantic type is manyMany even though the primary call was hasMany.
129
+ const hasJunction = /->(?:via|viaTable)\s*\(/.test(m.source);
130
+ const type = hasJunction ? "manyMany" : baseType;
131
+ model.relations.push({
132
+ name: relName.charAt(0).toLowerCase() + relName.slice(1),
133
+ type,
134
+ target_class: targetClass,
135
+ });
136
+ }
137
+ // Extract rule validators (loose regex on rules() method source)
138
+ const rulesMethod = methods.find((m) => m.name === "rules");
139
+ if (rulesMethod?.source) {
140
+ const ruleMatches = rulesMethod.source.matchAll(/\[\s*\[?['"]?[\w,\s'"]+['"]?\]?\s*,\s*['"]([\w]+)['"]/g);
141
+ for (const rm of ruleMatches) {
142
+ if (rm[1] && !model.rules.includes(rm[1]))
143
+ model.rules.push(rm[1]);
144
+ }
145
+ }
146
+ // Extract behaviors from behaviors() method
147
+ const behaviorsMethod = methods.find((m) => m.name === "behaviors");
148
+ if (behaviorsMethod?.source) {
149
+ const bMatches = behaviorsMethod.source.matchAll(/([A-Z]\w+Behavior)(?:::class)?/g);
150
+ for (const bm of bMatches) {
151
+ if (bm[1] && !model.behaviors.includes(bm[1]))
152
+ model.behaviors.push(bm[1]);
153
+ }
154
+ }
155
+ models.push(model);
156
+ }
157
+ return { models, total: models.length };
158
+ }
159
+ export async function tracePhpEvent(repo, options) {
160
+ const index = await getCodeIndex(repo);
161
+ if (!index)
162
+ throw new Error(`Repository "${repo}" not found.`);
163
+ const eventMap = new Map();
164
+ const getOrCreate = (name) => {
165
+ let e = eventMap.get(name);
166
+ if (!e) {
167
+ e = { event_name: name, triggers: [], listeners: [] };
168
+ eventMap.set(name, e);
169
+ }
170
+ return e;
171
+ };
172
+ // Scan PHP file symbols for event triggers and listeners
173
+ const phpSymbols = index.symbols.filter((s) => s.file.endsWith(".php") && s.source);
174
+ for (const sym of phpSymbols) {
175
+ const source = sym.source;
176
+ // Triggers: ->trigger('eventName') or Event::trigger(...)
177
+ const triggerRe = /->trigger\s*\(\s*['"]([^'"]+)['"]/g;
178
+ let match;
179
+ while ((match = triggerRe.exec(source)) !== null) {
180
+ const eventName = match[1];
181
+ if (options?.event_name && eventName !== options.event_name)
182
+ continue;
183
+ const line = sym.start_line + (source.slice(0, match.index).match(/\n/g)?.length ?? 0);
184
+ getOrCreate(eventName).triggers.push({
185
+ file: sym.file,
186
+ line,
187
+ context: extractLineContext(source, match.index),
188
+ });
189
+ }
190
+ // Listeners: ->on('eventName', ...) or Event::on(...)
191
+ const listenerRe = /(?:->|::)on\s*\(\s*['"]([^'"]+)['"]/g;
192
+ while ((match = listenerRe.exec(source)) !== null) {
193
+ const eventName = match[1];
194
+ if (options?.event_name && eventName !== options.event_name)
195
+ continue;
196
+ const line = sym.start_line + (source.slice(0, match.index).match(/\n/g)?.length ?? 0);
197
+ getOrCreate(eventName).listeners.push({
198
+ file: sym.file,
199
+ line,
200
+ context: extractLineContext(source, match.index),
201
+ });
202
+ }
203
+ }
204
+ const events = [...eventMap.values()];
205
+ return { events, total: events.length };
206
+ }
207
+ export async function findPhpViews(repo, options) {
208
+ const index = await getCodeIndex(repo);
209
+ if (!index)
210
+ throw new Error(`Repository "${repo}" not found.`);
211
+ const mappings = [];
212
+ // Find action methods in controllers
213
+ const controllers = index.symbols.filter((s) => s.kind === "class" && s.name.endsWith("Controller") && s.file.endsWith(".php"));
214
+ for (const ctrl of controllers) {
215
+ if (options?.controller && !ctrl.name.includes(options.controller))
216
+ continue;
217
+ const actions = index.symbols.filter((s) => s.parent === ctrl.id && s.kind === "method" && s.name.startsWith("action"));
218
+ for (const action of actions) {
219
+ if (!action.source)
220
+ continue;
221
+ // Match $this->render('viewName'), renderPartial('...'), renderAjax('...')
222
+ const renderRe = /\$this->render(?:Partial|Ajax|AsJson)?\s*\(\s*['"]([^'"]+)['"]/g;
223
+ let match;
224
+ while ((match = renderRe.exec(action.source)) !== null) {
225
+ const viewName = match[1];
226
+ // Yii2 convention: views/{controller-id}/{view}.php
227
+ const controllerId = pascalToKebab(ctrl.name.replace(/Controller$/, ""));
228
+ const viewFile = `views/${controllerId}/${viewName}.php`;
229
+ const exists = index.files.some((f) => f.path === viewFile || f.path.endsWith("/" + viewFile));
230
+ const line = action.start_line + (action.source.slice(0, match.index).match(/\n/g)?.length ?? 0);
231
+ mappings.push({
232
+ controller: ctrl.name,
233
+ action: action.name,
234
+ view_name: viewName,
235
+ view_file: exists ? viewFile : null,
236
+ render_line: line,
237
+ });
238
+ }
239
+ }
240
+ }
241
+ return { mappings, total: mappings.length };
242
+ }
243
+ export async function resolvePhpService(repo, options) {
244
+ const index = await getCodeIndex(repo);
245
+ if (!index)
246
+ throw new Error(`Repository "${repo}" not found.`);
247
+ const services = [];
248
+ const configFiles = index.files.filter((f) => /config\/(web|console|main|db)\.php$/.test(f.path));
249
+ for (const cf of configFiles) {
250
+ let source;
251
+ try {
252
+ source = await readFile(join(index.root, cf.path), "utf-8");
253
+ }
254
+ catch {
255
+ continue;
256
+ }
257
+ // Match component definitions: 'componentName' => ['class' => 'FQCN', ...]
258
+ const componentRe = /['"]([\w-]+)['"]\s*=>\s*\[\s*['"]class['"]\s*=>\s*['"]([\w\\]+)['"]/g;
259
+ let match;
260
+ while ((match = componentRe.exec(source)) !== null) {
261
+ const name = match[1];
262
+ const cls = match[2];
263
+ if (options?.service_name && name !== options.service_name)
264
+ continue;
265
+ // Resolve class to file via PSR-4
266
+ let filePath = null;
267
+ try {
268
+ const resolved = await resolvePhpNamespace(repo, cls);
269
+ if (resolved.exists)
270
+ filePath = resolved.file_path;
271
+ }
272
+ catch { /* ignore */ }
273
+ services.push({
274
+ name,
275
+ class: cls,
276
+ file: filePath,
277
+ config_file: cf.path,
278
+ });
279
+ }
280
+ }
281
+ return { services, total: services.length };
282
+ }
283
+ const PHP_SECURITY_CHECKS = [
284
+ { pattern: "sql-injection-php", severity: "critical" },
285
+ { pattern: "xss-php", severity: "critical" },
286
+ { pattern: "eval-php", severity: "critical" },
287
+ { pattern: "exec-php", severity: "critical" },
288
+ { pattern: "unserialize-php", severity: "high" },
289
+ { pattern: "file-include-var", severity: "high" },
290
+ { pattern: "unescaped-yii-view", severity: "high" },
291
+ { pattern: "raw-query-yii", severity: "high" },
292
+ ];
293
+ export async function phpSecurityScan(repo, options) {
294
+ const selectedChecks = options?.checks
295
+ ? PHP_SECURITY_CHECKS.filter((c) => options.checks.includes(c.pattern))
296
+ : PHP_SECURITY_CHECKS;
297
+ const findings = [];
298
+ const summary = { critical: 0, high: 0, medium: 0, low: 0, total: 0 };
299
+ // Run pattern checks in parallel
300
+ const results = await Promise.all(selectedChecks.map((check) => searchPatterns(repo, check.pattern, {
301
+ file_pattern: options?.file_pattern ?? ".php",
302
+ include_tests: false,
303
+ }).then((r) => ({ check, result: r })).catch(() => null)));
304
+ for (const res of results) {
305
+ if (!res)
306
+ continue;
307
+ for (const m of res.result.matches) {
308
+ findings.push({
309
+ severity: res.check.severity,
310
+ pattern: res.check.pattern,
311
+ file: m.file,
312
+ line: m.start_line,
313
+ context: m.context,
314
+ description: "", // description populated by searchPatterns but not in PatternMatch type
315
+ });
316
+ summary[res.check.severity]++;
317
+ summary.total++;
318
+ }
319
+ }
320
+ return {
321
+ findings,
322
+ summary,
323
+ checks_run: selectedChecks.map((c) => c.pattern),
324
+ };
325
+ }
326
+ // ---------------------------------------------------------------------------
327
+ // 7h. find_php_n_plus_one — detect foreach + relation access without ->with()
328
+ // ---------------------------------------------------------------------------
329
+ /**
330
+ * Common ActiveRecord scalar field names. Property access like $user->id or
331
+ * $user->created_at inside a foreach is NOT a relation (no N+1 risk), so we
332
+ * allow-list these to cut false positives.
333
+ */
334
+ const SCALAR_FIELD_NAMES = new Set([
335
+ "id", "name", "title", "created_at", "updated_at", "deleted_at", "status",
336
+ "email", "slug", "code", "type", "value", "label", "description", "enabled",
337
+ "active", "position", "sort", "order", "count", "total", "amount", "price",
338
+ "uuid", "hash", "token", "key", "url", "path", "image", "avatar",
339
+ ]);
340
+ /**
341
+ * PHP method names that look like `get*` but are NOT ActiveRecord relation
342
+ * getters. `$item->save()` / `$item->validate()` inside a foreach is fine;
343
+ * flagging them as N+1 would be a false positive. These names are stripped
344
+ * from the `get\w+()` method-call detection before the eager-load check.
345
+ */
346
+ const METHOD_CALL_BLOCKLIST = new Set([
347
+ "save", "validate", "delete", "refresh", "load", "populate", "toArray",
348
+ "afterSave", "beforeSave", "beforeDelete", "afterDelete",
349
+ "getAttributes", "getAttribute", "getIsNewRecord", "getErrors", "getFirstError",
350
+ "getOldAttributes", "getDirtyAttributes", "getPrimaryKey", "getTableSchema",
351
+ ]);
352
+ /**
353
+ * Detect N+1 query patterns in Yii2/Eloquent controllers.
354
+ *
355
+ * Pattern: `foreach ($items as $item) { $item->relation->... }` without a
356
+ * prior `->with('relation')` call in the same method scope. This is the
357
+ * most common N+1 anti-pattern in Yii2 ActiveRecord code.
358
+ *
359
+ * Known limitations (acceptable for a "discovery" tool, not a gate):
360
+ * - Regex-based — can miss multi-line foreach bodies split across nested blocks
361
+ * - Doesn't cross function boundaries — eager loading in caller is invisible
362
+ * - False positives on nested loops if the outer collection is already eager-loaded
363
+ */
364
+ export async function findPhpNPlusOne(repo, options) {
365
+ const index = await getCodeIndex(repo);
366
+ if (!index)
367
+ throw new Error(`Repository "${repo}" not found.`);
368
+ const findings = [];
369
+ const limit = options?.limit ?? 100;
370
+ const filePattern = options?.file_pattern;
371
+ // Normalize `getProfile` → `profile` so the ->with() check matches whether
372
+ // the relation is accessed as a property or via its auto-generated getter.
373
+ const normalizeGetter = (name) => {
374
+ const bare = name.replace(/^get/, "");
375
+ return bare.length > 0 ? bare.charAt(0).toLowerCase() + bare.slice(1) : "";
376
+ };
377
+ // A finding is emitted exactly once per (foreach × relation-name) tuple so
378
+ // that chained patterns don't double-report the same relation that the
379
+ // property pattern already caught in the same loop body.
380
+ const emitFinding = (sym, foreachIdx, relation, pattern, seen) => {
381
+ if (!relation || seen.has(relation))
382
+ return findings.length >= limit;
383
+ seen.add(relation);
384
+ if (SCALAR_FIELD_NAMES.has(relation.toLowerCase()))
385
+ return findings.length >= limit;
386
+ const beforeForeach = sym.source.slice(0, foreachIdx);
387
+ const withRe = new RegExp(`\\bwith\\s*\\(\\s*['"]${relation}['"]`);
388
+ if (withRe.test(beforeForeach))
389
+ return findings.length >= limit;
390
+ const lineOffset = beforeForeach.split("\n").length - 1;
391
+ findings.push({
392
+ file: sym.file,
393
+ method: sym.name,
394
+ line: sym.start_line + lineOffset,
395
+ relation,
396
+ pattern,
397
+ });
398
+ return findings.length >= limit;
399
+ };
400
+ for (const sym of index.symbols) {
401
+ if (sym.kind !== "method" || !sym.file.endsWith(".php") || !sym.source)
402
+ continue;
403
+ if (filePattern && !sym.file.includes(filePattern))
404
+ continue;
405
+ const src = sym.source;
406
+ const foreachRe = /foreach\s*\(\s*\$(\w+)\s+as\s+(?:\$\w+\s*=>\s*)?\$(\w+)\s*\)/g;
407
+ let fm;
408
+ while ((fm = foreachRe.exec(src)) !== null) {
409
+ const itemVar = fm[2];
410
+ const foreachIdx = fm.index;
411
+ const after = src.slice(foreachIdx);
412
+ // Guard against double-counting: each distinct relation name reported
413
+ // once per foreach, regardless of which pattern matched first.
414
+ const seen = new Set();
415
+ // Pattern 1 — property access: $item->profile
416
+ // The negative lookahead `(?![\w(])` blocks both:
417
+ // 1) following word chars (prevents backtracking to a partial capture
418
+ // like `$item->getProfile()` matching "getProfil" as a property);
419
+ // 2) an opening paren (excludes method calls, handled by pattern 2).
420
+ const propRe = new RegExp(`\\$${itemVar}->(\\w+)(?![\\w(])`, "g");
421
+ let m;
422
+ while ((m = propRe.exec(after)) !== null) {
423
+ if (emitFinding({ file: sym.file, name: sym.name, source: src, start_line: sym.start_line }, foreachIdx, m[1], "foreach-access-without-with", seen)) {
424
+ return { findings, total: findings.length };
425
+ }
426
+ }
427
+ // Pattern 2 — getter method call: $item->getProfile()
428
+ // Normalize to bare relation name for both the dedup and the ->with() check.
429
+ const getterRe = new RegExp(`\\$${itemVar}->(get\\w+)\\s*\\(\\s*\\)`, "g");
430
+ while ((m = getterRe.exec(after)) !== null) {
431
+ const rawMethod = m[1];
432
+ if (METHOD_CALL_BLOCKLIST.has(rawMethod))
433
+ continue;
434
+ const normalized = normalizeGetter(rawMethod);
435
+ if (!normalized || METHOD_CALL_BLOCKLIST.has(normalized.toLowerCase()))
436
+ continue;
437
+ if (emitFinding({ file: sym.file, name: sym.name, source: src, start_line: sym.start_line }, foreachIdx, normalized, "foreach-getter-without-with", seen)) {
438
+ return { findings, total: findings.length };
439
+ }
440
+ }
441
+ // Pattern 3 — chained access: $item->rel->sub (the first segment is the trigger)
442
+ const chainRe = new RegExp(`\\$${itemVar}->(\\w+)->\\w`, "g");
443
+ while ((m = chainRe.exec(after)) !== null) {
444
+ if (emitFinding({ file: sym.file, name: sym.name, source: src, start_line: sym.start_line }, foreachIdx, m[1], "foreach-chained-without-with", seen)) {
445
+ return { findings, total: findings.length };
446
+ }
447
+ }
448
+ }
449
+ }
450
+ return { findings, total: findings.length };
451
+ }
452
+ /**
453
+ * Flag oversized PHP classes. Two scopes:
454
+ *
455
+ * - `scope: "activerecord"` (default) — only models extending ActiveRecord.
456
+ * Uses `analyzeActiveRecord` for model detection and counts relations as a
457
+ * third threshold alongside methods and lines. Classic Yii2 god-model case:
458
+ * Survey.php in Mobi2 with 175 methods, 30 relations, 2291 lines.
459
+ *
460
+ * - `scope: "all"` — every PHP class in the index, regardless of base class.
461
+ * Captures service god-classes (UserService with 80 methods), component
462
+ * aggregates, and any other PHP class that outgrew its responsibility.
463
+ * `relation_count` is 0 for non-AR classes — the `min_relations` check is
464
+ * skipped so a service with 60 methods isn't hidden by a relation threshold.
465
+ *
466
+ * Thresholds default to 50/15/500 but are configurable for both scopes.
467
+ */
468
+ export async function findPhpGodModel(repo, options) {
469
+ const index = await getCodeIndex(repo);
470
+ if (!index)
471
+ throw new Error(`Repository "${repo}" not found.`);
472
+ const minM = options?.min_methods ?? 50;
473
+ const minR = options?.min_relations ?? 15;
474
+ const minL = options?.min_lines ?? 500;
475
+ const scope = options?.scope ?? "activerecord";
476
+ const models = [];
477
+ if (scope === "activerecord") {
478
+ const ar = await analyzeActiveRecord(repo);
479
+ for (const m of ar.models) {
480
+ // Look up the class symbol by (name, kind, file) — file match keeps
481
+ // duplicate class names in different paths reported independently.
482
+ const classSym = index.symbols.find((s) => s.name === m.name && s.kind === "class" && s.file === m.file);
483
+ const lineCount = classSym ? classSym.end_line - classSym.start_line : 0;
484
+ const reasons = [];
485
+ if (m.methods.length > minM)
486
+ reasons.push(`methods: ${m.methods.length} > ${minM}`);
487
+ if (m.relations.length > minR)
488
+ reasons.push(`relations: ${m.relations.length} > ${minR}`);
489
+ if (lineCount > minL)
490
+ reasons.push(`lines: ${lineCount} > ${minL}`);
491
+ if (reasons.length > 0) {
492
+ models.push({
493
+ name: m.name,
494
+ file: m.file,
495
+ method_count: m.methods.length,
496
+ relation_count: m.relations.length,
497
+ line_count: lineCount,
498
+ reasons,
499
+ });
500
+ }
501
+ }
502
+ }
503
+ else {
504
+ // scope === "all" — iterate every PHP class symbol directly.
505
+ const classSyms = index.symbols.filter((s) => s.kind === "class" && s.file.endsWith(".php"));
506
+ for (const cls of classSyms) {
507
+ const methodCount = index.symbols.filter((s) => s.parent === cls.id && s.kind === "method").length;
508
+ const lineCount = cls.end_line - cls.start_line;
509
+ const reasons = [];
510
+ if (methodCount > minM)
511
+ reasons.push(`methods: ${methodCount} > ${minM}`);
512
+ if (lineCount > minL)
513
+ reasons.push(`lines: ${lineCount} > ${minL}`);
514
+ // min_relations intentionally skipped in "all" scope — not AR, no relations
515
+ if (reasons.length > 0) {
516
+ models.push({
517
+ name: cls.name,
518
+ file: cls.file,
519
+ method_count: methodCount,
520
+ relation_count: 0,
521
+ line_count: lineCount,
522
+ reasons,
523
+ });
524
+ }
525
+ }
526
+ }
527
+ // Sort by severity (number of reasons desc, then methods desc)
528
+ models.sort((a, b) => b.reasons.length - a.reasons.length || b.method_count - a.method_count);
529
+ return { models, total: models.length };
530
+ }
531
+ const AUDIT_TIMEOUT = 8000;
532
+ export async function phpProjectAudit(repo, options) {
533
+ const startTime = Date.now();
534
+ const gates = [];
535
+ const allChecks = ["security", "activerecord", "complexity", "dead_code", "patterns", "clones", "hotspots", "n_plus_one", "god_model"];
536
+ const enabled = new Set(options?.checks ?? allChecks);
537
+ const fp = options?.file_pattern ?? ".php";
538
+ const secOpts = {};
539
+ if (options?.file_pattern)
540
+ secOpts.file_pattern = options.file_pattern;
541
+ const tasks = [];
542
+ if (enabled.has("security"))
543
+ tasks.push({ name: "security", run: () => phpSecurityScan(repo, secOpts) });
544
+ if (enabled.has("activerecord"))
545
+ tasks.push({ name: "activerecord", run: () => analyzeActiveRecord(repo, options?.file_pattern ? { file_pattern: options.file_pattern } : undefined) });
546
+ if (enabled.has("complexity"))
547
+ tasks.push({ name: "complexity", run: async () => { const { analyzeComplexity } = await import("./complexity-tools.js"); return analyzeComplexity(repo, { file_pattern: fp, top_n: 10 }); } });
548
+ if (enabled.has("dead_code"))
549
+ tasks.push({ name: "dead_code", run: async () => { const { findDeadCode } = await import("./symbol-tools.js"); return findDeadCode(repo, { file_pattern: fp }); } });
550
+ if (enabled.has("patterns"))
551
+ tasks.push({ name: "patterns", run: () => searchPatterns(repo, "empty-catch", { file_pattern: fp }) });
552
+ if (enabled.has("clones"))
553
+ tasks.push({ name: "clones", run: async () => { const { findClones } = await import("./clone-tools.js"); return findClones(repo, { file_pattern: fp }); } });
554
+ if (enabled.has("hotspots"))
555
+ tasks.push({ name: "hotspots", run: async () => { const { analyzeHotspots } = await import("./hotspot-tools.js"); return analyzeHotspots(repo, {}); } });
556
+ if (enabled.has("n_plus_one"))
557
+ tasks.push({ name: "n_plus_one", run: () => findPhpNPlusOne(repo, options?.file_pattern ? { file_pattern: options.file_pattern } : undefined) });
558
+ if (enabled.has("god_model"))
559
+ tasks.push({ name: "god_model", run: () => findPhpGodModel(repo) });
560
+ const settled = await Promise.allSettled(tasks.map(async (t) => {
561
+ const s = Date.now();
562
+ const r = await Promise.race([t.run(), new Promise((ok) => setTimeout(() => ok("TIMEOUT"), AUDIT_TIMEOUT))]);
563
+ return { name: t.name, result: r, ms: Date.now() - s };
564
+ }));
565
+ let securityResult = { findings: [], summary: { critical: 0, high: 0, medium: 0, low: 0, total: 0 }, checks_run: [] };
566
+ let arResult = { models: [], total: 0 };
567
+ let totalFindings = 0;
568
+ for (const s of settled) {
569
+ if (s.status === "rejected") {
570
+ gates.push({ name: "unknown", status: "error", findings_count: 0, duration_ms: 0, error: String(s.reason) });
571
+ continue;
572
+ }
573
+ const { name, result, ms } = s.value;
574
+ if (result === "TIMEOUT") {
575
+ gates.push({ name, status: "timeout", findings_count: 0, duration_ms: ms });
576
+ continue;
577
+ }
578
+ let count = 0;
579
+ // activerecord is informational (model count), not a problem finding — excluded from totalFindings and health score
580
+ if (name === "security") {
581
+ securityResult = result;
582
+ count = securityResult.summary.total;
583
+ }
584
+ else if (name === "activerecord") {
585
+ arResult = result;
586
+ count = arResult.total;
587
+ }
588
+ else if (name === "complexity")
589
+ count = result?.summary?.above_threshold ?? 0;
590
+ else if (name === "dead_code")
591
+ count = result?.candidates?.length ?? 0;
592
+ else if (name === "patterns")
593
+ count = result?.matches?.length ?? 0;
594
+ else if (name === "clones")
595
+ count = result?.clones?.length ?? 0;
596
+ else if (name === "hotspots")
597
+ count = result?.hotspots?.length ?? 0;
598
+ else if (name === "n_plus_one")
599
+ count = result?.findings?.length ?? 0;
600
+ else if (name === "god_model")
601
+ count = result?.models?.length ?? 0;
602
+ if (name !== "activerecord")
603
+ totalFindings += count;
604
+ gates.push({ name, status: "ok", findings_count: count, duration_ms: ms });
605
+ }
606
+ const sec = securityResult.summary;
607
+ // Logarithmic penalties — a few critical findings are serious, but hundreds of
608
+ // complexity warnings shouldn't tank the score to 0. Each gate uses log2 scaling
609
+ // so 1 finding ≈ 0, 10 ≈ 17, 100 ≈ 33, 1000 ≈ 50 penalty points.
610
+ const secPenalty = sec.total > 0 ? Math.round(Math.log2(sec.total + 1) * (sec.critical > 0 ? 8 : 4)) : 0;
611
+ const qualityFindings = totalFindings - sec.total;
612
+ const qualPenalty = qualityFindings > 0 ? Math.round(Math.log2(qualityFindings + 1) * 4) : 0;
613
+ const healthScore = Math.max(0, Math.min(100, 100 - secPenalty - qualPenalty));
614
+ const topRisks = gates.filter(g => g.findings_count > 0 && g.name !== "activerecord").sort((a, b) => b.findings_count - a.findings_count).slice(0, 3).map(g => `${g.name}: ${g.findings_count} findings`);
615
+ return {
616
+ repo, duration_ms: Date.now() - startTime,
617
+ checks_run: gates.filter(g => g.status === "ok").map(g => g.name),
618
+ gates,
619
+ summary: { total_findings: totalFindings, critical: sec.critical, high: sec.high, medium: sec.medium, low: sec.low, health_score: healthScore, top_risks: topRisks },
620
+ security: securityResult,
621
+ activerecord: arResult,
622
+ };
623
+ }
624
+ // ---------------------------------------------------------------------------
625
+ // Helpers
626
+ // ---------------------------------------------------------------------------
627
+ async function readJsonSafe(path) {
628
+ try {
629
+ const content = await readFile(path, "utf-8");
630
+ return JSON.parse(content);
631
+ }
632
+ catch {
633
+ return null;
634
+ }
635
+ }
636
+ function extractLineContext(source, index) {
637
+ const lineStart = source.lastIndexOf("\n", index) + 1;
638
+ const lineEnd = source.indexOf("\n", index);
639
+ const end = lineEnd === -1 ? source.length : lineEnd;
640
+ return source.slice(lineStart, end).trim().slice(0, 200);
641
+ }
642
+ function pascalToKebab(s) {
643
+ return s.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
644
+ }
645
+ //# sourceMappingURL=php-tools.js.map