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,564 @@
1
+ """Dart extractor. Moved verbatim from graphify/extract.py."""
2
+ from __future__ import annotations
3
+
4
+ import re
5
+
6
+ from pathlib import Path
7
+ from graphify.extractors.base import _file_stem, _make_id
8
+
9
+
10
+ def extract_dart(path: Path) -> dict:
11
+ """Extract classes, mixins, functions, imports, generic calls, and annotations from a .dart file using regex."""
12
+ try:
13
+ src = path.read_text(encoding="utf-8", errors="replace")
14
+ except OSError:
15
+ return {"error": f"cannot read {path}"}
16
+
17
+ # Remove inline and multi-line comments while leaving string literals untouched to prevent stripping URLs/paths inside strings
18
+ comment_string_pattern = re.compile(
19
+ r'"""(?:\\.|[\s\S])*?"""'
20
+ r"|'''(?:\\.|[\s\S])*?'''"
21
+ r'|"(?:\\.|[^"\\])*"'
22
+ r"|'(?:\\.|[^'\\])*'"
23
+ r"|/\*[\s\S]*?\*/"
24
+ r"|//[^\n]*"
25
+ )
26
+ def _comment_replace(match: re.Match) -> str:
27
+ token = match.group(0)
28
+ if token.startswith("/"):
29
+ # Blank to the SAME number of newlines the comment spanned, not
30
+ # to an empty string: every offset computed against src_clean
31
+ # below (including every source_location line number) is only
32
+ # valid if src_clean's line count tracks the original file's.
33
+ # Deleting a multi-line comment outright shifted every later
34
+ # line number up by the comment's own height (#3365).
35
+ return "\n" * token.count("\n")
36
+ return token
37
+ src_clean = comment_string_pattern.sub(_comment_replace, src)
38
+
39
+ def _line_at(offset: int) -> int:
40
+ return src_clean[:offset].count("\n") + 1
41
+
42
+ stem = _file_stem(path)
43
+ file_nid = _make_id(str(path))
44
+
45
+ # Check if this is a part-of file and redirect to parent
46
+ part_of_match = re.search(r"^\s*part\s+of\s+['\"]([^'\"]+)['\"]", src_clean, re.MULTILINE)
47
+ is_part = False
48
+ if part_of_match:
49
+ parent_ref = part_of_match.group(1)
50
+ if parent_ref.endswith(".dart"):
51
+ try:
52
+ parent_path = (path.parent / parent_ref).resolve()
53
+ if parent_path.exists():
54
+ stem = _file_stem(parent_path)
55
+ file_nid = _make_id(str(parent_path))
56
+ is_part = True
57
+ except Exception:
58
+ pass
59
+
60
+ nodes = []
61
+ if not is_part:
62
+ nodes.append({"id": file_nid, "label": path.name, "file_type": "code",
63
+ "source_file": str(path), "source_location": None})
64
+ edges = []
65
+ defined: set[str] = set()
66
+
67
+ def add_node(nid: str, label: str, ftype: str = "code", source_file: str | None = str(path),
68
+ line: int | None = None) -> None:
69
+ if nid not in defined:
70
+ nodes.append({"id": nid, "label": label, "file_type": ftype,
71
+ "source_file": source_file,
72
+ "source_location": f"L{line}" if line else None})
73
+ defined.add(nid)
74
+
75
+ def add_edge(src_id: str, tgt_id: str, relation: str, weight: float = 1.0, context: str | None = None,
76
+ line: int | None = None) -> None:
77
+ edge = {"source": src_id, "target": tgt_id, "relation": relation,
78
+ "confidence": "EXTRACTED", "confidence_score": 1.0,
79
+ "source_file": str(path),
80
+ "source_location": f"L{line}" if line else None, "weight": weight}
81
+ if context:
82
+ edge["context"] = context
83
+ edges.append(edge)
84
+
85
+ def _split_types(text: str) -> list[str]:
86
+ parts = []
87
+ current = []
88
+ depth = 0
89
+ for char in text:
90
+ if char == "<":
91
+ depth += 1
92
+ current.append(char)
93
+ elif char == ">":
94
+ depth -= 1
95
+ current.append(char)
96
+ elif char == "," and depth == 0:
97
+ parts.append("".join(current).strip())
98
+ current = []
99
+ else:
100
+ current.append(char)
101
+ if current:
102
+ parts.append("".join(current).strip())
103
+ return [p for p in parts if p]
104
+
105
+ def _find_matching_brace(text: str, start_pos: int) -> int:
106
+ brace_count = 0
107
+ in_double_quote = False
108
+ in_single_quote = False
109
+ escape = False
110
+
111
+ first_brace = text.find("{", start_pos)
112
+ if first_brace == -1:
113
+ return len(text)
114
+
115
+ brace_count = 1
116
+ i = first_brace + 1
117
+ n = len(text)
118
+ while i < n:
119
+ char = text[i]
120
+ if escape:
121
+ escape = False
122
+ i += 1
123
+ continue
124
+ if char == "\\":
125
+ escape = True
126
+ i += 1
127
+ continue
128
+ if text[i:i+3] == '"""' and not in_single_quote:
129
+ i += 3
130
+ end = text.find('"""', i)
131
+ i = end + 3 if end != -1 else n
132
+ continue
133
+ if text[i:i+3] == "'''" and not in_double_quote:
134
+ i += 3
135
+ end = text.find("'''", i)
136
+ i = end + 3 if end != -1 else n
137
+ continue
138
+ if char == '"' and not in_single_quote:
139
+ in_double_quote = not in_double_quote
140
+ elif char == "'" and not in_double_quote:
141
+ in_single_quote = not in_single_quote
142
+ elif not in_double_quote and not in_single_quote:
143
+ if char == "{":
144
+ brace_count += 1
145
+ elif char == "}":
146
+ brace_count -= 1
147
+ if brace_count == 0:
148
+ return i + 1
149
+ i += 1
150
+ return len(text)
151
+
152
+ # 1. Classes, mixins, and enums declarations (with inheritance, mixins, interfaces, and generics)
153
+ # Supports multiple combined modifiers (e.g., abstract base class, mixin class) without capturing "class" as a name
154
+ class_pattern = r"^\s*(?:(?:abstract|sealed|base|interface|final|mixin)\s+)*(?:class|mixin|enum|extension\s+type)\s+(\w+)"
155
+ for m in re.finditer(class_pattern, src_clean, re.MULTILINE):
156
+ class_name = m.group(1)
157
+ # Anchor on the capture group, not the whole match: the pattern's
158
+ # leading ^\s* makes m.start() land before the blank/indent lines
159
+ # that precede the declaration, reporting the line too early (#3365).
160
+ class_line = _line_at(m.start(1))
161
+ class_nid = _make_id(stem, class_name)
162
+ add_node(class_nid, class_name, line=class_line)
163
+ add_edge(file_nid, class_nid, "defines", line=class_line)
164
+
165
+ # Manually parse extends/on, with, and implements in header to handle nested generics brackets balanced
166
+ start_idx = m.end()
167
+ rest = src_clean[start_idx : start_idx + 500]
168
+
169
+ # Skip class generic parameters
170
+ if rest.lstrip().startswith("<"):
171
+ offset = rest.find("<")
172
+ depth = 1
173
+ i = offset + 1
174
+ while i < len(rest) and depth > 0:
175
+ if rest[i] == "<": depth += 1
176
+ elif rest[i] == ">": depth -= 1
177
+ i += 1
178
+ rest = rest[i:]
179
+
180
+ # Skip primary constructor (e.g. extension type MyExt(int id))
181
+ if rest.lstrip().startswith("("):
182
+ offset = rest.find("(")
183
+ depth = 1
184
+ i = offset + 1
185
+ while i < len(rest) and depth > 0:
186
+ if rest[i] == "(": depth += 1
187
+ elif rest[i] == ")": depth -= 1
188
+ i += 1
189
+ rest = rest[i:]
190
+
191
+ header_end = rest.find("{")
192
+ if header_end == -1:
193
+ header_end = rest.find(";")
194
+ if header_end == -1:
195
+ header_end = len(rest)
196
+ header = rest[:header_end]
197
+
198
+ base_class = None
199
+ generics = None
200
+ mixins_list = []
201
+ interfaces_list = []
202
+
203
+ # Parse extends or on
204
+ extends_m = re.search(r"^\s*(?:extends|on)\s+([a-zA-Z0-9_.]+)", header)
205
+ if extends_m:
206
+ base_class = extends_m.group(1)
207
+ rest_header = header[extends_m.end():]
208
+ if rest_header.strip().startswith("<"):
209
+ start_idx = rest_header.find("<")
210
+ depth = 1
211
+ i = start_idx + 1
212
+ while i < len(rest_header) and depth > 0:
213
+ if rest_header[i] == "<":
214
+ depth += 1
215
+ elif rest_header[i] == ">":
216
+ depth -= 1
217
+ if depth == 0:
218
+ generics = rest_header[start_idx + 1 : i]
219
+ break
220
+ i += 1
221
+ if generics is not None:
222
+ header = rest_header[i + 1:]
223
+ else:
224
+ header = rest_header
225
+ else:
226
+ header = rest_header
227
+
228
+ # Parse with
229
+ with_m = re.search(r"^\s*with\s+", header)
230
+ if with_m:
231
+ rest_header = header[with_m.end():]
232
+ impl_idx = rest_header.find("implements")
233
+ if impl_idx != -1:
234
+ mixins_str = rest_header[:impl_idx]
235
+ header = rest_header[impl_idx:]
236
+ else:
237
+ mixins_str = rest_header
238
+ header = ""
239
+ mixins_list = _split_types(mixins_str)
240
+
241
+ # Parse implements
242
+ impl_m = re.search(r"^\s*implements\s+", header)
243
+ if impl_m:
244
+ interfaces_list = _split_types(header[impl_m.end():])
245
+
246
+ # Map extends inheritance relation
247
+ if base_class:
248
+ base_nid = _make_id(base_class)
249
+ add_node(base_nid, base_class, source_file=None)
250
+ add_edge(class_nid, base_nid, "inherits", line=class_line)
251
+
252
+ # Map generic type arguments (e.g. MyBloc extends Bloc<MyEvent, MyState>)
253
+ if generics:
254
+ for gen in _split_types(generics):
255
+ gen_clean = gen.split("<")[0].strip()
256
+ if gen_clean not in {"String", "int", "double", "bool", "num", "dynamic", "Object", "void"}:
257
+ gen_nid = _make_id(gen_clean)
258
+ add_node(gen_nid, gen_clean, source_file=None)
259
+ add_edge(class_nid, gen_nid, "references", line=class_line)
260
+
261
+ # Map mixins
262
+ for mixin in mixins_list:
263
+ mixin_clean = mixin.split("<")[0].strip()
264
+ mixin_nid = _make_id(mixin_clean)
265
+ add_node(mixin_nid, mixin_clean, source_file=None)
266
+ add_edge(class_nid, mixin_nid, "mixes_in", line=class_line)
267
+
268
+ # Map interfaces
269
+ for interface in interfaces_list:
270
+ interface_clean = interface.split("<")[0].strip()
271
+ interface_nid = _make_id(interface_clean)
272
+ add_node(interface_nid, interface_clean, source_file=None)
273
+ add_edge(class_nid, interface_nid, "implements", line=class_line)
274
+
275
+ # Extract class body for precise framework dependencies and event handling
276
+ start_idx = m.start()
277
+ brace_pos = src_clean.find("{", start_idx)
278
+ semi_pos = src_clean.find(";", start_idx)
279
+
280
+ has_body = brace_pos != -1
281
+ if has_body and semi_pos != -1 and semi_pos < brace_pos:
282
+ has_body = False
283
+
284
+ if has_body:
285
+ end_pos = _find_matching_brace(src_clean, start_idx)
286
+ class_body = src_clean[brace_pos:end_pos]
287
+
288
+ # Bloc event registration: on<MyEvent>()
289
+ for em in re.finditer(r"\bon<(\w+)>\s*\(", class_body):
290
+ event_name = em.group(1)
291
+ event_nid = _make_id(event_name)
292
+ add_node(event_nid, event_name, source_file=None)
293
+ add_edge(class_nid, event_nid, "calls", context="bloc_event",
294
+ line=_line_at(brace_pos + em.start(1)))
295
+
296
+ # Bloc state emissions: emit(MyState) or yield MyState
297
+ for sm in re.finditer(r"\b(?:emit|yield)\s*\(?\s*(?:const\s+)?([A-Z]\w*)\b", class_body):
298
+ state_name = sm.group(1)
299
+ if state_name not in {"String", "List", "Map", "Set", "Future", "Stream", "Object"}:
300
+ state_nid = _make_id(state_name)
301
+ add_node(state_nid, state_name, source_file=None)
302
+ add_edge(class_nid, state_nid, "calls", context="emit_state",
303
+ line=_line_at(brace_pos + sm.start(1)))
304
+
305
+ # Bloc event additions: widget.add(MyEvent()) or bloc.add(MyEvent())
306
+ for am in re.finditer(r"\b(?:\w*[Bb]loc\w*|context\.read<\w+>\(\))\.add\(\s*(?:const\s+)?([A-Z]\w*)\b", class_body):
307
+ event_name = am.group(1)
308
+ if event_name not in {"String", "List", "Map", "Set", "Future", "Stream", "Object"}:
309
+ event_nid = _make_id(event_name)
310
+ add_node(event_nid, event_name, source_file=None)
311
+ add_edge(class_nid, event_nid, "calls", context="bloc_add_event",
312
+ line=_line_at(brace_pos + am.start(1)))
313
+
314
+ # Riverpod provider references: ref.watch(provider)
315
+ for rm in re.finditer(r"\bref\.(?:watch|read|listen)\s*\(\s*(\w+)\b", class_body):
316
+ provider_name = rm.group(1)
317
+ provider_nid = _make_id(provider_name)
318
+ add_node(provider_nid, provider_name, source_file=None)
319
+ add_edge(class_nid, provider_nid, "references", context="riverpod_reference",
320
+ line=_line_at(brace_pos + rm.start(1)))
321
+
322
+ # Widget to Bloc references: BlocBuilder<MyBloc, ...>
323
+ for bm in re.finditer(r"\bBloc(?:Builder|Listener|Consumer|Provider|Selector)\s*<\s*([a-zA-Z0-9_]+)\b", class_body):
324
+ bloc_name = bm.group(1)
325
+ if bloc_name not in {"String", "int", "double", "bool", "num", "dynamic", "Object", "void"}:
326
+ bloc_nid = _make_id(bloc_name)
327
+ add_node(bloc_nid, bloc_name, source_file=None)
328
+ add_edge(class_nid, bloc_nid, "references", context="bloc_widget_binding",
329
+ line=_line_at(brace_pos + bm.start(1)))
330
+
331
+ # context.read<MyBloc>() or BlocProvider.of<MyBloc>(context)
332
+ for lm in re.finditer(r"\b(?:read|watch|select|of)\s*<([a-zA-Z0-9_]+)>", class_body):
333
+ bloc_name = lm.group(1)
334
+ if bloc_name not in {"String", "int", "double", "bool", "num", "dynamic", "Object", "void"}:
335
+ bloc_nid = _make_id(bloc_name)
336
+ add_node(bloc_nid, bloc_name, source_file=None)
337
+ add_edge(class_nid, bloc_nid, "references", context="bloc_lookup",
338
+ line=_line_at(brace_pos + lm.start(1)))
339
+
340
+ # 2. Annotations mapping (class, mixin, enum, or function level annotations)
341
+ # Support: @riverpod, @Riverpod(...), @injectable, @singleton, @RoutePage(), @HiveType(typeId: 0), @RestApi()
342
+ # Matches `@annotation` and links it to the next class/mixin/enum/function declaration in the file
343
+ annotation_pattern = r"@(\w+)(?:\([^)]*\))?"
344
+ for am in re.finditer(annotation_pattern, src_clean):
345
+ annotation_name = am.group(1)
346
+ if annotation_name in {"override", "deprecated", "required", "protected", "mustCallSuper"}:
347
+ continue
348
+ annotation_pos = am.end()
349
+ intervening_text = src_clean[annotation_pos : annotation_pos + 300]
350
+
351
+ class_m = re.search(r"^\s*(?:(?:abstract|sealed|base|interface|final|mixin)\s+)*(?:class|mixin|enum|extension\s+type)\s+(\w+)", intervening_text, re.MULTILINE)
352
+ func_m = re.search(r"^\s*(?:factory\s+|static\s+|async\s+|external\s+|abstract\s+)?(?:\([^)]+\)|[a-zA-Z0-9_<>,.?]+)(?:\s+[a-zA-Z0-9_<>,.?]+){0,3}\s+(\w+)\s*\(", intervening_text, re.MULTILINE)
353
+
354
+ target_nid = None
355
+ target_name = None
356
+ target_type = None
357
+
358
+ if class_m and func_m:
359
+ if class_m.start() < func_m.start():
360
+ target_name = class_m.group(1)
361
+ target_type = "class"
362
+ target_nid = _make_id(stem, target_name)
363
+ else:
364
+ target_name = func_m.group(1)
365
+ target_type = "function"
366
+ target_nid = _make_id(stem, target_name)
367
+ elif class_m:
368
+ target_name = class_m.group(1)
369
+ target_type = "class"
370
+ target_nid = _make_id(stem, target_name)
371
+ elif func_m:
372
+ target_name = func_m.group(1)
373
+ target_type = "function"
374
+ target_nid = _make_id(stem, target_name)
375
+
376
+ if target_nid and target_name:
377
+ actual_intervening = intervening_text[:min(class_m.start() if class_m else 300, func_m.start() if func_m else 300)]
378
+ if ";" not in actual_intervening and "}" not in actual_intervening and "{" not in actual_intervening:
379
+ annotation_line = _line_at(am.start(1))
380
+ annotation_nid = _make_id("annotation", annotation_name.lower())
381
+ add_node(annotation_nid, f"@{annotation_name}", ftype="concept", source_file=None)
382
+ add_edge(target_nid, annotation_nid, "configures", line=annotation_line)
383
+
384
+ # Riverpod specific provider generation mapping (supports camelCase class and functional providers)
385
+ if annotation_name.lower() == "riverpod":
386
+ if target_type == "class":
387
+ provider_name = target_name[0].lower() + target_name[1:] + "Provider" if len(target_name) > 1 else target_name.lower() + "Provider"
388
+ else:
389
+ provider_name = target_name + "Provider"
390
+ provider_nid = _make_id(provider_name)
391
+ add_node(provider_nid, provider_name, ftype="concept", source_file=str(path), line=annotation_line)
392
+ add_edge(target_nid, provider_nid, "defines", context="riverpod_provider", line=annotation_line)
393
+
394
+ # 2.5 Typedefs (Type Aliases)
395
+ typedef_pattern = r"^\s*typedef\s+(\w+)\s*(?:<[^>]+>)?\s*=\s*([a-zA-Z0-9_<>,.?\s]+);"
396
+ for m in re.finditer(typedef_pattern, src_clean, re.MULTILINE):
397
+ typedef_name = m.group(1)
398
+ typedef_line = _line_at(m.start(1))
399
+ target_type = m.group(2).split("<")[0].split(".")[-1].strip()
400
+ if target_type not in {"String", "int", "double", "bool", "num", "dynamic", "Object", "List", "Map", "Set", "void", "Function"}:
401
+ typedef_nid = _make_id(stem, typedef_name)
402
+ add_node(typedef_nid, typedef_name, line=typedef_line)
403
+ add_edge(file_nid, typedef_nid, "defines", line=typedef_line)
404
+ target_nid = _make_id(target_type)
405
+ add_node(target_nid, target_type, source_file=None)
406
+ add_edge(typedef_nid, target_nid, "references", context="typedef", line=typedef_line)
407
+
408
+ # 3. Extensions (extension MyExt on MyClass)
409
+ ext_pattern = r"^\s{0,4}extension\s+(\w+)?(?:<[^>]+>)?\s+on\s+(\w+)"
410
+ for m in re.finditer(ext_pattern, src_clean, re.MULTILINE):
411
+ ext_name = m.group(1) or f"{stem}_anonymous_extension"
412
+ target_class = m.group(2)
413
+ ext_line = _line_at(m.start(2))
414
+
415
+ ext_nid = _make_id(stem, ext_name)
416
+ label = m.group(1) or f"Extension on {target_class}"
417
+ add_node(ext_nid, label, line=ext_line)
418
+ add_edge(file_nid, ext_nid, "defines", line=ext_line)
419
+
420
+ target_nid = _make_id(target_class)
421
+ add_node(target_nid, target_class, source_file=None)
422
+ add_edge(ext_nid, target_nid, "extends", line=ext_line)
423
+
424
+ # 4. Top-level and class-level variable declarations (generic variables, records, late, and destructuring)
425
+ # Restrict indentation to 0-2 spaces to avoid matching local variables inside functions or switch expressions
426
+ var_pattern = r"^\s{0,2}(?:late\s+)?(?:(?:final|const|var)\s+)?(?:\([^)]+\)\s+|([a-zA-Z0-9_<>,.?]+(?:\s+[a-zA-Z0-9_<>,.?]+){0,3})\s+)?(?:(\w+)|(?:\w+\s*)?\(([^)]+)\))\s*(?:=|$|;)"
427
+ for m in re.finditer(var_pattern, src_clean, re.MULTILINE):
428
+ var_type = m.group(1)
429
+ single_name = m.group(2)
430
+ destructured_names = m.group(3)
431
+
432
+ if not re.match(r"^\s*(?:late|final|const|var)\b", m.group(0)) and not var_type:
433
+ continue
434
+
435
+ if single_name:
436
+ if single_name not in {"if", "for", "while", "switch", "catch", "return"}:
437
+ var_line = _line_at(m.start(2))
438
+ var_nid = _make_id(stem, single_name)
439
+ add_node(var_nid, single_name, line=var_line)
440
+ add_edge(file_nid, var_nid, "defines", line=var_line)
441
+
442
+ if var_type and var_type not in {"String", "int", "double", "bool", "num", "dynamic", "Object", "List", "Map", "Set", "void"}:
443
+ clean_type = var_type.split("<")[0].split(".")[-1].strip()
444
+ type_nid = _make_id(clean_type)
445
+ add_node(type_nid, clean_type, source_file=None)
446
+ add_edge(file_nid, type_nid, "references", context="variable_type", line=var_line)
447
+ elif destructured_names:
448
+ destructure_line = _line_at(m.start(3))
449
+ for name in [n.strip() for n in destructured_names.split(",") if n.strip()]:
450
+ if ":" in name:
451
+ name = name.split(":")[-1].strip()
452
+ if re.match(r"^[a-zA-Z_]\w*$", name) and not re.match(r"^[A-Z]", name):
453
+ if name not in {"if", "for", "while", "switch", "catch", "return"}:
454
+ var_nid = _make_id(stem, name)
455
+ add_node(var_nid, name, line=destructure_line)
456
+ add_edge(file_nid, var_nid, "defines", line=destructure_line)
457
+
458
+ # 5. Top-level and member functions/methods (supports typed/generic/record return types and Riverpod/Bloc references)
459
+ # Restrict indentation to 0-2 spaces to avoid matching nested local functions or methods inside multiline switch statements
460
+ method_pattern = r"^\s{0,2}(?:factory\s+|static\s+|async\s+|external\s+|abstract\s+)?(?:\([^)]+\)|[a-zA-Z0-9_<>,.?]+)(?:\s+[a-zA-Z0-9_<>,.?]+){0,3}\s+(\w+(?:\.\w+)?)\s*\("
461
+ for m in re.finditer(method_pattern, src_clean, re.MULTILINE):
462
+ raw_name = m.group(1)
463
+ name = raw_name.split(".")[-1]
464
+ if name in {"if", "for", "while", "switch", "catch", "return", "void", "dynamic", "final", "const", "get", "set"}:
465
+ continue
466
+ if re.match(r"^[A-Z]", name):
467
+ continue
468
+ method_line = _line_at(m.start(1))
469
+ nid = _make_id(stem, name)
470
+ add_node(nid, name, line=method_line)
471
+ add_edge(file_nid, nid, "defines", line=method_line)
472
+
473
+ # Get function body using matching brace to extract Riverpod reference patterns
474
+ start_idx = m.start()
475
+ brace_pos = src_clean.find("{", start_idx)
476
+ semi_pos = src_clean.find(";", start_idx)
477
+ arrow_pos = src_clean.find("=>", start_idx)
478
+
479
+ has_body = brace_pos != -1
480
+ if has_body and semi_pos != -1 and semi_pos < brace_pos:
481
+ has_body = False
482
+ if has_body and arrow_pos != -1 and arrow_pos < brace_pos:
483
+ has_body = False
484
+
485
+ if has_body:
486
+ end_pos = _find_matching_brace(src_clean, start_idx)
487
+ func_body = src_clean[brace_pos:end_pos]
488
+
489
+ # Extract Riverpod provider references: ref.watch(provider)
490
+ for rm in re.finditer(r"\bref\.(?:watch|read|listen)\s*\(\s*(\w+)\b", func_body):
491
+ provider_name = rm.group(1)
492
+ provider_nid = _make_id(provider_name)
493
+ add_node(provider_nid, provider_name, source_file=None)
494
+ add_edge(nid, provider_nid, "references", context="riverpod_reference",
495
+ line=_line_at(brace_pos + rm.start(1)))
496
+
497
+ # Extract Bloc event additions: widget.add(MyEvent()) or bloc.add(MyEvent())
498
+ for am in re.finditer(r"\b(?:\w*[Bb]loc\w*|context\.read<\w+>\(\))\.add\(\s*(?:const\s+)?([A-Z]\w*)\b", func_body):
499
+ event_name = am.group(1)
500
+ if event_name not in {"String", "List", "Map", "Set", "Future", "Stream", "Object"}:
501
+ event_nid = _make_id(event_name)
502
+ add_node(event_nid, event_name, source_file=None)
503
+ add_edge(nid, event_nid, "calls", context="bloc_add_event",
504
+ line=_line_at(brace_pos + am.start(1)))
505
+
506
+ # context.read<MyBloc>() or BlocProvider.of<MyBloc>(context)
507
+ for lm in re.finditer(r"\b(?:read|watch|select|of)\s*<([a-zA-Z0-9_]+)>", func_body):
508
+ bloc_name = lm.group(1)
509
+ if bloc_name not in {"String", "int", "double", "bool", "num", "dynamic", "Object", "void"}:
510
+ bloc_nid = _make_id(bloc_name)
511
+ add_node(bloc_nid, bloc_name, source_file=None)
512
+ add_edge(nid, bloc_nid, "references", context="bloc_lookup",
513
+ line=_line_at(brace_pos + lm.start(1)))
514
+
515
+ # Universal Navigation Patters (GoRouter, AutoRoute, Navigator)
516
+ for nm in re.finditer(r"\b(?:go|push|goNamed|pushNamed|replace|replaceNamed)\s*\(\s*(?:context\s*,\s*)?['\"]([a-zA-Z0-9_/?=&%-]+)['\"]", func_body):
517
+ route_path = nm.group(1)
518
+ route_nid = _make_id("route", route_path.replace("/", "_").replace("?", "_").replace("=", "_").replace("&", "_"))
519
+ add_node(route_nid, f"Route {route_path}", ftype="concept", source_file=None)
520
+ add_edge(nid, route_nid, "navigates", context="route_path",
521
+ line=_line_at(brace_pos + nm.start(1)))
522
+
523
+ for cm in re.finditer(r"\b(?:go|push|goNamed|pushNamed|replace|replaceNamed)\s*\(\s*(?:context\s*,\s*)?([A-Z][a-zA-Z0-9_]*\.[a-zA-Z0-9_]+)", func_body):
524
+ route_const = cm.group(1)
525
+ route_nid = _make_id("route", route_const.replace(".", "_"))
526
+ add_node(route_nid, route_const, ftype="concept", source_file=None)
527
+ add_edge(nid, route_nid, "navigates", context="route_const",
528
+ line=_line_at(brace_pos + cm.start(1)))
529
+
530
+ for om in re.finditer(r"\b(?:push|replace)\s*\(\s*(?:context\s*,\s*)?.*?\b([A-Z]\w*(?:Route|Screen|Page))\b", func_body):
531
+ route_class = om.group(1)
532
+ route_nid = _make_id(route_class)
533
+ add_node(route_nid, route_class, source_file=None)
534
+ add_edge(nid, route_nid, "navigates", context="route_object",
535
+ line=_line_at(brace_pos + om.start(1)))
536
+
537
+ # 6. Imports and Exports
538
+ for m in re.finditer(r"""^\s*import\s+['"]([^'"]+)['"]""", src_clean, re.MULTILINE):
539
+ pkg = m.group(1)
540
+ tgt_nid = _make_id(pkg)
541
+ add_node(tgt_nid, pkg, source_file=None)
542
+ add_edge(file_nid, tgt_nid, "imports", line=_line_at(m.start(1)))
543
+
544
+ for m in re.finditer(r"""^\s*export\s+['"]([^'"]+)['"]""", src_clean, re.MULTILINE):
545
+ pkg = m.group(1)
546
+ tgt_nid = _make_id(pkg)
547
+ add_node(tgt_nid, pkg, source_file=None)
548
+ add_edge(file_nid, tgt_nid, "exports", line=_line_at(m.start(1)))
549
+
550
+ # 7. Generic Invocations / Type Lookups (Universal Dependency Lookup)
551
+ # Matches any method call with type parameters: methodName<Type>() or object.methodName<Type>()
552
+ # Automatically extracts GetIt, Injectable, Riverpod, Provider, BlocProvider, and InheritedWidget type lookups!
553
+ generic_call_pattern = r"\b\w+<([a-zA-Z0-9_.]+(?:<[a-zA-Z0-9_.,\s<>]+>)?)\s*>\s*\("
554
+ type_blacklist = {"String", "int", "double", "bool", "num", "dynamic", "Object", "List", "Map", "Set", "Future", "Stream", "void"}
555
+ for m in re.finditer(generic_call_pattern, src_clean):
556
+ type_name = m.group(1).split(".")[-1].strip()
557
+ clean_name = type_name.split("<")[0].strip()
558
+ if clean_name not in type_blacklist:
559
+ target_nid = _make_id(clean_name)
560
+ add_node(target_nid, clean_name, source_file=None)
561
+ add_edge(file_nid, target_nid, "references", context="type_lookup",
562
+ line=_line_at(m.start(1)))
563
+
564
+ return {"nodes": nodes, "edges": edges}