pycode-kg 0.27.1__tar.gz → 0.28.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.
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/PKG-INFO +12 -12
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/README.md +6 -6
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/pyproject.toml +7 -28
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/__init__.py +1 -1
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/cli/cmd_hooks.py +9 -2
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/mcp_server.py +19 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/pycodekg_thorough_analysis.py +33 -1
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/report.py +29 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/LICENSE +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/.DS_Store +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/__main__.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/analysis/__init__.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/analysis/bridge.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/analysis/centrality.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/analysis/framework_detector.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/app.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/architecture.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/build_pycodekg_sqlite.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/cli/__init__.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/cli/cmd_analyze.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/cli/cmd_architecture.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/cli/cmd_bridges.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/cli/cmd_build.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/cli/cmd_build_full.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/cli/cmd_centrality.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/cli/cmd_explain.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/cli/cmd_framework_nodes.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/cli/cmd_init.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/cli/cmd_mcp.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/cli/cmd_model.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/cli/cmd_query.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/cli/cmd_quilt.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/cli/cmd_snapshot.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/cli/cmd_viz.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/cli/main.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/cli/options.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/config.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/explain.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/graph.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/graph_html.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/index.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/kg.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/layout3d.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/module/__init__.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/module/base.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/module/extractor.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/module/types.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/pycodekg.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/ranking/__init__.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/ranking/cli_rank.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/ranking/coderank.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/render.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/resolution.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/scene3d.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/snapshots.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/sql/004_add_centrality_table.sql +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/store.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/theme.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/utils.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/visitor.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/viz3d.py +0 -0
- {pycode_kg-0.27.1 → pycode_kg-0.28.1}/src/pycode_kg/viz3d_timeline.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pycode-kg
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.28.1
|
|
4
4
|
Summary: A tool to build a searchable knowledge graph from Python repositories
|
|
5
5
|
License-Expression: Elastic-2.0
|
|
6
6
|
License-File: LICENSE
|
|
@@ -21,11 +21,11 @@ Provides-Extra: viz3d
|
|
|
21
21
|
Requires-Dist: PyQt5 (>=5.15.0) ; extra == "all"
|
|
22
22
|
Requires-Dist: PyQt5 (>=5.15.0) ; extra == "viz3d"
|
|
23
23
|
Requires-Dist: click (>=8.1.0,<9)
|
|
24
|
-
Requires-Dist: kgmodule-utils[semantic,viz3d] (>=0.
|
|
25
|
-
Requires-Dist: kgmodule-utils[viz3d-qt,viz3d-render] (>=0.
|
|
24
|
+
Requires-Dist: kgmodule-utils[semantic,viz3d] (>=0.23.0)
|
|
25
|
+
Requires-Dist: kgmodule-utils[viz3d-qt,viz3d-render] (>=0.23.0) ; extra == "viz3d"
|
|
26
26
|
Requires-Dist: markdown (>=3.6) ; extra == "all"
|
|
27
27
|
Requires-Dist: markdown (>=3.6) ; extra == "viz3d"
|
|
28
|
-
Requires-Dist: mcp (>=1.
|
|
28
|
+
Requires-Dist: mcp (>=1.3.0,<2)
|
|
29
29
|
Requires-Dist: networkx (>=3.0)
|
|
30
30
|
Requires-Dist: numpy (>=1.24.0)
|
|
31
31
|
Requires-Dist: pandas (>=2.0.0)
|
|
@@ -38,8 +38,8 @@ Requires-Dist: pyvis (>=0.3.2) ; extra == "viz"
|
|
|
38
38
|
Requires-Dist: pyvista (>=0.44.0) ; extra == "all"
|
|
39
39
|
Requires-Dist: pyvistaqt (>=0.11.0) ; extra == "all"
|
|
40
40
|
Requires-Dist: pyvistaqt (>=0.11.0) ; extra == "viz3d"
|
|
41
|
-
Requires-Dist: quiltwright (>=0.
|
|
42
|
-
Requires-Dist: quiltwright (>=0.
|
|
41
|
+
Requires-Dist: quiltwright (>=0.15.0) ; extra == "all"
|
|
42
|
+
Requires-Dist: quiltwright (>=0.15.0) ; extra == "viz3d"
|
|
43
43
|
Requires-Dist: rich (>=14.3.3,<15)
|
|
44
44
|
Requires-Dist: sentence-transformers (>=5.4.1)
|
|
45
45
|
Requires-Dist: sqlite-vec (==0.1.9)
|
|
@@ -60,10 +60,10 @@ Description-Content-Type: text/markdown
|
|
|
60
60
|
|
|
61
61
|
[](https://www.python.org/)
|
|
62
62
|
[](https://www.elastic.co/licensing/elastic-license)
|
|
63
|
-
[](https://github.com/Flux-Frontiers/pycode_kg/releases)
|
|
64
64
|
[](https://github.com/Flux-Frontiers/pycode_kg/actions/workflows/ci.yml)
|
|
65
65
|
[](https://python-poetry.org/)
|
|
66
|
-
[](https://doi.org/10.5281/zenodo.19737993)
|
|
67
67
|
|
|
68
68
|
# PyCodeKG — A Knowledge Graph for Python Codebases
|
|
69
69
|
|
|
@@ -255,7 +255,7 @@ src/pycode_kg/
|
|
|
255
255
|
└── viz3d_timeline.py # Metric history timeline
|
|
256
256
|
```
|
|
257
257
|
|
|
258
|
-
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.
|
|
258
|
+
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.28.1.md](docs/analysis_v0.28.1.md), produced (of course) by `pycodekg analyze` against this very repo.
|
|
259
259
|
|
|
260
260
|
---
|
|
261
261
|
|
|
@@ -280,15 +280,15 @@ The MCP server, the CLI, and the Streamlit app are thin wrappers over the same s
|
|
|
280
280
|
|
|
281
281
|
If you use PyCodeKG in your research or project, please cite it:
|
|
282
282
|
|
|
283
|
-
[](https://doi.org/10.5281/zenodo.19737993)
|
|
284
284
|
|
|
285
|
-
> Suchanek, E. G. (2026). *PyCodeKG: A Knowledge Graph for Python Codebases* (Version 0.
|
|
285
|
+
> Suchanek, E. G. (2026). *PyCodeKG: A Knowledge Graph for Python Codebases* (Version 0.28.1) [Software]. Flux-Frontiers. https://doi.org/10.5281/zenodo.19737993
|
|
286
286
|
|
|
287
287
|
```bibtex
|
|
288
288
|
@software{suchanek_pycode_kg,
|
|
289
289
|
author = {Suchanek, Eric G.},
|
|
290
290
|
title = {{PyCodeKG}: A Knowledge Graph for Python Codebases},
|
|
291
|
-
version = {0.
|
|
291
|
+
version = {0.28.1},
|
|
292
292
|
year = {2026},
|
|
293
293
|
publisher = {Flux-Frontiers},
|
|
294
294
|
url = {https://github.com/Flux-Frontiers/pycode_kg},
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
|
|
6
6
|
[](https://www.python.org/)
|
|
7
7
|
[](https://www.elastic.co/licensing/elastic-license)
|
|
8
|
-
[](https://github.com/Flux-Frontiers/pycode_kg/releases)
|
|
9
9
|
[](https://github.com/Flux-Frontiers/pycode_kg/actions/workflows/ci.yml)
|
|
10
10
|
[](https://python-poetry.org/)
|
|
11
|
-
[](https://doi.org/10.5281/zenodo.19737993)
|
|
12
12
|
|
|
13
13
|
# PyCodeKG — A Knowledge Graph for Python Codebases
|
|
14
14
|
|
|
@@ -200,7 +200,7 @@ src/pycode_kg/
|
|
|
200
200
|
└── viz3d_timeline.py # Metric history timeline
|
|
201
201
|
```
|
|
202
202
|
|
|
203
|
-
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.
|
|
203
|
+
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.28.1.md](docs/analysis_v0.28.1.md), produced (of course) by `pycodekg analyze` against this very repo.
|
|
204
204
|
|
|
205
205
|
---
|
|
206
206
|
|
|
@@ -225,15 +225,15 @@ The MCP server, the CLI, and the Streamlit app are thin wrappers over the same s
|
|
|
225
225
|
|
|
226
226
|
If you use PyCodeKG in your research or project, please cite it:
|
|
227
227
|
|
|
228
|
-
[](https://doi.org/10.5281/zenodo.19737993)
|
|
229
229
|
|
|
230
|
-
> Suchanek, E. G. (2026). *PyCodeKG: A Knowledge Graph for Python Codebases* (Version 0.
|
|
230
|
+
> Suchanek, E. G. (2026). *PyCodeKG: A Knowledge Graph for Python Codebases* (Version 0.28.1) [Software]. Flux-Frontiers. https://doi.org/10.5281/zenodo.19737993
|
|
231
231
|
|
|
232
232
|
```bibtex
|
|
233
233
|
@software{suchanek_pycode_kg,
|
|
234
234
|
author = {Suchanek, Eric G.},
|
|
235
235
|
title = {{PyCodeKG}: A Knowledge Graph for Python Codebases},
|
|
236
|
-
version = {0.
|
|
236
|
+
version = {0.28.1},
|
|
237
237
|
year = {2026},
|
|
238
238
|
publisher = {Flux-Frontiers},
|
|
239
239
|
url = {https://github.com/Flux-Frontiers/pycode_kg},
|
|
@@ -70,7 +70,7 @@ torch = [
|
|
|
70
70
|
# ---------------------------------------------------------------------------
|
|
71
71
|
[project]
|
|
72
72
|
name = "pycode-kg"
|
|
73
|
-
version = "0.
|
|
73
|
+
version = "0.28.1"
|
|
74
74
|
description = "A tool to build a searchable knowledge graph from Python repositories"
|
|
75
75
|
readme = "README.md"
|
|
76
76
|
license = "Elastic-2.0"
|
|
@@ -96,7 +96,7 @@ dependencies = [
|
|
|
96
96
|
# FastMCP from mcp, so an unbounded floor lets a clean install pull 2.x and
|
|
97
97
|
# break `pycodekg-mcp` at import time. Lift this only alongside a port to
|
|
98
98
|
# the standalone fastmcp package.
|
|
99
|
-
"mcp>=1.
|
|
99
|
+
"mcp>=1.3.0,<2",
|
|
100
100
|
"networkx>=3.0",
|
|
101
101
|
"numpy>=1.24.0",
|
|
102
102
|
"pandas>=2.0.0",
|
|
@@ -169,7 +169,7 @@ dependencies = [
|
|
|
169
169
|
# and the _domain_metrics() hook, none of which exist before 0.20.0.
|
|
170
170
|
# Against 0.19.x the manager silently reports itself as "kg-utils" and
|
|
171
171
|
# drops module_node_counts entirely.
|
|
172
|
-
"kgmodule-utils[semantic,viz3d]>=0.
|
|
172
|
+
"kgmodule-utils[semantic,viz3d]>=0.23.0",
|
|
173
173
|
]
|
|
174
174
|
|
|
175
175
|
[project.optional-dependencies]
|
|
@@ -194,7 +194,7 @@ viz3d = [
|
|
|
194
194
|
# the four-step build/render/write/cast itself. It subsumes [viz3d-render]
|
|
195
195
|
# (it declares pyvista too), but both are named so the dependency reads as
|
|
196
196
|
# what it is rather than as a side effect.
|
|
197
|
-
"kgmodule-utils[viz3d-render,viz3d-qt]>=0.
|
|
197
|
+
"kgmodule-utils[viz3d-render,viz3d-qt]>=0.23.0",
|
|
198
198
|
"pyvistaqt>=0.11.0",
|
|
199
199
|
"trame-vtk>=2.0.0",
|
|
200
200
|
# Looking Glass quilts. 0.4.0 supplies depth_report() and widened
|
|
@@ -204,7 +204,7 @@ viz3d = [
|
|
|
204
204
|
# 0.14.1 is what kgmodule-utils[viz3d-qt] >=0.22.0 needs transitively, for
|
|
205
205
|
# resolve_view_cone; declared here so this file states the real floor
|
|
206
206
|
# rather than leaving it to the resolver to discover.
|
|
207
|
-
"quiltwright>=0.
|
|
207
|
+
"quiltwright>=0.15.0",
|
|
208
208
|
]
|
|
209
209
|
|
|
210
210
|
# Cross-KG sibling packages (doc-kg, pycode-kg, agent-kg, kg-rag) are NOT
|
|
@@ -229,7 +229,7 @@ all = [
|
|
|
229
229
|
"pyvis>=0.3.2",
|
|
230
230
|
"pyvista>=0.44.0",
|
|
231
231
|
"pyvistaqt>=0.11.0",
|
|
232
|
-
"quiltwright>=0.
|
|
232
|
+
"quiltwright>=0.15.0",
|
|
233
233
|
"streamlit>=1.56.0",
|
|
234
234
|
"trame-vtk>=2.0.0",
|
|
235
235
|
]
|
|
@@ -249,30 +249,9 @@ pdoc = ">=14.0.0"
|
|
|
249
249
|
pre-commit = ">=4.5.1"
|
|
250
250
|
pytest = ">=9.0.3"
|
|
251
251
|
pytest-cov = ">=5.0.0"
|
|
252
|
-
ruff = ">=0.
|
|
252
|
+
ruff = ">=0.15,<0.16"
|
|
253
253
|
ty = ">=0.0.41"
|
|
254
254
|
|
|
255
|
-
# Maintainer tooling, not a dependency of this package. `.mcp.json` serves a
|
|
256
|
-
# `dockg` MCP server from `.venv/bin/dockg`, so the CLI has to be in this repo's
|
|
257
|
-
# environment — but nothing under src/ imports doc_kg, and declaring it as a real
|
|
258
|
-
# dependency would put it in the published wheel for every consumer.
|
|
259
|
-
#
|
|
260
|
-
# A Poetry group is the right shape: it is locked and installable but never
|
|
261
|
-
# written into wheel metadata, so `pip install pycode-kg` is unaffected.
|
|
262
|
-
#
|
|
263
|
-
# poetry install --with kg # gets the dockg CLI into .venv/bin
|
|
264
|
-
# poetry install # default — the group is optional, skipped
|
|
265
|
-
#
|
|
266
|
-
# `kg` is the fleet-wide name for this group so there is one command to remember;
|
|
267
|
-
# contents are per-repo and minimal, covering only what that repo actually
|
|
268
|
-
# invokes. Safe to lock because doc-kg keeps its own pycode-kg in a group too, so
|
|
269
|
-
# the published metadata on either side has no cycle for the resolver to chase.
|
|
270
|
-
[tool.poetry.group.kg]
|
|
271
|
-
optional = true
|
|
272
|
-
|
|
273
|
-
[tool.poetry.group.kg.dependencies]
|
|
274
|
-
doc-kg = ">=0.22.0"
|
|
275
|
-
|
|
276
255
|
[project.urls]
|
|
277
256
|
Homepage = "https://github.com/Flux-Frontiers/pycode_kg"
|
|
278
257
|
Repository = "https://github.com/Flux-Frontiers/pycode_kg"
|
|
@@ -105,10 +105,17 @@ DEFAULT_BRANCH="${DEFAULT_BRANCH#origin/}"
|
|
|
105
105
|
[ "$BRANCH" != "${DEFAULT_BRANCH:-main}" ] && exit 0
|
|
106
106
|
|
|
107
107
|
# Rebuild the local index to keep it in sync with staged content.
|
|
108
|
-
|
|
108
|
+
# pycodekg is a tool this repo runs, not a dependency it declares: the fleet
|
|
109
|
+
# installs it once, globally (uv tool install pycode-kg), so PATH is the normal
|
|
110
|
+
# case. A .venv copy is honoured if present, for a checkout that still has one.
|
|
111
|
+
PYCODEKG="$(command -v pycodekg 2>/dev/null || true)"
|
|
112
|
+
[ -n "$PYCODEKG" ] || PYCODEKG="$REPO_ROOT/.venv/bin/pycodekg"
|
|
113
|
+
[ -x "$PYCODEKG" ] || { echo "[pycodekg] not found on PATH or in .venv; uv tool install pycode-kg" >&2; exit 1; }
|
|
114
|
+
|
|
115
|
+
"$PYCODEKG" build --repo "$REPO_ROOT" || exit 1
|
|
109
116
|
|
|
110
117
|
# Snapshot PyCodeKG (version auto-detected from installed package).
|
|
111
|
-
"$
|
|
118
|
+
"$PYCODEKG" snapshot save \\
|
|
112
119
|
--repo . \\
|
|
113
120
|
--tree-hash "$TREE_HASH" \\
|
|
114
121
|
--branch "$BRANCH" \\
|
|
@@ -142,6 +142,8 @@ from __future__ import annotations
|
|
|
142
142
|
import argparse
|
|
143
143
|
import json
|
|
144
144
|
import sys
|
|
145
|
+
from collections.abc import AsyncIterator
|
|
146
|
+
from contextlib import asynccontextmanager
|
|
145
147
|
from pathlib import Path
|
|
146
148
|
|
|
147
149
|
from mcp.server.fastmcp import FastMCP
|
|
@@ -266,6 +268,22 @@ def _snapshot_freshness(snapshot_total_nodes: int) -> dict:
|
|
|
266
268
|
# MCP server
|
|
267
269
|
# ---------------------------------------------------------------------------
|
|
268
270
|
|
|
271
|
+
|
|
272
|
+
@asynccontextmanager
|
|
273
|
+
async def _lifespan(_server: FastMCP) -> AsyncIterator[None]:
|
|
274
|
+
"""Close the graph's SQLite connection(s) when the server shuts down.
|
|
275
|
+
|
|
276
|
+
``main()`` sets the module-level ``_kg`` before ``mcp.run()`` calls into
|
|
277
|
+
this, and both the stdio and SSE transports route through the same
|
|
278
|
+
underlying ``Server.run()``, so this fires on either one.
|
|
279
|
+
"""
|
|
280
|
+
try:
|
|
281
|
+
yield
|
|
282
|
+
finally:
|
|
283
|
+
if _kg is not None:
|
|
284
|
+
_kg.close()
|
|
285
|
+
|
|
286
|
+
|
|
269
287
|
mcp = FastMCP(
|
|
270
288
|
"pycodekg",
|
|
271
289
|
instructions=(
|
|
@@ -396,6 +414,7 @@ mcp = FastMCP(
|
|
|
396
414
|
"- **Structure-aware query**: query_ranked(q='database connection', mode='hybrid')\n"
|
|
397
415
|
"- **PPR query**: query_ranked(q='...', mode='ppr') → explain_rank(node_id, q='...')"
|
|
398
416
|
),
|
|
417
|
+
lifespan=_lifespan,
|
|
399
418
|
)
|
|
400
419
|
|
|
401
420
|
|
|
@@ -43,6 +43,7 @@ from collections.abc import Callable
|
|
|
43
43
|
from dataclasses import asdict, dataclass
|
|
44
44
|
from pathlib import Path
|
|
45
45
|
|
|
46
|
+
from kg_utils.vector_backend import VectorStoreNotFoundError
|
|
46
47
|
from rich.columns import Columns
|
|
47
48
|
from rich.console import Console
|
|
48
49
|
from rich.panel import Panel
|
|
@@ -444,6 +445,8 @@ class PyCodeKGAnalyzer:
|
|
|
444
445
|
self.concern_analysis: list[dict] = [] # populated by _analyze_concerns
|
|
445
446
|
# Single-line phase output: each phase fn writes its summary here
|
|
446
447
|
self._phase_result: str = ""
|
|
448
|
+
# Phases that raised; rendered in the report (see _run_phase)
|
|
449
|
+
self.phase_failures: list[dict] = []
|
|
447
450
|
|
|
448
451
|
# ── total phase count (update if phases are added/removed) ────────────────
|
|
449
452
|
_TOTAL_PHASES = 15
|
|
@@ -455,13 +458,39 @@ class PyCodeKGAnalyzer:
|
|
|
455
458
|
instead of printing directly, keeping the terminal output to one line
|
|
456
459
|
per phase.
|
|
457
460
|
|
|
461
|
+
A phase that cannot run must not take the others and the report with
|
|
462
|
+
it. The concrete case is ``pycodekg build-sqlite``, a supported command
|
|
463
|
+
that builds the graph without the vector index: the fan-out and
|
|
464
|
+
concern-ranking phases seed on a semantic query and raise
|
|
465
|
+
``VectorStoreNotFoundError``, while every other phase reads the graph
|
|
466
|
+
only. The failure is recorded in :attr:`phase_failures` and rendered in
|
|
467
|
+
the report, so a degraded run names its missing sections instead of
|
|
468
|
+
quietly omitting them.
|
|
469
|
+
|
|
458
470
|
:param num: Phase number (1-based).
|
|
459
471
|
:param name: Human-readable phase label.
|
|
460
472
|
:param fn: Zero-argument callable to execute.
|
|
461
473
|
"""
|
|
462
474
|
self._phase_result = ""
|
|
463
475
|
t0 = time.monotonic()
|
|
464
|
-
|
|
476
|
+
try:
|
|
477
|
+
fn()
|
|
478
|
+
except Exception as exc: # noqa: BLE001 -- see docstring
|
|
479
|
+
elapsed = time.monotonic() - t0
|
|
480
|
+
self.phase_failures.append(
|
|
481
|
+
{
|
|
482
|
+
"phase": num,
|
|
483
|
+
"name": name,
|
|
484
|
+
"error": str(exc),
|
|
485
|
+
"missing_index": isinstance(exc, VectorStoreNotFoundError),
|
|
486
|
+
}
|
|
487
|
+
)
|
|
488
|
+
logger.warning("Phase %d (%s) failed: %s", num, name, exc)
|
|
489
|
+
self.console.print(
|
|
490
|
+
f" [cyan]▶ Phase {num:2d}/{self._TOTAL_PHASES}:[/cyan]"
|
|
491
|
+
f" {name} [yellow]skipped: {exc}[/yellow] [green]({elapsed:.1f}s)[/green]"
|
|
492
|
+
)
|
|
493
|
+
return
|
|
465
494
|
elapsed = time.monotonic() - t0
|
|
466
495
|
result = f" {self._phase_result}" if self._phase_result else ""
|
|
467
496
|
self.console.print(
|
|
@@ -2505,6 +2534,9 @@ class PyCodeKGAnalyzer:
|
|
|
2505
2534
|
"coderank_top_nodes": self.coderank_top_nodes,
|
|
2506
2535
|
# Option D: concern-based hybrid ranking
|
|
2507
2536
|
"concern_analysis": self.concern_analysis,
|
|
2537
|
+
# An empty section above may mean a phase did not run, not that
|
|
2538
|
+
# the codebase has nothing to show.
|
|
2539
|
+
"phase_failures": self.phase_failures,
|
|
2508
2540
|
}
|
|
2509
2541
|
|
|
2510
2542
|
def to_markdown(self, *, metadata: str = "", elapsed_seconds: float | None = None) -> str:
|
|
@@ -124,6 +124,35 @@ def render_markdown(analyzer, *, metadata: str = "", elapsed_seconds: float | No
|
|
|
124
124
|
out += [f"# {repo_name} Analysis", "", f"**Generated:** {generated}"]
|
|
125
125
|
rule()
|
|
126
126
|
|
|
127
|
+
# ── Incomplete Analysis ──────────────────────────────────────────────
|
|
128
|
+
# A phase that could not run leaves its section empty. Said plainly, that
|
|
129
|
+
# is a missing build step; left unsaid, it reads as a finding about the code.
|
|
130
|
+
failures = analyzer.phase_failures
|
|
131
|
+
if failures:
|
|
132
|
+
out += [
|
|
133
|
+
"## Incomplete Analysis",
|
|
134
|
+
"",
|
|
135
|
+
f"{len(failures)} of {analyzer._TOTAL_PHASES} phases could not run. "
|
|
136
|
+
"The sections they produce are missing from this report -- that is a "
|
|
137
|
+
"gap in the analysis, not a finding about the code.",
|
|
138
|
+
"",
|
|
139
|
+
]
|
|
140
|
+
out += md_table(
|
|
141
|
+
["Phase", "Name", "Reason"],
|
|
142
|
+
[(f["phase"], f["name"], f"`{f['error']}`") for f in failures],
|
|
143
|
+
aligns="rll",
|
|
144
|
+
)
|
|
145
|
+
if any(f["missing_index"] for f in failures):
|
|
146
|
+
out += [
|
|
147
|
+
"",
|
|
148
|
+
"> The semantic index is missing. Only the fan-out and concern-based "
|
|
149
|
+
"ranking phases need it; every other phase reads the SQLite graph "
|
|
150
|
+
"directly, which is why the rest of this report is complete. Run "
|
|
151
|
+
"`pycodekg build-index` (or `pycodekg build`) to build it, then "
|
|
152
|
+
"re-run the analysis.",
|
|
153
|
+
]
|
|
154
|
+
rule()
|
|
155
|
+
|
|
127
156
|
# ── Executive Summary ────────────────────────────────────────────────
|
|
128
157
|
out += [
|
|
129
158
|
"## Executive Summary",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|