ckgraphify 0.1.4__tar.gz → 0.2.1__tar.gz

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 (45) hide show
  1. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/PKG-INFO +8 -8
  2. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/README.md +7 -7
  3. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/__main__.py +4 -24
  4. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/business_map.py +0 -15
  5. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/graph_main_frontend.py +32 -3
  6. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/graph_main_frontend_sdk.py +311 -22
  7. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/graph_main_merge.py +30 -21
  8. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/graph_main_trace.py +8 -0
  9. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/repo_registry.py +36 -6
  10. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/pyproject.toml +1 -1
  11. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/skill/skill-codex.md +6 -6
  12. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/skill/skill.md +20 -71
  13. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/LICENSE +0 -0
  14. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/MANIFEST.in +0 -0
  15. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/ckgraphify.egg-info/SOURCES.txt +0 -0
  16. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/__init__.py +0 -0
  17. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/analyze.py +0 -0
  18. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/benchmark.py +0 -0
  19. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/bridge_mtop.py +0 -0
  20. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/build.py +0 -0
  21. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/cache.py +0 -0
  22. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/callflow_html.py +0 -0
  23. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/cluster.py +0 -0
  24. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/dedup.py +0 -0
  25. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/detect.py +0 -0
  26. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/export.py +0 -0
  27. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/extract.py +0 -0
  28. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/global_graph.py +0 -0
  29. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/google_workspace.py +0 -0
  30. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/graph_main_backend.py +0 -0
  31. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/graph_main_html.py +0 -0
  32. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/hooks.py +0 -0
  33. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/ingest.py +0 -0
  34. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/llm.py +0 -0
  35. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/manifest.py +0 -0
  36. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/report.py +0 -0
  37. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/security.py +0 -0
  38. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/serve.py +0 -0
  39. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/transcribe.py +0 -0
  40. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/tree_html.py +0 -0
  41. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/validate.py +0 -0
  42. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/watch.py +0 -0
  43. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/graphify/wiki.py +0 -0
  44. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/setup.cfg +0 -0
  45. {ckgraphify-0.1.4 → ckgraphify-0.2.1}/skill/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ckgraphify
3
- Version: 0.1.4
3
+ Version: 0.2.1
4
4
  Summary: AI coding assistant skill for Claude Code and Codex - graph-main boundary graphs, multi-repo call chains, business-map concepts, and business search
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/safishamsi/graphify
@@ -106,17 +106,17 @@ python3 -m pip install -e ./ckgraphify && CLAUDE_PLUGIN_ROOT=/Users/alsc/code/sh
106
106
 
107
107
  ## 生成并合并仓库图谱
108
108
 
109
- 下面命令会分别进入 `${CLAUDE_PLUGIN_ROOT}/repos/kl-health` 和 `${CLAUDE_PLUGIN_ROOT}/repos/ele-newretail-health-audit` 生成 `graphify-out/graph-main.json`,然后合并到 `${CLAUDE_PLUGIN_ROOT}/graphify-out/graph-hc.json`。
109
+ 下面命令会分别进入 `${CLAUDE_PLUGIN_ROOT}/repos/kl-health` 和 `${CLAUDE_PLUGIN_ROOT}/repos/ele-newretail-health-audit` 生成 `graphify-out/graph-main.json`,然后合并到 `${CLAUDE_PLUGIN_ROOT}/graphify-out/graph.json`。
110
110
 
111
111
  ```bash
112
- scripts/graph-main-repos.sh --repos-root "${CLAUDE_PLUGIN_ROOT}/repos" --out "${CLAUDE_PLUGIN_ROOT}/graphify-out/graph-hc.json" kl-health ele-newretail-health-audit
112
+ scripts/graph-main-repos.sh --repos-root "${CLAUDE_PLUGIN_ROOT}/repos" --out "${CLAUDE_PLUGIN_ROOT}/graphify-out/graph.json" kl-health ele-newretail-health-audit
113
113
  ```
114
114
 
115
115
  也可以传入更多 `${CLAUDE_PLUGIN_ROOT}/repos/` 下的仓库名:
116
116
 
117
117
  ```bash
118
118
  # 健康卡 init all repos
119
- scripts/graph-main-repos.sh --repos-root "${CLAUDE_PLUGIN_ROOT}/repos" --out "${CLAUDE_PLUGIN_ROOT}/graphify-out/graph-hc.json" ele-newretail-drug ele-newretail-health-task ele-newretail-drug-grow ele-newretail-health-audit ele-newretail-health-client ele-newretail-drug-trade kl-health health-vip-card medicine-unicore p ele-newretail-venus ele-newretail-summaryx china-alsc-sales-eleme-newretail-app
119
+ scripts/graph-main-repos.sh --repos-root "${CLAUDE_PLUGIN_ROOT}/repos" --out "${CLAUDE_PLUGIN_ROOT}/graphify-out/graph.json" ele-newretail-drug ele-newretail-health-task ele-newretail-drug-grow ele-newretail-health-audit ele-newretail-health-client ele-newretail-drug-trade kl-health health-vip-card medicine-unicore p ele-newretail-venus ele-newretail-summaryx china-alsc-sales-eleme-newretail-app
120
120
  ```
121
121
 
122
122
  ```bash
@@ -124,7 +124,7 @@ scripts/graph-main-repos.sh --repos-root "${CLAUDE_PLUGIN_ROOT}/repos" --out "${
124
124
  scripts/graph-main-repos.sh \
125
125
  --incremental \
126
126
  --repos-root "${CLAUDE_PLUGIN_ROOT}/repos" \
127
- --out "${CLAUDE_PLUGIN_ROOT}/graphify-out/graph-hc.json" \
127
+ --out "${CLAUDE_PLUGIN_ROOT}/graphify-out/graph.json" \
128
128
  p medicine-unicore
129
129
  ```
130
130
 
@@ -136,7 +136,7 @@ scripts/graph-main-repos.sh \
136
136
  关于ehealth-member实体的全部链路
137
137
  ```bash
138
138
  graphify main-trace \
139
- --graph "${CLAUDE_PLUGIN_ROOT}/graphify-out/graph-hc.json" \
139
+ --graph "${CLAUDE_PLUGIN_ROOT}/graphify-out/graph.json" \
140
140
  --from ehealth-member \
141
141
  --max-depth 8
142
142
  ```
@@ -144,7 +144,7 @@ graphify main-trace \
144
144
  选择ehealth-member实体的/shopping/healthCard/createHealthCard 支线链路
145
145
  ```bash
146
146
  graphify main-trace \
147
- --graph "${CLAUDE_PLUGIN_ROOT}/graphify-out/graph-hc.json" \
147
+ --graph "${CLAUDE_PLUGIN_ROOT}/graphify-out/graph.json" \
148
148
  --from ehealth-member \
149
149
  --api /shopping/healthCard/createHealthCard \
150
150
  --max-depth 8
@@ -154,7 +154,7 @@ graphify main-trace \
154
154
 
155
155
  ```bash
156
156
  graphify main-trace \
157
- --graph "${CLAUDE_PLUGIN_ROOT}/graphify-out/graph-hc.json" \
157
+ --graph "${CLAUDE_PLUGIN_ROOT}/graphify-out/graph.json" \
158
158
  --from ehealth-member \
159
159
  --max-depth 8 --sources
160
160
  ```
@@ -13,17 +13,17 @@ python3 -m pip install -e ./ckgraphify && CLAUDE_PLUGIN_ROOT=/Users/alsc/code/sh
13
13
 
14
14
  ## 生成并合并仓库图谱
15
15
 
16
- 下面命令会分别进入 `${CLAUDE_PLUGIN_ROOT}/repos/kl-health` 和 `${CLAUDE_PLUGIN_ROOT}/repos/ele-newretail-health-audit` 生成 `graphify-out/graph-main.json`,然后合并到 `${CLAUDE_PLUGIN_ROOT}/graphify-out/graph-hc.json`。
16
+ 下面命令会分别进入 `${CLAUDE_PLUGIN_ROOT}/repos/kl-health` 和 `${CLAUDE_PLUGIN_ROOT}/repos/ele-newretail-health-audit` 生成 `graphify-out/graph-main.json`,然后合并到 `${CLAUDE_PLUGIN_ROOT}/graphify-out/graph.json`。
17
17
 
18
18
  ```bash
19
- scripts/graph-main-repos.sh --repos-root "${CLAUDE_PLUGIN_ROOT}/repos" --out "${CLAUDE_PLUGIN_ROOT}/graphify-out/graph-hc.json" kl-health ele-newretail-health-audit
19
+ scripts/graph-main-repos.sh --repos-root "${CLAUDE_PLUGIN_ROOT}/repos" --out "${CLAUDE_PLUGIN_ROOT}/graphify-out/graph.json" kl-health ele-newretail-health-audit
20
20
  ```
21
21
 
22
22
  也可以传入更多 `${CLAUDE_PLUGIN_ROOT}/repos/` 下的仓库名:
23
23
 
24
24
  ```bash
25
25
  # 健康卡 init all repos
26
- scripts/graph-main-repos.sh --repos-root "${CLAUDE_PLUGIN_ROOT}/repos" --out "${CLAUDE_PLUGIN_ROOT}/graphify-out/graph-hc.json" ele-newretail-drug ele-newretail-health-task ele-newretail-drug-grow ele-newretail-health-audit ele-newretail-health-client ele-newretail-drug-trade kl-health health-vip-card medicine-unicore p ele-newretail-venus ele-newretail-summaryx china-alsc-sales-eleme-newretail-app
26
+ scripts/graph-main-repos.sh --repos-root "${CLAUDE_PLUGIN_ROOT}/repos" --out "${CLAUDE_PLUGIN_ROOT}/graphify-out/graph.json" ele-newretail-drug ele-newretail-health-task ele-newretail-drug-grow ele-newretail-health-audit ele-newretail-health-client ele-newretail-drug-trade kl-health health-vip-card medicine-unicore p ele-newretail-venus ele-newretail-summaryx china-alsc-sales-eleme-newretail-app
27
27
  ```
28
28
 
29
29
  ```bash
@@ -31,7 +31,7 @@ scripts/graph-main-repos.sh --repos-root "${CLAUDE_PLUGIN_ROOT}/repos" --out "${
31
31
  scripts/graph-main-repos.sh \
32
32
  --incremental \
33
33
  --repos-root "${CLAUDE_PLUGIN_ROOT}/repos" \
34
- --out "${CLAUDE_PLUGIN_ROOT}/graphify-out/graph-hc.json" \
34
+ --out "${CLAUDE_PLUGIN_ROOT}/graphify-out/graph.json" \
35
35
  p medicine-unicore
36
36
  ```
37
37
 
@@ -43,7 +43,7 @@ scripts/graph-main-repos.sh \
43
43
  关于ehealth-member实体的全部链路
44
44
  ```bash
45
45
  graphify main-trace \
46
- --graph "${CLAUDE_PLUGIN_ROOT}/graphify-out/graph-hc.json" \
46
+ --graph "${CLAUDE_PLUGIN_ROOT}/graphify-out/graph.json" \
47
47
  --from ehealth-member \
48
48
  --max-depth 8
49
49
  ```
@@ -51,7 +51,7 @@ graphify main-trace \
51
51
  选择ehealth-member实体的/shopping/healthCard/createHealthCard 支线链路
52
52
  ```bash
53
53
  graphify main-trace \
54
- --graph "${CLAUDE_PLUGIN_ROOT}/graphify-out/graph-hc.json" \
54
+ --graph "${CLAUDE_PLUGIN_ROOT}/graphify-out/graph.json" \
55
55
  --from ehealth-member \
56
56
  --api /shopping/healthCard/createHealthCard \
57
57
  --max-depth 8
@@ -61,7 +61,7 @@ graphify main-trace \
61
61
 
62
62
  ```bash
63
63
  graphify main-trace \
64
- --graph "${CLAUDE_PLUGIN_ROOT}/graphify-out/graph-hc.json" \
64
+ --graph "${CLAUDE_PLUGIN_ROOT}/graphify-out/graph.json" \
65
65
  --from ehealth-member \
66
66
  --max-depth 8 --sources
67
67
  ```
@@ -92,11 +92,6 @@ _PLATFORM_CONFIG: dict[str, dict] = {
92
92
  }
93
93
 
94
94
 
95
- def _claude_plugin_root() -> Path | None:
96
- raw = os.environ.get("CLAUDE_PLUGIN_ROOT", "").strip()
97
- return Path(raw).expanduser().resolve() if raw else None
98
-
99
-
100
95
  def _claude_project_dir() -> Path | None:
101
96
  raw = os.environ.get("CLAUDE_PROJECT_DIR", "").strip()
102
97
  return Path(raw).expanduser().resolve() if raw else None
@@ -106,9 +101,6 @@ def _default_repos_root() -> Path:
106
101
  project_dir = _claude_project_dir()
107
102
  if project_dir is not None:
108
103
  return project_dir / "repos"
109
- plugin_root = _claude_plugin_root()
110
- if plugin_root is not None:
111
- return plugin_root / "repos"
112
104
  return Path.home() / ".graphify" / "repos"
113
105
 
114
106
 
@@ -126,11 +118,7 @@ def install(platform: str = "claude") -> None:
126
118
  print(f"error: {cfg['skill_file']} not found in package - reinstall ckgraphify", file=sys.stderr)
127
119
  sys.exit(1)
128
120
 
129
- plugin_root = _claude_plugin_root() if platform == "claude" else None
130
- installing_to_plugin = plugin_root is not None
131
- if plugin_root is not None:
132
- skill_dst = plugin_root / "skills" / "ckgraphify" / "SKILL.md"
133
- elif platform == "claude" and os.environ.get("CLAUDE_CONFIG_DIR"):
121
+ if platform == "claude" and os.environ.get("CLAUDE_CONFIG_DIR"):
134
122
  _claude_base = Path(os.environ["CLAUDE_CONFIG_DIR"])
135
123
  skill_dst = _claude_base / "skills" / "ckgraphify" / "SKILL.md"
136
124
  else:
@@ -149,7 +137,7 @@ def install(platform: str = "claude") -> None:
149
137
  (skill_dst.parent / ".ckgraphify_version").write_text(__version__, encoding="utf-8")
150
138
  print(f" skill installed -> {skill_dst}")
151
139
 
152
- if cfg["claude_md"] and not installing_to_plugin:
140
+ if cfg["claude_md"]:
153
141
  # Register in ~/.claude/CLAUDE.md (Claude Code only)
154
142
  claude_md = Path.home() / ".claude" / "CLAUDE.md"
155
143
  if claude_md.exists():
@@ -163,9 +151,6 @@ def install(platform: str = "claude") -> None:
163
151
  claude_md.parent.mkdir(parents=True, exist_ok=True)
164
152
  claude_md.write_text(_SKILL_REGISTRATION.lstrip(), encoding="utf-8")
165
153
  print(f" CLAUDE.md -> created at {claude_md}")
166
- elif installing_to_plugin:
167
- print(f" plugin root -> {plugin_root}")
168
-
169
154
  # Refresh version stamps in all other previously-installed skill dirs so
170
155
  # stale-version warnings don't fire for platforms not explicitly re-installed.
171
156
  _refresh_all_version_stamps()
@@ -485,7 +470,6 @@ def _clone_repo(url: str, branch: str | None = None, out_dir: Path | None = None
485
470
 
486
471
  Clones into ~/.graphify/repos/<owner>/<repo> by default so repeated
487
472
  runs on the same URL reuse the existing clone (git pull instead of clone).
488
- In Claude Code plugin mode, the cache lives under ${CLAUDE_PLUGIN_ROOT}/repos.
489
473
  """
490
474
  import subprocess as _sp
491
475
  import re as _re
@@ -1330,9 +1314,9 @@ def main() -> None:
1330
1314
  sys.exit(1)
1331
1315
  else:
1332
1316
  graph_paths.append(Path(a)); i += 1
1333
- if len(graph_paths) < 2:
1317
+ if len(graph_paths) < 1:
1334
1318
  print(
1335
- "Usage: graphify merge-main-graphs <graph-main1.json> <graph-main2.json> [...] "
1319
+ "Usage: graphify merge-main-graphs <graph-main1.json> [graph-main2.json ...] "
1336
1320
  "[--out graph-main-merged.json] [--report report.md]",
1337
1321
  file=sys.stderr,
1338
1322
  )
@@ -1887,10 +1871,6 @@ def main() -> None:
1887
1871
  i += 1
1888
1872
  else:
1889
1873
  i += 1
1890
- if repos_root is None:
1891
- plugin_root = _claude_plugin_root()
1892
- if plugin_root is not None:
1893
- repos_root = plugin_root / "repos"
1894
1874
  if repos_root is None:
1895
1875
  print(
1896
1876
  "Usage: graphify bridge-mtop --repos-root <path> [--graph-in in.json] "
@@ -300,9 +300,6 @@ def find_kg_banks_root(start: Path | None = None) -> Path | None:
300
300
 
301
301
 
302
302
  def default_business_map_path(start: Path | None = None) -> Path:
303
- plugin_root = os.environ.get("CLAUDE_PLUGIN_ROOT", "").strip()
304
- if plugin_root:
305
- return Path(plugin_root).expanduser().resolve() / "graphify-out" / "business-map.json"
306
303
  root = find_kg_banks_root(start)
307
304
  if root is not None:
308
305
  return root / "graphify-out" / "business-map.json"
@@ -342,18 +339,6 @@ def resolve_bound_graph_path(map_path: Path, graph_ref: str) -> Path:
342
339
 
343
340
 
344
341
  def default_business_graph_path(map_path: Path, start: Path | None = None) -> Path | None:
345
- plugin_root = os.environ.get("CLAUDE_PLUGIN_ROOT", "").strip()
346
- if plugin_root:
347
- graphify_out = Path(plugin_root).expanduser().resolve() / "graphify-out"
348
- hc_graph = graphify_out / "graph-hc.json"
349
- if hc_graph.exists():
350
- return hc_graph
351
- candidates = sorted(
352
- p for p in graphify_out.glob("graph*.json")
353
- if p.name != "business-map.json"
354
- )
355
- return candidates[0] if candidates else None
356
-
357
342
  if Path(map_path).exists():
358
343
  graph_ref = _business_map_graph_ref(load_business_map(Path(map_path)))
359
344
  if graph_ref:
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import json
4
4
  import os
5
5
  import re
6
+ import hashlib
6
7
  from collections import deque
7
8
  from dataclasses import dataclass
8
9
  from datetime import datetime, timezone
@@ -12,6 +13,7 @@ from urllib.parse import urlparse
12
13
  from graphify.graph_main_frontend_sdk import (
13
14
  collect_sdk_dependencies,
14
15
  collect_sdk_exports,
16
+ collect_sdk_internal_links,
15
17
  detect_frontend_sdk_repo,
16
18
  )
17
19
  from graphify.repo_registry import repo_metadata_for_root
@@ -1132,6 +1134,13 @@ def build_graph_main_frontend(*, repo_root: Path, out_path: Path) -> GraphMainFr
1132
1134
  edge_ids.add(k)
1133
1135
  edges.append(e)
1134
1136
 
1137
+ def unique_node_id(prefix: str, suffix: str, canonical: str) -> str:
1138
+ base = f"{prefix}__{_safe_id(suffix)}"
1139
+ if base not in node_ids:
1140
+ return base
1141
+ digest = hashlib.sha1(canonical.encode("utf-8")).hexdigest()[:8]
1142
+ return f"{base}_{digest}"
1143
+
1135
1144
  page_by_key: dict[str, str] = {}
1136
1145
  component_by_key: dict[str, str] = {}
1137
1146
  mtop_by_key: dict[str, str] = {}
@@ -1232,7 +1241,7 @@ def build_graph_main_frontend(*, repo_root: Path, out_path: Path) -> GraphMainFr
1232
1241
  if canonical in sdk_export_by_key:
1233
1242
  return sdk_export_by_key[canonical]
1234
1243
  suffix = canonical.split(":", 1)[-1]
1235
- nid = f"sdk_export__{_safe_id(suffix)}"
1244
+ nid = unique_node_id("sdk_export", suffix, canonical)
1236
1245
  add_node(
1237
1246
  {
1238
1247
  "id": nid,
@@ -1266,7 +1275,7 @@ def build_graph_main_frontend(*, repo_root: Path, out_path: Path) -> GraphMainFr
1266
1275
  if canonical in sdk_dependency_by_key:
1267
1276
  return sdk_dependency_by_key[canonical]
1268
1277
  suffix = canonical.split(":", 1)[-1]
1269
- nid = f"sdk_dependency__{_safe_id(suffix)}"
1278
+ nid = unique_node_id("sdk_dependency", suffix, canonical)
1270
1279
  add_node(
1271
1280
  {
1272
1281
  "id": nid,
@@ -1768,7 +1777,8 @@ def build_graph_main_frontend(*, repo_root: Path, out_path: Path) -> GraphMainFr
1768
1777
  mode = "frontend-sdk"
1769
1778
  detection["mode"] = mode
1770
1779
  detection["sdk_detection"] = sdk_detection.as_dict()
1771
- for export in collect_sdk_exports(repo_root):
1780
+ sdk_exports = collect_sdk_exports(repo_root)
1781
+ for export in sdk_exports:
1772
1782
  ensure_sdk_export(
1773
1783
  kind=export.kind,
1774
1784
  label=export.label,
@@ -1779,6 +1789,25 @@ def build_graph_main_frontend(*, repo_root: Path, out_path: Path) -> GraphMainFr
1779
1789
  source_file=export.source_file,
1780
1790
  source_location=export.source_location,
1781
1791
  )
1792
+ for link in collect_sdk_internal_links(repo_root, sdk_exports):
1793
+ source_nid = sdk_export_by_key.get(link.source_key)
1794
+ target_nid = sdk_export_by_key.get(link.target_key)
1795
+ if not source_nid or not target_nid:
1796
+ continue
1797
+ add_edge(
1798
+ {
1799
+ "source": source_nid,
1800
+ "target": target_nid,
1801
+ "relation": link.relation,
1802
+ "confidence": "EXTRACTED",
1803
+ "confidence_score": 1.0,
1804
+ "source_file": link.source_file,
1805
+ "source_location": link.source_location,
1806
+ "weight": 1.0,
1807
+ "evidence": link.evidence,
1808
+ **({"resolution_kind": link.resolution_kind} if link.resolution_kind else {}),
1809
+ }
1810
+ )
1782
1811
  else:
1783
1812
  detection["sdk_detection"] = sdk_detection.as_dict()
1784
1813
  propagated_file_owners = propagate_file_owners(max_depth=3)