seer-mcp 0.1.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 (371) hide show
  1. package/.vscode/settings.json +3 -0
  2. package/LICENSE +176 -0
  3. package/README.md +272 -0
  4. package/README_dev.md +199 -0
  5. package/dist/bundle/ci.d.ts +47 -0
  6. package/dist/bundle/ci.d.ts.map +1 -0
  7. package/dist/bundle/ci.js +113 -0
  8. package/dist/bundle/ci.js.map +1 -0
  9. package/dist/bundle/contract.d.ts +111 -0
  10. package/dist/bundle/contract.d.ts.map +1 -0
  11. package/dist/bundle/contract.js +352 -0
  12. package/dist/bundle/contract.js.map +1 -0
  13. package/dist/bundle/export.d.ts +36 -0
  14. package/dist/bundle/export.d.ts.map +1 -0
  15. package/dist/bundle/export.js +152 -0
  16. package/dist/bundle/export.js.map +1 -0
  17. package/dist/bundle/external.d.ts +66 -0
  18. package/dist/bundle/external.d.ts.map +1 -0
  19. package/dist/bundle/external.js +238 -0
  20. package/dist/bundle/external.js.map +1 -0
  21. package/dist/bundle/format.d.ts +94 -0
  22. package/dist/bundle/format.d.ts.map +1 -0
  23. package/dist/bundle/format.js +42 -0
  24. package/dist/bundle/format.js.map +1 -0
  25. package/dist/bundle/import.d.ts +49 -0
  26. package/dist/bundle/import.d.ts.map +1 -0
  27. package/dist/bundle/import.js +116 -0
  28. package/dist/bundle/import.js.map +1 -0
  29. package/dist/cli/index.d.ts +3 -0
  30. package/dist/cli/index.d.ts.map +1 -0
  31. package/dist/cli/index.js +1402 -0
  32. package/dist/cli/index.js.map +1 -0
  33. package/dist/cli/init.d.ts +48 -0
  34. package/dist/cli/init.d.ts.map +1 -0
  35. package/dist/cli/init.js +284 -0
  36. package/dist/cli/init.js.map +1 -0
  37. package/dist/db/schema.d.ts +3 -0
  38. package/dist/db/schema.d.ts.map +1 -0
  39. package/dist/db/schema.js +616 -0
  40. package/dist/db/schema.js.map +1 -0
  41. package/dist/db/store.d.ts +1011 -0
  42. package/dist/db/store.d.ts.map +1 -0
  43. package/dist/db/store.js +3888 -0
  44. package/dist/db/store.js.map +1 -0
  45. package/dist/graph/pagerank.d.ts +9 -0
  46. package/dist/graph/pagerank.d.ts.map +1 -0
  47. package/dist/graph/pagerank.js +47 -0
  48. package/dist/graph/pagerank.js.map +1 -0
  49. package/dist/indexer/architecture.d.ts +72 -0
  50. package/dist/indexer/architecture.d.ts.map +1 -0
  51. package/dist/indexer/architecture.js +112 -0
  52. package/dist/indexer/architecture.js.map +1 -0
  53. package/dist/indexer/behavior.d.ts +75 -0
  54. package/dist/indexer/behavior.d.ts.map +1 -0
  55. package/dist/indexer/behavior.js +395 -0
  56. package/dist/indexer/behavior.js.map +1 -0
  57. package/dist/indexer/boundaries.d.ts +60 -0
  58. package/dist/indexer/boundaries.d.ts.map +1 -0
  59. package/dist/indexer/boundaries.js +366 -0
  60. package/dist/indexer/boundaries.js.map +1 -0
  61. package/dist/indexer/churn.d.ts +15 -0
  62. package/dist/indexer/churn.d.ts.map +1 -0
  63. package/dist/indexer/churn.js +49 -0
  64. package/dist/indexer/churn.js.map +1 -0
  65. package/dist/indexer/classify.d.ts +9 -0
  66. package/dist/indexer/classify.d.ts.map +1 -0
  67. package/dist/indexer/classify.js +90 -0
  68. package/dist/indexer/classify.js.map +1 -0
  69. package/dist/indexer/context.d.ts +176 -0
  70. package/dist/indexer/context.d.ts.map +1 -0
  71. package/dist/indexer/context.js +193 -0
  72. package/dist/indexer/context.js.map +1 -0
  73. package/dist/indexer/continuity.d.ts +67 -0
  74. package/dist/indexer/continuity.d.ts.map +1 -0
  75. package/dist/indexer/continuity.js +288 -0
  76. package/dist/indexer/continuity.js.map +1 -0
  77. package/dist/indexer/detectchanges.d.ts +32 -0
  78. package/dist/indexer/detectchanges.d.ts.map +1 -0
  79. package/dist/indexer/detectchanges.js +74 -0
  80. package/dist/indexer/detectchanges.js.map +1 -0
  81. package/dist/indexer/discovery.d.ts +37 -0
  82. package/dist/indexer/discovery.d.ts.map +1 -0
  83. package/dist/indexer/discovery.js +136 -0
  84. package/dist/indexer/discovery.js.map +1 -0
  85. package/dist/indexer/externaldeps.d.ts +18 -0
  86. package/dist/indexer/externaldeps.d.ts.map +1 -0
  87. package/dist/indexer/externaldeps.js +288 -0
  88. package/dist/indexer/externaldeps.js.map +1 -0
  89. package/dist/indexer/freshness.d.ts +48 -0
  90. package/dist/indexer/freshness.d.ts.map +1 -0
  91. package/dist/indexer/freshness.js +128 -0
  92. package/dist/indexer/freshness.js.map +1 -0
  93. package/dist/indexer/git.d.ts +144 -0
  94. package/dist/indexer/git.d.ts.map +1 -0
  95. package/dist/indexer/git.js +444 -0
  96. package/dist/indexer/git.js.map +1 -0
  97. package/dist/indexer/index.d.ts +145 -0
  98. package/dist/indexer/index.d.ts.map +1 -0
  99. package/dist/indexer/index.js +930 -0
  100. package/dist/indexer/index.js.map +1 -0
  101. package/dist/indexer/modules.d.ts +62 -0
  102. package/dist/indexer/modules.d.ts.map +1 -0
  103. package/dist/indexer/modules.js +293 -0
  104. package/dist/indexer/modules.js.map +1 -0
  105. package/dist/indexer/preflight.d.ts +154 -0
  106. package/dist/indexer/preflight.d.ts.map +1 -0
  107. package/dist/indexer/preflight.js +399 -0
  108. package/dist/indexer/preflight.js.map +1 -0
  109. package/dist/indexer/protoScanner.d.ts +34 -0
  110. package/dist/indexer/protoScanner.d.ts.map +1 -0
  111. package/dist/indexer/protoScanner.js +133 -0
  112. package/dist/indexer/protoScanner.js.map +1 -0
  113. package/dist/indexer/risk.d.ts +115 -0
  114. package/dist/indexer/risk.d.ts.map +1 -0
  115. package/dist/indexer/risk.js +194 -0
  116. package/dist/indexer/risk.js.map +1 -0
  117. package/dist/indexer/serviceHostScanner.d.ts +25 -0
  118. package/dist/indexer/serviceHostScanner.d.ts.map +1 -0
  119. package/dist/indexer/serviceHostScanner.js +95 -0
  120. package/dist/indexer/serviceHostScanner.js.map +1 -0
  121. package/dist/indexer/serviceLinks.d.ts +105 -0
  122. package/dist/indexer/serviceLinks.d.ts.map +1 -0
  123. package/dist/indexer/serviceLinks.js +509 -0
  124. package/dist/indexer/serviceLinks.js.map +1 -0
  125. package/dist/indexer/shapehash.d.ts +98 -0
  126. package/dist/indexer/shapehash.d.ts.map +1 -0
  127. package/dist/indexer/shapehash.js +354 -0
  128. package/dist/indexer/shapehash.js.map +1 -0
  129. package/dist/indexer/skeleton.d.ts +15 -0
  130. package/dist/indexer/skeleton.d.ts.map +1 -0
  131. package/dist/indexer/skeleton.js +136 -0
  132. package/dist/indexer/skeleton.js.map +1 -0
  133. package/dist/indexer/symbolhistory.d.ts +41 -0
  134. package/dist/indexer/symbolhistory.d.ts.map +1 -0
  135. package/dist/indexer/symbolhistory.js +124 -0
  136. package/dist/indexer/symbolhistory.js.map +1 -0
  137. package/dist/indexer/watcher.d.ts +68 -0
  138. package/dist/indexer/watcher.d.ts.map +1 -0
  139. package/dist/indexer/watcher.js +179 -0
  140. package/dist/indexer/watcher.js.map +1 -0
  141. package/dist/mcp/server.d.ts +80 -0
  142. package/dist/mcp/server.d.ts.map +1 -0
  143. package/dist/mcp/server.js +1610 -0
  144. package/dist/mcp/server.js.map +1 -0
  145. package/dist/parser/index.d.ts +8 -0
  146. package/dist/parser/index.d.ts.map +1 -0
  147. package/dist/parser/index.js +33 -0
  148. package/dist/parser/index.js.map +1 -0
  149. package/dist/parser/languages/cpp.d.ts +3 -0
  150. package/dist/parser/languages/cpp.d.ts.map +1 -0
  151. package/dist/parser/languages/cpp.js +350 -0
  152. package/dist/parser/languages/cpp.js.map +1 -0
  153. package/dist/parser/languages/csharp.d.ts +3 -0
  154. package/dist/parser/languages/csharp.d.ts.map +1 -0
  155. package/dist/parser/languages/csharp.js +239 -0
  156. package/dist/parser/languages/csharp.js.map +1 -0
  157. package/dist/parser/languages/go.d.ts +3 -0
  158. package/dist/parser/languages/go.d.ts.map +1 -0
  159. package/dist/parser/languages/go.js +259 -0
  160. package/dist/parser/languages/go.js.map +1 -0
  161. package/dist/parser/languages/java.d.ts +3 -0
  162. package/dist/parser/languages/java.d.ts.map +1 -0
  163. package/dist/parser/languages/java.js +391 -0
  164. package/dist/parser/languages/java.js.map +1 -0
  165. package/dist/parser/languages/python.d.ts +3 -0
  166. package/dist/parser/languages/python.d.ts.map +1 -0
  167. package/dist/parser/languages/python.js +396 -0
  168. package/dist/parser/languages/python.js.map +1 -0
  169. package/dist/parser/languages/rust.d.ts +3 -0
  170. package/dist/parser/languages/rust.d.ts.map +1 -0
  171. package/dist/parser/languages/rust.js +159 -0
  172. package/dist/parser/languages/rust.js.map +1 -0
  173. package/dist/parser/languages/typescript.d.ts +3 -0
  174. package/dist/parser/languages/typescript.d.ts.map +1 -0
  175. package/dist/parser/languages/typescript.js +1442 -0
  176. package/dist/parser/languages/typescript.js.map +1 -0
  177. package/dist/parser/parserContext.d.ts +77 -0
  178. package/dist/parser/parserContext.d.ts.map +1 -0
  179. package/dist/parser/parserContext.js +354 -0
  180. package/dist/parser/parserContext.js.map +1 -0
  181. package/dist/parser/walker.d.ts +81 -0
  182. package/dist/parser/walker.d.ts.map +1 -0
  183. package/dist/parser/walker.js +217 -0
  184. package/dist/parser/walker.js.map +1 -0
  185. package/dist/parser/worker.d.ts +66 -0
  186. package/dist/parser/worker.d.ts.map +1 -0
  187. package/dist/parser/worker.js +129 -0
  188. package/dist/parser/worker.js.map +1 -0
  189. package/dist/parser/workerpool.d.ts +107 -0
  190. package/dist/parser/workerpool.d.ts.map +1 -0
  191. package/dist/parser/workerpool.js +383 -0
  192. package/dist/parser/workerpool.js.map +1 -0
  193. package/dist/scip/format.d.ts +87 -0
  194. package/dist/scip/format.d.ts.map +1 -0
  195. package/dist/scip/format.js +31 -0
  196. package/dist/scip/format.js.map +1 -0
  197. package/dist/scip/import.d.ts +37 -0
  198. package/dist/scip/import.d.ts.map +1 -0
  199. package/dist/scip/import.js +180 -0
  200. package/dist/scip/import.js.map +1 -0
  201. package/dist/types.d.ts +392 -0
  202. package/dist/types.d.ts.map +1 -0
  203. package/dist/types.js +4 -0
  204. package/dist/types.js.map +1 -0
  205. package/docs/architecture.md +105 -0
  206. package/docs/benchmarks/methodology.md +134 -0
  207. package/docs/benchmarks/raw-results.md +71 -0
  208. package/docs/benchmarks.md +74 -0
  209. package/docs/cli.md +148 -0
  210. package/docs/examples/behavior-tests.md +70 -0
  211. package/docs/examples/change-history.md +85 -0
  212. package/docs/examples/pre-edit-context.md +81 -0
  213. package/docs/examples/service-links.md +88 -0
  214. package/docs/examples.md +80 -0
  215. package/docs/faq.md +70 -0
  216. package/docs/internals.md +104 -0
  217. package/docs/languages.md +70 -0
  218. package/docs/limits.md +52 -0
  219. package/docs/mcp.md +199 -0
  220. package/docs/quickstart.md +119 -0
  221. package/docs/testing.md +123 -0
  222. package/docs/tools.md +115 -0
  223. package/package.json +52 -0
  224. package/research-codebase.md +578 -0
  225. package/seer-cli-docs.md +326 -0
  226. package/seer-master-guide.md +246 -0
  227. package/src/bundle/ci.ts +141 -0
  228. package/src/bundle/contract.ts +387 -0
  229. package/src/bundle/export.ts +175 -0
  230. package/src/bundle/external.ts +285 -0
  231. package/src/bundle/format.ts +92 -0
  232. package/src/bundle/import.ts +157 -0
  233. package/src/cli/index.ts +1249 -0
  234. package/src/cli/init.ts +389 -0
  235. package/src/db/schema.ts +614 -0
  236. package/src/db/store.ts +4306 -0
  237. package/src/graph/pagerank.ts +53 -0
  238. package/src/indexer/architecture.ts +148 -0
  239. package/src/indexer/behavior.ts +466 -0
  240. package/src/indexer/boundaries.ts +374 -0
  241. package/src/indexer/churn.ts +58 -0
  242. package/src/indexer/classify.ts +96 -0
  243. package/src/indexer/context.ts +340 -0
  244. package/src/indexer/continuity.ts +322 -0
  245. package/src/indexer/detectchanges.ts +94 -0
  246. package/src/indexer/discovery.ts +176 -0
  247. package/src/indexer/externaldeps.ts +243 -0
  248. package/src/indexer/freshness.ts +166 -0
  249. package/src/indexer/git.ts +453 -0
  250. package/src/indexer/index.ts +1092 -0
  251. package/src/indexer/modules.ts +358 -0
  252. package/src/indexer/preflight.ts +548 -0
  253. package/src/indexer/protoScanner.ts +147 -0
  254. package/src/indexer/risk.ts +304 -0
  255. package/src/indexer/serviceHostScanner.ts +92 -0
  256. package/src/indexer/serviceLinks.ts +543 -0
  257. package/src/indexer/shapehash.ts +370 -0
  258. package/src/indexer/skeleton.ts +169 -0
  259. package/src/indexer/symbolhistory.ts +172 -0
  260. package/src/indexer/watcher.ts +206 -0
  261. package/src/mcp/server.ts +1659 -0
  262. package/src/parser/index.ts +37 -0
  263. package/src/parser/languages/cpp.ts +361 -0
  264. package/src/parser/languages/csharp.ts +235 -0
  265. package/src/parser/languages/go.ts +259 -0
  266. package/src/parser/languages/java.ts +382 -0
  267. package/src/parser/languages/python.ts +370 -0
  268. package/src/parser/languages/rust.ts +164 -0
  269. package/src/parser/languages/typescript.ts +1435 -0
  270. package/src/parser/parserContext.ts +392 -0
  271. package/src/parser/walker.ts +306 -0
  272. package/src/parser/worker.ts +181 -0
  273. package/src/parser/workerpool.ts +448 -0
  274. package/src/scip/format.ts +83 -0
  275. package/src/scip/import.ts +216 -0
  276. package/src/types.ts +457 -0
  277. package/tests/benchmark-service-links.ts +244 -0
  278. package/tests/bug-regressions.ts +626 -0
  279. package/tests/filters.ts +264 -0
  280. package/tests/fixtures/Counter.tsx +38 -0
  281. package/tests/fixtures/caller.ts +7 -0
  282. package/tests/fixtures/collisions.ts +23 -0
  283. package/tests/fixtures/local_helper.ts +5 -0
  284. package/tests/fixtures/overloads.java +17 -0
  285. package/tests/fixtures/remote_helper.ts +4 -0
  286. package/tests/fixtures/sample.c +15 -0
  287. package/tests/fixtures/sample.cpp +47 -0
  288. package/tests/fixtures/sample.cs +62 -0
  289. package/tests/fixtures/sample.go +68 -0
  290. package/tests/fixtures/sample.h +30 -0
  291. package/tests/fixtures/sample.java +85 -0
  292. package/tests/fixtures/sample.py +46 -0
  293. package/tests/fixtures/sample.rs +78 -0
  294. package/tests/fixtures/sample.ts +76 -0
  295. package/tests/fixtures-service/HttpClients.cs +30 -0
  296. package/tests/fixtures-service/HttpClients.java +24 -0
  297. package/tests/fixtures-service/billing.ts +15 -0
  298. package/tests/fixtures-service/docker-compose.yml +15 -0
  299. package/tests/fixtures-service/gateway.ts +10 -0
  300. package/tests/fixtures-service/get_user.ts +11 -0
  301. package/tests/fixtures-service/graphql_client.ts +63 -0
  302. package/tests/fixtures-service/graphql_server.ts +30 -0
  303. package/tests/fixtures-service/grpc_client.go +30 -0
  304. package/tests/fixtures-service/http_clients.go +23 -0
  305. package/tests/fixtures-service/http_clients.py +38 -0
  306. package/tests/fixtures-service/http_clients.ts +49 -0
  307. package/tests/fixtures-service/k8s/payment-service.yaml +22 -0
  308. package/tests/fixtures-service/k8s_calls.ts +20 -0
  309. package/tests/fixtures-service/messaging.ts +87 -0
  310. package/tests/fixtures-service/trpc_client.ts +39 -0
  311. package/tests/fixtures-service/trpc_server.ts +39 -0
  312. package/tests/fixtures-service/user_service.proto +33 -0
  313. package/tests/fixtures-trackcd/Cargo.toml +11 -0
  314. package/tests/fixtures-trackcd/SpringController.java +36 -0
  315. package/tests/fixtures-trackcd/auth_service.ts +19 -0
  316. package/tests/fixtures-trackcd/complex_module.py +50 -0
  317. package/tests/fixtures-trackcd/express_app.js +30 -0
  318. package/tests/fixtures-trackcd/fastapi_app.py +49 -0
  319. package/tests/fixtures-trackcd/fastify_object_routes.js +32 -0
  320. package/tests/fixtures-trackcd/go.mod +8 -0
  321. package/tests/fixtures-trackcd/package.json +15 -0
  322. package/tests/fixtures-trackcd/requirements.txt +4 -0
  323. package/tests/fixtures-trackcd/tests/auth_service.test.ts +13 -0
  324. package/tests/fixtures-tracke/auth/AuthService.ts +23 -0
  325. package/tests/fixtures-tracke/auth/crypto.ts +7 -0
  326. package/tests/fixtures-tracke/billing/Billing.ts +20 -0
  327. package/tests/fixtures-tracke/billing/Invoice.ts +10 -0
  328. package/tests/fixtures-tracke/billing/server.ts +17 -0
  329. package/tests/fixtures-tracke/package.json +7 -0
  330. package/tests/fixtures-tracke/tests/auth.test.ts +23 -0
  331. package/tests/fixtures-tracke/tests/billing.test.ts +14 -0
  332. package/tests/fixtures-trackf/package.json +5 -0
  333. package/tests/fixtures-trackf/src/auth.ts +26 -0
  334. package/tests/fixtures-trackf/src/handlers.ts +35 -0
  335. package/tests/fixtures-tracki/billing/routes.ts +12 -0
  336. package/tests/fixtures-tracki/gateway/client.ts +13 -0
  337. package/tests/git-features.ts +267 -0
  338. package/tests/init.ts +141 -0
  339. package/tests/mcp-jit.ts +130 -0
  340. package/tests/mcp-smoke.ts +191 -0
  341. package/tests/mcp-trackcd.ts +169 -0
  342. package/tests/mcp-tracke.ts +229 -0
  343. package/tests/mcp-trackf.ts +330 -0
  344. package/tests/mcp-trackg.ts +219 -0
  345. package/tests/mcp-tracki.ts +174 -0
  346. package/tests/mcp-watcher.ts +126 -0
  347. package/tests/optspec.ts +194 -0
  348. package/tests/parallel-index.ts +333 -0
  349. package/tests/parallel-read.ts +125 -0
  350. package/tests/parallel-recovery.ts +241 -0
  351. package/tests/perf-callers.ts +145 -0
  352. package/tests/query-parity.ts +184 -0
  353. package/tests/query-perf.ts +55 -0
  354. package/tests/scale-parallel-parity.ts +225 -0
  355. package/tests/scale-test.ts +523 -0
  356. package/tests/smoke.ts +396 -0
  357. package/tests/trackcd.ts +325 -0
  358. package/tests/tracke-collisions.ts +255 -0
  359. package/tests/tracke.ts +314 -0
  360. package/tests/trackf-bugs.ts +406 -0
  361. package/tests/trackf.ts +390 -0
  362. package/tests/trackg.ts +1372 -0
  363. package/tests/tracki-boundaries.ts +202 -0
  364. package/tests/tracki-continuity.ts +253 -0
  365. package/tests/tracki-contract-diff.ts +249 -0
  366. package/tests/tracki-external-bundles.ts +341 -0
  367. package/tests/tracki-preflight.ts +251 -0
  368. package/tests/verify-roles.ts +51 -0
  369. package/tests/worker-parity.ts +286 -0
  370. package/tests/worker-pool.ts +262 -0
  371. package/tsconfig.json +20 -0
@@ -0,0 +1,370 @@
1
+ import type Parser from 'web-tree-sitter';
2
+ import type { SymbolDef, RouteDef, ConfigKeyRead, ServiceCallDef } from '../../types.js';
3
+ import type { LanguageExtractor } from '../walker.js';
4
+ import { firstLine } from '../walker.js';
5
+
6
+ const PY_BRANCH_NODES = new Set<string>([
7
+ 'if_statement', 'elif_clause', 'while_statement', 'for_statement',
8
+ 'try_statement', 'except_clause', 'case_clause',
9
+ 'conditional_expression',
10
+ ]);
11
+
12
+ const PY_NESTING_NODES = new Set<string>([
13
+ 'if_statement', 'while_statement', 'for_statement', 'try_statement', 'except_clause',
14
+ 'match_statement',
15
+ ]);
16
+
17
+ // FastAPI / Flask decorator method names. Flask uses `app.route(...)` with a
18
+ // `methods=[...]` kwarg; FastAPI has per-method decorators (`@app.get(...)`).
19
+ const FASTAPI_DECORATOR_METHODS = new Set(['get', 'post', 'put', 'patch', 'delete', 'head', 'options']);
20
+
21
+ // HTTP-client verbs (outbound). Subset of FastAPI verbs + 'request' (httpx/requests).
22
+ const PY_HTTP_CLIENT_VERBS = new Set([
23
+ 'get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'request',
24
+ ]);
25
+
26
+ // Superset of every node type the Python tryExtract* functions may accept.
27
+ const PY_CANDIDATE_NODE_TYPES = [
28
+ // tryExtractDefinition
29
+ 'function_definition',
30
+ 'class_definition',
31
+ // tryExtractCallName + tryExtractConfigKey
32
+ 'call',
33
+ // tryExtractImport
34
+ 'import_statement',
35
+ 'import_from_statement',
36
+ // tryExtractRoute
37
+ 'decorator',
38
+ // tryExtractConfigKey
39
+ 'subscript',
40
+ ] as const;
41
+
42
+ export const pythonExtractor: LanguageExtractor = {
43
+ languageName: 'python',
44
+ extensions: ['.py', '.pyw'],
45
+ branchNodeTypes: PY_BRANCH_NODES,
46
+ nestingNodeTypes: PY_NESTING_NODES,
47
+ candidateNodeTypes: PY_CANDIDATE_NODE_TYPES,
48
+
49
+ tryExtractDefinition(node: Parser.SyntaxNode): SymbolDef | null {
50
+ switch (node.type) {
51
+ case 'function_definition': {
52
+ const nameNode = node.childForFieldName('name');
53
+ if (!nameNode) return null;
54
+ return {
55
+ name: nameNode.text,
56
+ kind: 'function',
57
+ lineStart: node.startPosition.row,
58
+ lineEnd: node.endPosition.row,
59
+ colStart: node.startPosition.column,
60
+ colEnd: node.endPosition.column,
61
+ signature: firstLine(node),
62
+ };
63
+ }
64
+ case 'class_definition': {
65
+ const nameNode = node.childForFieldName('name');
66
+ if (!nameNode) return null;
67
+ return {
68
+ name: nameNode.text,
69
+ kind: 'class',
70
+ lineStart: node.startPosition.row,
71
+ lineEnd: node.endPosition.row,
72
+ colStart: node.startPosition.column,
73
+ colEnd: node.endPosition.column,
74
+ signature: firstLine(node),
75
+ };
76
+ }
77
+ default:
78
+ return null;
79
+ }
80
+ },
81
+
82
+ tryExtractCallName(node: Parser.SyntaxNode): string | null {
83
+ if (node.type !== 'call') return null;
84
+ const funcNode = node.childForFieldName('function');
85
+ if (!funcNode) return null;
86
+
87
+ if (funcNode.type === 'identifier') return funcNode.text;
88
+
89
+ if (funcNode.type === 'attribute') {
90
+ return funcNode.childForFieldName('attribute')?.text ?? null;
91
+ }
92
+
93
+ return null;
94
+ },
95
+
96
+ tryExtractImport(node: Parser.SyntaxNode): string | null {
97
+ if (node.type === 'import_statement') {
98
+ const names = node.childrenForFieldName('name');
99
+ return names[0]?.text ?? null;
100
+ }
101
+ if (node.type === 'import_from_statement') {
102
+ return node.childForFieldName('module_name')?.text ?? null;
103
+ }
104
+ return null;
105
+ },
106
+
107
+ /**
108
+ * FastAPI / Flask route decorators:
109
+ * @app.get("/users") → FastAPI
110
+ * @router.post("/items") → FastAPI router
111
+ * @app.route("/x", methods=["GET", "POST"]) → Flask
112
+ *
113
+ * The decorator node sits ABOVE the function_definition in tree-sitter; we
114
+ * detect when we're on a decorator and emit one route per HTTP method. The
115
+ * decorated function's name becomes the handler.
116
+ */
117
+ tryExtractRoute(node: Parser.SyntaxNode): RouteDef[] | null {
118
+ if (node.type !== 'decorator') return null;
119
+ // decorator → expression → call(_object.attr, args)
120
+ const exprChild = node.namedChildren[0];
121
+ if (!exprChild || exprChild.type !== 'call') return null;
122
+ const fn = exprChild.childForFieldName('function');
123
+ if (!fn || fn.type !== 'attribute') return null;
124
+ const methodName = fn.childForFieldName('attribute')?.text;
125
+ if (!methodName) return null;
126
+
127
+ const args = exprChild.childForFieldName('arguments');
128
+ if (!args) return null;
129
+
130
+ // First positional arg is the path.
131
+ let routePath: string | null = null;
132
+ for (const child of args.namedChildren) {
133
+ if (child.type === 'string') { routePath = stripPyQuotes(child.text); break; }
134
+ if (child.type === 'concatenated_string') { routePath = stripPyQuotes(child.text); break; }
135
+ if (child.type === 'keyword_argument') break; // first kwarg means positional path is missing
136
+ }
137
+ if (!routePath) return null;
138
+
139
+ // Find the decorated function's name (the sibling/parent function_definition).
140
+ let handlerName: string | undefined;
141
+ const parent = node.parent;
142
+ if (parent && parent.type === 'decorated_definition') {
143
+ const def = parent.childForFieldName('definition');
144
+ if (def) {
145
+ const nm = def.childForFieldName('name');
146
+ if (nm) handlerName = nm.text;
147
+ }
148
+ }
149
+
150
+ if (FASTAPI_DECORATOR_METHODS.has(methodName.toLowerCase())) {
151
+ return [{
152
+ method: methodName.toUpperCase(),
153
+ path: routePath,
154
+ framework: 'fastapi',
155
+ handlerName,
156
+ line: node.startPosition.row,
157
+ }];
158
+ }
159
+
160
+ // Flask: @app.route("/x", methods=["GET", ...])
161
+ if (methodName === 'route') {
162
+ const methods: string[] = [];
163
+ for (const child of args.namedChildren) {
164
+ if (child.type === 'keyword_argument') {
165
+ const nameNode = child.childForFieldName('name');
166
+ const value = child.childForFieldName('value');
167
+ if (nameNode?.text === 'methods' && value && value.type === 'list') {
168
+ for (const m of value.namedChildren) {
169
+ if (m.type === 'string') methods.push(stripPyQuotes(m.text).toUpperCase());
170
+ }
171
+ }
172
+ }
173
+ }
174
+ const finalMethods = methods.length > 0 ? methods : ['GET'];
175
+ return finalMethods.map(m => ({
176
+ method: m,
177
+ path: routePath!,
178
+ framework: 'flask',
179
+ handlerName,
180
+ line: node.startPosition.row,
181
+ }));
182
+ }
183
+
184
+ return null;
185
+ },
186
+
187
+ /**
188
+ * Detect HTTP client calls in Python — requests.<verb>(url, …),
189
+ * httpx.<verb>(url, …), aiohttp/requests.Session() instances.
190
+ *
191
+ * requests.get('/health') ← yes
192
+ * requests.post('/api/x', json=…) ← yes
193
+ * httpx.AsyncClient().get('/api/x') ← yes (any obj.<verb>(literalUrl))
194
+ * client.get('/api/x') ← yes (generic obj.<verb>)
195
+ * logger.get('count') ← no (not a URL-shaped arg)
196
+ */
197
+ tryExtractServiceCalls(node: Parser.SyntaxNode): ServiceCallDef[] | null {
198
+ if (node.type !== 'call') return null;
199
+ // A call that is the direct payload of a decorator (`@app.post("/x")`) is a
200
+ // FastAPI/router ROUTE registration, not an outbound client call. The
201
+ // walker's route/service-call de-dup is node-level and only suppresses the
202
+ // service call when the SAME node yielded a route; here the route is
203
+ // emitted on the parent `decorator` node, so we must guard here to avoid a
204
+ // false-positive http-client call that the resolver would then self-link to
205
+ // the service's own route. (Flask `@app.route` never reached this path since
206
+ // 'route' isn't an HTTP verb, but FastAPI `@app.get/post/...` did.)
207
+ if (node.parent?.type === 'decorator') return null;
208
+ const fn = node.childForFieldName('function');
209
+ if (!fn || fn.type !== 'attribute') return null;
210
+ const obj = fn.childForFieldName('object');
211
+ const attr = fn.childForFieldName('attribute');
212
+ if (!obj || !attr) return null;
213
+
214
+ const verb = attr.text.toLowerCase();
215
+ if (!PY_HTTP_CLIENT_VERBS.has(verb)) return null;
216
+
217
+ // Categorize: known-library object (requests/httpx) → label by name.
218
+ let framework: string;
219
+ const objText = obj.text;
220
+ if (obj.type === 'identifier' && (objText === 'requests' || objText === 'httpx')) {
221
+ framework = objText;
222
+ } else {
223
+ framework = 'http-client';
224
+ }
225
+
226
+ const args = node.childForFieldName('arguments');
227
+ if (!args) return null;
228
+ const first = args.namedChildren[0];
229
+ if (!first) return null;
230
+
231
+ let raw: string | null = null;
232
+ let envKey: string | undefined;
233
+ if (first.type === 'string') raw = stripPyQuotes(first.text);
234
+ else if (first.type === 'concatenated_string') raw = stripPyQuotes(first.text);
235
+ else if (first.type === 'binary_operator') {
236
+ // Crude: os.environ['BASE_URL'] + '/charge'
237
+ const r = readBinaryConcat(first);
238
+ if (r) { raw = r.text; envKey = r.envKey; }
239
+ } else if (first.type === 'string' || first.type === 'f_string') {
240
+ raw = first.text.startsWith('f') ? null : stripPyQuotes(first.text);
241
+ }
242
+ if (!raw) return null;
243
+ if (!pyLooksLikeHttpTarget(raw)) return null;
244
+
245
+ const def: ServiceCallDef = {
246
+ protocol: 'http',
247
+ method: verb === 'request' ? 'ANY' : verb.toUpperCase(),
248
+ rawTarget: raw.slice(0, 240),
249
+ framework,
250
+ line: node.startPosition.row,
251
+ confidence: 0.85,
252
+ };
253
+ if (envKey) def.envKey = envKey;
254
+ return [def];
255
+ },
256
+
257
+ /**
258
+ * Python env var reads: `os.getenv("NAME")`, `os.environ["NAME"]`,
259
+ * `os.environ.get("NAME")`. `getenv("X", "default")` also handled.
260
+ */
261
+ tryExtractConfigKey(node: Parser.SyntaxNode): ConfigKeyRead | null {
262
+ // os.getenv / os.environ.get / dotenv.get
263
+ if (node.type === 'call') {
264
+ const fn = node.childForFieldName('function');
265
+ if (fn && fn.type === 'attribute') {
266
+ const obj = fn.childForFieldName('object');
267
+ const attr = fn.childForFieldName('attribute');
268
+ if (!obj || !attr) return null;
269
+ if (attr.text === 'getenv' && obj.text === 'os') {
270
+ return firstStringArg(node, 'env');
271
+ }
272
+ if (attr.text === 'get' && obj.type === 'attribute') {
273
+ // os.environ.get("X")
274
+ const oo = obj.childForFieldName('object');
275
+ const oa = obj.childForFieldName('attribute');
276
+ if (oo?.text === 'os' && oa?.text === 'environ') {
277
+ return firstStringArg(node, 'env');
278
+ }
279
+ }
280
+ }
281
+ }
282
+ // os.environ["NAME"]
283
+ if (node.type === 'subscript') {
284
+ const value = node.childForFieldName('value');
285
+ const subscript = node.childForFieldName('subscript');
286
+ if (value && subscript && value.type === 'attribute') {
287
+ const obj = value.childForFieldName('object');
288
+ const attr = value.childForFieldName('attribute');
289
+ if (obj?.text === 'os' && attr?.text === 'environ' && subscript.type === 'string') {
290
+ const key = stripPyQuotes(subscript.text);
291
+ if (key) return { key, source: 'env', line: node.startPosition.row };
292
+ }
293
+ }
294
+ }
295
+ return null;
296
+ },
297
+ };
298
+
299
+ function firstStringArg(callNode: Parser.SyntaxNode, source: 'env' | 'config'): ConfigKeyRead | null {
300
+ const args = callNode.childForFieldName('arguments');
301
+ if (!args) return null;
302
+ for (const child of args.namedChildren) {
303
+ if (child.type === 'string') {
304
+ const key = stripPyQuotes(child.text);
305
+ if (key) return { key, source, line: callNode.startPosition.row };
306
+ return null;
307
+ }
308
+ if (child.type === 'keyword_argument') return null;
309
+ }
310
+ return null;
311
+ }
312
+
313
+ function stripPyQuotes(s: string): string {
314
+ // Python strings can be prefixed (r"...", b"...", f"...") — strip prefix
315
+ // then the quote pair. Triple quotes get stripped to inner content.
316
+ let t = s;
317
+ // Strip prefix letters
318
+ while (t.length > 0 && /[a-zA-Z]/.test(t[0])) t = t.slice(1);
319
+ if (t.startsWith('"""') && t.endsWith('"""')) return t.slice(3, -3);
320
+ if (t.startsWith("'''") && t.endsWith("'''")) return t.slice(3, -3);
321
+ return t.replace(/^['"]|['"]$/g, '');
322
+ }
323
+
324
+ function pyLooksLikeHttpTarget(s: string): boolean {
325
+ if (!s) return false;
326
+ if (s.startsWith('/')) return true;
327
+ if (/^https?:\/\//i.test(s)) return true;
328
+ if (/^[a-zA-Z0-9._-]+\/[a-zA-Z0-9_-]/.test(s)) return true;
329
+ return false;
330
+ }
331
+
332
+ /**
333
+ * Walk a binary `+` chain like `os.environ['BASE'] + '/charge'`, returning
334
+ * the concatenated literal text where possible plus any env key seen.
335
+ */
336
+ function readBinaryConcat(node: Parser.SyntaxNode): { text: string; envKey?: string } | null {
337
+ let text = '';
338
+ let envKey: string | undefined;
339
+ function visit(n: Parser.SyntaxNode): void {
340
+ if (n.type === 'string') text += stripPyQuotes(n.text);
341
+ else if (n.type === 'subscript') {
342
+ const value = n.childForFieldName('value');
343
+ const sub = n.childForFieldName('subscript');
344
+ if (value?.type === 'attribute') {
345
+ const obj = value.childForFieldName('object');
346
+ const attr = value.childForFieldName('attribute');
347
+ if (!envKey && obj?.text === 'os' && attr?.text === 'environ' && sub?.type === 'string') {
348
+ envKey = stripPyQuotes(sub.text);
349
+ }
350
+ }
351
+ } else if (n.type === 'call') {
352
+ // os.getenv("X")
353
+ const fn = n.childForFieldName('function');
354
+ if (fn?.type === 'attribute') {
355
+ const o = fn.childForFieldName('object');
356
+ const a = fn.childForFieldName('attribute');
357
+ if (!envKey && o?.text === 'os' && a?.text === 'getenv') {
358
+ const args = n.childForFieldName('arguments');
359
+ const first = args?.namedChildren[0];
360
+ if (first?.type === 'string') envKey = stripPyQuotes(first.text);
361
+ }
362
+ }
363
+ } else if (n.type === 'binary_operator') {
364
+ for (const c of n.namedChildren) visit(c);
365
+ }
366
+ }
367
+ visit(node);
368
+ if (!text && !envKey) return null;
369
+ return { text, envKey };
370
+ }
@@ -0,0 +1,164 @@
1
+ import type Parser from 'web-tree-sitter';
2
+ import type { SymbolDef } from '../../types.js';
3
+ import type { LanguageExtractor } from '../walker.js';
4
+ import { firstLine } from '../walker.js';
5
+
6
+ const RUST_BRANCH_NODES = new Set<string>([
7
+ 'if_expression', 'while_expression', 'while_let_expression', 'for_expression',
8
+ 'loop_expression', 'match_arm',
9
+ ]);
10
+
11
+ const RUST_NESTING_NODES = new Set<string>([
12
+ 'if_expression', 'while_expression', 'while_let_expression', 'for_expression',
13
+ 'loop_expression', 'match_expression',
14
+ ]);
15
+
16
+ const RUST_CANDIDATE_NODE_TYPES = [
17
+ // tryExtractDefinition
18
+ 'function_item',
19
+ 'struct_item',
20
+ 'enum_item',
21
+ 'trait_item',
22
+ 'type_item',
23
+ // tryExtractCallName
24
+ 'call_expression',
25
+ // tryExtractImport
26
+ 'use_declaration',
27
+ // tryExtractContextName — impl Foo { ... }
28
+ 'impl_item',
29
+ ] as const;
30
+
31
+ export const rustExtractor: LanguageExtractor = {
32
+ languageName: 'rust',
33
+ extensions: ['.rs'],
34
+ branchNodeTypes: RUST_BRANCH_NODES,
35
+ nestingNodeTypes: RUST_NESTING_NODES,
36
+ candidateNodeTypes: RUST_CANDIDATE_NODE_TYPES,
37
+
38
+ tryExtractDefinition(node: Parser.SyntaxNode): SymbolDef | null {
39
+ switch (node.type) {
40
+ case 'function_item': {
41
+ const nameNode = node.childForFieldName('name');
42
+ if (!nameNode) return null;
43
+ return {
44
+ name: nameNode.text,
45
+ kind: 'function',
46
+ lineStart: node.startPosition.row,
47
+ lineEnd: node.endPosition.row,
48
+ colStart: node.startPosition.column,
49
+ colEnd: node.endPosition.column,
50
+ signature: firstLine(node),
51
+ };
52
+ }
53
+
54
+ case 'struct_item': {
55
+ const nameNode = node.childForFieldName('name');
56
+ if (!nameNode) return null;
57
+ return {
58
+ name: nameNode.text,
59
+ kind: 'struct',
60
+ lineStart: node.startPosition.row,
61
+ lineEnd: node.endPosition.row,
62
+ colStart: node.startPosition.column,
63
+ colEnd: node.endPosition.column,
64
+ signature: firstLine(node),
65
+ };
66
+ }
67
+
68
+ case 'enum_item': {
69
+ const nameNode = node.childForFieldName('name');
70
+ if (!nameNode) return null;
71
+ return {
72
+ name: nameNode.text,
73
+ kind: 'enum',
74
+ lineStart: node.startPosition.row,
75
+ lineEnd: node.endPosition.row,
76
+ colStart: node.startPosition.column,
77
+ colEnd: node.endPosition.column,
78
+ signature: firstLine(node),
79
+ };
80
+ }
81
+
82
+ case 'trait_item': {
83
+ const nameNode = node.childForFieldName('name');
84
+ if (!nameNode) return null;
85
+ return {
86
+ name: nameNode.text,
87
+ kind: 'interface', // closest semantic equivalent
88
+ lineStart: node.startPosition.row,
89
+ lineEnd: node.endPosition.row,
90
+ colStart: node.startPosition.column,
91
+ colEnd: node.endPosition.column,
92
+ signature: firstLine(node),
93
+ };
94
+ }
95
+
96
+ case 'type_item': {
97
+ const nameNode = node.childForFieldName('name');
98
+ if (!nameNode) return null;
99
+ return {
100
+ name: nameNode.text,
101
+ kind: 'type',
102
+ lineStart: node.startPosition.row,
103
+ lineEnd: node.endPosition.row,
104
+ colStart: node.startPosition.column,
105
+ colEnd: node.endPosition.column,
106
+ signature: firstLine(node),
107
+ };
108
+ }
109
+
110
+ default:
111
+ return null;
112
+ }
113
+ },
114
+
115
+ tryExtractCallName(node: Parser.SyntaxNode): string | null {
116
+ if (node.type !== 'call_expression') return null;
117
+ const funcNode = node.childForFieldName('function');
118
+ if (!funcNode) return null;
119
+
120
+ // foo()
121
+ if (funcNode.type === 'identifier') return funcNode.text;
122
+
123
+ // Type::method() or path::to::func()
124
+ // Skip ::new — it's a constructor pattern, not a meaningful symbol to track
125
+ if (funcNode.type === 'scoped_identifier') {
126
+ const name = funcNode.childForFieldName('name')?.text ?? null;
127
+ return name === 'new' ? null : name;
128
+ }
129
+
130
+ // receiver.method()
131
+ if (funcNode.type === 'field_expression') {
132
+ return funcNode.childForFieldName('field')?.text ?? null;
133
+ }
134
+
135
+ return null;
136
+ },
137
+
138
+ tryExtractImport(node: Parser.SyntaxNode): string | null {
139
+ // use std::collections::HashMap;
140
+ if (node.type === 'use_declaration') {
141
+ const tree = node.childForFieldName('argument');
142
+ if (tree) return tree.text;
143
+ }
144
+ return null;
145
+ },
146
+
147
+ /**
148
+ * Rust `impl Foo { ... }` and `impl Trait for Foo { ... }` aren't symbols
149
+ * themselves, but methods inside them belong to `Foo`. Return `Foo` so the
150
+ * walker can qualify nested function names accordingly.
151
+ */
152
+ tryExtractContextName(node: Parser.SyntaxNode): string | null {
153
+ if (node.type !== 'impl_item') return null;
154
+ // `impl_item` has a `type` field naming the target type. For
155
+ // `impl Trait for Type`, the `type` field is still the target type.
156
+ const typeNode = node.childForFieldName('type');
157
+ if (!typeNode) return null;
158
+ // For generic types like `Repository<User>`, just keep the bare type ident
159
+ if (typeNode.type === 'generic_type') {
160
+ return typeNode.childForFieldName('type')?.text ?? typeNode.text;
161
+ }
162
+ return typeNode.text;
163
+ },
164
+ };