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,527 @@
1
+ """Go extractor. Moved verbatim from graphify/extract.py."""
2
+ from __future__ import annotations
3
+
4
+
5
+ import hashlib
6
+ from pathlib import Path
7
+ from graphify.extractors.base import _LANGUAGE_BUILTIN_GLOBALS, _file_stem, _make_id, _read_text
8
+
9
+
10
+ _GO_PREDECLARED_TYPES = frozenset({
11
+ "bool", "byte", "complex64", "complex128", "error", "float32", "float64",
12
+ "int", "int8", "int16", "int32", "int64", "rune", "string",
13
+ "uint", "uint8", "uint16", "uint32", "uint64", "uintptr", "any", "comparable",
14
+ })
15
+
16
+ # Go predeclared functions, filtered only when the callee is a BARE identifier.
17
+ # The Go resolver looks a callee up by name, so an unexported method that happens
18
+ # to share a builtin's name (`func (h *history) append(...)`) absorbs every
19
+ # builtin call in the corpus: on an 8.9k-node Go codebase one such method
20
+ # collected 330 phantom inbound `calls` edges, inventing twelve database-layer ->
21
+ # service-layer edges — a layering violation absent from the source.
22
+ #
23
+ # Deliberately language-local (mirroring _RUST_TRAIT_METHOD_BLOCKLIST) rather
24
+ # than added to the shared _LANGUAGE_BUILTIN_GLOBALS: `new`, `close` and friends
25
+ # are ordinary method names in the ~11 other languages that consult the shared
26
+ # set — listing them there kills every in-file Rust `Type::new()` edge.
27
+ #
28
+ # Bare-identifier-only for the same reason within Go: `h.append(v)` and
29
+ # `pkg.Delete(x)` are selector_expression callees and are genuine calls, so the
30
+ # filter must not reach them. Builtin *types* stay out (see
31
+ # _GO_PREDECLARED_TYPES): Go conversions are call-shaped too, but they produced
32
+ # no phantom edges on that corpus and filtering them would suppress genuine
33
+ # constructor-like calls.
34
+ #
35
+ # The set is the Go spec's predeclared function list in full. Being Go-local and
36
+ # bare-identifier-only makes completeness safe here: `len`, `max`, `min` and
37
+ # `print` carry the same shadowing hazard as `append`, and a principled boundary
38
+ # (the spec list) beats a hand-picked subset.
39
+ _GO_PREDECLARED_FUNCS = frozenset({
40
+ "append", "cap", "clear", "close", "complex", "copy", "delete", "imag",
41
+ "len", "make", "max", "min", "new", "panic", "print", "println", "real",
42
+ "recover",
43
+ })
44
+
45
+ def _go_collect_type_refs(node, source: bytes, generic: bool, out: list[tuple[str, str]]) -> None:
46
+ """Walk a Go type expression; append (name, role) tuples."""
47
+ if node is None:
48
+ return
49
+ t = node.type
50
+ if t == "type_identifier":
51
+ text = _read_text(node, source)
52
+ if text and text not in _GO_PREDECLARED_TYPES:
53
+ out.append((text, "generic_arg" if generic else "type"))
54
+ return
55
+ if t == "qualified_type":
56
+ # Keep the package qualifier so the generic stub rewire cannot attach
57
+ # `testing.T` to an unrelated local type or function named T.
58
+ text = _read_text(node, source)
59
+ if text:
60
+ out.append((text, "generic_arg" if generic else "type"))
61
+ return
62
+ if t == "generic_type":
63
+ type_field = node.child_by_field_name("type")
64
+ if type_field is not None:
65
+ sub: list[tuple[str, str]] = []
66
+ _go_collect_type_refs(type_field, source, generic, sub)
67
+ out.extend(sub)
68
+ for c in node.children:
69
+ if c.type == "type_arguments":
70
+ for arg in c.children:
71
+ if arg.is_named:
72
+ _go_collect_type_refs(arg, source, True, out)
73
+ return
74
+ if t in ("pointer_type", "slice_type", "array_type", "map_type",
75
+ "channel_type", "parenthesized_type"):
76
+ for c in node.children:
77
+ if c.is_named:
78
+ _go_collect_type_refs(c, source, generic, out)
79
+ return
80
+ if node.is_named:
81
+ for c in node.children:
82
+ if c.is_named:
83
+ _go_collect_type_refs(c, source, generic, out)
84
+
85
+ def extract_go(path: Path) -> dict:
86
+ """Extract functions, methods, type declarations, and imports from a .go file."""
87
+ try:
88
+ import tree_sitter_go as tsgo
89
+ from tree_sitter import Language, Parser
90
+ except ImportError:
91
+ return {"nodes": [], "edges": [], "error": "tree-sitter-go not installed"}
92
+
93
+ try:
94
+ language = Language(tsgo.language())
95
+ parser = Parser(language)
96
+ source = path.read_bytes()
97
+ tree = parser.parse(source)
98
+ root = tree.root_node
99
+ except Exception as e:
100
+ return {"nodes": [], "edges": [], "error": str(e)}
101
+
102
+ stem = _file_stem(path)
103
+ # Use directory name as package scope so methods on the same type across
104
+ # multiple files in a package share one canonical type node.
105
+ pkg_scope = path.parent.name or stem
106
+ str_path = str(path)
107
+ nodes: list[dict] = []
108
+ edges: list[dict] = []
109
+ seen_ids: set[str] = set()
110
+ function_bodies: list[tuple[str, object]] = []
111
+ # local package name (including aliases) -> written Go import path
112
+ go_imported_pkgs: dict[str, str] = {}
113
+
114
+ def add_node(nid: str, label: str, line: int) -> None:
115
+ if nid not in seen_ids:
116
+ seen_ids.add(nid)
117
+ nodes.append({
118
+ "id": nid,
119
+ "label": label,
120
+ "file_type": "code",
121
+ "source_file": str_path,
122
+ "source_location": f"L{line}",
123
+ })
124
+
125
+ def add_edge(src: str, tgt: str, relation: str, line: int,
126
+ confidence: str = "EXTRACTED", weight: float = 1.0,
127
+ context: str | None = None) -> None:
128
+ edge = {
129
+ "source": src,
130
+ "target": tgt,
131
+ "relation": relation,
132
+ "confidence": confidence,
133
+ "source_file": str_path,
134
+ "source_location": f"L{line}",
135
+ "weight": weight,
136
+ }
137
+ if context:
138
+ edge["context"] = context
139
+ edges.append(edge)
140
+
141
+ file_nid = _make_id(str(path))
142
+ add_node(file_nid, path.name, 1)
143
+
144
+ def ensure_named_node(name: str, line: int) -> str:
145
+ nid = _make_id(pkg_scope, name)
146
+ if nid in seen_ids:
147
+ return nid
148
+ nid = _make_id(name)
149
+ if nid not in seen_ids:
150
+ # The name isn't declared in this file, so this is a cross-file reference
151
+ # (e.g. a type defined in another file of the package). Emit a SOURCELESS
152
+ # stub — like the inheritance-base path in the other extractors — so the
153
+ # corpus-level rewire can collapse it onto the real definition. A sourced
154
+ # stub here makes _disambiguate_colliding_node_ids bake the referencing
155
+ # file's path (with extension) into the id and blocks the rewire, which is
156
+ # the phantom-duplicate-node bug (#1402).
157
+ seen_ids.add(nid)
158
+ nodes.append({
159
+ "id": nid,
160
+ "label": name,
161
+ "file_type": "code",
162
+ "source_file": "",
163
+ "source_location": "",
164
+ "origin_file": str_path,
165
+ })
166
+ return nid
167
+
168
+ def emit_go_method_refs(func_node, func_nid: str, line: int) -> None:
169
+ params = func_node.child_by_field_name("parameters")
170
+ if params is not None:
171
+ for p in params.children:
172
+ if p.type != "parameter_declaration":
173
+ continue
174
+ type_node = p.child_by_field_name("type")
175
+ refs: list[tuple[str, str]] = []
176
+ _go_collect_type_refs(type_node, source, False, refs)
177
+ for ref_name, role in refs:
178
+ ctx = "generic_arg" if role == "generic_arg" else "parameter_type"
179
+ tgt = ensure_named_node(ref_name, line)
180
+ if tgt != func_nid:
181
+ add_edge(func_nid, tgt, "references", line, context=ctx)
182
+ result = func_node.child_by_field_name("result")
183
+ if result is not None:
184
+ if result.type == "parameter_list":
185
+ for p in result.children:
186
+ if p.type != "parameter_declaration":
187
+ continue
188
+ type_node = p.child_by_field_name("type")
189
+ if type_node is None:
190
+ for c in p.children:
191
+ if c.is_named:
192
+ type_node = c
193
+ break
194
+ refs = []
195
+ _go_collect_type_refs(type_node, source, False, refs)
196
+ for ref_name, role in refs:
197
+ ctx = "generic_arg" if role == "generic_arg" else "return_type"
198
+ tgt = ensure_named_node(ref_name, line)
199
+ if tgt != func_nid:
200
+ add_edge(func_nid, tgt, "references", line, context=ctx)
201
+ else:
202
+ refs = []
203
+ _go_collect_type_refs(result, source, False, refs)
204
+ for ref_name, role in refs:
205
+ ctx = "generic_arg" if role == "generic_arg" else "return_type"
206
+ tgt = ensure_named_node(ref_name, line)
207
+ if tgt != func_nid:
208
+ add_edge(func_nid, tgt, "references", line, context=ctx)
209
+
210
+ # Node IDs are casefolded (ids.py), so `Run` and `run` declared in one file
211
+ # produce the same id and add_node silently dropped the second — the unexported
212
+ # half vanished from the graph and its call sites bound by bare name to a
213
+ # same-named function in another package, which Go's visibility rules make
214
+ # impossible (#2779). Salt the non-canonical member of a case-only collision
215
+ # so both survive.
216
+ #
217
+ # The EXPORTED member keeps the plain id. Only exported symbols are reachable
218
+ # across packages, so cross-package edges (and edges cached in graph.json from
219
+ # files an incremental rebuild does not touch) target the exported one — keeping
220
+ # its id stable means adding/removing an unexported sibling in an update never
221
+ # re-points them. Docs likewise reference the exported API, so the casefolded id
222
+ # a semantic node produces lands on the symbol it actually describes. Calls to
223
+ # the unexported sibling can only come from the same package and only resolve
224
+ # once the sibling exists, so salting it re-points nothing. When the collision
225
+ # has no unique exported member (`Run`/`RUN`), every member is salted rather
226
+ # than picking one arbitrarily, so the result never depends on declaration order.
227
+ case_groups: dict[str, set[str]] = {}
228
+
229
+ def _receiver_type_of(node) -> str | None:
230
+ receiver = node.child_by_field_name("receiver")
231
+ if not receiver:
232
+ return None
233
+ for param in receiver.children:
234
+ if param.type == "parameter_declaration":
235
+ type_node = param.child_by_field_name("type")
236
+ if type_node:
237
+ return _read_text(type_node, source).lstrip("*").strip()
238
+ break
239
+ return None
240
+
241
+ def _plain_symbol_nid(node) -> tuple[str, str] | None:
242
+ name_node = node.child_by_field_name("name")
243
+ if not name_node:
244
+ return None
245
+ name = _read_text(name_node, source)
246
+ if node.type == "method_declaration":
247
+ receiver_type = _receiver_type_of(node)
248
+ base = _make_id(pkg_scope, receiver_type) if receiver_type else stem
249
+ else:
250
+ base = stem
251
+ return _make_id(base, name), name
252
+
253
+ def _scan_declarations(node) -> None:
254
+ if node.type in ("function_declaration", "method_declaration"):
255
+ found = _plain_symbol_nid(node)
256
+ if found:
257
+ case_groups.setdefault(found[0], set()).add(found[1])
258
+ return
259
+ for child in node.children:
260
+ _scan_declarations(child)
261
+
262
+ def symbol_nid(plain_nid: str, name: str) -> str:
263
+ names = case_groups.get(plain_nid) or set()
264
+ if len(names) < 2:
265
+ return plain_nid
266
+ exported = [n for n in names if n[:1].isupper()]
267
+ if len(exported) == 1 and name == exported[0]:
268
+ return plain_nid
269
+ salt = hashlib.sha1(name.encode("utf-8"), usedforsecurity=False).hexdigest()[:6]
270
+ return _make_id(plain_nid, salt)
271
+
272
+ def walk(node) -> None:
273
+ t = node.type
274
+
275
+ if t == "function_declaration":
276
+ name_node = node.child_by_field_name("name")
277
+ if name_node:
278
+ func_name = _read_text(name_node, source)
279
+ line = node.start_point[0] + 1
280
+ func_nid = symbol_nid(_make_id(stem, func_name), func_name)
281
+ add_node(func_nid, f"{func_name}()", line)
282
+ add_edge(file_nid, func_nid, "contains", line)
283
+ emit_go_method_refs(node, func_nid, line)
284
+ body = node.child_by_field_name("body")
285
+ if body:
286
+ function_bodies.append((func_nid, body))
287
+ return
288
+
289
+ if t == "method_declaration":
290
+ receiver = node.child_by_field_name("receiver")
291
+ receiver_type: str | None = None
292
+ if receiver:
293
+ for param in receiver.children:
294
+ if param.type == "parameter_declaration":
295
+ type_node = param.child_by_field_name("type")
296
+ if type_node:
297
+ receiver_type = _read_text(type_node, source).lstrip("*").strip()
298
+ break
299
+ name_node = node.child_by_field_name("name")
300
+ if not name_node:
301
+ return
302
+ method_name = _read_text(name_node, source)
303
+ line = node.start_point[0] + 1
304
+
305
+ if receiver_type:
306
+ parent_nid = _make_id(pkg_scope, receiver_type)
307
+ add_node(parent_nid, receiver_type, line)
308
+ method_nid = symbol_nid(_make_id(parent_nid, method_name), method_name)
309
+ add_node(method_nid, f".{method_name}()", line)
310
+ add_edge(parent_nid, method_nid, "method", line)
311
+ else:
312
+ method_nid = symbol_nid(_make_id(stem, method_name), method_name)
313
+ add_node(method_nid, f"{method_name}()", line)
314
+ add_edge(file_nid, method_nid, "contains", line)
315
+
316
+ emit_go_method_refs(node, method_nid, line)
317
+ body = node.child_by_field_name("body")
318
+ if body:
319
+ function_bodies.append((method_nid, body))
320
+ return
321
+
322
+ if t == "type_declaration":
323
+ for child in node.children:
324
+ if child.type != "type_spec":
325
+ continue
326
+ name_node = child.child_by_field_name("name")
327
+ if not name_node:
328
+ continue
329
+ type_name = _read_text(name_node, source)
330
+ line = child.start_point[0] + 1
331
+ type_nid = _make_id(pkg_scope, type_name)
332
+ add_node(type_nid, type_name, line)
333
+ add_edge(file_nid, type_nid, "contains", line)
334
+ # Type body: struct fields (with embeds) or interface embedding.
335
+ type_body = None
336
+ for tc in child.children:
337
+ if tc.type in ("struct_type", "interface_type"):
338
+ type_body = tc
339
+ break
340
+ if type_body is None:
341
+ continue
342
+ if type_body.type == "struct_type":
343
+ for fdl in type_body.children:
344
+ if fdl.type != "field_declaration_list":
345
+ continue
346
+ for field in fdl.children:
347
+ if field.type != "field_declaration":
348
+ continue
349
+ has_name = any(
350
+ fc.type == "field_identifier" for fc in field.children
351
+ )
352
+ type_node = field.child_by_field_name("type")
353
+ if type_node is None:
354
+ for fc in field.children:
355
+ if fc.is_named and fc.type != "field_identifier":
356
+ type_node = fc
357
+ break
358
+ refs: list[tuple[str, str]] = []
359
+ _go_collect_type_refs(type_node, source, False, refs)
360
+ for ref_name, role in refs:
361
+ tgt = ensure_named_node(ref_name, field.start_point[0] + 1)
362
+ if tgt == type_nid:
363
+ continue
364
+ if not has_name and role == "type":
365
+ add_edge(type_nid, tgt, "embeds",
366
+ field.start_point[0] + 1)
367
+ else:
368
+ ctx = "generic_arg" if role == "generic_arg" else "field"
369
+ add_edge(type_nid, tgt, "references",
370
+ field.start_point[0] + 1, context=ctx)
371
+ elif type_body.type == "interface_type":
372
+ for elem in type_body.children:
373
+ if elem.type != "type_elem":
374
+ continue
375
+ # A type_elem that is a generics type-set constraint -
376
+ # a union (`A | B`) or an approximation (`~T`) - is NOT
377
+ # interface embedding. Go only embeds a lone interface
378
+ # type; union/approximation terms can never be embedded,
379
+ # so they must not emit `embeds` heritage edges. Keep the
380
+ # type link as a `references` (type_constraint) edge.
381
+ is_type_set = any(
382
+ c.type == "|" or c.type == "negated_type"
383
+ for c in elem.children
384
+ )
385
+ refs = []
386
+ for sub in elem.children:
387
+ if sub.is_named:
388
+ _go_collect_type_refs(sub, source, False, refs)
389
+ for ref_name, role in refs:
390
+ tgt = ensure_named_node(ref_name, elem.start_point[0] + 1)
391
+ if tgt == type_nid:
392
+ continue
393
+ if is_type_set:
394
+ add_edge(type_nid, tgt, "references",
395
+ elem.start_point[0] + 1, context="type_constraint")
396
+ elif role == "type":
397
+ add_edge(type_nid, tgt, "embeds",
398
+ elem.start_point[0] + 1)
399
+ else:
400
+ add_edge(type_nid, tgt, "references",
401
+ elem.start_point[0] + 1, context="generic_arg")
402
+ return
403
+
404
+ if t == "import_declaration":
405
+ for child in node.children:
406
+ if child.type == "import_spec_list":
407
+ for spec in child.children:
408
+ if spec.type == "import_spec":
409
+ path_node = spec.child_by_field_name("path")
410
+ if path_node:
411
+ raw = _read_text(path_node, source).strip('"')
412
+ # Prefix with go_pkg_ so stdlib names (e.g. "context")
413
+ # don't collide with local files of the same basename.
414
+ tgt_nid = _make_id("go", "pkg", raw)
415
+ add_edge(file_nid, tgt_nid, "imports_from", spec.start_point[0] + 1, context="import")
416
+ # Track local name (alias or last path segment)
417
+ alias = spec.child_by_field_name("name")
418
+ local_name = _read_text(alias, source) if alias else raw.split("/")[-1]
419
+ if local_name and local_name != "_" and local_name != ".":
420
+ go_imported_pkgs[local_name] = raw
421
+ elif child.type == "import_spec":
422
+ path_node = child.child_by_field_name("path")
423
+ if path_node:
424
+ raw = _read_text(path_node, source).strip('"')
425
+ tgt_nid = _make_id("go", "pkg", raw)
426
+ add_edge(file_nid, tgt_nid, "imports_from", child.start_point[0] + 1, context="import")
427
+ alias = child.child_by_field_name("name")
428
+ local_name = _read_text(alias, source) if alias else raw.split("/")[-1]
429
+ if local_name and local_name != "_" and local_name != ".":
430
+ go_imported_pkgs[local_name] = raw
431
+ return
432
+
433
+ for child in node.children:
434
+ walk(child)
435
+
436
+ _scan_declarations(root)
437
+ walk(root)
438
+
439
+ label_to_nid: dict[str, str] = {}
440
+ for n in nodes:
441
+ raw = n["label"]
442
+ normalised = raw.strip("()").lstrip(".")
443
+ label_to_nid[normalised] = n["id"]
444
+
445
+ seen_call_pairs: set[tuple[str, str]] = set()
446
+ raw_calls: list[dict] = []
447
+
448
+ def walk_calls(node, caller_nid: str) -> None:
449
+ if node.type in ("function_declaration", "method_declaration"):
450
+ return
451
+ if node.type == "call_expression":
452
+ func_node = node.child_by_field_name("function")
453
+ callee_name: str | None = None
454
+ is_member_call: bool = False
455
+ is_bare_identifier: bool = False
456
+ package_receiver: str | None = None
457
+ import_path: str | None = None
458
+ if func_node:
459
+ if func_node.type == "identifier":
460
+ is_bare_identifier = True
461
+ callee_name = _read_text(func_node, source)
462
+ elif func_node.type == "selector_expression":
463
+ field = func_node.child_by_field_name("field")
464
+ operand = func_node.child_by_field_name("operand")
465
+ receiver_name = _read_text(operand, source) if operand else ""
466
+ # Package-qualified call (e.g. fmt.Println) → allow cross-file resolution.
467
+ # Receiver method call (e.g. s.logger.Log) → skip, no import evidence.
468
+ is_member_call = receiver_name not in go_imported_pkgs
469
+ if not is_member_call:
470
+ package_receiver = receiver_name
471
+ import_path = go_imported_pkgs[receiver_name]
472
+ if field:
473
+ callee_name = _read_text(field, source)
474
+ if is_bare_identifier and callee_name in _GO_PREDECLARED_FUNCS:
475
+ # A bare `append(s, x)` is the builtin, never the same-named
476
+ # method a sibling file happens to declare. Skipping before both
477
+ # branches drops the in-file phantom edge and keeps the name out
478
+ # of raw_calls, so the cross-file pass cannot bind it either.
479
+ callee_name = None
480
+ if callee_name and callee_name not in _LANGUAGE_BUILTIN_GLOBALS:
481
+ # Never resolve an imported selector through a bare local name.
482
+ tgt_nid = None if import_path else label_to_nid.get(callee_name)
483
+ if tgt_nid and tgt_nid != caller_nid:
484
+ pair = (caller_nid, tgt_nid)
485
+ if pair not in seen_call_pairs:
486
+ seen_call_pairs.add(pair)
487
+ line = node.start_point[0] + 1
488
+ edges.append({
489
+ "source": caller_nid,
490
+ "target": tgt_nid,
491
+ "relation": "calls",
492
+ "context": "call",
493
+ "confidence": "EXTRACTED",
494
+ "source_file": str_path,
495
+ "source_location": f"L{line}",
496
+ "weight": 1.0,
497
+ })
498
+ elif callee_name:
499
+ raw_calls.append({
500
+ "caller_nid": caller_nid,
501
+ "callee": callee_name,
502
+ "is_member_call": is_member_call,
503
+ "language": "go",
504
+ "receiver": package_receiver,
505
+ "import_path": import_path,
506
+ "source_file": str_path,
507
+ "source_location": f"L{node.start_point[0] + 1}",
508
+ })
509
+ for child in node.children:
510
+ walk_calls(child, caller_nid)
511
+
512
+ for caller_nid, body_node in function_bodies:
513
+ walk_calls(body_node, caller_nid)
514
+
515
+ valid_ids = seen_ids
516
+ clean_edges = []
517
+ for edge in edges:
518
+ src, tgt = edge["source"], edge["target"]
519
+ if src in valid_ids and (tgt in valid_ids or edge["relation"] in ("imports", "imports_from")):
520
+ clean_edges.append(edge)
521
+
522
+ return {
523
+ "nodes": nodes,
524
+ "edges": clean_edges,
525
+ "raw_calls": raw_calls,
526
+ "go_imports": dict(go_imported_pkgs),
527
+ }