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,289 @@
1
+ """OCaml extractor (own module, optional tree-sitter-ocaml dependency).
2
+
3
+ Handles implementation files (.ml, `language_ocaml`) and interface files
4
+ (.mli, `language_ocaml_interface`). Interfaces have no expression bodies, so
5
+ they contribute definitions and `open` imports but no `calls` edges.
6
+ """
7
+ from __future__ import annotations
8
+
9
+
10
+ from pathlib import Path
11
+ from graphify.extractors.base import _file_stem, _make_id, _read_text
12
+
13
+
14
+ def extract_ocaml(path: Path) -> dict:
15
+ """Extract modules, values, functions, types, variant constructors, `open`
16
+ imports, and (for .ml) function calls from an OCaml source file."""
17
+ try:
18
+ import tree_sitter_ocaml as tsocaml
19
+ from tree_sitter import Language, Parser
20
+ except ImportError:
21
+ return {"nodes": [], "edges": [], "error": "tree-sitter-ocaml not installed"}
22
+
23
+ try:
24
+ if path.suffix == ".mli":
25
+ language = Language(tsocaml.language_ocaml_interface())
26
+ else:
27
+ language = Language(tsocaml.language_ocaml())
28
+ parser = Parser(language)
29
+ source = path.read_bytes()
30
+ tree = parser.parse(source)
31
+ root = tree.root_node
32
+ except Exception as e:
33
+ return {"nodes": [], "edges": [], "error": str(e)}
34
+
35
+ stem = _file_stem(path)
36
+ str_path = str(path)
37
+ nodes: list[dict] = []
38
+ edges: list[dict] = []
39
+ seen_ids: set[str] = set()
40
+
41
+ # Same-file definition table for call resolution. A name defined more than
42
+ # once in the file is marked ambiguous and never resolved locally.
43
+ local_defs: dict[str, str] = {}
44
+ ambiguous: set[str] = set()
45
+ # Names of modules DEFINED in this file. Used to decide whether a qualified
46
+ # call `M.f` may bind to a local `f`: if `M` is not a local module it is an
47
+ # external library (e.g. `Reg_spec.create`), so binding to a same-named local
48
+ # `f` would be a false edge (and a self-loop when the caller is that `f`).
49
+ local_modules: set[str] = set()
50
+ # (caller_nid, callee_name, qualifier_root_module_or_None, full_path_text,
51
+ # line) recorded on pass 1, resolved on pass 2 so that forward references
52
+ # (e.g. `let rec ... and ...`) resolve correctly.
53
+ call_sites: list[tuple[str, str, str | None, str, int]] = []
54
+
55
+ def add_node(nid: str, label: str, line: int) -> None:
56
+ if nid not in seen_ids:
57
+ seen_ids.add(nid)
58
+ nodes.append({
59
+ "id": nid,
60
+ "label": label,
61
+ "file_type": "code",
62
+ "source_file": str_path,
63
+ "source_location": f"L{line}",
64
+ })
65
+
66
+ def add_edge(src: str, tgt: str, relation: str, line: int,
67
+ confidence: str = "EXTRACTED", weight: float = 1.0) -> None:
68
+ edges.append({
69
+ "source": src,
70
+ "target": tgt,
71
+ "relation": relation,
72
+ "confidence": confidence,
73
+ "source_file": str_path,
74
+ "source_location": f"L{line}",
75
+ "weight": weight,
76
+ })
77
+
78
+ file_nid = _make_id(str(path))
79
+ add_node(file_nid, path.name, 1)
80
+
81
+ def ref_stub(name: str) -> str:
82
+ """Mint a SOURCELESS stub for a cross-file reference target (an `open`ed
83
+ module or a call to a name not defined in this file). The corpus-level
84
+ rewire collapses it onto the unique real definition of that name;
85
+ external names (Stdlib, Core, ...) dangle and are pruned. A *sourced*
86
+ stub here would bake this file's path into the id and block the rewire
87
+ (the #1402 phantom-duplicate bug)."""
88
+ nid = _make_id(name)
89
+ if nid not in seen_ids:
90
+ seen_ids.add(nid)
91
+ nodes.append({
92
+ "id": nid,
93
+ "label": name,
94
+ "file_type": "code",
95
+ "source_file": "",
96
+ "source_location": "",
97
+ "origin_file": str_path,
98
+ })
99
+ return nid
100
+
101
+ def line_of(node) -> int:
102
+ return node.start_point[0] + 1
103
+
104
+ def named_child_text(node, child_type: str) -> str | None:
105
+ for child in node.children:
106
+ if child.type == child_type:
107
+ return _read_text(child, source)
108
+ return None
109
+
110
+ def last_name(path_node) -> str | None:
111
+ """Final component of a *_path node: `Geo.area` -> `area`,
112
+ `Stdlib.List` -> `List`. The qualifier is nested under a child
113
+ `module_path`, so the final name is always a *direct* child of the path
114
+ node -- a deep search would wrongly return the module qualifier."""
115
+ found: str | None = None
116
+ for n in path_node.children:
117
+ if n.type in ("value_name", "module_name", "constructor_name"):
118
+ found = _read_text(n, source)
119
+ return found
120
+
121
+ def path_root_module(path_node) -> str | None:
122
+ """Leftmost (outermost) module segment qualifying a *_path node:
123
+ `Reg_spec.create` -> `Reg_spec`, `Stdlib.List.map` -> `Stdlib`. Returns
124
+ None when the path is unqualified (`create`), which has no child
125
+ module_path."""
126
+ mp = next((c for c in path_node.children if c.type == "module_path"), None)
127
+ if mp is None:
128
+ return None
129
+ node = mp
130
+ while True:
131
+ inner = next((c for c in node.children if c.type == "module_path"), None)
132
+ if inner is None:
133
+ break
134
+ node = inner
135
+ mn = next((c for c in node.children if c.type == "module_name"), None)
136
+ return _read_text(mn, source) if mn is not None else None
137
+
138
+ def register_def(name: str, nid: str) -> None:
139
+ if name in ambiguous:
140
+ return
141
+ if name in local_defs and local_defs[name] != nid:
142
+ ambiguous.add(name)
143
+ local_defs.pop(name, None)
144
+ return
145
+ local_defs[name] = nid
146
+
147
+ def emit_type(binding, container_nid: str) -> None:
148
+ type_name = named_child_text(binding, "type_constructor")
149
+ if not type_name:
150
+ return
151
+ line = line_of(binding)
152
+ nid = _make_id(stem, type_name)
153
+ add_node(nid, type_name, line)
154
+ add_edge(container_nid, nid, "defines" if container_nid == file_nid else "contains", line)
155
+ register_def(type_name, nid)
156
+ # Variant constructors: variant_declaration -> constructor_declaration -> constructor_name
157
+ for vd in binding.children:
158
+ if vd.type != "variant_declaration":
159
+ continue
160
+ for cd in vd.children:
161
+ if cd.type != "constructor_declaration":
162
+ continue
163
+ cname = named_child_text(cd, "constructor_name")
164
+ if not cname:
165
+ continue
166
+ cnid = _make_id(stem, type_name, cname)
167
+ add_node(cnid, cname, line_of(cd))
168
+ add_edge(nid, cnid, "contains", line_of(cd))
169
+
170
+ def walk(node, container_nid: str, enclosing_value: str) -> None:
171
+ t = node.type
172
+
173
+ if t == "open_module":
174
+ mp = next((c for c in node.children if c.type == "module_path"), None)
175
+ name = last_name(mp) if mp is not None else None
176
+ if name:
177
+ add_edge(container_nid, ref_stub(name), "imports_from", line_of(node),
178
+ confidence="INFERRED")
179
+ return
180
+
181
+ if t == "module_definition":
182
+ binding = next((c for c in node.children if c.type == "module_binding"), None)
183
+ if binding is not None:
184
+ mname = named_child_text(binding, "module_name")
185
+ if mname:
186
+ line = line_of(node)
187
+ mnid = _make_id(stem, mname)
188
+ add_node(mnid, mname, line)
189
+ add_edge(container_nid, mnid,
190
+ "defines" if container_nid == file_nid else "contains", line)
191
+ register_def(mname, mnid)
192
+ local_modules.add(mname)
193
+ for child in binding.children:
194
+ walk(child, mnid, enclosing_value)
195
+ return
196
+
197
+ if t == "module_type_definition": # .mli
198
+ mname = named_child_text(node, "module_type_name")
199
+ if mname:
200
+ line = line_of(node)
201
+ mnid = _make_id(stem, mname)
202
+ add_node(mnid, mname, line)
203
+ add_edge(container_nid, mnid,
204
+ "defines" if container_nid == file_nid else "contains", line)
205
+ register_def(mname, mnid)
206
+ local_modules.add(mname)
207
+ for child in node.children:
208
+ walk(child, mnid, enclosing_value)
209
+ return
210
+
211
+ if t == "value_definition": # .ml
212
+ # Only structure/top-level bindings are real definitions. A local
213
+ # `let x = e in body` is also a value_definition, but nested under a
214
+ # let_expression; it must NOT mint a node or steal call attribution
215
+ # from the enclosing named function.
216
+ is_toplevel = node.parent is not None and node.parent.type in (
217
+ "compilation_unit", "structure")
218
+ for lb in node.children:
219
+ if lb.type != "let_binding":
220
+ continue
221
+ vname = named_child_text(lb, "value_name")
222
+ new_scope = enclosing_value
223
+ if vname and is_toplevel:
224
+ line = line_of(lb)
225
+ nid = _make_id(stem, vname)
226
+ add_node(nid, vname, line)
227
+ add_edge(container_nid, nid,
228
+ "defines" if container_nid == file_nid else "contains", line)
229
+ register_def(vname, nid)
230
+ new_scope = nid
231
+ # Descend into the body (unit/pattern/local bindings keep the
232
+ # outer scope) to collect nested definitions and call sites.
233
+ for child in lb.children:
234
+ walk(child, container_nid, new_scope)
235
+ return
236
+
237
+ if t == "value_specification": # .mli
238
+ vname = named_child_text(node, "value_name")
239
+ if vname:
240
+ line = line_of(node)
241
+ nid = _make_id(stem, vname)
242
+ add_node(nid, vname, line)
243
+ add_edge(container_nid, nid,
244
+ "defines" if container_nid == file_nid else "contains", line)
245
+ register_def(vname, nid)
246
+ return
247
+
248
+ if t == "type_definition":
249
+ for binding in node.children:
250
+ if binding.type == "type_binding":
251
+ emit_type(binding, container_nid)
252
+ return
253
+
254
+ if t == "application_expression":
255
+ fn = node.named_children[0] if node.named_children else None
256
+ if fn is not None and fn.type == "value_path":
257
+ callee = last_name(fn)
258
+ if callee:
259
+ caller = enclosing_value if enclosing_value else file_nid
260
+ call_sites.append((caller, callee, path_root_module(fn),
261
+ _read_text(fn, source), line_of(node)))
262
+ # Fall through: arguments may contain further applications/definitions.
263
+
264
+ for child in node.children:
265
+ walk(child, container_nid, enclosing_value)
266
+
267
+ walk(root, file_nid, "")
268
+
269
+ for caller, callee, qualifier, full_path, line in call_sites:
270
+ # A qualified call `M.f` where `M` is NOT a module defined in this file
271
+ # is an external-library call (e.g. `Reg_spec.create`). Binding it to a
272
+ # same-named local `f` would be a false edge (and a `create -> create`
273
+ # self-loop when the caller is that local `f`), so keep it distinct: a
274
+ # stub keyed by the FULL qualified name (`Reg_spec.create`) never
275
+ # collapses onto the local `f` in the corpus rewire. Unqualified calls,
276
+ # and qualified calls into a locally-defined module, still resolve to a
277
+ # local definition; a bare-name stub still allows the cross-file rewire
278
+ # to collapse `Geo.area` onto another file's `area` (#hardcaml).
279
+ if qualifier is not None and qualifier not in local_modules:
280
+ if callee in local_defs:
281
+ add_edge(caller, ref_stub(full_path), "calls", line, confidence="INFERRED")
282
+ else:
283
+ add_edge(caller, ref_stub(callee), "calls", line, confidence="INFERRED")
284
+ elif callee in local_defs:
285
+ add_edge(caller, local_defs[callee], "calls", line)
286
+ else:
287
+ add_edge(caller, ref_stub(callee), "calls", line, confidence="INFERRED")
288
+
289
+ return {"nodes": nodes, "edges": edges}