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,311 @@
1
+ """Fortran 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 _file_stem, _make_id, _read_text
7
+
8
+
9
+ _FORTRAN_CPP_EXTS = {".F", ".F90", ".F95", ".F03", ".F08"}
10
+
11
+ def _cpp_preprocess(path: Path) -> bytes:
12
+ """Run cpp -w -P on a capital-F Fortran file and return preprocessed bytes.
13
+
14
+ Falls back to raw file bytes if cpp is not available. Capital-F extensions
15
+ conventionally require C preprocessor expansion (#ifdef MPI, #define REAL8, etc.)
16
+ before parsing.
17
+
18
+ Security (F-007): we pass `-nostdinc` and `-I /dev/null` so a malicious
19
+ source file containing `#include "/home/victim/.ssh/id_rsa"` (or any other
20
+ include directive) cannot inline arbitrary host files into the output that
21
+ we then ship to an LLM. Without these flags `cpp` happily resolves any
22
+ relative or absolute include path it can read, which is a corpus-side
23
+ file-exfiltration vector.
24
+ """
25
+ import shutil
26
+ import subprocess
27
+ if not shutil.which("cpp"):
28
+ return path.read_bytes()
29
+ try:
30
+ # Pass an absolute path so a corpus file named like "-I/etc/x.F90" cannot
31
+ # be parsed by cpp as an option (cpp does not accept a "--" end-of-options
32
+ # terminator). What matters is that an absolute path cannot begin with
33
+ # "-" — not that it begins with "/", which only holds on POSIX (a Windows
34
+ # absolute path starts with a drive letter, and is equally safe).
35
+ result = subprocess.run(
36
+ ["cpp", "-w", "-P", "-nostdinc", "-I", "/dev/null", str(path.resolve())],
37
+ capture_output=True,
38
+ timeout=30,
39
+ )
40
+ if result.returncode == 0 and result.stdout:
41
+ return result.stdout
42
+ except Exception:
43
+ pass
44
+ return path.read_bytes()
45
+
46
+ def extract_fortran(path: Path) -> dict:
47
+ """Extract programs, modules, subroutines, functions, use statements, and calls from Fortran files.
48
+
49
+ Capital-F extensions (.F, .F90, etc.) are run through the C preprocessor before
50
+ parsing so #ifdef/#define macros are resolved.
51
+ """
52
+ try:
53
+ import tree_sitter_fortran as tsfortran
54
+ from tree_sitter import Language, Parser
55
+ except ImportError:
56
+ return {"nodes": [], "edges": [], "error": "tree-sitter-fortran not installed"}
57
+
58
+ try:
59
+ language = Language(tsfortran.language())
60
+ parser = Parser(language)
61
+ source = _cpp_preprocess(path) if path.suffix in _FORTRAN_CPP_EXTS else path.read_bytes()
62
+ tree = parser.parse(source)
63
+ root = tree.root_node
64
+ except Exception as e:
65
+ return {"nodes": [], "edges": [], "error": str(e)}
66
+
67
+ stem = _file_stem(path)
68
+ str_path = str(path)
69
+ nodes: list[dict] = []
70
+ edges: list[dict] = []
71
+ seen_ids: set[str] = set()
72
+ scope_bodies: list[tuple[str, object]] = []
73
+
74
+ def add_node(nid: str, label: str, line: int) -> None:
75
+ if nid not in seen_ids:
76
+ seen_ids.add(nid)
77
+ nodes.append({
78
+ "id": nid,
79
+ "label": label,
80
+ "file_type": "code",
81
+ "source_file": str_path,
82
+ "source_location": f"L{line}",
83
+ })
84
+
85
+ def add_edge(src: str, tgt: str, relation: str, line: int,
86
+ confidence: str = "EXTRACTED", weight: float = 1.0,
87
+ context: str | None = None) -> None:
88
+ edge = {
89
+ "source": src,
90
+ "target": tgt,
91
+ "relation": relation,
92
+ "confidence": confidence,
93
+ "source_file": str_path,
94
+ "source_location": f"L{line}",
95
+ "weight": weight,
96
+ }
97
+ if context:
98
+ edge["context"] = context
99
+ edges.append(edge)
100
+
101
+ file_nid = _make_id(str(path))
102
+ add_node(file_nid, path.name, 1)
103
+
104
+ def _fortran_name(stmt_node) -> str | None:
105
+ """Extract name from a *_statement node. Fortran is case-insensitive; lowercase."""
106
+ for child in stmt_node.children:
107
+ if child.type in ("name", "identifier"):
108
+ return _read_text(child, source).lower()
109
+ return None
110
+
111
+ def ensure_named_node(name: str, line: int) -> str:
112
+ nid = _make_id(stem, name)
113
+ if nid in seen_ids:
114
+ return nid
115
+ nid = _make_id(name)
116
+ if nid not in seen_ids:
117
+ # The name isn't defined in this file, so this is a cross-file reference
118
+ # (e.g. a `Thing` type annotation imported from another module). Emit a
119
+ # SOURCELESS stub — like the inheritance-base path below — so the
120
+ # corpus-level rewire can collapse it onto the real definition. A sourced
121
+ # stub here makes _disambiguate_colliding_node_ids bake the referencing
122
+ # file's path (with extension) into the id and blocks the rewire, which is
123
+ # the phantom-duplicate-node bug (#1402).
124
+ seen_ids.add(nid)
125
+ nodes.append({
126
+ "id": nid,
127
+ "label": name,
128
+ "file_type": "code",
129
+ "source_file": "",
130
+ "source_location": "",
131
+ "origin_file": str_path,
132
+ })
133
+ return nid
134
+
135
+ def emit_signature_refs(scope_node, fn_nid: str, is_function: bool) -> None:
136
+ """Emit references[parameter_type] / references[return_type] edges for
137
+ a subroutine/function based on its variable_declaration siblings."""
138
+ stmt_type = "function_statement" if is_function else "subroutine_statement"
139
+ stmt = next((c for c in scope_node.children if c.type == stmt_type), None)
140
+ if stmt is None:
141
+ return
142
+ param_names: set[str] = set()
143
+ params_node = next((c for c in stmt.children if c.type == "parameters"), None)
144
+ if params_node is not None:
145
+ for c in params_node.children:
146
+ if c.type == "identifier":
147
+ param_names.add(_read_text(c, source).lower())
148
+ result_name: str | None = None
149
+ if is_function:
150
+ result_node = next((c for c in stmt.children if c.type == "function_result"), None)
151
+ if result_node is not None:
152
+ res_id = next((c for c in result_node.children if c.type == "identifier"), None)
153
+ if res_id is not None:
154
+ result_name = _read_text(res_id, source).lower()
155
+ else:
156
+ # implicit result variable: same name as the function
157
+ result_name = _fortran_name(stmt)
158
+ for child in scope_node.children:
159
+ if child.type != "variable_declaration":
160
+ continue
161
+ derived = next((c for c in child.children if c.type == "derived_type"), None)
162
+ if derived is None:
163
+ continue
164
+ type_name_node = next((c for c in derived.children if c.type == "type_name"), None)
165
+ if type_name_node is None:
166
+ continue
167
+ type_name = _read_text(type_name_node, source).lower()
168
+ for var in child.children:
169
+ if var.type != "identifier":
170
+ continue
171
+ var_name = _read_text(var, source).lower()
172
+ var_line = var.start_point[0] + 1
173
+ if var_name in param_names:
174
+ tgt = ensure_named_node(type_name, var_line)
175
+ if tgt != fn_nid:
176
+ add_edge(fn_nid, tgt, "references", var_line, context="parameter_type")
177
+ elif is_function and var_name == result_name:
178
+ tgt = ensure_named_node(type_name, var_line)
179
+ if tgt != fn_nid:
180
+ add_edge(fn_nid, tgt, "references", var_line, context="return_type")
181
+
182
+ def walk_calls(node, scope_nid: str) -> None:
183
+ if node is None:
184
+ return
185
+ t = node.type
186
+ if t in ("subroutine", "function", "module", "program", "internal_procedures"):
187
+ return
188
+ # call FOO(args) — tree-sitter-fortran uses subroutine_call
189
+ if t == "subroutine_call":
190
+ name_node = next((c for c in node.children if c.type == "identifier"), None)
191
+ if name_node:
192
+ callee = _read_text(name_node, source).lower()
193
+ target_nid = _make_id(stem, callee)
194
+ add_edge(scope_nid, target_nid, "calls", node.start_point[0] + 1,
195
+ confidence="EXTRACTED", context="call")
196
+ # x = compute(args) — function invocations are `call_expression`, which
197
+ # shares Fortran's `name(...)` syntax with array indexing. Only emit a
198
+ # call edge when the callee resolves to a procedure defined in this file
199
+ # (an array variable produces no matching node), so array accesses can't
200
+ # fabricate spurious `calls` edges.
201
+ elif t == "call_expression":
202
+ name_node = next((c for c in node.children if c.type == "identifier"), None)
203
+ if name_node:
204
+ callee = _read_text(name_node, source).lower()
205
+ target_nid = _make_id(stem, callee)
206
+ if target_nid in seen_ids and target_nid != scope_nid:
207
+ add_edge(scope_nid, target_nid, "calls", node.start_point[0] + 1,
208
+ confidence="EXTRACTED", context="call")
209
+ for child in node.children:
210
+ walk_calls(child, scope_nid)
211
+
212
+ def walk(node, scope_nid: str) -> None:
213
+ t = node.type
214
+
215
+ if t == "program":
216
+ stmt = next((c for c in node.children if c.type == "program_statement"), None)
217
+ name = _fortran_name(stmt) if stmt else None
218
+ if name:
219
+ nid = _make_id(stem, name)
220
+ line = node.start_point[0] + 1
221
+ add_node(nid, name, line)
222
+ add_edge(file_nid, nid, "defines", line)
223
+ scope_bodies.append((nid, node))
224
+ for child in node.children:
225
+ walk(child, nid)
226
+ return
227
+
228
+ if t == "module":
229
+ stmt = next((c for c in node.children if c.type == "module_statement"), None)
230
+ name = _fortran_name(stmt) if stmt else None
231
+ if name:
232
+ nid = _make_id(stem, name)
233
+ line = node.start_point[0] + 1
234
+ add_node(nid, name, line)
235
+ add_edge(file_nid, nid, "defines", line)
236
+ for child in node.children:
237
+ walk(child, nid)
238
+ return
239
+
240
+ # subroutines/functions inside a module live under internal_procedures
241
+ if t == "internal_procedures":
242
+ for child in node.children:
243
+ walk(child, scope_nid)
244
+ return
245
+
246
+ if t == "derived_type_definition":
247
+ stmt = next((c for c in node.children if c.type == "derived_type_statement"), None)
248
+ if stmt is not None:
249
+ name_node = next((c for c in stmt.children if c.type == "type_name"), None)
250
+ if name_node is not None:
251
+ type_name = _read_text(name_node, source).lower()
252
+ type_nid = _make_id(stem, type_name)
253
+ line = node.start_point[0] + 1
254
+ add_node(type_nid, type_name, line)
255
+ add_edge(scope_nid, type_nid, "defines", line)
256
+ return
257
+
258
+ if t == "subroutine":
259
+ stmt = next((c for c in node.children if c.type == "subroutine_statement"), None)
260
+ name = _fortran_name(stmt) if stmt else None
261
+ if name:
262
+ nid = _make_id(stem, name)
263
+ line = node.start_point[0] + 1
264
+ add_node(nid, f"{name}()", line)
265
+ add_edge(scope_nid, nid, "defines", line)
266
+ scope_bodies.append((nid, node))
267
+ emit_signature_refs(node, nid, is_function=False)
268
+ for child in node.children:
269
+ walk(child, nid)
270
+ return
271
+
272
+ if t == "function":
273
+ stmt = next((c for c in node.children if c.type == "function_statement"), None)
274
+ name = _fortran_name(stmt) if stmt else None
275
+ if name:
276
+ nid = _make_id(stem, name)
277
+ line = node.start_point[0] + 1
278
+ add_node(nid, f"{name}()", line)
279
+ add_edge(scope_nid, nid, "defines", line)
280
+ scope_bodies.append((nid, node))
281
+ emit_signature_refs(node, nid, is_function=True)
282
+ for child in node.children:
283
+ walk(child, nid)
284
+ return
285
+
286
+ if t == "use_statement":
287
+ line = node.start_point[0] + 1
288
+ # tree-sitter-fortran uses module_name node for the used module
289
+ name_node = next((c for c in node.children if c.type in ("module_name", "name", "identifier")), None)
290
+ if name_node:
291
+ mod_name = _read_text(name_node, source).lower()
292
+ imp_nid = _make_id(mod_name)
293
+ add_node(imp_nid, mod_name, line)
294
+ add_edge(scope_nid, imp_nid, "imports", line, context="use")
295
+ return
296
+
297
+ for child in node.children:
298
+ walk(child, scope_nid)
299
+
300
+ walk(root, file_nid)
301
+
302
+ _stmt_headers = {
303
+ "subroutine_statement", "function_statement",
304
+ "program_statement", "module_statement",
305
+ }
306
+ for scope_nid, body_node in scope_bodies:
307
+ for child in body_node.children:
308
+ if child.type not in _stmt_headers:
309
+ walk_calls(child, scope_nid)
310
+
311
+ return {"nodes": nodes, "edges": edges}