codenib 0.1.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 (386) hide show
  1. codenib/__init__.py +62 -0
  2. codenib/__main__.py +7 -0
  3. codenib/_lazy.py +36 -0
  4. codenib/_version.py +26 -0
  5. codenib/agent/__init__.py +159 -0
  6. codenib/agent/agent_types.py +40 -0
  7. codenib/agent/boundary.py +129 -0
  8. codenib/agent/compile.py +219 -0
  9. codenib/agent/extract_agent.py +101 -0
  10. codenib/agent/harness.py +326 -0
  11. codenib/agent/history.py +242 -0
  12. codenib/agent/lsp_graph.py +878 -0
  13. codenib/agent/lsp_provider.py +484 -0
  14. codenib/agent/rerank_agent.py +419 -0
  15. codenib/agent/resource_guard.py +90 -0
  16. codenib/agent/route_context.py +391 -0
  17. codenib/agent/runner.py +2005 -0
  18. codenib/agent/runtime/__init__.py +16 -0
  19. codenib/agent/runtime/context.py +189 -0
  20. codenib/agent/runtime/trace.py +101 -0
  21. codenib/agent/skills/__init__.py +25 -0
  22. codenib/agent/skills/_graphnav.py +235 -0
  23. codenib/agent/skills/bm25_search/__init__.py +0 -0
  24. codenib/agent/skills/bm25_search/config.yaml +63 -0
  25. codenib/agent/skills/bm25_search/executor.py +80 -0
  26. codenib/agent/skills/bm25_search/skill.md +47 -0
  27. codenib/agent/skills/code_to_query/__init__.py +0 -0
  28. codenib/agent/skills/code_to_query/config.yaml +38 -0
  29. codenib/agent/skills/code_to_query/executor.py +69 -0
  30. codenib/agent/skills/code_to_query/skill.md +7 -0
  31. codenib/agent/skills/codenib_context/__init__.py +0 -0
  32. codenib/agent/skills/codenib_context/config.yaml +44 -0
  33. codenib/agent/skills/codenib_context/executor.py +184 -0
  34. codenib/agent/skills/codenib_context/skill.md +21 -0
  35. codenib/agent/skills/context.py +107 -0
  36. codenib/agent/skills/core.py +153 -0
  37. codenib/agent/skills/crossencoder_rerank/__init__.py +3 -0
  38. codenib/agent/skills/crossencoder_rerank/config.yaml +41 -0
  39. codenib/agent/skills/crossencoder_rerank/executor.py +62 -0
  40. codenib/agent/skills/crossencoder_rerank/skill.md +7 -0
  41. codenib/agent/skills/embedding_search/__init__.py +0 -0
  42. codenib/agent/skills/embedding_search/config.yaml +53 -0
  43. codenib/agent/skills/embedding_search/executor.py +57 -0
  44. codenib/agent/skills/embedding_search/skill.md +47 -0
  45. codenib/agent/skills/find_callees/__init__.py +0 -0
  46. codenib/agent/skills/find_callees/config.yaml +22 -0
  47. codenib/agent/skills/find_callees/executor.py +23 -0
  48. codenib/agent/skills/find_callees/skill.md +11 -0
  49. codenib/agent/skills/find_callers/__init__.py +0 -0
  50. codenib/agent/skills/find_callers/config.yaml +22 -0
  51. codenib/agent/skills/find_callers/executor.py +23 -0
  52. codenib/agent/skills/find_callers/skill.md +11 -0
  53. codenib/agent/skills/hybrid_search/__init__.py +0 -0
  54. codenib/agent/skills/hybrid_search/config.yaml +38 -0
  55. codenib/agent/skills/hybrid_search/executor.py +132 -0
  56. codenib/agent/skills/hybrid_search/skill.md +53 -0
  57. codenib/agent/skills/llm_rerank/__init__.py +0 -0
  58. codenib/agent/skills/llm_rerank/config.yaml +33 -0
  59. codenib/agent/skills/llm_rerank/executor.py +37 -0
  60. codenib/agent/skills/llm_rerank/skill.md +7 -0
  61. codenib/agent/skills/loader.py +240 -0
  62. codenib/agent/skills/lsp_definition/config.yaml +35 -0
  63. codenib/agent/skills/lsp_definition/executor.py +32 -0
  64. codenib/agent/skills/lsp_definition/skill.md +18 -0
  65. codenib/agent/skills/lsp_references/config.yaml +40 -0
  66. codenib/agent/skills/lsp_references/executor.py +34 -0
  67. codenib/agent/skills/lsp_references/skill.md +17 -0
  68. codenib/agent/skills/lsp_route/config.yaml +35 -0
  69. codenib/agent/skills/lsp_route/executor.py +34 -0
  70. codenib/agent/skills/lsp_route/skill.md +23 -0
  71. codenib/agent/skills/registry.py +136 -0
  72. codenib/agent/skills/repository_search/config.yaml +49 -0
  73. codenib/agent/skills/repository_search/executor.py +512 -0
  74. codenib/agent/skills/repository_search/skill.md +28 -0
  75. codenib/agent/skills/trace/__init__.py +0 -0
  76. codenib/agent/skills/trace/config.yaml +30 -0
  77. codenib/agent/skills/trace/executor.py +27 -0
  78. codenib/agent/skills/trace/skill.md +12 -0
  79. codenib/agent/skills/typecheck.py +302 -0
  80. codenib/agent/tool_schema.py +158 -0
  81. codenib/agent/tools/__init__.py +41 -0
  82. codenib/agent/tools/defaults.py +1045 -0
  83. codenib/agent/tools/spec.py +103 -0
  84. codenib/agent/utils.py +149 -0
  85. codenib/cli.py +1079 -0
  86. codenib/clients/__init__.py +5 -0
  87. codenib/clients/claude_agent.py +534 -0
  88. codenib/clients/codex_agent.py +314 -0
  89. codenib/code_chunker.py +730 -0
  90. codenib/code_chunking/__init__.py +113 -0
  91. codenib/code_chunking/base.py +592 -0
  92. codenib/code_chunking/cpp_chunker.py +278 -0
  93. codenib/code_chunking/csharp_chunker.py +210 -0
  94. codenib/code_chunking/go_chunker.py +213 -0
  95. codenib/code_chunking/java_chunker.py +188 -0
  96. codenib/code_chunking/js_chunker.py +251 -0
  97. codenib/code_chunking/kotlin_chunker.py +253 -0
  98. codenib/code_chunking/lua_chunker.py +94 -0
  99. codenib/code_chunking/php_chunker.py +204 -0
  100. codenib/code_chunking/python_chunker.py +267 -0
  101. codenib/code_chunking/ruby_chunker.py +198 -0
  102. codenib/code_chunking/rust_chunker.py +195 -0
  103. codenib/code_chunking/scala_chunker.py +182 -0
  104. codenib/code_chunking/swift_chunker.py +182 -0
  105. codenib/compat_pickle.py +48 -0
  106. codenib/compiler/__init__.py +93 -0
  107. codenib/compiler/index_builders.py +868 -0
  108. codenib/compiler/index_compiler.py +488 -0
  109. codenib/compiler/manifest.py +221 -0
  110. codenib/compiler/params.py +126 -0
  111. codenib/compiler/resources.py +267 -0
  112. codenib/compiler/skill_context.py +641 -0
  113. codenib/compiler/snapshot_store.py +311 -0
  114. codenib/compiler/verification.py +131 -0
  115. codenib/dataset/__init__.py +21 -0
  116. codenib/dataset/base.py +78 -0
  117. codenib/dataset/codenib_base.py +374 -0
  118. codenib/dataset/codenib_synthesis.py +540 -0
  119. codenib/dataset/collect/difficulty_classifier.py +511 -0
  120. codenib/dataset/collect/swebench_sample.py +766 -0
  121. codenib/dataset/gt_locate.py +831 -0
  122. codenib/dataset/local_json.py +184 -0
  123. codenib/dataset/locbench.py +254 -0
  124. codenib/dataset/swebench.py +344 -0
  125. codenib/dataset/swebench_multilingual.py +283 -0
  126. codenib/dataset/synthesize/__init__.py +71 -0
  127. codenib/dataset/synthesize/_agent.py +125 -0
  128. codenib/dataset/synthesize/_types.py +146 -0
  129. codenib/dataset/synthesize/context_loader.py +601 -0
  130. codenib/dataset/synthesize/query_curator.py +713 -0
  131. codenib/dataset/synthesize/query_synthesizer.py +546 -0
  132. codenib/dataset/synthesize/verifier.py +425 -0
  133. codenib/dataset/synthesize/vocab_guard.py +264 -0
  134. codenib/dataset/utils.py +380 -0
  135. codenib/eval/agent_runner/__init__.py +296 -0
  136. codenib/eval/agent_runner/baseline.py +299 -0
  137. codenib/eval/agent_runner/batch.py +176 -0
  138. codenib/eval/agent_runner/contexts.py +135 -0
  139. codenib/eval/agent_runner/feedback.py +135 -0
  140. codenib/eval/agent_runner/feedback_summary.py +326 -0
  141. codenib/eval/agent_runner/format_diagnostics.py +115 -0
  142. codenib/eval/agent_runner/live_lsp_provider.py +377 -0
  143. codenib/eval/agent_runner/loc_baseline.py +355 -0
  144. codenib/eval/agent_runner/lsp_agent_ab.py +577 -0
  145. codenib/eval/agent_runner/lsp_agent_study.py +408 -0
  146. codenib/eval/agent_runner/lsp_agent_study_analysis.py +411 -0
  147. codenib/eval/agent_runner/lsp_agent_study_artifacts.py +413 -0
  148. codenib/eval/agent_runner/lsp_agent_study_manifest.py +402 -0
  149. codenib/eval/agent_runner/lsp_agent_study_runner.py +694 -0
  150. codenib/eval/agent_runner/lsp_baseline.py +251 -0
  151. codenib/eval/agent_runner/lsp_latency.py +267 -0
  152. codenib/eval/agent_runner/lsp_provider_cli.py +273 -0
  153. codenib/eval/agent_runner/lsp_provider_validation.py +594 -0
  154. codenib/eval/agent_runner/lsp_readiness.py +167 -0
  155. codenib/eval/agent_runner/lsp_replay_benchmark.py +1072 -0
  156. codenib/eval/agent_runner/metrics.py +80 -0
  157. codenib/eval/agent_runner/orchestrator.py +173 -0
  158. codenib/eval/agent_runner/pareto.py +154 -0
  159. codenib/eval/agent_runner/prebuilt.py +484 -0
  160. codenib/eval/agent_runner/preload.py +219 -0
  161. codenib/eval/agent_runner/promotion.py +172 -0
  162. codenib/eval/agent_runner/query_sweep.py +432 -0
  163. codenib/eval/agent_runner/results.py +69 -0
  164. codenib/eval/agent_runner/scoring.py +134 -0
  165. codenib/eval/agent_runner/sweep.py +619 -0
  166. codenib/eval/agent_runner/sweep_config.py +160 -0
  167. codenib/eval/agent_runner/symbols.py +67 -0
  168. codenib/eval/agent_runner/trace_summary.py +405 -0
  169. codenib/eval/agent_runner/verify_expand.py +219 -0
  170. codenib/eval/artifact_bundle.py +322 -0
  171. codenib/eval/artifact_integrity.py +332 -0
  172. codenib/eval/artifact_manifest.py +236 -0
  173. codenib/eval/experiments/__init__.py +5 -0
  174. codenib/eval/experiments/lsp_agent_study_policy.py +55 -0
  175. codenib/eval/loc_agent_runner.py +37 -0
  176. codenib/eval/reports/__init__.py +5 -0
  177. codenib/eval/reports/cost_arm_report.py +810 -0
  178. codenib/eval/retrieval_eval.py +557 -0
  179. codenib/graph/__init__.py +44 -0
  180. codenib/graph/backend_alignment.py +184 -0
  181. codenib/graph/code_graph.py +1218 -0
  182. codenib/graph/dependency.py +229 -0
  183. codenib/graph/hierarchy.py +828 -0
  184. codenib/graph/incremental/__init__.py +15 -0
  185. codenib/graph/incremental/change_mgr.py +165 -0
  186. codenib/graph/incremental/graph_patcher.py +139 -0
  187. codenib/graph/incremental/lsp_client.py +1271 -0
  188. codenib/graph/incremental/patcher_base.py +1364 -0
  189. codenib/graph/incremental/patcher_cpp.py +796 -0
  190. codenib/graph/incremental/patcher_go.py +56 -0
  191. codenib/graph/incremental/patcher_python.py +43 -0
  192. codenib/graph/incremental/patcher_rust.py +113 -0
  193. codenib/graph/incremental/patcher_ts.py +49 -0
  194. codenib/graph/incremental/subgraph_mgr.py +1028 -0
  195. codenib/graph/layers.py +325 -0
  196. codenib/graph/roi_subgraph.py +394 -0
  197. codenib/graph/setup.py +689 -0
  198. codenib/graph/traverse_graph.py +237 -0
  199. codenib/index/__init__.py +42 -0
  200. codenib/index/embedding/__init__.py +48 -0
  201. codenib/index/embedding/builders.py +190 -0
  202. codenib/index/embedding/model_policy.py +62 -0
  203. codenib/index/embedding/prompt_registry.py +87 -0
  204. codenib/index/embedding/vector_store.py +1458 -0
  205. codenib/index/incremental/__init__.py +30 -0
  206. codenib/index/incremental/chunk_store.py +409 -0
  207. codenib/index/incremental/embeddings_cache.py +196 -0
  208. codenib/index/incremental/git_diff.py +221 -0
  209. codenib/index/incremental/index_updater.py +283 -0
  210. codenib/index/incremental/state.py +92 -0
  211. codenib/index/regex_idx/__init__.py +7 -0
  212. codenib/index/regex_idx/regex_idx.py +150 -0
  213. codenib/index/rerank/__init__.py +23 -0
  214. codenib/index/rerank/cross_encoder.py +356 -0
  215. codenib/index/sparse_idx/__init__.py +9 -0
  216. codenib/index/sparse_idx/bm25_index.py +724 -0
  217. codenib/index/trigram/__init__.py +17 -0
  218. codenib/index/trigram/zoekt_searcher.py +371 -0
  219. codenib/languages.py +1035 -0
  220. codenib/llm/__init__.py +33 -0
  221. codenib/llm/diagnostics.py +170 -0
  222. codenib/llm/litellm_chat.py +422 -0
  223. codenib/llm/options.py +139 -0
  224. codenib/llm/usage.py +182 -0
  225. codenib/log_utils.py +301 -0
  226. codenib/ls_index/__init__.py +17 -0
  227. codenib/ls_index/clangd_decode.py +912 -0
  228. codenib/ls_index/clangd_indexer.py +1400 -0
  229. codenib/ls_index/index_quality.py +442 -0
  230. codenib/ls_index/lsp_graph_decode.py +449 -0
  231. codenib/ls_index/lsp_indexer.py +246 -0
  232. codenib/ls_router.py +507 -0
  233. codenib/mcp/__init__.py +13 -0
  234. codenib/mcp/__main__.py +9 -0
  235. codenib/mcp/context.py +211 -0
  236. codenib/mcp/prompts.py +76 -0
  237. codenib/mcp/server.py +463 -0
  238. codenib/mcp/tools/__init__.py +9 -0
  239. codenib/mcp/tools/dependency.py +56 -0
  240. codenib/mcp/tools/lsp.py +119 -0
  241. codenib/mcp/tools/search.py +224 -0
  242. codenib/model/__init__.py +49 -0
  243. codenib/model/agentless_pipeline.py +484 -0
  244. codenib/model/bm25_retrieve_pipeline.py +100 -0
  245. codenib/model/dense_graph_expand_rerank_pipeline.py +300 -0
  246. codenib/model/embedding_retrieve_pipeline.py +152 -0
  247. codenib/model/graph_augmented_rerank_pipeline.py +19 -0
  248. codenib/model/graph_retrieve_pipeline.py +259 -0
  249. codenib/model/hybrid_retrieve_pipeline.py +256 -0
  250. codenib/model/retrieval_planner.py +377 -0
  251. codenib/model/retrieve_rerank_pipeline.py +1023 -0
  252. codenib/ops/expand.py +299 -0
  253. codenib/ops/filter.py +159 -0
  254. codenib/ops/rerank.py +246 -0
  255. codenib/ops/retrieve.py +293 -0
  256. codenib/ops/transform.py +34 -0
  257. codenib/paths.py +91 -0
  258. codenib/profiler.py +506 -0
  259. codenib/repository_filters.py +103 -0
  260. codenib/repository_summary.py +161 -0
  261. codenib/scip_interface/__init__.py +120 -0
  262. codenib/scip_interface/lsp_occurrence_index.py +327 -0
  263. codenib/scip_interface/rust_analyzer.py +29 -0
  264. codenib/scip_interface/scip-environment.yml +14 -0
  265. codenib/scip_interface/scip.proto +890 -0
  266. codenib/scip_interface/scip_decode_core.py +190 -0
  267. codenib/scip_interface/scip_decode_csharp.py +143 -0
  268. codenib/scip_interface/scip_decode_go.py +433 -0
  269. codenib/scip_interface/scip_decode_java.py +1117 -0
  270. codenib/scip_interface/scip_decode_php.py +194 -0
  271. codenib/scip_interface/scip_decode_python.py +400 -0
  272. codenib/scip_interface/scip_decode_ruby.py +464 -0
  273. codenib/scip_interface/scip_decode_rust.py +584 -0
  274. codenib/scip_interface/scip_decode_ts.py +542 -0
  275. codenib/scip_interface/scip_decode_utils.py +52 -0
  276. codenib/scip_interface/scip_indexer_base.py +728 -0
  277. codenib/scip_interface/scip_indexer_csharp.py +166 -0
  278. codenib/scip_interface/scip_indexer_go.py +164 -0
  279. codenib/scip_interface/scip_indexer_java.py +201 -0
  280. codenib/scip_interface/scip_indexer_php.py +548 -0
  281. codenib/scip_interface/scip_indexer_python.py +500 -0
  282. codenib/scip_interface/scip_indexer_ruby.py +369 -0
  283. codenib/scip_interface/scip_indexer_rust.py +183 -0
  284. codenib/scip_interface/scip_indexer_ts.py +616 -0
  285. codenib/scip_interface/scip_install.sh +8 -0
  286. codenib/scip_interface/scip_pb2.py +80 -0
  287. codenib/search.py +371 -0
  288. codenib/source_fingerprint.py +198 -0
  289. codenib/types.py +105 -0
  290. codenib/utils.py +93 -0
  291. codenib/web/__init__.py +9 -0
  292. codenib/web/app.py +467 -0
  293. codenib/web/codemap.py +753 -0
  294. codenib/web/commit_window.py +296 -0
  295. codenib/web/config.py +331 -0
  296. codenib/web/edge_label.py +332 -0
  297. codenib/web/frontend/assets/AskBar-Q_2zSw_D.js +31 -0
  298. codenib/web/frontend/assets/CodeGraph-C5zIOx4j.js +3 -0
  299. codenib/web/frontend/assets/CodePanel-BE7Wg1wI.js +1 -0
  300. codenib/web/frontend/assets/Codemap-C5Xm7Go_.js +2 -0
  301. codenib/web/frontend/assets/GraphView-CBBbxS9a.js +2 -0
  302. codenib/web/frontend/assets/Header-CUW7gfQl.js +1 -0
  303. codenib/web/frontend/assets/HighlightedBlock-H7FGwbAI.js +1 -0
  304. codenib/web/frontend/assets/HighlightedCode-CTRFPFV9.js +2 -0
  305. codenib/web/frontend/assets/Mermaid-NhydXLyh.js +303 -0
  306. codenib/web/frontend/assets/api-DMyba6Hn.js +1 -0
  307. codenib/web/frontend/assets/arc-Cj6TUG7b.js +1 -0
  308. codenib/web/frontend/assets/architectureDiagram-3BPJPVTR-BXC7SbpZ.js +36 -0
  309. codenib/web/frontend/assets/blockDiagram-GPEHLZMM-BYhhgH_O.js +132 -0
  310. codenib/web/frontend/assets/c4Diagram-AAUBKEIU-BPe9Xb1K.js +10 -0
  311. codenib/web/frontend/assets/channel-DGptmZnr.js +1 -0
  312. codenib/web/frontend/assets/chunk-2J33WTMH-mJuoTx1I.js +1 -0
  313. codenib/web/frontend/assets/chunk-4BX2VUAB-DyIPfnHV.js +1 -0
  314. codenib/web/frontend/assets/chunk-55IACEB6-C8bSn9Qq.js +1 -0
  315. codenib/web/frontend/assets/chunk-727SXJPM-CgiBqZFm.js +206 -0
  316. codenib/web/frontend/assets/chunk-AQP2D5EJ-DOOlLotN.js +231 -0
  317. codenib/web/frontend/assets/chunk-FMBD7UC4-mf47mSz2.js +15 -0
  318. codenib/web/frontend/assets/chunk-ND2GUHAM-D6lobpla.js +1 -0
  319. codenib/web/frontend/assets/chunk-QZHKN3VN-D9a00sWs.js +1 -0
  320. codenib/web/frontend/assets/classDiagram-4FO5ZUOK-wDtvnBsq.js +1 -0
  321. codenib/web/frontend/assets/classDiagram-v2-Q7XG4LA2-wDtvnBsq.js +1 -0
  322. codenib/web/frontend/assets/cose-bilkent-S5V4N54A-JwC1FU9v.js +1 -0
  323. codenib/web/frontend/assets/cytoscape.esm-CkSuTymj.js +321 -0
  324. codenib/web/frontend/assets/dagre-BM42HDAG-BTm6Sb-x.js +4 -0
  325. codenib/web/frontend/assets/defaultLocale-DX6XiGOO.js +1 -0
  326. codenib/web/frontend/assets/diagram-2AECGRRQ-C7XNwfvk.js +43 -0
  327. codenib/web/frontend/assets/diagram-5GNKFQAL-DcLriIgZ.js +10 -0
  328. codenib/web/frontend/assets/diagram-KO2AKTUF-Bo-6Bpfo.js +3 -0
  329. codenib/web/frontend/assets/diagram-LMA3HP47-M4Nh-qq-.js +24 -0
  330. codenib/web/frontend/assets/diagram-OG6HWLK6-BUT1QVEf.js +24 -0
  331. codenib/web/frontend/assets/erDiagram-TEJ5UH35-DViEMoeP.js +85 -0
  332. codenib/web/frontend/assets/flowDiagram-I6XJVG4X-DjBB1gSM.js +162 -0
  333. codenib/web/frontend/assets/ganttDiagram-6RSMTGT7-Drhb89TN.js +292 -0
  334. codenib/web/frontend/assets/gitGraphDiagram-PVQCEYII-CwqblmoY.js +106 -0
  335. codenib/web/frontend/assets/graph--OzhPTMs.js +1 -0
  336. codenib/web/frontend/assets/highlight-CDab0zVI.css +1 -0
  337. codenib/web/frontend/assets/highlight-CnfLc-V2.js +5 -0
  338. codenib/web/frontend/assets/index-BHP8c3Tq.js +9 -0
  339. codenib/web/frontend/assets/index-CKJrqu86.css +1 -0
  340. codenib/web/frontend/assets/infoDiagram-5YYISTIA-BKq62LUG.js +2 -0
  341. codenib/web/frontend/assets/init-Gi6I4Gst.js +1 -0
  342. codenib/web/frontend/assets/ishikawaDiagram-YF4QCWOH-DBAgfp9d.js +70 -0
  343. codenib/web/frontend/assets/journeyDiagram-JHISSGLW-DwHStEV9.js +139 -0
  344. codenib/web/frontend/assets/kanban-definition-UN3LZRKU-CWPLLTQn.js +89 -0
  345. codenib/web/frontend/assets/katex-HP8lGamR.js +257 -0
  346. codenib/web/frontend/assets/layout-SsrduOYp.js +1 -0
  347. codenib/web/frontend/assets/linear-DNcGZFnN.js +1 -0
  348. codenib/web/frontend/assets/mindmap-definition-RKZ34NQL-FZHYTpdO.js +96 -0
  349. codenib/web/frontend/assets/ordinal-Cboi1Yqb.js +1 -0
  350. codenib/web/frontend/assets/page-Bn-Q8MBy.js +6 -0
  351. codenib/web/frontend/assets/page-CESv5Ezw.js +2 -0
  352. codenib/web/frontend/assets/page-CHsElUu2.js +1 -0
  353. codenib/web/frontend/assets/page-D1WhIRzi.js +1 -0
  354. codenib/web/frontend/assets/pieDiagram-4H26LBE5-DKlC7xtb.js +30 -0
  355. codenib/web/frontend/assets/quadrantDiagram-W4KKPZXB-DldkGUZS.js +7 -0
  356. codenib/web/frontend/assets/requirementDiagram-4Y6WPE33-Dndb6w-j.js +84 -0
  357. codenib/web/frontend/assets/sankeyDiagram-5OEKKPKP-B2ewtJAC.js +40 -0
  358. codenib/web/frontend/assets/sequenceDiagram-3UESZ5HK-2N2KbYFt.js +162 -0
  359. codenib/web/frontend/assets/stateDiagram-AJRCARHV-CLwUuVlz.js +1 -0
  360. codenib/web/frontend/assets/stateDiagram-v2-BHNVJYJU-IiZdW_Og.js +1 -0
  361. codenib/web/frontend/assets/timeline-definition-PNZ67QCA-D_IdllIp.js +120 -0
  362. codenib/web/frontend/assets/vennDiagram-CIIHVFJN-CSU7pI9M.js +34 -0
  363. codenib/web/frontend/assets/wardley-L42UT6IY-Bv1Eg1Al.js +161 -0
  364. codenib/web/frontend/assets/wardleyDiagram-YWT4CUSO-Di3bYwLm.js +78 -0
  365. codenib/web/frontend/assets/xychartDiagram-2RQKCTM6-CPHWEBiN.js +7 -0
  366. codenib/web/frontend/codenib-icon.svg +34 -0
  367. codenib/web/frontend/index.html +29 -0
  368. codenib/web/frontend/runtime-config.js +1 -0
  369. codenib/web/launcher.py +252 -0
  370. codenib/web/local.py +196 -0
  371. codenib/web/repo_registry.py +561 -0
  372. codenib/web/schemas.py +329 -0
  373. codenib/web/static_server.py +213 -0
  374. codenib/wiki/__init__.py +15 -0
  375. codenib/wiki/agent_wiki.py +4665 -0
  376. codenib/wiki/builder.py +849 -0
  377. codenib/wiki/evidence.py +905 -0
  378. codenib/wiki/narrator.py +258 -0
  379. codenib/wiki/outline.py +1432 -0
  380. codenib/wiki/quality.py +932 -0
  381. codenib-0.1.0.dist-info/METADATA +293 -0
  382. codenib-0.1.0.dist-info/RECORD +386 -0
  383. codenib-0.1.0.dist-info/WHEEL +5 -0
  384. codenib-0.1.0.dist-info/entry_points.txt +13 -0
  385. codenib-0.1.0.dist-info/licenses/LICENSE +201 -0
  386. codenib-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,584 @@
1
+ #!/usr/bin/env python3
2
+
3
+ # SPDX-FileCopyrightText: 2025-2026 CodeNib Contributors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ """
8
+ SCIP decoder for Rust projects.
9
+
10
+ Decodes SCIP index files into CodeGraph format, focusing on:
11
+ - Structs (treated as classes)
12
+ - Impl blocks and methods
13
+ - Free functions
14
+ - Traits (treated as classes)
15
+ """
16
+ import re
17
+
18
+ try:
19
+ import tomllib
20
+ except ModuleNotFoundError:
21
+ import tomli as tomllib
22
+ from pathlib import Path
23
+
24
+ from ..graph.code_graph import CodeGraph
25
+ from ..log_utils import get_logger, register_scip_logger
26
+ from ..types import (
27
+ EDGE_TYPE_CONTAIN,
28
+ NODE_TYPE_CLASS,
29
+ NODE_TYPE_FIELD,
30
+ NODE_TYPE_FUNCTION,
31
+ NODE_TYPE_METHOD,
32
+ ROOT_NODE,
33
+ )
34
+ from .scip_indexer_base import extract_scip_blocks, extract_symbol
35
+
36
+
37
+ class SCIPRustGraphDecoder:
38
+ """
39
+ Decoder for Rust SCIP indices.
40
+
41
+ Parses decoded SCIP index files and builds a CodeGraph representing:
42
+ - Structs as classes
43
+ - Traits as classes
44
+ - Impl blocks and their methods
45
+ - Free functions
46
+ - References between symbols
47
+ """
48
+
49
+ def __init__(self, index_file_path, project_root=None):
50
+ """
51
+ Initialize the Rust SCIP decoder.
52
+
53
+ Args:
54
+ index_file_path: Path to the decoded SCIP index file
55
+ project_root: Root directory of the Rust project
56
+ """
57
+ self.index_file_path = index_file_path
58
+ self.project_root = Path(project_root) if project_root else None
59
+ self.code_graph = CodeGraph(project_root)
60
+ self.indexed_directories = set()
61
+ self.logger = get_logger(__name__)
62
+ # Register this module for SCIP debug logging
63
+ register_scip_logger(__name__)
64
+
65
+ # Internal workspace crate names (loaded lazily on first document)
66
+ self.internal_crates = None
67
+
68
+ def decode(self):
69
+ """
70
+ Decode the SCIP index and build the CodeGraph.
71
+
72
+ Returns:
73
+ CodeGraph: The constructed code graph
74
+ """
75
+ self.logger.info(f"Starting SCIP Rust decode from {self.index_file_path}")
76
+ try:
77
+ with open(self.index_file_path, "r") as f:
78
+ content = f.read()
79
+ except Exception as e:
80
+ self.logger.error(f"Error reading SCIP index file: {e}")
81
+ raise
82
+
83
+ # Parse documents
84
+ document_blocks = re.findall(
85
+ r"documents\s*{(.*?)(?=documents\s*{|$)", content, re.DOTALL
86
+ )
87
+
88
+ # Add the root node to the graph
89
+ self.code_graph.add_root_node(ROOT_NODE)
90
+
91
+ # Process all documents. Batch edge insertion: per-edge add_edges is
92
+ # O(E^2) (igraph reindexes each call); buffering flushes once as O(E).
93
+ with self.code_graph.batch_edges():
94
+ for document in document_blocks:
95
+ self._process_document(document)
96
+
97
+ return self.code_graph
98
+
99
+ def _process_document(self, document_text):
100
+ """
101
+ Process a single document block from the SCIP index.
102
+
103
+ Args:
104
+ document_text: Text content of the document block
105
+ """
106
+ # Extract file path
107
+ file_match = re.search(r'relative_path:\s*"([^"]+)"', document_text)
108
+ if not file_match:
109
+ return
110
+
111
+ file_path = file_match.group(1)
112
+
113
+ # Only process Rust files
114
+ if not file_path.endswith(".rs"):
115
+ return
116
+
117
+ # Add directory hierarchy
118
+ dir_path = Path(file_path).parent
119
+ while dir_path != dir_path.parent: # Stop at the root directory
120
+ dir_path_str = str(dir_path)
121
+ if dir_path_str not in self.indexed_directories:
122
+ self.code_graph.add_directory_node(dir_path_str)
123
+ self.indexed_directories.add(dir_path_str)
124
+ # Add containment edge from parent directory
125
+ self.code_graph._add_edge(
126
+ str(dir_path.parent), dir_path_str, EDGE_TYPE_CONTAIN
127
+ )
128
+ dir_path = dir_path.parent
129
+
130
+ # Add file node
131
+ self.code_graph.add_file_node(file_path)
132
+
133
+ # Add file containment edge
134
+ self.code_graph._add_edge(
135
+ str(Path(file_path).parent), file_path, EDGE_TYPE_CONTAIN
136
+ )
137
+
138
+ # Load workspace crate names once (for third-party filtering)
139
+ if self.internal_crates is None:
140
+ self.internal_crates = self._load_workspace_crates()
141
+
142
+ # Process occurrences
143
+ # Brace-balanced: see extract_scip_blocks docstring (regex truncates
144
+ # on symbols containing literal "{" / "}").
145
+ occurrences = extract_scip_blocks(document_text, "occurrences")
146
+ for occurrence in occurrences:
147
+ self._process_occurrence(occurrence, file_path)
148
+
149
+ def _load_workspace_crates(self):
150
+ """
151
+ Parse Cargo.toml to extract internal workspace crate names.
152
+
153
+ Reads [workspace.members] glob patterns, resolves them to directories,
154
+ then reads each member's Cargo.toml to get [package].name.
155
+
156
+ Returns:
157
+ Set of internal crate names, or empty set if parsing fails.
158
+ """
159
+ if not self.project_root:
160
+ return set()
161
+
162
+ cargo_toml_path = self.project_root / "Cargo.toml"
163
+ if not cargo_toml_path.exists():
164
+ return set()
165
+
166
+ try:
167
+ with open(cargo_toml_path, "rb") as f:
168
+ cargo_data = tomllib.load(f)
169
+ except Exception as e:
170
+ self.logger.warning(f"Failed to parse {cargo_toml_path}: {e}")
171
+ return set()
172
+
173
+ # Get workspace member patterns
174
+ workspace = cargo_data.get("workspace", {})
175
+ member_patterns = workspace.get("members", [])
176
+ if not member_patterns:
177
+ # Single-crate project: use [package].name
178
+ pkg_name = cargo_data.get("package", {}).get("name")
179
+ return {pkg_name} if pkg_name else set()
180
+
181
+ # Resolve glob patterns to actual member directories
182
+ internal_crates = set()
183
+
184
+ # Include the root package itself (e.g. ripgrep defines both
185
+ # [package] name="ripgrep" and [workspace] members=["crates/*"]).
186
+ root_pkg_name = cargo_data.get("package", {}).get("name")
187
+ if root_pkg_name:
188
+ internal_crates.add(root_pkg_name)
189
+
190
+ for pattern in member_patterns:
191
+ # Malformed member globs ("", trailing slash, absolute paths) make
192
+ # Path.glob raise on Python 3.12; skip them instead of aborting.
193
+ pattern = str(pattern).strip().rstrip("/")
194
+ if not pattern or pattern.startswith("/"):
195
+ continue
196
+ try:
197
+ members = list(self.project_root.glob(pattern))
198
+ except (ValueError, IndexError, OSError):
199
+ continue
200
+ for member_dir in members:
201
+ if not member_dir.is_dir():
202
+ continue
203
+ member_cargo = member_dir / "Cargo.toml"
204
+ if not member_cargo.exists():
205
+ continue
206
+ try:
207
+ with open(member_cargo, "rb") as f:
208
+ member_data = tomllib.load(f)
209
+ crate_name = member_data.get("package", {}).get("name")
210
+ if crate_name:
211
+ internal_crates.add(crate_name)
212
+ except Exception:
213
+ continue
214
+
215
+ self.logger.info(f"Loaded {len(internal_crates)} internal workspace crates")
216
+ return internal_crates
217
+
218
+ def _process_occurrence(self, occurrence_text, file_path):
219
+ """
220
+ Process a single occurrence from the SCIP index.
221
+
222
+ Args:
223
+ occurrence_text: Text content of the occurrence
224
+ file_path: Path to the file containing this occurrence
225
+ """
226
+ # Extract ranges
227
+ ranges = re.findall(r"range:\s*(\d+)", occurrence_text)
228
+ if len(ranges) < 3:
229
+ return
230
+
231
+ line = int(ranges[0])
232
+ col_start = int(ranges[1])
233
+ col_end = int(ranges[2])
234
+
235
+ # Extract symbol (unescape-aware; see extract_symbol docstring).
236
+ symbol = extract_symbol(occurrence_text)
237
+ if not symbol:
238
+ return
239
+
240
+ # Skip local symbols (anonymous/unnamed)
241
+ if "local " in symbol:
242
+ return
243
+
244
+ # Skip module symbols — last SCIP descriptor ends with "/" (e.g. "linter/", "crate/").
245
+ # In Rust SCIP, "/" suffix is exclusively used for Module kind.
246
+ if symbol.split()[-1].endswith("/"):
247
+ return
248
+
249
+ # Filter symbols by origin
250
+ # Format: "rust-analyzer <manager> <crate_name> ..."
251
+ # cargo <crate> -> internal or third-party crate
252
+ # crate -> current project crate (always keep)
253
+ # rust-lang/rust -> stdlib (always skip)
254
+ parts = symbol.split(" ")
255
+ if len(parts) >= 3:
256
+ if parts[1] == "cargo":
257
+ if self.internal_crates and parts[2] not in self.internal_crates:
258
+ return
259
+ elif parts[1] != "crate":
260
+ return
261
+
262
+ # Extract symbol_roles
263
+ symbol_roles_match = re.search(r"symbol_roles:\s*(\d+)", occurrence_text)
264
+ if symbol_roles_match:
265
+ symbol_roles = int(symbol_roles_match.group(1))
266
+ else:
267
+ symbol_roles = 0 # Reference
268
+
269
+ # Extract enclosing range if available
270
+ enclosing_ranges = re.findall(r"enclosing_range:\s*(\d+)", occurrence_text)
271
+
272
+ # Process the symbol
273
+ self._process_symbol(
274
+ symbol, file_path, line, col_start, col_end, symbol_roles, enclosing_ranges
275
+ )
276
+
277
+ def _extract_impl_display(self, impl_member, symbol_type=None):
278
+ """Extract display name from impl pattern like [ImplType][Trait]method.
279
+
280
+ Args:
281
+ impl_member: Everything after 'impl#', e.g. '[VecBuffer][Buffer]snapshot'
282
+ or '[Error][From<io::Error>]from'
283
+
284
+ Returns:
285
+ Display string like 'VecBuffer.snapshot()' or 'Error<From<io::Error>>.from()'
286
+ """
287
+ impl_member = impl_member.replace("`", "")
288
+
289
+ # Parse bracket segments: [ImplType], [Trait], then remaining method
290
+ brackets = []
291
+ rest = impl_member
292
+ while rest.startswith("["):
293
+ # Find matching close bracket (handle nested <> inside)
294
+ depth = 0
295
+ end = -1
296
+ for i, ch in enumerate(rest):
297
+ if ch == "[":
298
+ depth += 1
299
+ elif ch == "]":
300
+ depth -= 1
301
+ if depth == 0:
302
+ end = i
303
+ break
304
+ if end == -1:
305
+ break
306
+ brackets.append(rest[1:end]) # content inside [ ]
307
+ rest = rest[end + 1 :]
308
+
309
+ # brackets[0] = ImplType, brackets[1] = Trait (if present)
310
+ impl_type = brackets[0] if brackets else ""
311
+ trait_name = brackets[1] if len(brackets) > 1 else ""
312
+ method_name = rest
313
+
314
+ # Remove lifetime-only generics (e.g. <'a> or <'a, 'db>)
315
+ # Note: escaped quotes (\\') appear in SCIP symbols
316
+ trait_name = re.sub(r"^<['\\'a-z_,\s]+>$", "", trait_name)
317
+ impl_type = re.sub(r"<['\\'a-z_,\s]+>", "", impl_type)
318
+
319
+ # Build display: ImplType[<Trait>].method()
320
+ if trait_name:
321
+ type_display = f"{impl_type}<{trait_name}>"
322
+ else:
323
+ type_display = impl_type
324
+
325
+ if method_name:
326
+ suffix = "" if symbol_type == NODE_TYPE_FIELD else "()"
327
+ return f"{type_display}.{method_name}{suffix}"
328
+ else:
329
+ return type_display
330
+
331
+ def _extract_symbol_display(self, unified_symbol, symbol_type=None):
332
+ """Extract human-readable symbol display from the unified_symbol key.
333
+
334
+ The unified_symbol key is in SCIP format: crate/module/Type#member
335
+ This extracts just the symbol part (Type.member) without crate/module prefix.
336
+
337
+ Examples:
338
+ ruff_linter/Checker#check → Checker.check()
339
+ ruff_linter/check_path → check_path()
340
+ ruff_linter/Checker → Checker
341
+ ruff_linter/SourceKind#Jupyter → SourceKind.Jupyter
342
+ ruff_linter/impl#[VecBuffer][Buffer]snapshot → VecBuffer.snapshot()
343
+ """
344
+ sym = unified_symbol
345
+
346
+ if "#" in sym:
347
+ type_path, member = sym.split("#", 1)
348
+ type_name = type_path.rsplit("/", 1)[-1] # last segment before #
349
+
350
+ if type_name == "impl":
351
+ return self._extract_impl_display(member, symbol_type)
352
+
353
+ # Clean backticks and lifetime-only generics from member
354
+ member = member.replace("`", "")
355
+ member = re.sub(r"<['\\'a-z_,\s]+>", "", member)
356
+
357
+ # Nested type: Type#Variant# → member still has trailing content
358
+ # Strip trailing # from member (e.g. enum variant Type#Variant#)
359
+ member = member.rstrip("#")
360
+
361
+ if member:
362
+ suffix = "" if symbol_type == NODE_TYPE_FIELD else "()"
363
+ return f"{type_name}.{member}{suffix}"
364
+ else:
365
+ return type_name
366
+ else:
367
+ func_name = sym.rsplit("/", 1)[-1]
368
+ func_name = func_name.replace("`", "")
369
+ if symbol_type in (NODE_TYPE_FUNCTION, NODE_TYPE_METHOD):
370
+ return f"{func_name}()"
371
+ return func_name
372
+
373
+ def _get_unified_name(self, unified_symbol, file_path, symbol_type=None):
374
+ """Generate unified_name in format file_path:SymbolDisplay.
375
+
376
+ Args:
377
+ unified_symbol: The name key (e.g. 'ruff_linter/Checker#check')
378
+ file_path: File path of the symbol (e.g. 'crates/ruff_linter/src/checker.rs')
379
+ symbol_type: NODE_TYPE_* constant
380
+
381
+ Returns:
382
+ Unified name like 'crates/ruff_linter/src/checker.rs:Checker.check()'
383
+ """
384
+ symbol_display = self._extract_symbol_display(unified_symbol, symbol_type)
385
+ if file_path and symbol_display:
386
+ return f"{file_path}:{symbol_display}"
387
+ return file_path or symbol_display or unified_symbol
388
+
389
+ def _unify_symbol_name(self, symbol, file_path):
390
+ """
391
+ Unify Rust symbol names, preserving SCIP format for uniqueness.
392
+ Prepends crate name to avoid cross-crate collisions in workspace projects.
393
+
394
+ Examples:
395
+ - rust-analyzer cargo nu-cli 0.93.1 commands/main().
396
+ -> nu-cli/commands/main
397
+ - rust-analyzer cargo nu-cli 0.93.1 Options#
398
+ -> nu-cli/Options
399
+ - rust-analyzer cargo nu-cli 0.93.1 Options#area().
400
+ -> nu-cli/Options#area
401
+
402
+ Args:
403
+ symbol: Original symbol name from SCIP
404
+ file_path: File path where the symbol was found (stored as node attribute)
405
+
406
+ Returns:
407
+ Unified symbol name in SCIP format
408
+ (``crate/module/Type#method`` or ``crate/module/function``)
409
+ """
410
+ # Extract the actual symbol part (after version or URL)
411
+ # Format: "rust-analyzer cargo <crate_name> <version> <symbol_path>"
412
+ # Split into at most 5 parts so that symbol_path (which may contain
413
+ # spaces in generics like `<B, E>`) stays intact.
414
+ parts = symbol.split(" ", 4)
415
+ if len(parts) < 5:
416
+ return None
417
+
418
+ # Extract crate name for scoping
419
+ crate_prefix = parts[2] if parts[1] == "cargo" else None
420
+
421
+ symbol_part = parts[4].rstrip(".")
422
+
423
+ # Clean up trailing markers
424
+ unified = symbol_part.rstrip("#/()")
425
+
426
+ # Prepend crate name to avoid cross-crate collisions
427
+ if crate_prefix:
428
+ unified = f"{crate_prefix}/{unified}"
429
+
430
+ return unified
431
+
432
+ def _classify_symbol_type(self, unified_symbol, original_symbol):
433
+ """
434
+ Classify the symbol type based on Rust conventions.
435
+
436
+ Args:
437
+ unified_symbol: Unified symbol name in SCIP format (like "shapes/Rectangle#area")
438
+ original_symbol: Original symbol from SCIP
439
+
440
+ Returns:
441
+ Symbol type constant (NODE_TYPE_CLASS, NODE_TYPE_METHOD, NODE_TYPE_FUNCTION)
442
+ """
443
+ # Member: has # with something after it
444
+ if "#" in unified_symbol:
445
+ parts = unified_symbol.split("#")
446
+ if len(parts) > 1 and parts[1]:
447
+ # Distinguish field vs method using original SCIP symbol:
448
+ # method ends with "()." (e.g. "impl#[Type]method().")
449
+ # field ends with "." (e.g. "Type#field.")
450
+ if original_symbol and original_symbol.endswith("()."):
451
+ return NODE_TYPE_METHOD
452
+ else:
453
+ return NODE_TYPE_FIELD
454
+ else:
455
+ # Just type (ends with #)
456
+ return NODE_TYPE_CLASS
457
+
458
+ # Check last component to determine type
459
+ last_component = unified_symbol.split("/")[-1]
460
+
461
+ # Struct/Trait: starts with uppercase
462
+ if last_component and last_component[0].isupper():
463
+ return NODE_TYPE_CLASS
464
+
465
+ # Default to function
466
+ return NODE_TYPE_FUNCTION
467
+
468
+ def _process_symbol(
469
+ self,
470
+ symbol,
471
+ file_path,
472
+ line,
473
+ col_start,
474
+ col_end,
475
+ symbol_roles,
476
+ enclosing_ranges,
477
+ ):
478
+ """
479
+ Process a symbol occurrence and add it to the graph.
480
+
481
+ Args:
482
+ symbol: Symbol name from SCIP
483
+ file_path: File containing the symbol
484
+ line: Line number
485
+ col_start: Column start
486
+ col_end: Column end
487
+ symbol_roles: Symbol roles (1=definition, 0=reference)
488
+ enclosing_ranges: Enclosing scope ranges
489
+ """
490
+ self.logger.scip_debug(
491
+ f"Processing Rust symbol: {symbol} at {file_path}:{line}, roles: {symbol_roles}"
492
+ )
493
+
494
+ # Unify symbol name
495
+ unified_symbol = self._unify_symbol_name(symbol, file_path)
496
+ if not unified_symbol:
497
+ return
498
+
499
+ # Classify symbol type
500
+ symbol_type = self._classify_symbol_type(unified_symbol, symbol)
501
+
502
+ # Exit scopes based on current line
503
+ try:
504
+ self.code_graph.exit_scopes_by_line(line)
505
+ except Exception as e:
506
+ self.logger.error(f"Error exiting scopes at line {line}: {e}")
507
+ raise
508
+
509
+ # Handle definition (check if definition bit is set)
510
+ is_definition = symbol_roles & 1 # Bitwise AND to check definition bit
511
+ if is_definition:
512
+ if enclosing_ranges and len(enclosing_ranges) >= 4:
513
+ # Multi-line enclosing range: [start_line, start_col, end_line, end_col]
514
+ scope_start_line = int(enclosing_ranges[0])
515
+ scope_end_line = int(enclosing_ranges[2])
516
+
517
+ self.code_graph.add_symbol_node(
518
+ unified_symbol, line, scope_start_line, scope_end_line, symbol_type
519
+ )
520
+ if unified_symbol in self.code_graph.name_to_vertex:
521
+ vertex_id = self.code_graph.name_to_vertex[unified_symbol]
522
+ self.code_graph.graph.vs[vertex_id]["unified_name"] = (
523
+ self._get_unified_name(unified_symbol, file_path, symbol_type)
524
+ )
525
+
526
+ self.code_graph.add_containment_edge(unified_symbol)
527
+
528
+ if symbol_type in [
529
+ NODE_TYPE_CLASS,
530
+ NODE_TYPE_FUNCTION,
531
+ NODE_TYPE_METHOD,
532
+ ]:
533
+ self.code_graph.update_current_scope(
534
+ unified_symbol, scope_start_line, scope_end_line
535
+ )
536
+ elif enclosing_ranges and len(enclosing_ranges) == 3:
537
+ # Single-line enclosing range: [line, col_start, col_end]
538
+ # Fill range into node but don't push onto scope stack
539
+ enc_line = int(enclosing_ranges[0])
540
+ self.code_graph.add_symbol_node(
541
+ unified_symbol, line, enc_line, enc_line, symbol_type
542
+ )
543
+ if unified_symbol in self.code_graph.name_to_vertex:
544
+ vertex_id = self.code_graph.name_to_vertex[unified_symbol]
545
+ self.code_graph.graph.vs[vertex_id]["unified_name"] = (
546
+ self._get_unified_name(unified_symbol, file_path, symbol_type)
547
+ )
548
+
549
+ self.code_graph.add_containment_edge(unified_symbol)
550
+ else:
551
+ # No enclosing range (stable rust-analyzer)
552
+ self.code_graph.add_symbol_node(
553
+ unified_symbol, line, symbol_type=symbol_type
554
+ )
555
+ if unified_symbol in self.code_graph.name_to_vertex:
556
+ vertex_id = self.code_graph.name_to_vertex[unified_symbol]
557
+ self.code_graph.graph.vs[vertex_id]["unified_name"] = (
558
+ self._get_unified_name(unified_symbol, file_path, symbol_type)
559
+ )
560
+
561
+ self.code_graph._add_edge(
562
+ self.code_graph.current_scope, unified_symbol, EDGE_TYPE_CONTAIN
563
+ )
564
+
565
+ # Handle reference (not a definition)
566
+ else:
567
+ self.code_graph.add_symbol_reference(
568
+ unified_symbol, file_path, symbol_type, anchor_line=line
569
+ )
570
+ if unified_symbol in self.code_graph.name_to_vertex:
571
+ vid = self.code_graph.name_to_vertex[unified_symbol]
572
+ if not self.code_graph.graph.vs[vid].attributes().get("unified_name"):
573
+ self.code_graph.graph.vs[vid]["unified_name"] = (
574
+ self._get_unified_name(unified_symbol, file_path, symbol_type)
575
+ )
576
+
577
+ def save_graph(self, output_path):
578
+ """
579
+ Save the code graph to a file.
580
+
581
+ Args:
582
+ output_path: Path where the graph should be saved
583
+ """
584
+ self.code_graph.save_graph(output_path)