graphitect 0.2.0__py3-none-any.whl

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 (336) hide show
  1. graphify/__init__.py +30 -0
  2. graphify/__main__.py +757 -0
  3. graphify/_minhash.py +107 -0
  4. graphify/affected.py +318 -0
  5. graphify/always_on/agents-md.md +12 -0
  6. graphify/always_on/antigravity-rules.md +14 -0
  7. graphify/always_on/claude-md.md +9 -0
  8. graphify/always_on/gemini-md.md +9 -0
  9. graphify/always_on/kiro-steering.md +5 -0
  10. graphify/always_on/vscode-instructions.md +17 -0
  11. graphify/analyze.py +769 -0
  12. graphify/benchmark.py +152 -0
  13. graphify/build.py +2300 -0
  14. graphify/cache.py +1746 -0
  15. graphify/callflow_html.py +2051 -0
  16. graphify/cargo_introspect.py +109 -0
  17. graphify/cli.py +4745 -0
  18. graphify/cluster.py +409 -0
  19. graphify/command-kilo.md +15 -0
  20. graphify/cross_repo_calls.py +216 -0
  21. graphify/cross_repo_types.py +75 -0
  22. graphify/csharp_dispatch.py +154 -0
  23. graphify/dedup.py +1213 -0
  24. graphify/detect.py +2566 -0
  25. graphify/diagnostics.py +406 -0
  26. graphify/export.py +1349 -0
  27. graphify/exporters/__init__.py +1 -0
  28. graphify/exporters/base.py +14 -0
  29. graphify/exporters/graphdb.py +173 -0
  30. graphify/exporters/html.py +637 -0
  31. graphify/extract.py +7856 -0
  32. graphify/extractors/MIGRATION.md +107 -0
  33. graphify/extractors/__init__.py +66 -0
  34. graphify/extractors/apex.py +215 -0
  35. graphify/extractors/base.py +85 -0
  36. graphify/extractors/bash.py +579 -0
  37. graphify/extractors/blade.py +53 -0
  38. graphify/extractors/commonlisp.py +540 -0
  39. graphify/extractors/csharp.py +448 -0
  40. graphify/extractors/dart.py +564 -0
  41. graphify/extractors/dm.py +494 -0
  42. graphify/extractors/elixir.py +241 -0
  43. graphify/extractors/engine.py +6509 -0
  44. graphify/extractors/fortran.py +311 -0
  45. graphify/extractors/go.py +527 -0
  46. graphify/extractors/json_config.py +240 -0
  47. graphify/extractors/julia.py +289 -0
  48. graphify/extractors/markdown.py +408 -0
  49. graphify/extractors/models.py +131 -0
  50. graphify/extractors/objc.py +566 -0
  51. graphify/extractors/ocaml.py +289 -0
  52. graphify/extractors/pascal.py +688 -0
  53. graphify/extractors/pascal_forms.py +196 -0
  54. graphify/extractors/powershell.py +522 -0
  55. graphify/extractors/razor.py +192 -0
  56. graphify/extractors/resolution.py +3584 -0
  57. graphify/extractors/robot.py +296 -0
  58. graphify/extractors/rust.py +470 -0
  59. graphify/extractors/sln.py +92 -0
  60. graphify/extractors/sql.py +720 -0
  61. graphify/extractors/terraform.py +181 -0
  62. graphify/extractors/verilog.py +329 -0
  63. graphify/extractors/zig.py +181 -0
  64. graphify/file_slice.py +246 -0
  65. graphify/global_graph.py +194 -0
  66. graphify/google_workspace.py +237 -0
  67. graphify/hooks.py +933 -0
  68. graphify/ids.py +93 -0
  69. graphify/ingest.py +358 -0
  70. graphify/install.py +2366 -0
  71. graphify/llm.py +3544 -0
  72. graphify/manifest.py +4 -0
  73. graphify/manifest_ingest.py +311 -0
  74. graphify/mcp_ingest.py +386 -0
  75. graphify/multigraph_compat.py +212 -0
  76. graphify/pascal_resolution.py +129 -0
  77. graphify/paths.py +436 -0
  78. graphify/pg_introspect.py +165 -0
  79. graphify/prs.py +770 -0
  80. graphify/querylog.py +80 -0
  81. graphify/reflect.py +882 -0
  82. graphify/report.py +346 -0
  83. graphify/resolver_registry.py +85 -0
  84. graphify/ruby_resolution.py +242 -0
  85. graphify/scip_ingest.py +363 -0
  86. graphify/security.py +460 -0
  87. graphify/semantic_cleanup.py +336 -0
  88. graphify/serve.py +2608 -0
  89. graphify/skill-agents.md +710 -0
  90. graphify/skill-aider.md +1283 -0
  91. graphify/skill-amp.md +710 -0
  92. graphify/skill-claw.md +713 -0
  93. graphify/skill-codex.md +710 -0
  94. graphify/skill-copilot.md +713 -0
  95. graphify/skill-devin.md +1410 -0
  96. graphify/skill-droid.md +710 -0
  97. graphify/skill-kilo.md +722 -0
  98. graphify/skill-kiro.md +713 -0
  99. graphify/skill-opencode.md +705 -0
  100. graphify/skill-pi.md +713 -0
  101. graphify/skill-trae.md +711 -0
  102. graphify/skill-vscode.md +709 -0
  103. graphify/skill-windows.md +755 -0
  104. graphify/skill.md +713 -0
  105. graphify/skills/agents/references/add-watch.md +56 -0
  106. graphify/skills/agents/references/exports.md +87 -0
  107. graphify/skills/agents/references/extraction-spec.md +70 -0
  108. graphify/skills/agents/references/github-and-merge.md +46 -0
  109. graphify/skills/agents/references/hooks.md +33 -0
  110. graphify/skills/agents/references/query.md +311 -0
  111. graphify/skills/agents/references/transcribe.md +52 -0
  112. graphify/skills/agents/references/update.md +210 -0
  113. graphify/skills/amp/references/add-watch.md +56 -0
  114. graphify/skills/amp/references/exports.md +87 -0
  115. graphify/skills/amp/references/extraction-spec.md +70 -0
  116. graphify/skills/amp/references/github-and-merge.md +46 -0
  117. graphify/skills/amp/references/hooks.md +33 -0
  118. graphify/skills/amp/references/query.md +311 -0
  119. graphify/skills/amp/references/transcribe.md +52 -0
  120. graphify/skills/amp/references/update.md +210 -0
  121. graphify/skills/claude/references/add-watch.md +56 -0
  122. graphify/skills/claude/references/exports.md +87 -0
  123. graphify/skills/claude/references/extraction-spec.md +70 -0
  124. graphify/skills/claude/references/github-and-merge.md +46 -0
  125. graphify/skills/claude/references/hooks.md +33 -0
  126. graphify/skills/claude/references/query.md +311 -0
  127. graphify/skills/claude/references/transcribe.md +52 -0
  128. graphify/skills/claude/references/update.md +210 -0
  129. graphify/skills/claw/references/add-watch.md +56 -0
  130. graphify/skills/claw/references/exports.md +87 -0
  131. graphify/skills/claw/references/extraction-spec.md +31 -0
  132. graphify/skills/claw/references/github-and-merge.md +46 -0
  133. graphify/skills/claw/references/hooks.md +33 -0
  134. graphify/skills/claw/references/query.md +311 -0
  135. graphify/skills/claw/references/transcribe.md +52 -0
  136. graphify/skills/claw/references/update.md +210 -0
  137. graphify/skills/codex/references/add-watch.md +56 -0
  138. graphify/skills/codex/references/exports.md +87 -0
  139. graphify/skills/codex/references/extraction-spec.md +31 -0
  140. graphify/skills/codex/references/github-and-merge.md +46 -0
  141. graphify/skills/codex/references/hooks.md +33 -0
  142. graphify/skills/codex/references/query.md +311 -0
  143. graphify/skills/codex/references/transcribe.md +52 -0
  144. graphify/skills/codex/references/update.md +210 -0
  145. graphify/skills/copilot/references/add-watch.md +56 -0
  146. graphify/skills/copilot/references/exports.md +87 -0
  147. graphify/skills/copilot/references/extraction-spec.md +70 -0
  148. graphify/skills/copilot/references/github-and-merge.md +46 -0
  149. graphify/skills/copilot/references/hooks.md +33 -0
  150. graphify/skills/copilot/references/query.md +311 -0
  151. graphify/skills/copilot/references/transcribe.md +52 -0
  152. graphify/skills/copilot/references/update.md +210 -0
  153. graphify/skills/droid/references/add-watch.md +56 -0
  154. graphify/skills/droid/references/exports.md +87 -0
  155. graphify/skills/droid/references/extraction-spec.md +70 -0
  156. graphify/skills/droid/references/github-and-merge.md +46 -0
  157. graphify/skills/droid/references/hooks.md +33 -0
  158. graphify/skills/droid/references/query.md +311 -0
  159. graphify/skills/droid/references/transcribe.md +52 -0
  160. graphify/skills/droid/references/update.md +210 -0
  161. graphify/skills/kilo/references/add-watch.md +56 -0
  162. graphify/skills/kilo/references/exports.md +87 -0
  163. graphify/skills/kilo/references/extraction-spec.md +70 -0
  164. graphify/skills/kilo/references/github-and-merge.md +46 -0
  165. graphify/skills/kilo/references/hooks.md +33 -0
  166. graphify/skills/kilo/references/query.md +311 -0
  167. graphify/skills/kilo/references/transcribe.md +52 -0
  168. graphify/skills/kilo/references/update.md +210 -0
  169. graphify/skills/kiro/references/add-watch.md +56 -0
  170. graphify/skills/kiro/references/exports.md +87 -0
  171. graphify/skills/kiro/references/extraction-spec.md +31 -0
  172. graphify/skills/kiro/references/github-and-merge.md +46 -0
  173. graphify/skills/kiro/references/hooks.md +33 -0
  174. graphify/skills/kiro/references/query.md +311 -0
  175. graphify/skills/kiro/references/transcribe.md +52 -0
  176. graphify/skills/kiro/references/update.md +210 -0
  177. graphify/skills/opencode/references/add-watch.md +56 -0
  178. graphify/skills/opencode/references/exports.md +87 -0
  179. graphify/skills/opencode/references/extraction-spec.md +70 -0
  180. graphify/skills/opencode/references/github-and-merge.md +46 -0
  181. graphify/skills/opencode/references/hooks.md +33 -0
  182. graphify/skills/opencode/references/query.md +311 -0
  183. graphify/skills/opencode/references/transcribe.md +52 -0
  184. graphify/skills/opencode/references/update.md +210 -0
  185. graphify/skills/pi/references/add-watch.md +56 -0
  186. graphify/skills/pi/references/exports.md +87 -0
  187. graphify/skills/pi/references/extraction-spec.md +31 -0
  188. graphify/skills/pi/references/github-and-merge.md +46 -0
  189. graphify/skills/pi/references/hooks.md +33 -0
  190. graphify/skills/pi/references/query.md +311 -0
  191. graphify/skills/pi/references/transcribe.md +52 -0
  192. graphify/skills/pi/references/update.md +210 -0
  193. graphify/skills/trae/references/add-watch.md +56 -0
  194. graphify/skills/trae/references/exports.md +87 -0
  195. graphify/skills/trae/references/extraction-spec.md +70 -0
  196. graphify/skills/trae/references/github-and-merge.md +46 -0
  197. graphify/skills/trae/references/hooks.md +35 -0
  198. graphify/skills/trae/references/query.md +311 -0
  199. graphify/skills/trae/references/transcribe.md +52 -0
  200. graphify/skills/trae/references/update.md +210 -0
  201. graphify/skills/vscode/references/add-watch.md +56 -0
  202. graphify/skills/vscode/references/exports.md +87 -0
  203. graphify/skills/vscode/references/extraction-spec.md +70 -0
  204. graphify/skills/vscode/references/github-and-merge.md +46 -0
  205. graphify/skills/vscode/references/hooks.md +33 -0
  206. graphify/skills/vscode/references/query.md +311 -0
  207. graphify/skills/vscode/references/transcribe.md +52 -0
  208. graphify/skills/vscode/references/update.md +210 -0
  209. graphify/skills/windows/references/add-watch.md +56 -0
  210. graphify/skills/windows/references/exports.md +87 -0
  211. graphify/skills/windows/references/extraction-spec.md +70 -0
  212. graphify/skills/windows/references/github-and-merge.md +46 -0
  213. graphify/skills/windows/references/hooks.md +33 -0
  214. graphify/skills/windows/references/query.md +311 -0
  215. graphify/skills/windows/references/transcribe.md +52 -0
  216. graphify/skills/windows/references/update.md +210 -0
  217. graphify/symbol_resolution.py +556 -0
  218. graphify/transcribe.py +186 -0
  219. graphify/tree_html.py +603 -0
  220. graphify/validate.py +95 -0
  221. graphify/watch.py +2280 -0
  222. graphify/wiki.py +405 -0
  223. graphitect/__init__.py +28 -0
  224. graphitect/__main__.py +4 -0
  225. graphitect/_vendor/__init__.py +2 -0
  226. graphitect/_vendor/archify/LICENSE +22 -0
  227. graphitect/_vendor/archify/SKILL.md +137 -0
  228. graphitect/_vendor/archify/THIRD_PARTY_NOTICES.md +69 -0
  229. graphitect/_vendor/archify/assets/JetBrainsMono-OFL.txt +93 -0
  230. graphitect/_vendor/archify/assets/template.html +14935 -0
  231. graphitect/_vendor/archify/bin/archify.mjs +2091 -0
  232. graphitect/_vendor/archify/bin/open-artifact.mjs +86 -0
  233. graphitect/_vendor/archify/bin/preview.mjs +653 -0
  234. graphitect/_vendor/archify/bin/visual-check.mjs +829 -0
  235. graphitect/_vendor/archify/brand-marks/README.md +31 -0
  236. graphitect/_vendor/archify/brand-marks/catalog.json +131 -0
  237. graphitect/_vendor/archify/delta/architecture-delta.mjs +1221 -0
  238. graphitect/_vendor/archify/examples/agent-run.lifecycle.json +60 -0
  239. graphitect/_vendor/archify/examples/agent-tool-call.workflow.json +94 -0
  240. graphitect/_vendor/archify/examples/async-job-roundtrip.sequence.json +61 -0
  241. graphitect/_vendor/archify/examples/brand-aware-delivery.architecture.json +47 -0
  242. graphitect/_vendor/archify/examples/cache-miss-request.sequence.json +82 -0
  243. graphitect/_vendor/archify/examples/checkout-platform.base.architecture.json +31 -0
  244. graphitect/_vendor/archify/examples/checkout-platform.head.architecture.json +31 -0
  245. graphitect/_vendor/archify/examples/dataflow-product-analytics.html +15045 -0
  246. graphitect/_vendor/archify/examples/deployment-release.lifecycle.json +49 -0
  247. graphitect/_vendor/archify/examples/event-stream.dataflow.json +57 -0
  248. graphitect/_vendor/archify/examples/incident-response.workflow.json +64 -0
  249. graphitect/_vendor/archify/examples/lifecycle-agent-run.html +14980 -0
  250. graphitect/_vendor/archify/examples/product-analytics.dataflow.json +76 -0
  251. graphitect/_vendor/archify/examples/production-deployment.architecture.json +71 -0
  252. graphitect/_vendor/archify/examples/release-delivery.workflow.json +62 -0
  253. graphitect/_vendor/archify/examples/sequence-cache-miss-request.html +15060 -0
  254. graphitect/_vendor/archify/examples/web-app-rendered.html +15009 -0
  255. graphitect/_vendor/archify/examples/web-app.architecture.json +46 -0
  256. graphitect/_vendor/archify/examples/workflow-agent-tool-call-rendered.html +15051 -0
  257. graphitect/_vendor/archify/migrations/workflow-v2.mjs +279 -0
  258. graphitect/_vendor/archify/package-lock.json +149 -0
  259. graphitect/_vendor/archify/package.json +39 -0
  260. graphitect/_vendor/archify/recipes/scenarios.mjs +391 -0
  261. graphitect/_vendor/archify/references/authoring-contract.md +243 -0
  262. graphitect/_vendor/archify/references/brand-marks.md +65 -0
  263. graphitect/_vendor/archify/references/delivery-contract.md +120 -0
  264. graphitect/_vendor/archify/references/viewer-runtime.md +45 -0
  265. graphitect/_vendor/archify/renderers/architecture/grid.mjs +62 -0
  266. graphitect/_vendor/archify/renderers/architecture/render-architecture.mjs +1078 -0
  267. graphitect/_vendor/archify/renderers/dataflow/README.md +104 -0
  268. graphitect/_vendor/archify/renderers/dataflow/render-dataflow.mjs +483 -0
  269. graphitect/_vendor/archify/renderers/lifecycle/README.md +115 -0
  270. graphitect/_vendor/archify/renderers/lifecycle/render-lifecycle.mjs +561 -0
  271. graphitect/_vendor/archify/renderers/sequence/README.md +114 -0
  272. graphitect/_vendor/archify/renderers/sequence/render-sequence.mjs +464 -0
  273. graphitect/_vendor/archify/renderers/shared/brand-marks.mjs +563 -0
  274. graphitect/_vendor/archify/renderers/shared/cli.mjs +218 -0
  275. graphitect/_vendor/archify/renderers/shared/desktop-readability.mjs +26 -0
  276. graphitect/_vendor/archify/renderers/shared/diagnostics.mjs +127 -0
  277. graphitect/_vendor/archify/renderers/shared/engineering-profiles.mjs +157 -0
  278. graphitect/_vendor/archify/renderers/shared/generated-brand-marks.mjs +2003 -0
  279. graphitect/_vendor/archify/renderers/shared/generated-validators.mjs +13 -0
  280. graphitect/_vendor/archify/renderers/shared/geometry.mjs +1423 -0
  281. graphitect/_vendor/archify/renderers/shared/i18n.mjs +595 -0
  282. graphitect/_vendor/archify/renderers/shared/layout-report.mjs +40 -0
  283. graphitect/_vendor/archify/renderers/shared/legend.mjs +217 -0
  284. graphitect/_vendor/archify/renderers/shared/output-path.mjs +340 -0
  285. graphitect/_vendor/archify/renderers/shared/repository-evidence.mjs +238 -0
  286. graphitect/_vendor/archify/renderers/shared/repository-location.mjs +58 -0
  287. graphitect/_vendor/archify/renderers/shared/text-fit.mjs +49 -0
  288. graphitect/_vendor/archify/renderers/shared/utils.mjs +232 -0
  289. graphitect/_vendor/archify/renderers/shared/validator.mjs +86 -0
  290. graphitect/_vendor/archify/renderers/workflow/README.md +223 -0
  291. graphitect/_vendor/archify/renderers/workflow/render-workflow.mjs +35 -0
  292. graphitect/_vendor/archify/renderers/workflow/workflow-compiler.mjs +4400 -0
  293. graphitect/_vendor/archify/renderers/workflow/workflow-migration-geometry.mjs +144 -0
  294. graphitect/_vendor/archify/schemas/README.md +211 -0
  295. graphitect/_vendor/archify/schemas/architecture.schema.json +178 -0
  296. graphitect/_vendor/archify/schemas/common.schema.json +115 -0
  297. graphitect/_vendor/archify/schemas/dataflow.schema.json +243 -0
  298. graphitect/_vendor/archify/schemas/lifecycle.schema.json +266 -0
  299. graphitect/_vendor/archify/schemas/sequence.schema.json +223 -0
  300. graphitect/_vendor/archify/schemas/workflow.schema.json +428 -0
  301. graphitect/_vendor/archify/scripts/check-render-output.mjs +836 -0
  302. graphitect/_vendor/archify/scripts/check-update.mjs +1667 -0
  303. graphitect/_vendor/archify/scripts/generate-brand-marks.mjs +141 -0
  304. graphitect/_vendor/archify/scripts/generate-validators.mjs +66 -0
  305. graphitect/_vendor/archify/scripts/render-examples.mjs +26 -0
  306. graphitect/_vendor/archify/scripts/update-contract.mjs +182 -0
  307. graphitect/_vendor/archify/skill-release.json +10 -0
  308. graphitect/cli.py +981 -0
  309. graphitect/deliver/__init__.py +5 -0
  310. graphitect/deliver/archify_adapter.py +1877 -0
  311. graphitect/deliver/archify_ir.py +160 -0
  312. graphitect/deliver/archify_repair.py +135 -0
  313. graphitect/deliver/doc_compiler.py +916 -0
  314. graphitect/ground/__init__.py +5 -0
  315. graphitect/ground/describe_source.py +27 -0
  316. graphitect/ground/fullread_source.py +56 -0
  317. graphitect/ground/graphify_source.py +107 -0
  318. graphitect/models.py +118 -0
  319. graphitect/skill/SKILL.md +80 -0
  320. graphitect/skill/agents/openai.yaml +4 -0
  321. graphitect/synthesize/__init__.py +5 -0
  322. graphitect/synthesize/engine.py +281 -0
  323. graphitect/synthesize/llm_backend.py +331 -0
  324. graphitect/synthesize/questions.py +139 -0
  325. graphitect/synthesize/rubric.py +104 -0
  326. graphitect-0.2.0.dist-info/METADATA +284 -0
  327. graphitect-0.2.0.dist-info/RECORD +336 -0
  328. graphitect-0.2.0.dist-info/WHEEL +5 -0
  329. graphitect-0.2.0.dist-info/entry_points.txt +2 -0
  330. graphitect-0.2.0.dist-info/licenses/LICENSE +21 -0
  331. graphitect-0.2.0.dist-info/licenses/LICENSE-ARCHIFY-MIT +22 -0
  332. graphitect-0.2.0.dist-info/licenses/LICENSE-GRAPHIFY-APACHE-2.0 +202 -0
  333. graphitect-0.2.0.dist-info/licenses/LICENSE-GRAPHIFY-MIT +21 -0
  334. graphitect-0.2.0.dist-info/licenses/NOTICE-ARCHIFY-THIRD-PARTY.md +69 -0
  335. graphitect-0.2.0.dist-info/licenses/NOTICE-GRAPHIFY +8 -0
  336. graphitect-0.2.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,470 @@
1
+ """Rust extractor. Moved verbatim from graphify/extract.py."""
2
+ from __future__ import annotations
3
+
4
+
5
+ from pathlib import Path
6
+ from graphify.extractors.base import _LANGUAGE_BUILTIN_GLOBALS, _file_stem, _make_id, _read_text
7
+
8
+
9
+ def _rust_collect_type_refs(node, source: bytes, generic: bool, out: list[tuple[str, str]]) -> None:
10
+ """Walk a Rust type expression; append (name, role) tuples."""
11
+ if node is None:
12
+ return
13
+ t = node.type
14
+ if t == "primitive_type":
15
+ return
16
+ if t == "type_identifier":
17
+ text = _read_text(node, source)
18
+ if text:
19
+ out.append((text, "generic_arg" if generic else "type"))
20
+ return
21
+ if t == "scoped_type_identifier":
22
+ text = _read_text(node, source).rsplit("::", 1)[-1]
23
+ if text:
24
+ out.append((text, "generic_arg" if generic else "type"))
25
+ return
26
+ if t == "generic_type":
27
+ name_node = node.child_by_field_name("type")
28
+ if name_node is None:
29
+ for c in node.children:
30
+ if c.type in ("type_identifier", "scoped_type_identifier"):
31
+ name_node = c
32
+ break
33
+ if name_node is not None:
34
+ text = _read_text(name_node, source).rsplit("::", 1)[-1]
35
+ if text:
36
+ out.append((text, "generic_arg" if generic else "type"))
37
+ for c in node.children:
38
+ if c.type == "type_arguments":
39
+ for arg in c.children:
40
+ if arg.is_named:
41
+ _rust_collect_type_refs(arg, source, True, out)
42
+ return
43
+ if t in ("reference_type", "pointer_type", "array_type", "tuple_type", "slice_type"):
44
+ for c in node.children:
45
+ if c.is_named:
46
+ _rust_collect_type_refs(c, source, generic, out)
47
+ return
48
+ if node.is_named:
49
+ for c in node.children:
50
+ if c.is_named:
51
+ _rust_collect_type_refs(c, source, generic, out)
52
+
53
+ _RUST_TRAIT_METHOD_BLOCKLIST: frozenset[str] = frozenset({
54
+ "new", "default", "parse", "from_str", "now", "clone", "into", "from",
55
+ "to_string", "to_owned", "len", "is_empty", "iter", "next", "build",
56
+ "start", "run", "init", "app", "get", "set", "push", "pop", "insert",
57
+ "remove", "contains", "collect", "map", "filter", "unwrap", "expect",
58
+ "ok", "err", "some", "none", "send", "recv", "lock", "read", "write",
59
+ })
60
+
61
+ def extract_rust(path: Path) -> dict:
62
+ """Extract functions, structs, enums, traits, impl methods, statics/consts, and use declarations from a .rs file."""
63
+ try:
64
+ import tree_sitter_rust as tsrust
65
+ from tree_sitter import Language, Parser
66
+ except ImportError:
67
+ return {"nodes": [], "edges": [], "error": "tree-sitter-rust not installed"}
68
+
69
+ try:
70
+ language = Language(tsrust.language())
71
+ parser = Parser(language)
72
+ source = path.read_bytes()
73
+ tree = parser.parse(source)
74
+ root = tree.root_node
75
+ except Exception as e:
76
+ return {"nodes": [], "edges": [], "error": str(e)}
77
+
78
+ stem = _file_stem(path)
79
+ str_path = str(path)
80
+ nodes: list[dict] = []
81
+ edges: list[dict] = []
82
+ seen_ids: set[str] = set()
83
+ function_bodies: list[tuple[str, object]] = []
84
+
85
+ def add_node(nid: str, label: str, line: int) -> None:
86
+ if nid not in seen_ids:
87
+ seen_ids.add(nid)
88
+ nodes.append({
89
+ "id": nid,
90
+ "label": label,
91
+ "file_type": "code",
92
+ "source_file": str_path,
93
+ "source_location": f"L{line}",
94
+ })
95
+
96
+ def add_edge(src: str, tgt: str, relation: str, line: int,
97
+ confidence: str = "EXTRACTED", weight: float = 1.0,
98
+ context: str | None = None) -> None:
99
+ edge = {
100
+ "source": src,
101
+ "target": tgt,
102
+ "relation": relation,
103
+ "confidence": confidence,
104
+ "source_file": str_path,
105
+ "source_location": f"L{line}",
106
+ "weight": weight,
107
+ }
108
+ if context:
109
+ edge["context"] = context
110
+ edges.append(edge)
111
+
112
+ file_nid = _make_id(str(path))
113
+ add_node(file_nid, path.name, 1)
114
+
115
+ def ensure_named_node(name: str, line: int) -> str:
116
+ nid = _make_id(stem, name)
117
+ if nid in seen_ids:
118
+ return nid
119
+ nid = _make_id(name)
120
+ if nid not in seen_ids:
121
+ # The name isn't defined in this file, so this is a cross-file reference
122
+ # (e.g. a `Thing` type annotation imported from another module). Emit a
123
+ # SOURCELESS stub — like the inheritance-base path below — so the
124
+ # corpus-level rewire can collapse it onto the real definition. A sourced
125
+ # stub here makes _disambiguate_colliding_node_ids bake the referencing
126
+ # file's path (with extension) into the id and blocks the rewire, which is
127
+ # the phantom-duplicate-node bug (#1402).
128
+ seen_ids.add(nid)
129
+ nodes.append({
130
+ "id": nid,
131
+ "label": name,
132
+ "file_type": "code",
133
+ "source_file": "",
134
+ "source_location": "",
135
+ "origin_file": str_path,
136
+ })
137
+ return nid
138
+
139
+ def emit_param_return_refs(func_node, func_nid: str, line: int) -> None:
140
+ params = func_node.child_by_field_name("parameters")
141
+ if params is not None:
142
+ for p in params.children:
143
+ if p.type != "parameter":
144
+ continue
145
+ type_node = p.child_by_field_name("type")
146
+ refs: list[tuple[str, str]] = []
147
+ _rust_collect_type_refs(type_node, source, False, refs)
148
+ for ref_name, role in refs:
149
+ ctx = "generic_arg" if role == "generic_arg" else "parameter_type"
150
+ tgt = ensure_named_node(ref_name, line)
151
+ if tgt != func_nid:
152
+ add_edge(func_nid, tgt, "references", line, context=ctx)
153
+ return_type = func_node.child_by_field_name("return_type")
154
+ if return_type is not None:
155
+ refs = []
156
+ _rust_collect_type_refs(return_type, source, False, refs)
157
+ for ref_name, role in refs:
158
+ ctx = "generic_arg" if role == "generic_arg" else "return_type"
159
+ tgt = ensure_named_node(ref_name, line)
160
+ if tgt != func_nid:
161
+ add_edge(func_nid, tgt, "references", line, context=ctx)
162
+
163
+ def walk(node, parent_impl_nid: str | None = None) -> None:
164
+ t = node.type
165
+
166
+ if t == "function_item":
167
+ name_node = node.child_by_field_name("name")
168
+ if name_node:
169
+ func_name = _read_text(name_node, source)
170
+ line = node.start_point[0] + 1
171
+ if parent_impl_nid:
172
+ func_nid = _make_id(parent_impl_nid, func_name)
173
+ add_node(func_nid, f".{func_name}()", line)
174
+ add_edge(parent_impl_nid, func_nid, "method", line)
175
+ else:
176
+ func_nid = _make_id(stem, func_name)
177
+ add_node(func_nid, f"{func_name}()", line)
178
+ add_edge(file_nid, func_nid, "contains", line)
179
+ emit_param_return_refs(node, func_nid, line)
180
+ body = node.child_by_field_name("body")
181
+ if body:
182
+ function_bodies.append((func_nid, body))
183
+ return
184
+
185
+ if t == "function_signature_item":
186
+ # `fn greet(&self) -> String;` — a trait method with no body. This node type
187
+ # had no branch at all, so a trait's required methods were unreachable: the
188
+ # only method nodes in a graph came from impl blocks, and a trait with no
189
+ # implementor in the corpus contributed none. Mirrors function_item, minus
190
+ # the body walk (there is no body).
191
+ name_node = node.child_by_field_name("name")
192
+ if name_node:
193
+ func_name = _read_text(name_node, source)
194
+ line = node.start_point[0] + 1
195
+ if parent_impl_nid:
196
+ func_nid = _make_id(parent_impl_nid, func_name)
197
+ add_node(func_nid, f".{func_name}()", line)
198
+ add_edge(parent_impl_nid, func_nid, "method", line)
199
+ else:
200
+ func_nid = _make_id(stem, func_name)
201
+ add_node(func_nid, f"{func_name}()", line)
202
+ add_edge(file_nid, func_nid, "contains", line)
203
+ emit_param_return_refs(node, func_nid, line)
204
+ return
205
+
206
+ if t in ("struct_item", "enum_item", "trait_item"):
207
+ name_node = node.child_by_field_name("name")
208
+ if name_node:
209
+ item_name = _read_text(name_node, source)
210
+ line = node.start_point[0] + 1
211
+ item_nid = _make_id(stem, item_name)
212
+ add_node(item_nid, item_name, line)
213
+ add_edge(file_nid, item_nid, "contains", line)
214
+ if t == "trait_item":
215
+ for c in node.children:
216
+ if c.type != "trait_bounds":
217
+ continue
218
+ for sub in c.children:
219
+ if not sub.is_named:
220
+ continue
221
+ refs: list[tuple[str, str]] = []
222
+ _rust_collect_type_refs(sub, source, False, refs)
223
+ for idx, (ref_name, _role) in enumerate(refs):
224
+ tgt = ensure_named_node(ref_name, line)
225
+ if tgt == item_nid:
226
+ continue
227
+ rel = "inherits" if idx == 0 else "references"
228
+ if rel == "inherits":
229
+ add_edge(item_nid, tgt, "inherits", line)
230
+ else:
231
+ add_edge(item_nid, tgt, "references", line,
232
+ context="generic_arg")
233
+ if t == "struct_item":
234
+ for c in node.children:
235
+ if c.type != "field_declaration_list":
236
+ continue
237
+ for field in c.children:
238
+ if field.type != "field_declaration":
239
+ continue
240
+ type_node = field.child_by_field_name("type")
241
+ if type_node is None:
242
+ for fc in field.children:
243
+ if fc.type in ("type_identifier", "generic_type",
244
+ "scoped_type_identifier",
245
+ "reference_type", "primitive_type"):
246
+ type_node = fc
247
+ break
248
+ refs = []
249
+ _rust_collect_type_refs(type_node, source, False, refs)
250
+ for ref_name, role in refs:
251
+ ctx = "generic_arg" if role == "generic_arg" else "field"
252
+ tgt = ensure_named_node(ref_name, field.start_point[0] + 1)
253
+ if tgt != item_nid:
254
+ add_edge(item_nid, tgt, "references",
255
+ field.start_point[0] + 1, context=ctx)
256
+ # Tuple structs (`struct Wrapper(pub Logger, Config);`) nest their
257
+ # positional field types directly under ordered_field_declaration_list
258
+ # with no field_declaration wrapper -- the same shape handled for tuple
259
+ # enum variants below. Without this branch these field type references
260
+ # are silently dropped.
261
+ for c in node.children:
262
+ if c.type != "ordered_field_declaration_list":
263
+ continue
264
+ fline = c.start_point[0] + 1
265
+ for tc in c.children:
266
+ if tc.type not in ("type_identifier", "generic_type",
267
+ "scoped_type_identifier", "reference_type",
268
+ "primitive_type", "tuple_type", "array_type"):
269
+ continue
270
+ refs = []
271
+ _rust_collect_type_refs(tc, source, False, refs)
272
+ for ref_name, role in refs:
273
+ ctx = "generic_arg" if role == "generic_arg" else "field"
274
+ tgt = ensure_named_node(ref_name, fline)
275
+ if tgt != item_nid:
276
+ add_edge(item_nid, tgt, "references", fline, context=ctx)
277
+ if t == "enum_item":
278
+ # Variant payload types nest under enum_variant_list ->
279
+ # enum_variant -> ordered_field_declaration_list (tuple variant,
280
+ # `Click(Logger)`) | field_declaration_list (struct variant,
281
+ # `Resize { size: Dim }`). Neither was traversed, so every
282
+ # enum-variant type reference was silently dropped.
283
+ _TYPE_NODES = ("type_identifier", "generic_type",
284
+ "scoped_type_identifier", "reference_type",
285
+ "primitive_type", "tuple_type", "array_type")
286
+
287
+ def _emit_enum_type(type_node, at_line):
288
+ if type_node is None:
289
+ return
290
+ refs2: list[tuple[str, str]] = []
291
+ _rust_collect_type_refs(type_node, source, False, refs2)
292
+ for ref_name, role in refs2:
293
+ ctx = "generic_arg" if role == "generic_arg" else "field"
294
+ tgt = ensure_named_node(ref_name, at_line)
295
+ if tgt != item_nid:
296
+ add_edge(item_nid, tgt, "references", at_line, context=ctx)
297
+
298
+ for c in node.children:
299
+ if c.type != "enum_variant_list":
300
+ continue
301
+ for variant in c.children:
302
+ if variant.type != "enum_variant":
303
+ continue
304
+ vline = variant.start_point[0] + 1
305
+ for vc in variant.children:
306
+ if vc.type == "ordered_field_declaration_list":
307
+ for tc in vc.children:
308
+ if tc.type in _TYPE_NODES:
309
+ _emit_enum_type(tc, vline)
310
+ elif vc.type == "field_declaration_list":
311
+ for field in vc.children:
312
+ if field.type != "field_declaration":
313
+ continue
314
+ type_node = field.child_by_field_name("type")
315
+ _emit_enum_type(type_node, field.start_point[0] + 1)
316
+ if t == "trait_item":
317
+ # The methods a trait declares are its contract, and they were not
318
+ # reached: this branch returns below, so nothing walked the body.
319
+ # Descend the same way impl_item does, attributing each method to
320
+ # the trait node — so `explain <Trait>` can list what an
321
+ # implementor must provide.
322
+ body = node.child_by_field_name("body")
323
+ if body:
324
+ for child in body.children:
325
+ walk(child, parent_impl_nid=item_nid)
326
+ return
327
+
328
+ if t in ("static_item", "const_item"):
329
+ # `static NAME: T = …;` / `const NAME: T = …;` at module level, or an
330
+ # associated const inside an impl. Neither node type had a branch, so a
331
+ # constant reached the graph only through files that referenced it,
332
+ # never from the Rust that defines it (#3471).
333
+ name_node = node.child_by_field_name("name")
334
+ if name_node:
335
+ item_name = _read_text(name_node, source)
336
+ line = node.start_point[0] + 1
337
+ if parent_impl_nid:
338
+ item_nid = _make_id(parent_impl_nid, item_name)
339
+ add_node(item_nid, f".{item_name}", line)
340
+ add_edge(parent_impl_nid, item_nid, "contains", line)
341
+ else:
342
+ item_nid = _make_id(stem, item_name)
343
+ add_node(item_nid, item_name, line)
344
+ add_edge(file_nid, item_nid, "contains", line)
345
+ type_node = node.child_by_field_name("type")
346
+ if type_node is not None:
347
+ refs: list[tuple[str, str]] = []
348
+ _rust_collect_type_refs(type_node, source, False, refs)
349
+ for ref_name, role in refs:
350
+ tgt = ensure_named_node(ref_name, line)
351
+ if tgt == item_nid:
352
+ continue
353
+ ctx = "generic_arg" if role == "generic_arg" else "field"
354
+ add_edge(item_nid, tgt, "references", line, context=ctx)
355
+ return
356
+
357
+ if t == "impl_item":
358
+ type_node = node.child_by_field_name("type")
359
+ trait_node = node.child_by_field_name("trait")
360
+ impl_nid: str | None = None
361
+ if type_node:
362
+ type_name = _read_text(type_node, source).strip()
363
+ impl_nid = _make_id(stem, type_name)
364
+ add_node(impl_nid, type_name, node.start_point[0] + 1)
365
+ if trait_node is not None and impl_nid is not None:
366
+ refs: list[tuple[str, str]] = []
367
+ _rust_collect_type_refs(trait_node, source, False, refs)
368
+ for idx, (ref_name, _role) in enumerate(refs):
369
+ tgt = ensure_named_node(ref_name, node.start_point[0] + 1)
370
+ if tgt == impl_nid:
371
+ continue
372
+ if idx == 0:
373
+ add_edge(impl_nid, tgt, "implements", node.start_point[0] + 1)
374
+ else:
375
+ add_edge(impl_nid, tgt, "references", node.start_point[0] + 1,
376
+ context="generic_arg")
377
+ body = node.child_by_field_name("body")
378
+ if body:
379
+ for child in body.children:
380
+ walk(child, parent_impl_nid=impl_nid)
381
+ return
382
+
383
+ if t == "use_declaration":
384
+ arg = node.child_by_field_name("argument")
385
+ if arg:
386
+ raw = _read_text(arg, source)
387
+ clean = raw.split("{")[0].rstrip(":").rstrip("*").rstrip(":")
388
+ module_name = clean.split("::")[-1].strip()
389
+ if module_name:
390
+ tgt_nid = _make_id(module_name)
391
+ add_edge(file_nid, tgt_nid, "imports_from", node.start_point[0] + 1, context="import")
392
+ return
393
+
394
+ for child in node.children:
395
+ walk(child, parent_impl_nid=None)
396
+
397
+ walk(root)
398
+
399
+ label_to_nid: dict[str, str] = {}
400
+ for n in nodes:
401
+ raw = n["label"]
402
+ normalised = raw.strip("()").lstrip(".")
403
+ label_to_nid[normalised] = n["id"]
404
+
405
+ seen_call_pairs: set[tuple[str, str]] = set()
406
+ raw_calls: list[dict] = []
407
+
408
+ def walk_calls(node, caller_nid: str) -> None:
409
+ if node.type == "function_item":
410
+ return
411
+ if node.type == "call_expression":
412
+ func_node = node.child_by_field_name("function")
413
+ callee_name: str | None = None
414
+ is_member_call: bool = False
415
+ is_scoped_call: bool = False
416
+ if func_node:
417
+ if func_node.type == "identifier":
418
+ callee_name = _read_text(func_node, source)
419
+ elif func_node.type == "field_expression":
420
+ is_member_call = True
421
+ field = func_node.child_by_field_name("field")
422
+ if field:
423
+ callee_name = _read_text(field, source)
424
+ elif func_node.type == "scoped_identifier":
425
+ # Type::method() — still allow in-file EXTRACTED match, but
426
+ # skip cross-file resolution: bare last-segment lookup ignores
427
+ # crate boundaries and produces spurious INFERRED edges (#908).
428
+ is_scoped_call = True
429
+ name = func_node.child_by_field_name("name")
430
+ if name:
431
+ callee_name = _read_text(name, source)
432
+ if callee_name and callee_name not in _LANGUAGE_BUILTIN_GLOBALS:
433
+ tgt_nid = label_to_nid.get(callee_name)
434
+ if tgt_nid and tgt_nid != caller_nid:
435
+ pair = (caller_nid, tgt_nid)
436
+ if pair not in seen_call_pairs:
437
+ seen_call_pairs.add(pair)
438
+ line = node.start_point[0] + 1
439
+ edges.append({
440
+ "source": caller_nid,
441
+ "target": tgt_nid,
442
+ "relation": "calls",
443
+ "context": "call",
444
+ "confidence": "EXTRACTED",
445
+ "source_file": str_path,
446
+ "source_location": f"L{line}",
447
+ "weight": 1.0,
448
+ })
449
+ elif not is_scoped_call and callee_name.lower() not in _RUST_TRAIT_METHOD_BLOCKLIST:
450
+ raw_calls.append({
451
+ "caller_nid": caller_nid,
452
+ "callee": callee_name,
453
+ "is_member_call": is_member_call,
454
+ "source_file": str_path,
455
+ "source_location": f"L{node.start_point[0] + 1}",
456
+ })
457
+ for child in node.children:
458
+ walk_calls(child, caller_nid)
459
+
460
+ for caller_nid, body_node in function_bodies:
461
+ walk_calls(body_node, caller_nid)
462
+
463
+ valid_ids = seen_ids
464
+ clean_edges = []
465
+ for edge in edges:
466
+ src, tgt = edge["source"], edge["target"]
467
+ if src in valid_ids and (tgt in valid_ids or edge["relation"] in ("imports", "imports_from")):
468
+ clean_edges.append(edge)
469
+
470
+ return {"nodes": nodes, "edges": clean_edges, "raw_calls": raw_calls}
@@ -0,0 +1,92 @@
1
+ """Sln extractor. Moved verbatim from graphify/extract.py."""
2
+ from __future__ import annotations
3
+
4
+ import re
5
+
6
+ from pathlib import Path
7
+ from graphify.extractors.base import _make_id
8
+
9
+
10
+ def extract_sln(path: Path) -> dict:
11
+ """Extract projects and inter-project dependencies from a .sln file."""
12
+ try:
13
+ src = path.read_text(encoding="utf-8", errors="replace")
14
+ except OSError:
15
+ return {"nodes": [], "edges": [], "error": f"cannot read {path}"}
16
+
17
+ file_nid = _make_id(str(path))
18
+ str_path = str(path)
19
+ nodes: list[dict] = [{"id": file_nid, "label": path.name, "file_type": "code",
20
+ "source_file": str_path, "source_location": None}]
21
+ edges: list[dict] = []
22
+ seen_ids: set[str] = set()
23
+ seen_ids.add(file_nid)
24
+
25
+ _PROJECT_RE = re.compile(
26
+ r'Project\("[^"]*"\)\s*=\s*"([^"]+)"\s*,\s*"([^"]+)"\s*,\s*"([^"]*)"'
27
+ )
28
+ _DEP_RE = re.compile(r'\{([0-9a-fA-F-]+)\}\s*=\s*\{([0-9a-fA-F-]+)\}')
29
+
30
+ guid_to_nid: dict[str, str] = {}
31
+
32
+ for m in _PROJECT_RE.finditer(src):
33
+ proj_name = m.group(1)
34
+ proj_path = m.group(2).replace("\\", "/")
35
+ proj_guid = m.group(3).strip("{}")
36
+
37
+ # A solution folder is a VIRTUAL grouping, not a file: Visual Studio writes
38
+ # its name as both the display name and the "path" (proj_name == proj_path,
39
+ # no real file). Resolving it to an absolute path and keying the node id off
40
+ # that leaked the absolute scan path (incl. the OS username) into graph.json,
41
+ # because the CLI's id-relativization only remaps ids of real files in the
42
+ # scan set — a virtual folder never matches, so its absolute id survived
43
+ # (#1789). Use the folder name itself (relative, no filesystem resolution).
44
+ is_solution_folder = proj_path == proj_name
45
+ if is_solution_folder:
46
+ abs_proj = proj_name
47
+ else:
48
+ try:
49
+ abs_proj = str((path.parent / proj_path).resolve())
50
+ except Exception:
51
+ abs_proj = proj_path
52
+ proj_nid = _make_id(abs_proj)
53
+ if proj_nid and proj_nid not in seen_ids:
54
+ seen_ids.add(proj_nid)
55
+ nodes.append({"id": proj_nid, "label": proj_name,
56
+ "file_type": "code", "source_file": abs_proj,
57
+ "source_location": None})
58
+ edges.append({"source": file_nid, "target": proj_nid,
59
+ "relation": "contains", "confidence": "EXTRACTED",
60
+ "source_file": str_path, "weight": 1.0})
61
+ if proj_guid:
62
+ guid_to_nid[proj_guid.lower()] = proj_nid
63
+
64
+ in_dep_section = False
65
+ current_proj_guid: str | None = None
66
+ _PROJECT_LINE_RE = re.compile(r'Project\("[^"]*"\)\s*=\s*"[^"]+"\s*,\s*"[^"]+"\s*,\s*"\{([^}]+)\}"')
67
+ for line in src.splitlines():
68
+ proj_line_m = _PROJECT_LINE_RE.search(line)
69
+ if proj_line_m:
70
+ current_proj_guid = proj_line_m.group(1).lower()
71
+ continue
72
+ if line.strip() == "EndProject":
73
+ current_proj_guid = None
74
+ continue
75
+ if "ProjectSection(ProjectDependencies)" in line:
76
+ in_dep_section = True
77
+ continue
78
+ if in_dep_section and "EndProjectSection" in line:
79
+ in_dep_section = False
80
+ continue
81
+ if in_dep_section and current_proj_guid:
82
+ dep_m = _DEP_RE.search(line)
83
+ if dep_m:
84
+ to_guid = dep_m.group(1).lower()
85
+ from_nid = guid_to_nid.get(current_proj_guid)
86
+ to_nid = guid_to_nid.get(to_guid)
87
+ if from_nid and to_nid and from_nid != to_nid:
88
+ edges.append({"source": from_nid, "target": to_nid,
89
+ "relation": "imports", "confidence": "EXTRACTED",
90
+ "source_file": str_path, "weight": 1.0})
91
+
92
+ return {"nodes": nodes, "edges": edges}