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,448 @@
1
+ """C# cross-file resolution.
2
+
3
+ The config-driven C# *extractor* (``extract_csharp`` → ``_extract_generic``)
4
+ still lives in ``graphify/extract.py``; per ``extractors/MIGRATION.md`` the
5
+ config-driven languages cannot be ported one-by-one until the shared
6
+ ``_extract_generic`` core moves as its own coordinated batch. This module is
7
+ the C# home for the parts that *are* cleanly separable — today, the cross-file
8
+ type-reference resolver below — and is where ``extract_csharp`` will land when
9
+ the core migration happens.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import html
14
+ from pathlib import Path
15
+
16
+ from graphify.extractors.base import _make_id
17
+
18
+
19
+ def _build_csharp_type_def_index(all_nodes: list[dict]) -> dict[tuple[str, str], str]:
20
+ """Return deterministic ``(namespace, name) -> node_id`` C# type definitions."""
21
+ candidates: dict[tuple[str, str], list[dict]] = {}
22
+ for node in all_nodes:
23
+ if node.get("type") == "namespace":
24
+ continue
25
+ metadata = node.get("metadata") or {}
26
+ if not isinstance(metadata, dict):
27
+ metadata = {}
28
+ if metadata.get("is_nested_type"):
29
+ continue
30
+ nid = node.get("id")
31
+ label = node.get("label")
32
+ if not (isinstance(nid, str) and nid and isinstance(label, str) and label):
33
+ continue
34
+ source_file = node.get("source_file")
35
+ if (
36
+ not isinstance(source_file, str)
37
+ or not source_file.endswith(".cs")
38
+ or node.get("file_type") != "code"
39
+ ):
40
+ continue
41
+ if label.endswith(")") or label.startswith(".") or "." in label:
42
+ continue
43
+ namespace = metadata.get("namespace", "")
44
+ if not isinstance(namespace, str):
45
+ namespace = ""
46
+ candidates.setdefault((namespace, label), []).append(node)
47
+
48
+ return {
49
+ key: sorted(
50
+ nodes,
51
+ key=lambda node: (
52
+ str(node.get("source_file") or ""),
53
+ str(node.get("source_location") or ""),
54
+ str(node.get("id") or ""),
55
+ ),
56
+ )[0]["id"]
57
+ for key, nodes in candidates.items()
58
+ }
59
+
60
+
61
+ def _strip_trailing_csharp_generic_args(target_fqn: str) -> str:
62
+ target_fqn = target_fqn.strip()
63
+ if not target_fqn.endswith(">"):
64
+ return target_fqn
65
+ depth = 0
66
+ for index in range(len(target_fqn) - 1, -1, -1):
67
+ char = target_fqn[index]
68
+ if char == ">":
69
+ depth += 1
70
+ elif char == "<":
71
+ depth -= 1
72
+ if depth == 0:
73
+ return target_fqn[:index].strip()
74
+ return target_fqn
75
+
76
+
77
+ def _resolve_cross_file_csharp_imports(
78
+ per_file: list[dict],
79
+ paths: list[Path],
80
+ all_nodes: list[dict],
81
+ all_edges: list[dict],
82
+ ) -> None:
83
+ """Re-point resolvable C# ``using`` import edges to canonical internal nodes.
84
+
85
+ Namespace imports resolve only to canonical C# namespace nodes. Alias imports
86
+ resolve only when the alias target's prefix is a known canonical namespace and
87
+ the simple type name exists in the shared C# type-definition index. ``using
88
+ static`` and nested type aliases remain deliberate gaps because they need
89
+ member/nested-type modeling beyond this import pass.
90
+ """
91
+ _ = (per_file, paths)
92
+ namespace_id_by_label: dict[str, str] = {}
93
+ for node in sorted(
94
+ all_nodes,
95
+ key=lambda node: (
96
+ str(node.get("source_file") or ""),
97
+ str(node.get("source_location") or ""),
98
+ str(node.get("id") or ""),
99
+ ),
100
+ ):
101
+ if node.get("type") != "namespace":
102
+ continue
103
+ label = node.get("label")
104
+ nid = node.get("id")
105
+ if isinstance(label, str) and label and isinstance(nid, str) and nid:
106
+ namespace_id_by_label.setdefault(label, nid)
107
+
108
+ type_def_index = _build_csharp_type_def_index(all_nodes)
109
+ if not namespace_id_by_label and not type_def_index:
110
+ return
111
+
112
+ repointed_from: set[str] = set()
113
+ for edge in all_edges:
114
+ if edge.get("relation") != "imports":
115
+ continue
116
+ metadata = edge.get("metadata") or {}
117
+ if not isinstance(metadata, dict):
118
+ continue
119
+ using_kind = metadata.get("using_kind")
120
+ target_fqn = metadata.get("target_fqn")
121
+ if not using_kind or not isinstance(target_fqn, str) or not target_fqn:
122
+ continue
123
+
124
+ resolved = None
125
+ if using_kind == "namespace":
126
+ resolved = namespace_id_by_label.get(target_fqn)
127
+ elif using_kind == "alias":
128
+ base_fqn = _strip_trailing_csharp_generic_args(html.unescape(target_fqn))
129
+ prefix, sep, name = base_fqn.rpartition(".")
130
+ if sep and prefix in namespace_id_by_label:
131
+ resolved = type_def_index.get((prefix, name))
132
+
133
+ old_target = edge.get("target")
134
+ if resolved and resolved != old_target:
135
+ edge["target"] = resolved
136
+ if isinstance(old_target, str) and old_target:
137
+ repointed_from.add(old_target)
138
+
139
+ if not repointed_from:
140
+ return
141
+
142
+ still_referenced: set[str] = set()
143
+ for edge in all_edges:
144
+ still_referenced.add(edge.get("source"))
145
+ still_referenced.add(edge.get("target"))
146
+ all_nodes[:] = [
147
+ node for node in all_nodes
148
+ if node.get("id") not in repointed_from or node.get("id") in still_referenced
149
+ ]
150
+
151
+
152
+ _DOTNET_SOURCE_EXTS = (".cs", ".razor", ".cshtml")
153
+
154
+
155
+ def _is_cs_file(value: object) -> bool:
156
+ return isinstance(value, str) and value.endswith(".cs")
157
+
158
+
159
+ def _is_dotnet_source_file(value: object) -> bool:
160
+ return isinstance(value, str) and value.endswith(_DOTNET_SOURCE_EXTS)
161
+
162
+
163
+ def _metadata(value: object) -> dict:
164
+ return value if isinstance(value, dict) else {}
165
+
166
+
167
+ class CsharpNameResolver:
168
+ """Namespace/using/alias-aware C# simple-name resolution.
169
+
170
+ Factored out of ``_resolve_csharp_type_references`` so the member-call pass
171
+ (``_resolve_csharp_member_calls`` in ``graphify/extract.py``) can type a
172
+ receiver with the same scoping rules the type-reference side already applies
173
+ (the core of #1620), instead of bailing on any corpus-wide bare-name clash.
174
+
175
+ Built from graph-stamped facts only: the ``(namespace, name)`` type-definition
176
+ index plus the per-file ``using``/alias import edges (each carrying its lexical
177
+ scope — ``("file", None)`` applies file-wide; ``("namespace", scope_id)``
178
+ applies only where ``scope_id`` is in the referencing node's ``scope_chain``).
179
+ """
180
+
181
+ def __init__(self, all_nodes: list[dict], all_edges: list[dict]) -> None:
182
+ self.node_by_id: dict[str, dict] = {
183
+ node["id"]: node
184
+ for node in all_nodes
185
+ if isinstance(node.get("id"), str) and node.get("id")
186
+ }
187
+ self.type_def_index = _build_csharp_type_def_index(all_nodes)
188
+ self.known_namespaces = {
189
+ node.get("label")
190
+ for node in all_nodes
191
+ if node.get("type") == "namespace" and isinstance(node.get("label"), str)
192
+ }
193
+
194
+ self.namespace_usings_by_file: dict[str, list[tuple[str, str, str | None]]] = {}
195
+ self.aliases_by_file: dict[str, dict[str, list[tuple[str, str, str | None]]]] = {}
196
+
197
+ for edge in all_edges:
198
+ if edge.get("relation") != "imports":
199
+ continue
200
+ source_node = self.node_by_id.get(edge.get("source"))
201
+ if not (
202
+ source_node
203
+ and isinstance(source_node.get("label"), str)
204
+ and source_node.get("label", "").endswith(_DOTNET_SOURCE_EXTS)
205
+ ):
206
+ continue
207
+ source_file = source_node.get("source_file")
208
+ if not _is_dotnet_source_file(source_file):
209
+ continue
210
+ metadata = _metadata(edge.get("metadata"))
211
+ target_fqn = metadata.get("target_fqn")
212
+ if not isinstance(target_fqn, str) or not target_fqn:
213
+ continue
214
+ scope_kind = metadata.get("scope_kind") or "file"
215
+ scope_id = metadata.get("scope_id")
216
+ using_kind = metadata.get("using_kind")
217
+ if using_kind == "namespace":
218
+ entry = (target_fqn, scope_kind, scope_id)
219
+ bucket = self.namespace_usings_by_file.setdefault(source_file, [])
220
+ if entry not in bucket:
221
+ bucket.append(entry)
222
+ elif using_kind == "alias":
223
+ alias = metadata.get("alias")
224
+ if isinstance(alias, str) and alias:
225
+ entry = (target_fqn, scope_kind, scope_id)
226
+ bucket = self.aliases_by_file.setdefault(source_file, {}).setdefault(alias, [])
227
+ if entry not in bucket:
228
+ bucket.append(entry)
229
+
230
+ @staticmethod
231
+ def _namespace(node: dict | None) -> str:
232
+ metadata = _metadata((node or {}).get("metadata"))
233
+ namespace = metadata.get("namespace", "")
234
+ return namespace if isinstance(namespace, str) else ""
235
+
236
+ @staticmethod
237
+ def _scope_chain(node: dict) -> list[str]:
238
+ chain = _metadata(node.get("metadata")).get("scope_chain")
239
+ return chain if isinstance(chain, list) else []
240
+
241
+ def _using_in_scope(self, scope_kind: str, scope_id: str | None, source_node: dict) -> bool:
242
+ if scope_kind == "file":
243
+ return True
244
+ return scope_id is not None and scope_id in self._scope_chain(source_node)
245
+
246
+ def _scopes_for(self, source_node: dict, source_file: str) -> list[str]:
247
+ def _append_unique(items: list[str], value: str) -> None:
248
+ if value not in items:
249
+ items.append(value)
250
+
251
+ scopes: list[str] = []
252
+ _append_unique(scopes, self._namespace(source_node))
253
+ _append_unique(scopes, "")
254
+ for namespace, scope_kind, scope_id in self.namespace_usings_by_file.get(source_file, []):
255
+ if self._using_in_scope(scope_kind, scope_id, source_node):
256
+ _append_unique(scopes, namespace)
257
+ return scopes
258
+
259
+ def _resolve_alias(self, label: str, source_node: dict, source_file: str) -> str | None:
260
+ hits = set()
261
+ for target_fqn, scope_kind, scope_id in self.aliases_by_file.get(source_file, {}).get(label, []):
262
+ if not self._using_in_scope(scope_kind, scope_id, source_node):
263
+ continue
264
+ base_fqn = _strip_trailing_csharp_generic_args(html.unescape(target_fqn))
265
+ namespace, sep, simple_name = base_fqn.rpartition(".")
266
+ if not sep:
267
+ simple_name = namespace
268
+ namespace = ""
269
+ if not simple_name:
270
+ continue
271
+ hit = self.type_def_index.get((namespace, simple_name))
272
+ if hit:
273
+ hits.add(hit)
274
+ return next(iter(hits)) if len(hits) == 1 else None
275
+
276
+ def resolve_type_name(
277
+ self, label: str, source_node: dict, source_file: str
278
+ ) -> tuple[str | None, bool]:
279
+ """Resolve a simple type name to a definition node id, with a verdict.
280
+
281
+ Returns ``(node_id, decisive)``:
282
+ * ``(nid, True)`` — exactly one in-scope definition (or alias target).
283
+ * ``(None, True)`` — the name is claimed by this file's scoping (an
284
+ alias, or >1 in-scope candidates): genuinely ambiguous/unresolvable,
285
+ the caller must NOT fall back to a looser corpus-wide match.
286
+ * ``(None, False)`` — scoping knows nothing about the name; a caller
287
+ may fall back (e.g. to the corpus-wide unique bare-name match).
288
+ """
289
+ if label in self.aliases_by_file.get(source_file, {}):
290
+ return self._resolve_alias(label, source_node, source_file), True
291
+ candidates: list[str] = []
292
+ for namespace in self._scopes_for(source_node, source_file):
293
+ hit = self.type_def_index.get((namespace, label))
294
+ if hit and hit not in candidates:
295
+ candidates.append(hit)
296
+ if len(candidates) == 1:
297
+ return candidates[0], True
298
+ return None, bool(candidates)
299
+
300
+ def resolve_label(self, label: str, source_node: dict, source_file: str) -> str | None:
301
+ resolved, _decisive = self.resolve_type_name(label, source_node, source_file)
302
+ return resolved
303
+
304
+ def resolve_qualified(
305
+ self, label: str, qualifier: object, source_node: dict, source_file: str
306
+ ) -> str | None:
307
+ # Sound qualified resolution: an in-scope alias for Q shadows the namespace Q. For a qualified
308
+ # ref Q.label, look up (alias_target_namespace, label). If no in-scope alias, fall through to an
309
+ # exact known namespace. Dangle on ambiguity / no hit / unknown qualifier.
310
+ if not isinstance(qualifier, str) or not qualifier:
311
+ return None
312
+ in_scope = [
313
+ entry for entry in self.aliases_by_file.get(source_file, {}).get(qualifier, [])
314
+ if self._using_in_scope(entry[1], entry[2], source_node)
315
+ ]
316
+ if in_scope:
317
+ hits = set()
318
+ for target_fqn, _scope_kind, _scope_id in in_scope:
319
+ alias_ns = _strip_trailing_csharp_generic_args(html.unescape(target_fqn))
320
+ hit = self.type_def_index.get((alias_ns, label))
321
+ if hit:
322
+ hits.add(hit)
323
+ return next(iter(hits)) if len(hits) == 1 else None
324
+ if qualifier in self.known_namespaces:
325
+ return self.type_def_index.get((qualifier, label))
326
+ return None
327
+
328
+
329
+ def _resolve_csharp_type_references(
330
+ per_file: list[dict],
331
+ paths: list[Path],
332
+ all_nodes: list[dict],
333
+ all_edges: list[dict],
334
+ ) -> None:
335
+ """Arbitrate all C# ``inherits``/``implements``/``references`` targets.
336
+
337
+ The extractor emits provisional same-file bindings and sourceless stubs. This
338
+ pass is the single soundness gate: it uses only graph-stamped namespace/import
339
+ facts, keeps a binding only when the referenced simple name resolves to one
340
+ in-scope real type definition, and otherwise leaves the edge on a dangling stub.
341
+ """
342
+ _ = (per_file, paths)
343
+
344
+ resolver = CsharpNameResolver(all_nodes, all_edges)
345
+ node_by_id = resolver.node_by_id
346
+ aliases_by_file = resolver.aliases_by_file
347
+
348
+ def _resolve_label(label: str, source_node: dict, source_file: str) -> str | None:
349
+ return resolver.resolve_label(label, source_node, source_file)
350
+
351
+ def _resolve_qualified(label: str, qualifier: object, source_node: dict, source_file: str) -> str | None:
352
+ return resolver.resolve_qualified(label, qualifier, source_node, source_file)
353
+
354
+ def _is_placeholder(node: dict | None) -> bool:
355
+ return bool(node) and not node.get("source_file")
356
+
357
+ def _is_csharp_relevant_target(node: dict) -> bool:
358
+ if node.get("type") == "namespace":
359
+ return True
360
+ source_file = node.get("source_file")
361
+ return not source_file or _is_cs_file(source_file)
362
+
363
+ def _label_for_type_ref_target(target_node: dict, source_file: str) -> str | None:
364
+ label = target_node.get("label")
365
+ if not isinstance(label, str) or not label:
366
+ return None
367
+ if not label.endswith(".cs"):
368
+ return label
369
+
370
+ stem = label[:-3]
371
+ for alias in aliases_by_file.get(source_file, {}):
372
+ if alias.lower() == stem.lower() or _make_id(alias) == _make_id(stem):
373
+ return alias
374
+ return stem or None
375
+
376
+ def _dangling_stub_id(label: str, current_target: object) -> str:
377
+ current = node_by_id.get(current_target)
378
+ if _is_placeholder(current) and current.get("label") == label:
379
+ return str(current_target)
380
+
381
+ for node in all_nodes:
382
+ nid = node.get("id")
383
+ if (
384
+ isinstance(nid, str)
385
+ and node.get("label") == label
386
+ and _is_placeholder(node)
387
+ ):
388
+ return nid
389
+
390
+ stem = _make_id(label)
391
+ stub_id = stem
392
+ if stub_id in node_by_id:
393
+ stub_id = _make_id("csharp_type_ref", label)
394
+ suffix = 2
395
+ while stub_id in node_by_id:
396
+ stub_id = _make_id("csharp_type_ref", label, str(suffix))
397
+ suffix += 1
398
+ node = {
399
+ "id": stub_id,
400
+ "label": label,
401
+ "file_type": "code",
402
+ "source_file": "",
403
+ "source_location": "",
404
+ }
405
+ all_nodes.append(node)
406
+ node_by_id[stub_id] = node
407
+ return stub_id
408
+
409
+ REPOINT_RELATIONS = {"implements", "inherits", "references"}
410
+ repointed_from: set[str] = set()
411
+ for edge in all_edges:
412
+ if edge.get("relation") not in REPOINT_RELATIONS:
413
+ continue
414
+ source_file = edge.get("source_file")
415
+ if not _is_dotnet_source_file(source_file):
416
+ continue
417
+ source_node = node_by_id.get(edge.get("source"))
418
+ target_node = node_by_id.get(edge.get("target"))
419
+ if not source_node or not target_node:
420
+ continue
421
+ if not _is_csharp_relevant_target(target_node):
422
+ continue
423
+ metadata = _metadata(edge.get("metadata"))
424
+ label = metadata.get("ref_token") or _label_for_type_ref_target(target_node, source_file)
425
+ if not label:
426
+ continue
427
+ if metadata.get("qualified"):
428
+ resolved = _resolve_qualified(label, metadata.get("ref_qualifier"), source_node, source_file)
429
+ else:
430
+ resolved = _resolve_label(label, source_node, source_file)
431
+ target = edge.get("target")
432
+ desired = resolved or _dangling_stub_id(label, target)
433
+ if desired != target:
434
+ edge["target"] = desired
435
+ if isinstance(target, str) and _is_placeholder(target_node):
436
+ repointed_from.add(target)
437
+
438
+ if not repointed_from:
439
+ return
440
+
441
+ still_referenced: set[str] = set()
442
+ for edge in all_edges:
443
+ still_referenced.add(edge.get("source"))
444
+ still_referenced.add(edge.get("target"))
445
+ all_nodes[:] = [
446
+ node for node in all_nodes
447
+ if node.get("id") not in repointed_from or node.get("id") in still_referenced
448
+ ]