pycode-kg 0.18.2__tar.gz → 0.19.0__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 (64) hide show
  1. pycode_kg-0.19.0/PKG-INFO +291 -0
  2. pycode_kg-0.19.0/README.md +217 -0
  3. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/pyproject.toml +1 -1
  4. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/__init__.py +1 -1
  5. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/analysis/framework_detector.py +31 -28
  6. pycode_kg-0.19.0/src/pycode_kg/cli/cmd_explain.py +80 -0
  7. pycode_kg-0.19.0/src/pycode_kg/explain.py +257 -0
  8. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/mcp_server.py +25 -181
  9. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/pycodekg_thorough_analysis.py +41 -11
  10. pycode_kg-0.18.2/PKG-INFO +0 -248
  11. pycode_kg-0.18.2/README.md +0 -174
  12. pycode_kg-0.18.2/src/pycode_kg/cli/cmd_explain.py +0 -180
  13. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/LICENSE +0 -0
  14. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/.DS_Store +0 -0
  15. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/__main__.py +0 -0
  16. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/analysis/__init__.py +0 -0
  17. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/analysis/bridge.py +0 -0
  18. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/analysis/centrality.py +0 -0
  19. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/analysis/hybrid_rank.py +0 -0
  20. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/app.py +0 -0
  21. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/architecture.py +0 -0
  22. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/build_pycodekg_lancedb.py +0 -0
  23. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/build_pycodekg_sqlite.py +0 -0
  24. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/cli/__init__.py +0 -0
  25. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/cli/cmd_analyze.py +0 -0
  26. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/cli/cmd_architecture.py +0 -0
  27. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/cli/cmd_bridges.py +0 -0
  28. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/cli/cmd_build.py +0 -0
  29. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/cli/cmd_build_full.py +0 -0
  30. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/cli/cmd_centrality.py +0 -0
  31. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/cli/cmd_framework_nodes.py +0 -0
  32. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/cli/cmd_hooks.py +0 -0
  33. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/cli/cmd_init.py +0 -0
  34. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/cli/cmd_mcp.py +0 -0
  35. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/cli/cmd_model.py +0 -0
  36. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/cli/cmd_query.py +0 -0
  37. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/cli/cmd_snapshot.py +0 -0
  38. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/cli/cmd_viz.py +0 -0
  39. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/cli/main.py +0 -0
  40. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/cli/options.py +0 -0
  41. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/config.py +0 -0
  42. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/graph.py +0 -0
  43. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/index.py +0 -0
  44. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/kg.py +0 -0
  45. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/layout3d.py +0 -0
  46. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/module/__init__.py +0 -0
  47. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/module/base.py +0 -0
  48. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/module/extractor.py +0 -0
  49. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/module/types.py +0 -0
  50. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/pycodekg.py +0 -0
  51. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/pycodekg_query.py +0 -0
  52. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/pycodekg_snippet_packer.py +0 -0
  53. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/pycodekg_viz.py +0 -0
  54. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/pycodekg_viz3d.py +0 -0
  55. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/ranking/__init__.py +0 -0
  56. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/ranking/cli_rank.py +0 -0
  57. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/ranking/coderank.py +0 -0
  58. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/snapshots.py +0 -0
  59. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/sql/004_add_centrality_table.sql +0 -0
  60. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/store.py +0 -0
  61. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/utils.py +0 -0
  62. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/visitor.py +0 -0
  63. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/viz3d.py +0 -0
  64. {pycode_kg-0.18.2 → pycode_kg-0.19.0}/src/pycode_kg/viz3d_timeline.py +0 -0
@@ -0,0 +1,291 @@
1
+ Metadata-Version: 2.4
2
+ Name: pycode-kg
3
+ Version: 0.19.0
4
+ Summary: A tool to build a searchable knowledge graph from Python repositories
5
+ License-Expression: Elastic-2.0
6
+ License-File: LICENSE
7
+ Keywords: knowledge-graph,code-analysis,ast,lancedb,sqlite,semantic-search
8
+ Author: Eric G. Suchanek, PhD
9
+ Author-email: suchanek@mac.com
10
+ Requires-Python: >=3.12,<3.14
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Provides-Extra: all
19
+ Provides-Extra: dev
20
+ Provides-Extra: kgdeps
21
+ Provides-Extra: viz
22
+ Provides-Extra: viz3d
23
+ Requires-Dist: PyQt5 (>=5.15.0) ; extra == "all"
24
+ Requires-Dist: PyQt5 (>=5.15.0) ; extra == "viz3d"
25
+ Requires-Dist: click (>=8.1.0,<9)
26
+ Requires-Dist: detect-secrets (>=1.5.0) ; extra == "all"
27
+ Requires-Dist: detect-secrets (>=1.5.0) ; extra == "dev"
28
+ Requires-Dist: doc-kg (>=0.11.0) ; extra == "dev"
29
+ Requires-Dist: doc-kg (>=0.11.0) ; extra == "kgdeps"
30
+ Requires-Dist: kgmodule-utils (>=0.2.1)
31
+ Requires-Dist: lancedb (>=0.29.0)
32
+ Requires-Dist: markdown (>=3.6) ; extra == "all"
33
+ Requires-Dist: markdown (>=3.6) ; extra == "viz3d"
34
+ Requires-Dist: mcp (>=1.0.0)
35
+ Requires-Dist: mypy (>=1.10.0) ; extra == "all"
36
+ Requires-Dist: mypy (>=1.10.0) ; extra == "dev"
37
+ Requires-Dist: numpy (>=1.24.0)
38
+ Requires-Dist: pandas (>=2.0.0)
39
+ Requires-Dist: param (>=2.0.0) ; extra == "all"
40
+ Requires-Dist: param (>=2.0.0) ; extra == "viz3d"
41
+ Requires-Dist: pdoc (>=14.0.0) ; extra == "all"
42
+ Requires-Dist: pdoc (>=14.0.0) ; extra == "dev"
43
+ Requires-Dist: plotly (>=5.14.0) ; extra == "all"
44
+ Requires-Dist: plotly (>=5.14.0) ; extra == "viz"
45
+ Requires-Dist: pre-commit (>=4.5.1) ; extra == "all"
46
+ Requires-Dist: pre-commit (>=4.5.1) ; extra == "dev"
47
+ Requires-Dist: pylint (>=4.0.5) ; extra == "all"
48
+ Requires-Dist: pylint (>=4.0.5) ; extra == "dev"
49
+ Requires-Dist: pytest (>=8.0.0) ; extra == "all"
50
+ Requires-Dist: pytest (>=8.0.0) ; extra == "dev"
51
+ Requires-Dist: pytest-cov (>=5.0.0) ; extra == "all"
52
+ Requires-Dist: pytest-cov (>=5.0.0) ; extra == "dev"
53
+ Requires-Dist: pyvis (>=0.3.2) ; extra == "all"
54
+ Requires-Dist: pyvis (>=0.3.2) ; extra == "viz"
55
+ Requires-Dist: pyvista[jupyter] (>=0.44.0) ; extra == "all"
56
+ Requires-Dist: pyvista[jupyter] (>=0.44.0) ; extra == "viz3d"
57
+ Requires-Dist: pyvistaqt (>=0.11.0) ; extra == "all"
58
+ Requires-Dist: pyvistaqt (>=0.11.0) ; extra == "viz3d"
59
+ Requires-Dist: rich (>=14.3.3,<15)
60
+ Requires-Dist: ruff (>=0.4.0) ; extra == "all"
61
+ Requires-Dist: ruff (>=0.4.0) ; extra == "dev"
62
+ Requires-Dist: safetensors (>=0.5.0)
63
+ Requires-Dist: sentence-transformers (>=5.4.1)
64
+ Requires-Dist: streamlit (>=1.35.0) ; extra == "all"
65
+ Requires-Dist: streamlit (>=1.35.0) ; extra == "viz"
66
+ Requires-Dist: torch (>=2.5.1)
67
+ Requires-Dist: trame-vtk (>=2.0.0) ; extra == "all"
68
+ Requires-Dist: trame-vtk (>=2.0.0) ; extra == "viz3d"
69
+ Requires-Dist: transformers (>=4.57.6)
70
+ Project-URL: Homepage, https://github.com/Flux-Frontiers/pycode_kg
71
+ Project-URL: Repository, https://github.com/Flux-Frontiers/pycode_kg
72
+ Description-Content-Type: text/markdown
73
+
74
+
75
+ <p align="center">
76
+ <img src="assets/logos/pycodeKG.PNG" alt="PyCodeKG" width="200"/>
77
+ </p>
78
+
79
+ [![Python](https://img.shields.io/badge/python-3.12%20%7C%203.13-blue.svg)](https://www.python.org/)
80
+ [![License: Elastic-2.0](https://img.shields.io/badge/License-Elastic%202.0-blue.svg)](https://www.elastic.co/licensing/elastic-license)
81
+ [![Version](https://img.shields.io/badge/version-0.19.0-blue.svg)](https://github.com/Flux-Frontiers/pycode_kg/releases)
82
+ [![CI](https://github.com/Flux-Frontiers/pycode_kg/actions/workflows/ci.yml/badge.svg)](https://github.com/Flux-Frontiers/pycode_kg/actions/workflows/ci.yml)
83
+ [![Poetry](https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json)](https://python-poetry.org/)
84
+ [![DOI](https://zenodo.org/badge/1202379010.svg)](https://zenodo.org/badge/latestdoi/1202379010)
85
+
86
+ # PyCodeKG — A Knowledge Graph for Python Codebases
87
+
88
+ **PyCodeKG turns a Python codebase into a deterministic, queryable knowledge graph — and uses it to produce architectural analyses you can act on, with or without an LLM in the loop.**
89
+
90
+ It walks the AST of every module, class, function, and method in your repo, extracts the typed relationships that actually hold the code together (`CONTAINS`, `CALLS`, `IMPORTS`, `INHERITS`, `RESOLVES_TO`), and stores the result in SQLite. A LanceDB vector index sits alongside the graph so that *"authentication flow"* and *"verify_jwt"* both find the right place to start exploring. From there you can rank functions by structural importance, trace fan-in across import aliases, detect circular imports and dead code, render the call graph in 3D, snapshot metrics for diffing across releases, or hand the whole thing to Claude over MCP.
91
+
92
+ The original motivation was simple: **produce thorough, defensible analyses of Python codebases that don't depend on inference**. Every result is computed from the AST and the graph — no model is asked to guess. When an LLM is present, it consumes the *same* grounded output as a structured context pack, and the hallucinations that plague "embed-the-repo" tools largely disappear.
93
+
94
+ Everything runs on your laptop. No cloud APIs, no quotas, no source code leaving the machine.
95
+
96
+ [Technical Paper (PDF)](article/pycode_kg.pdf) · *Author: Eric G. Suchanek, PhD — Flux-Frontiers, Liberty TWP, OH*
97
+
98
+ ---
99
+
100
+ ## Sister projects
101
+
102
+ PyCodeKG is part of a growing family of knowledge-graph systems that share the same hybrid semantic-plus-structural design — each one applies it to a different kind of corpus:
103
+
104
+ - **[DocKG](https://github.com/Flux-Frontiers/doc_kg)** — Markdown and prose. Indexes PyCodeKG's own documentation, so the docs you're reading are themselves a queryable graph.
105
+ - **[MetaboKG](https://github.com/Flux-Frontiers/metabo_kg)** — metabolic pathway data (KEGG, SBML, BioPAX), with FBA / ODE simulation on top of the graph.
106
+ - **[DiaryKG](https://github.com/Flux-Frontiers/diary_kg)** — personal journals and diary corpora; semantic search and graph traversal over a writer's body of work.
107
+ - **[FTreeKG](https://github.com/Flux-Frontiers/FTreeKG)** — filesystem trees as a queryable graph of directories, files, and contents.
108
+ - **[AgentKG](https://github.com/Flux-Frontiers/agent_kg)** — conversational memory as a knowledge graph: turns, decisions, commitments, preferences, and the relationships between them.
109
+
110
+ Together they form **KGRAG**, a federated retrieval layer where one query can span code, documentation, journals, filesystems, agent memory, and domain data simultaneously.
111
+
112
+ ---
113
+
114
+ ## Two ways to use it
115
+
116
+ PyCodeKG is designed to be useful at both ends — as a standalone command-line analysis tool, and as a structured context layer for AI agents.
117
+
118
+ ### 1. Standalone — `pycodekg analyze`
119
+
120
+ This is the bread and butter. One command, one repo, one architectural report:
121
+
122
+ ```bash
123
+ pycodekg build --repo . # one-time index
124
+ pycodekg analyze . # the report
125
+ ```
126
+
127
+ `analyze` walks the graph and produces:
128
+
129
+ - **Complexity hotspots** — high fan-in (broadly depended on, breaking-change risk) and high fan-out (orchestrators, refactoring candidates) functions, with risk levels
130
+ - **Docstring coverage** — broken down by module, class, function, method
131
+ - **Circular import cycles** — module loops that cause hard-to-debug failures
132
+ - **Orphaned functions** — dead-code candidates with line counts (with caveats about entry points and reflection)
133
+ - **Module coupling** — the import graph, with the most tightly coupled pairs called out
134
+ - **Issues and strengths** — high-level callouts suitable for a design review or release note
135
+
136
+ It writes a Markdown report for humans and a timestamped JSON snapshot for tooling, CI gates, and trend tracking. Reach for `analyze` before any non-trivial refactor, at every release, and whenever you inherit an unfamiliar codebase. Full reference: [docs/Analyze.md](docs/Analyze.md).
137
+
138
+ ```bash
139
+ pycodekg analyze --quiet --json ~/.claude/pycodekg_analysis_latest.json
140
+ jq '.docstring_coverage.total' ~/.claude/pycodekg_analysis_latest.json
141
+ ```
142
+
143
+ ### 2. Agentic — MCP server for grounded AI workflows
144
+
145
+ Run `pycodekg mcp` and Claude (or any MCP-aware client) gets nineteen tools backed by the same graph: `graph_stats`, `query_codebase`, `pack_snippets`, `get_node`, `list_nodes`, `callers`, `explain`, `centrality`, `bridge_centrality`, `framework_nodes`, `analyze_repo`, `snapshot_list / show / diff`, and more. Setup for Claude Code, Claude Desktop, Kilo Code, Copilot, and Cline is a single line — see [docs/MCP.md](docs/MCP.md) and [docs/INSTALLATION.md](docs/INSTALLATION.md).
146
+
147
+ The agent benefit isn't subtle. Tools like `pack_snippets` return *actual source* with line numbers and surrounding context; `callers` returns the *real* fan-in resolved across import aliases, not a regex's best guess. The agent stops fabricating function signatures and starts citing them. Multi-step workflows — *"find the auth path, list its callers, summarize what changes if I rename it"* — collapse from dozens of `grep`s and file reads into a handful of source-grounded calls.
148
+
149
+ Independent assessments tend to put it the same way:
150
+
151
+ > "PyCodeKG compresses a multi-step workflow — semantic search, graph expansion, caller tracing, snippet retrieval, and architectural summarization — into a small set of tools that are fast to invoke and easy to chain. In practice, it let me move from broad orientation to intent-driven discovery and then to structural validation without dropping down into manual grep or repeated file reads."
152
+ > — *GPT-5 (via Cline)*
153
+
154
+ > "What sets it apart from 'search the repo with embeddings' tools is the structural layer… Verdict: 4.5/5 — recommend without reservation for any non-trivial Python codebase."
155
+ > — *Claude Opus 4.7*
156
+
157
+ > "PyCodeKG is dramatically more effective than traditional grep/file-reading workflows. Unique value: hybrid search combining natural-language intent with precise structural relationships."
158
+ > — *Claude Haiku 4.5*
159
+
160
+ Full reports in [assessments/](assessments/).
161
+
162
+ ---
163
+
164
+ ## Get started in 60 seconds
165
+
166
+ **Requirements:** Python ≥ 3.12, < 3.14
167
+
168
+ ```bash
169
+ pip install 'pycode-kg[viz,viz3d]' # base + Streamlit + 3-D viewer
170
+
171
+ cd /path/to/your/repo
172
+ pycodekg init --repo . # download model, build graph, install hooks, snapshot
173
+ pycodekg analyze . # the architectural report
174
+ ```
175
+
176
+ That's the recommended path. Variants (minimal install, MCP-only, contributor setup) are in [docs/INSTALLATION.md](docs/INSTALLATION.md). Every CLI subcommand is also exposed as a script alias (`pycodekg-analyze`, `pycodekg-build`, `pycodekg-mcp`, …) for use in Makefiles and Poetry projects.
177
+
178
+ ---
179
+
180
+ ## How retrieval works
181
+
182
+ Search is hybrid by design. A query like *"authentication flow"* runs in two phases:
183
+
184
+ 1. **Vector phase** — the query is embedded with a local sentence-transformer (cached after first download) and LanceDB returns the `k` closest functions, classes, and modules by cosine similarity.
185
+ 2. **Graph expansion phase** — each seed hit is expanded `hop` BFS steps along the typed edges (`CONTAINS`, `CALLS`, `IMPORTS`, `INHERITS`, `RESOLVES_TO`) so call chains and module relationships surface alongside the names that matched.
186
+
187
+ **Structure is treated as ground truth; the embeddings are strictly an acceleration layer.** When the graph and the vector index disagree, the graph wins. This is why fan-in lookups are accurate even for same-named symbols across modules — `RESOLVES_TO` edges bridge call sites through their import aliases, and `callers()` does a two-phase reverse traversal that grep simply cannot replicate.
188
+
189
+ The graph is built around four node kinds (module, class, function, method) and five edge relations. Schema and edge semantics are documented in [docs/CHEATSHEET.md](docs/CHEATSHEET.md).
190
+
191
+ ---
192
+
193
+ ## What you can actually do with it
194
+
195
+ | If you want to… | Reach for | Detail |
196
+ |---|---|---|
197
+ | **Get a thorough architectural report** | `pycodekg analyze` | [docs/Analyze.md](docs/Analyze.md) |
198
+ | **Generate a coherent architecture description** | `pycodekg architecture` | [docs/Architecture_usage.md](docs/Architecture_usage.md) |
199
+ | **Track metrics across releases** | `pycodekg snapshot save / list / diff` | [docs/SNAPSHOTS.md](docs/SNAPSHOTS.md) |
200
+ | **Identify the most structurally important code** | `pycodekg centrality` (SIR PageRank) | [docs/CODERANK.md](docs/CODERANK.md) |
201
+ | **Pull source-grounded context for an LLM** | `pycodekg pack "..." --format md` | [docs/CHEATSHEET.md](docs/CHEATSHEET.md) |
202
+ | **Run a hybrid semantic + structural query** | `pycodekg query "..."` | [docs/CHEATSHEET.md](docs/CHEATSHEET.md) |
203
+ | **Browse the graph interactively** | `pycodekg viz` (Streamlit) | [docs/INSTALLATION.md](docs/INSTALLATION.md) |
204
+ | **See call graphs in 3-D** | `pycodekg viz3d --layout funnel` | [docs/VIZ3D.md](docs/VIZ3D.md) |
205
+ | **Wire it into Claude / Copilot / Cline** | `pycodekg mcp` | [docs/MCP.md](docs/MCP.md) |
206
+
207
+ If you only read one doc after this one, read [docs/Analyze.md](docs/Analyze.md) — that's where most of the day-to-day value lives.
208
+
209
+ ---
210
+
211
+ ## Architecture
212
+
213
+ ```
214
+ src/pycode_kg/
215
+ ├── visitor.py # AST extraction (three-pass: structure, calls, dataflow)
216
+ ├── graph.py # GraphBuilder: file discovery + dispatch
217
+ ├── store.py # SQLite persistence + canonical edges
218
+ ├── index.py # LanceDB semantic index
219
+ ├── pycodekg.py # Public façade
220
+ ├── pycodekg_query.py # Hybrid query
221
+ ├── pycodekg_snippet_packer.py # Source-grounded packs
222
+ ├── pycodekg_thorough_analysis.py # `analyze` engine
223
+ ├── architecture.py # `architecture` description generator
224
+ ├── ranking/ # PageRank, bridge centrality, framework nodes
225
+ ├── snapshots.py # Temporal metric snapshots
226
+ ├── analysis/ # Coupling, cycles, orphans, hotspots
227
+ ├── cli/ # All `pycodekg-*` entry points
228
+ ├── mcp_server.py # MCP server (nineteen tools)
229
+ ├── app.py # Streamlit web app
230
+ ├── viz3d.py / layout3d.py # PyVista/PyQt5 3-D viewer
231
+ └── viz3d_timeline.py # Metric history timeline
232
+ ```
233
+
234
+ The MCP server, the CLI, and the Streamlit app are thin wrappers over the same store + index + ranking core — there is exactly one code path for each capability. The latest architectural deep-dive is in [docs/analysis_v0.19.0.md](docs/analysis_v0.19.0.md), produced (of course) by `pycodekg analyze` against this very repo.
235
+
236
+ ---
237
+
238
+ ## Documentation map
239
+
240
+ | Doc | What it covers |
241
+ |---|---|
242
+ | [docs/INSTALLATION.md](docs/INSTALLATION.md) | All install variants, MCP setup, contributor setup, troubleshooting |
243
+ | [docs/Analyze.md](docs/Analyze.md) | The `analyze` command — every metric, every flag, interpretation guide |
244
+ | [docs/Architecture_usage.md](docs/Architecture_usage.md) | Generating coherent architecture descriptions |
245
+ | [docs/SNAPSHOTS.md](docs/SNAPSHOTS.md) | Temporal metric snapshots, diffing across releases |
246
+ | [docs/CODERANK.md](docs/CODERANK.md) | SIR PageRank, bridge centrality, framework hubs |
247
+ | [docs/MCP.md](docs/MCP.md) | MCP server setup for Claude / Kilo / Copilot / Cline, tool reference |
248
+ | [docs/CHEATSHEET.md](docs/CHEATSHEET.md) | Every CLI flag and every MCP tool — one page |
249
+ | [docs/VIZ3D.md](docs/VIZ3D.md) | The 3-D PyVista viewer and layouts |
250
+ | [CHANGELOG.md](CHANGELOG.md) | Release history |
251
+
252
+ ---
253
+
254
+ ## Citation
255
+
256
+ If you use PyCodeKG in your research or project, please cite it:
257
+
258
+ [![DOI](https://zenodo.org/badge/1202379010.svg)](https://zenodo.org/badge/latestdoi/1202379010)
259
+
260
+ > Suchanek, E. G. (2026). *PyCodeKG: A Knowledge Graph for Python Codebases* (Version 0.19.0) [Software]. Flux-Frontiers. https://doi.org/10.5281/zenodo.19834777
261
+
262
+ ```bibtex
263
+ @software{suchanek_pycode_kg,
264
+ author = {Suchanek, Eric G.},
265
+ title = {{PyCodeKG}: A Knowledge Graph for Python Codebases},
266
+ version = {0.19.0},
267
+ year = {2026},
268
+ publisher = {Flux-Frontiers},
269
+ url = {https://github.com/Flux-Frontiers/pycode_kg},
270
+ doi = {10.5281/zenodo.19834777},
271
+ }
272
+ ```
273
+
274
+ ---
275
+
276
+ ## License
277
+
278
+ [Elastic License 2.0](https://www.elastic.co/licensing/elastic-license) — free for non-commercial and internal use; commercial redistribution or hosting requires a license from Flux-Frontiers.
279
+
280
+ ---
281
+
282
+ ## Support & acknowledgments
283
+
284
+ - **Issues** — [GitHub Issues](https://github.com/Flux-Frontiers/pycode_kg/issues)
285
+ - Sister projects [DocKG](https://github.com/Flux-Frontiers/doc_kg) and [MetaboKG](https://github.com/Flux-Frontiers/metabo_kg)
286
+ - LanceDB, sentence-transformers, PyVista, Streamlit, and FastMCP for the foundations
287
+
288
+ ---
289
+
290
+ *Built for Python developers and AI agents that work alongside them — egs · Last updated May 2026*
291
+
@@ -0,0 +1,217 @@
1
+
2
+ <p align="center">
3
+ <img src="assets/logos/pycodeKG.PNG" alt="PyCodeKG" width="200"/>
4
+ </p>
5
+
6
+ [![Python](https://img.shields.io/badge/python-3.12%20%7C%203.13-blue.svg)](https://www.python.org/)
7
+ [![License: Elastic-2.0](https://img.shields.io/badge/License-Elastic%202.0-blue.svg)](https://www.elastic.co/licensing/elastic-license)
8
+ [![Version](https://img.shields.io/badge/version-0.19.0-blue.svg)](https://github.com/Flux-Frontiers/pycode_kg/releases)
9
+ [![CI](https://github.com/Flux-Frontiers/pycode_kg/actions/workflows/ci.yml/badge.svg)](https://github.com/Flux-Frontiers/pycode_kg/actions/workflows/ci.yml)
10
+ [![Poetry](https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json)](https://python-poetry.org/)
11
+ [![DOI](https://zenodo.org/badge/1202379010.svg)](https://zenodo.org/badge/latestdoi/1202379010)
12
+
13
+ # PyCodeKG — A Knowledge Graph for Python Codebases
14
+
15
+ **PyCodeKG turns a Python codebase into a deterministic, queryable knowledge graph — and uses it to produce architectural analyses you can act on, with or without an LLM in the loop.**
16
+
17
+ It walks the AST of every module, class, function, and method in your repo, extracts the typed relationships that actually hold the code together (`CONTAINS`, `CALLS`, `IMPORTS`, `INHERITS`, `RESOLVES_TO`), and stores the result in SQLite. A LanceDB vector index sits alongside the graph so that *"authentication flow"* and *"verify_jwt"* both find the right place to start exploring. From there you can rank functions by structural importance, trace fan-in across import aliases, detect circular imports and dead code, render the call graph in 3D, snapshot metrics for diffing across releases, or hand the whole thing to Claude over MCP.
18
+
19
+ The original motivation was simple: **produce thorough, defensible analyses of Python codebases that don't depend on inference**. Every result is computed from the AST and the graph — no model is asked to guess. When an LLM is present, it consumes the *same* grounded output as a structured context pack, and the hallucinations that plague "embed-the-repo" tools largely disappear.
20
+
21
+ Everything runs on your laptop. No cloud APIs, no quotas, no source code leaving the machine.
22
+
23
+ [Technical Paper (PDF)](article/pycode_kg.pdf) · *Author: Eric G. Suchanek, PhD — Flux-Frontiers, Liberty TWP, OH*
24
+
25
+ ---
26
+
27
+ ## Sister projects
28
+
29
+ PyCodeKG is part of a growing family of knowledge-graph systems that share the same hybrid semantic-plus-structural design — each one applies it to a different kind of corpus:
30
+
31
+ - **[DocKG](https://github.com/Flux-Frontiers/doc_kg)** — Markdown and prose. Indexes PyCodeKG's own documentation, so the docs you're reading are themselves a queryable graph.
32
+ - **[MetaboKG](https://github.com/Flux-Frontiers/metabo_kg)** — metabolic pathway data (KEGG, SBML, BioPAX), with FBA / ODE simulation on top of the graph.
33
+ - **[DiaryKG](https://github.com/Flux-Frontiers/diary_kg)** — personal journals and diary corpora; semantic search and graph traversal over a writer's body of work.
34
+ - **[FTreeKG](https://github.com/Flux-Frontiers/FTreeKG)** — filesystem trees as a queryable graph of directories, files, and contents.
35
+ - **[AgentKG](https://github.com/Flux-Frontiers/agent_kg)** — conversational memory as a knowledge graph: turns, decisions, commitments, preferences, and the relationships between them.
36
+
37
+ Together they form **KGRAG**, a federated retrieval layer where one query can span code, documentation, journals, filesystems, agent memory, and domain data simultaneously.
38
+
39
+ ---
40
+
41
+ ## Two ways to use it
42
+
43
+ PyCodeKG is designed to be useful at both ends — as a standalone command-line analysis tool, and as a structured context layer for AI agents.
44
+
45
+ ### 1. Standalone — `pycodekg analyze`
46
+
47
+ This is the bread and butter. One command, one repo, one architectural report:
48
+
49
+ ```bash
50
+ pycodekg build --repo . # one-time index
51
+ pycodekg analyze . # the report
52
+ ```
53
+
54
+ `analyze` walks the graph and produces:
55
+
56
+ - **Complexity hotspots** — high fan-in (broadly depended on, breaking-change risk) and high fan-out (orchestrators, refactoring candidates) functions, with risk levels
57
+ - **Docstring coverage** — broken down by module, class, function, method
58
+ - **Circular import cycles** — module loops that cause hard-to-debug failures
59
+ - **Orphaned functions** — dead-code candidates with line counts (with caveats about entry points and reflection)
60
+ - **Module coupling** — the import graph, with the most tightly coupled pairs called out
61
+ - **Issues and strengths** — high-level callouts suitable for a design review or release note
62
+
63
+ It writes a Markdown report for humans and a timestamped JSON snapshot for tooling, CI gates, and trend tracking. Reach for `analyze` before any non-trivial refactor, at every release, and whenever you inherit an unfamiliar codebase. Full reference: [docs/Analyze.md](docs/Analyze.md).
64
+
65
+ ```bash
66
+ pycodekg analyze --quiet --json ~/.claude/pycodekg_analysis_latest.json
67
+ jq '.docstring_coverage.total' ~/.claude/pycodekg_analysis_latest.json
68
+ ```
69
+
70
+ ### 2. Agentic — MCP server for grounded AI workflows
71
+
72
+ Run `pycodekg mcp` and Claude (or any MCP-aware client) gets nineteen tools backed by the same graph: `graph_stats`, `query_codebase`, `pack_snippets`, `get_node`, `list_nodes`, `callers`, `explain`, `centrality`, `bridge_centrality`, `framework_nodes`, `analyze_repo`, `snapshot_list / show / diff`, and more. Setup for Claude Code, Claude Desktop, Kilo Code, Copilot, and Cline is a single line — see [docs/MCP.md](docs/MCP.md) and [docs/INSTALLATION.md](docs/INSTALLATION.md).
73
+
74
+ The agent benefit isn't subtle. Tools like `pack_snippets` return *actual source* with line numbers and surrounding context; `callers` returns the *real* fan-in resolved across import aliases, not a regex's best guess. The agent stops fabricating function signatures and starts citing them. Multi-step workflows — *"find the auth path, list its callers, summarize what changes if I rename it"* — collapse from dozens of `grep`s and file reads into a handful of source-grounded calls.
75
+
76
+ Independent assessments tend to put it the same way:
77
+
78
+ > "PyCodeKG compresses a multi-step workflow — semantic search, graph expansion, caller tracing, snippet retrieval, and architectural summarization — into a small set of tools that are fast to invoke and easy to chain. In practice, it let me move from broad orientation to intent-driven discovery and then to structural validation without dropping down into manual grep or repeated file reads."
79
+ > — *GPT-5 (via Cline)*
80
+
81
+ > "What sets it apart from 'search the repo with embeddings' tools is the structural layer… Verdict: 4.5/5 — recommend without reservation for any non-trivial Python codebase."
82
+ > — *Claude Opus 4.7*
83
+
84
+ > "PyCodeKG is dramatically more effective than traditional grep/file-reading workflows. Unique value: hybrid search combining natural-language intent with precise structural relationships."
85
+ > — *Claude Haiku 4.5*
86
+
87
+ Full reports in [assessments/](assessments/).
88
+
89
+ ---
90
+
91
+ ## Get started in 60 seconds
92
+
93
+ **Requirements:** Python ≥ 3.12, < 3.14
94
+
95
+ ```bash
96
+ pip install 'pycode-kg[viz,viz3d]' # base + Streamlit + 3-D viewer
97
+
98
+ cd /path/to/your/repo
99
+ pycodekg init --repo . # download model, build graph, install hooks, snapshot
100
+ pycodekg analyze . # the architectural report
101
+ ```
102
+
103
+ That's the recommended path. Variants (minimal install, MCP-only, contributor setup) are in [docs/INSTALLATION.md](docs/INSTALLATION.md). Every CLI subcommand is also exposed as a script alias (`pycodekg-analyze`, `pycodekg-build`, `pycodekg-mcp`, …) for use in Makefiles and Poetry projects.
104
+
105
+ ---
106
+
107
+ ## How retrieval works
108
+
109
+ Search is hybrid by design. A query like *"authentication flow"* runs in two phases:
110
+
111
+ 1. **Vector phase** — the query is embedded with a local sentence-transformer (cached after first download) and LanceDB returns the `k` closest functions, classes, and modules by cosine similarity.
112
+ 2. **Graph expansion phase** — each seed hit is expanded `hop` BFS steps along the typed edges (`CONTAINS`, `CALLS`, `IMPORTS`, `INHERITS`, `RESOLVES_TO`) so call chains and module relationships surface alongside the names that matched.
113
+
114
+ **Structure is treated as ground truth; the embeddings are strictly an acceleration layer.** When the graph and the vector index disagree, the graph wins. This is why fan-in lookups are accurate even for same-named symbols across modules — `RESOLVES_TO` edges bridge call sites through their import aliases, and `callers()` does a two-phase reverse traversal that grep simply cannot replicate.
115
+
116
+ The graph is built around four node kinds (module, class, function, method) and five edge relations. Schema and edge semantics are documented in [docs/CHEATSHEET.md](docs/CHEATSHEET.md).
117
+
118
+ ---
119
+
120
+ ## What you can actually do with it
121
+
122
+ | If you want to… | Reach for | Detail |
123
+ |---|---|---|
124
+ | **Get a thorough architectural report** | `pycodekg analyze` | [docs/Analyze.md](docs/Analyze.md) |
125
+ | **Generate a coherent architecture description** | `pycodekg architecture` | [docs/Architecture_usage.md](docs/Architecture_usage.md) |
126
+ | **Track metrics across releases** | `pycodekg snapshot save / list / diff` | [docs/SNAPSHOTS.md](docs/SNAPSHOTS.md) |
127
+ | **Identify the most structurally important code** | `pycodekg centrality` (SIR PageRank) | [docs/CODERANK.md](docs/CODERANK.md) |
128
+ | **Pull source-grounded context for an LLM** | `pycodekg pack "..." --format md` | [docs/CHEATSHEET.md](docs/CHEATSHEET.md) |
129
+ | **Run a hybrid semantic + structural query** | `pycodekg query "..."` | [docs/CHEATSHEET.md](docs/CHEATSHEET.md) |
130
+ | **Browse the graph interactively** | `pycodekg viz` (Streamlit) | [docs/INSTALLATION.md](docs/INSTALLATION.md) |
131
+ | **See call graphs in 3-D** | `pycodekg viz3d --layout funnel` | [docs/VIZ3D.md](docs/VIZ3D.md) |
132
+ | **Wire it into Claude / Copilot / Cline** | `pycodekg mcp` | [docs/MCP.md](docs/MCP.md) |
133
+
134
+ If you only read one doc after this one, read [docs/Analyze.md](docs/Analyze.md) — that's where most of the day-to-day value lives.
135
+
136
+ ---
137
+
138
+ ## Architecture
139
+
140
+ ```
141
+ src/pycode_kg/
142
+ ├── visitor.py # AST extraction (three-pass: structure, calls, dataflow)
143
+ ├── graph.py # GraphBuilder: file discovery + dispatch
144
+ ├── store.py # SQLite persistence + canonical edges
145
+ ├── index.py # LanceDB semantic index
146
+ ├── pycodekg.py # Public façade
147
+ ├── pycodekg_query.py # Hybrid query
148
+ ├── pycodekg_snippet_packer.py # Source-grounded packs
149
+ ├── pycodekg_thorough_analysis.py # `analyze` engine
150
+ ├── architecture.py # `architecture` description generator
151
+ ├── ranking/ # PageRank, bridge centrality, framework nodes
152
+ ├── snapshots.py # Temporal metric snapshots
153
+ ├── analysis/ # Coupling, cycles, orphans, hotspots
154
+ ├── cli/ # All `pycodekg-*` entry points
155
+ ├── mcp_server.py # MCP server (nineteen tools)
156
+ ├── app.py # Streamlit web app
157
+ ├── viz3d.py / layout3d.py # PyVista/PyQt5 3-D viewer
158
+ └── viz3d_timeline.py # Metric history timeline
159
+ ```
160
+
161
+ The MCP server, the CLI, and the Streamlit app are thin wrappers over the same store + index + ranking core — there is exactly one code path for each capability. The latest architectural deep-dive is in [docs/analysis_v0.19.0.md](docs/analysis_v0.19.0.md), produced (of course) by `pycodekg analyze` against this very repo.
162
+
163
+ ---
164
+
165
+ ## Documentation map
166
+
167
+ | Doc | What it covers |
168
+ |---|---|
169
+ | [docs/INSTALLATION.md](docs/INSTALLATION.md) | All install variants, MCP setup, contributor setup, troubleshooting |
170
+ | [docs/Analyze.md](docs/Analyze.md) | The `analyze` command — every metric, every flag, interpretation guide |
171
+ | [docs/Architecture_usage.md](docs/Architecture_usage.md) | Generating coherent architecture descriptions |
172
+ | [docs/SNAPSHOTS.md](docs/SNAPSHOTS.md) | Temporal metric snapshots, diffing across releases |
173
+ | [docs/CODERANK.md](docs/CODERANK.md) | SIR PageRank, bridge centrality, framework hubs |
174
+ | [docs/MCP.md](docs/MCP.md) | MCP server setup for Claude / Kilo / Copilot / Cline, tool reference |
175
+ | [docs/CHEATSHEET.md](docs/CHEATSHEET.md) | Every CLI flag and every MCP tool — one page |
176
+ | [docs/VIZ3D.md](docs/VIZ3D.md) | The 3-D PyVista viewer and layouts |
177
+ | [CHANGELOG.md](CHANGELOG.md) | Release history |
178
+
179
+ ---
180
+
181
+ ## Citation
182
+
183
+ If you use PyCodeKG in your research or project, please cite it:
184
+
185
+ [![DOI](https://zenodo.org/badge/1202379010.svg)](https://zenodo.org/badge/latestdoi/1202379010)
186
+
187
+ > Suchanek, E. G. (2026). *PyCodeKG: A Knowledge Graph for Python Codebases* (Version 0.19.0) [Software]. Flux-Frontiers. https://doi.org/10.5281/zenodo.19834777
188
+
189
+ ```bibtex
190
+ @software{suchanek_pycode_kg,
191
+ author = {Suchanek, Eric G.},
192
+ title = {{PyCodeKG}: A Knowledge Graph for Python Codebases},
193
+ version = {0.19.0},
194
+ year = {2026},
195
+ publisher = {Flux-Frontiers},
196
+ url = {https://github.com/Flux-Frontiers/pycode_kg},
197
+ doi = {10.5281/zenodo.19834777},
198
+ }
199
+ ```
200
+
201
+ ---
202
+
203
+ ## License
204
+
205
+ [Elastic License 2.0](https://www.elastic.co/licensing/elastic-license) — free for non-commercial and internal use; commercial redistribution or hosting requires a license from Flux-Frontiers.
206
+
207
+ ---
208
+
209
+ ## Support & acknowledgments
210
+
211
+ - **Issues** — [GitHub Issues](https://github.com/Flux-Frontiers/pycode_kg/issues)
212
+ - Sister projects [DocKG](https://github.com/Flux-Frontiers/doc_kg) and [MetaboKG](https://github.com/Flux-Frontiers/metabo_kg)
213
+ - LanceDB, sentence-transformers, PyVista, Streamlit, and FastMCP for the foundations
214
+
215
+ ---
216
+
217
+ *Built for Python developers and AI agents that work alongside them — egs · Last updated May 2026*
@@ -66,7 +66,7 @@ ruff = ">=0.4.0"
66
66
  # ---------------------------------------------------------------------------
67
67
  [project]
68
68
  name = "pycode-kg"
69
- version = "0.18.2"
69
+ version = "0.19.0"
70
70
  description = "A tool to build a searchable knowledge graph from Python repositories"
71
71
  readme = "README.md"
72
72
  license = "Elastic-2.0"
@@ -32,7 +32,7 @@ KGModule SDK (build new domain KGs)::
32
32
  from pycode_kg import KGModule, KGExtractor, PyCodeKGExtractor, NodeSpec, EdgeSpec
33
33
  """
34
34
 
35
- __version__ = "0.18.2"
35
+ __version__ = "0.19.0"
36
36
  __author__ = "Eric G. Suchanek, PhD"
37
37
 
38
38
  # Low-level primitives (locked v0 contract)
@@ -58,31 +58,31 @@ def detect_framework_nodes(
58
58
  :param db_path: Path to SQLite database (default "pycodekg.sqlite").
59
59
  :return: List of (node_id, framework_score, label) tuples, sorted by score descending.
60
60
  """
61
+ # Aggregate node-level SIR up to module level so we compare like-for-like
62
+ # against the module_connectivity metric. Both signals end up keyed by
63
+ # bare module path (e.g. "src/pycode_kg/store.py"), which we map to "mod:..."
64
+ # IDs for output.
65
+ from pycode_kg.analysis.centrality import ( # pylint: disable=import-outside-toplevel
66
+ StructuralImportanceRanker,
67
+ aggregate_module_scores,
68
+ )
69
+
61
70
  with sqlite3.connect(db_path) as con:
62
- # Get SIR (structural importance) and connectivity (interaction complexity) scores
63
- sir = dict(
64
- con.execute(
65
- "SELECT node_id, score FROM centrality_scores WHERE metric = 'sir_pagerank'"
66
- )
67
- )
68
71
  connectivity = dict(
69
72
  con.execute(
70
73
  "SELECT node_id, score FROM centrality_scores WHERE metric = 'module_connectivity'"
71
74
  )
72
75
  )
73
- # Get module names (for labelling)
74
- names = dict(con.execute("SELECT id, name FROM nodes WHERE kind = 'module'"))
75
- # Fetch qualnames for boilerplate filtering across all node kinds
76
- qualnames: dict[str, str] = dict(
77
- con.execute("SELECT id, COALESCE(qualname, name) FROM nodes")
76
+ module_id_by_path: dict[str, str] = dict(
77
+ con.execute("SELECT module_path, id FROM nodes WHERE kind = 'module'")
78
78
  )
79
79
 
80
- def _is_boilerplate(node_id: str) -> bool:
81
- qn = qualnames.get(node_id, node_id)
82
- short = qn.split(".")[-1] if "." in qn else qn
83
- return short in _BOILERPLATE_NAMES
80
+ ranker = StructuralImportanceRanker(db_path)
81
+ sir_records = ranker.compute()
82
+ sir_by_path: dict[str, float] = {
83
+ row["module_path"]: row["score"] for row in aggregate_module_scores(sir_records)
84
+ }
84
85
 
85
- # Normalize scores to [0, 1]
86
86
  def norm(d: dict) -> dict:
87
87
  if not d:
88
88
  return {}
@@ -90,20 +90,23 @@ def detect_framework_nodes(
90
90
  mn, mx = min(vals), max(vals)
91
91
  return {k: (v - mn) / (mx - mn) if mx > mn else 0.0 for k, v in d.items()}
92
92
 
93
- nsir = norm(sir)
93
+ nsir = norm(sir_by_path)
94
94
  nconnectivity = norm(connectivity)
95
95
 
96
- # Framework score: weighted sum (SIR 60% — importance, connectivity 40% — coupling)
97
- # Skip boilerplate methods that score high purely due to lifecycle call frequency.
98
- framework = {}
99
- for k in set(nsir) | set(nconnectivity):
100
- if _is_boilerplate(k):
101
- continue
102
- framework[k] = 0.6 * nsir.get(k, 0.0) + 0.4 * nconnectivity.get(k, 0.0)
96
+ # Framework score: weighted sum (SIR 60% — importance, connectivity 40% — coupling).
97
+ # Restrict to module paths only the tool advertises "Framework-like Modules",
98
+ # so methods/functions must not leak into the ranking. The previous implementation
99
+ # mixed key spaces (typed node IDs from SIR with bare paths from connectivity),
100
+ # which let methods like ``GraphStore.con`` appear in a "Modules" table.
101
+ framework: dict[str, float] = {}
102
+ for path in set(nsir) | set(nconnectivity):
103
+ framework[path] = 0.6 * nsir.get(path, 0.0) + 0.4 * nconnectivity.get(path, 0.0)
103
104
 
104
105
  ranked = sorted(framework.items(), key=lambda x: x[1], reverse=True)
105
- result = []
106
- for node_id, score in ranked[:limit]:
107
- label = names.get(node_id, node_id)
108
- result.append((node_id, score, label))
106
+ result: list[tuple[str, float, str]] = []
107
+ for path, score in ranked[:limit]:
108
+ node_id = module_id_by_path.get(path, f"mod:{path}")
109
+ result.append((node_id, score, path))
110
+ # _BOILERPLATE_NAMES retained for backward-compat at the import site.
111
+ _ = _BOILERPLATE_NAMES
109
112
  return result