pycode-kg 0.28.0__tar.gz → 0.28.2__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.28.0 → pycode_kg-0.28.2}/PKG-INFO +9 -9
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/README.md +4 -4
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/pyproject.toml +5 -5
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/__init__.py +1 -1
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/pycodekg_thorough_analysis.py +93 -1
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/report.py +29 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/LICENSE +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/.DS_Store +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/__main__.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/analysis/__init__.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/analysis/bridge.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/analysis/centrality.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/analysis/framework_detector.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/app.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/architecture.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/build_pycodekg_sqlite.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/cli/__init__.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/cli/cmd_analyze.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/cli/cmd_architecture.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/cli/cmd_bridges.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/cli/cmd_build.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/cli/cmd_build_full.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/cli/cmd_centrality.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/cli/cmd_explain.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/cli/cmd_framework_nodes.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/cli/cmd_hooks.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/cli/cmd_init.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/cli/cmd_mcp.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/cli/cmd_model.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/cli/cmd_query.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/cli/cmd_quilt.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/cli/cmd_snapshot.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/cli/cmd_viz.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/cli/main.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/cli/options.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/config.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/explain.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/graph.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/graph_html.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/index.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/kg.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/layout3d.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/mcp_server.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/module/__init__.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/module/base.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/module/extractor.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/module/types.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/pycodekg.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/ranking/__init__.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/ranking/cli_rank.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/ranking/coderank.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/render.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/resolution.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/scene3d.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/snapshots.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/sql/004_add_centrality_table.sql +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/store.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/theme.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/utils.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/visitor.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/src/pycode_kg/viz3d.py +0 -0
- {pycode_kg-0.28.0 → pycode_kg-0.28.2}/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.28.
|
|
3
|
+
Version: 0.28.2
|
|
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,8 +21,8 @@ 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.27.0)
|
|
25
|
+
Requires-Dist: kgmodule-utils[viz3d-qt,viz3d-render] (>=0.27.0) ; extra == "viz3d"
|
|
26
26
|
Requires-Dist: markdown (>=3.6) ; extra == "all"
|
|
27
27
|
Requires-Dist: markdown (>=3.6) ; extra == "viz3d"
|
|
28
28
|
Requires-Dist: mcp (>=1.3.0,<2)
|
|
@@ -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.16.0) ; extra == "all"
|
|
42
|
+
Requires-Dist: quiltwright (>=0.16.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,7 +60,7 @@ 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
66
|
[](https://doi.org/10.5281/zenodo.19737993)
|
|
@@ -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.28.
|
|
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.2.md](docs/analysis_v0.28.2.md), produced (of course) by `pycodekg analyze` against this very repo.
|
|
259
259
|
|
|
260
260
|
---
|
|
261
261
|
|
|
@@ -282,13 +282,13 @@ If you use PyCodeKG in your research or project, please cite it:
|
|
|
282
282
|
|
|
283
283
|
[](https://doi.org/10.5281/zenodo.19737993)
|
|
284
284
|
|
|
285
|
-
> Suchanek, E. G. (2026). *PyCodeKG: A Knowledge Graph for Python Codebases* (Version 0.28.
|
|
285
|
+
> Suchanek, E. G. (2026). *PyCodeKG: A Knowledge Graph for Python Codebases* (Version 0.28.2) [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.28.
|
|
291
|
+
version = {0.28.2},
|
|
292
292
|
year = {2026},
|
|
293
293
|
publisher = {Flux-Frontiers},
|
|
294
294
|
url = {https://github.com/Flux-Frontiers/pycode_kg},
|
|
@@ -5,7 +5,7 @@
|
|
|
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
11
|
[](https://doi.org/10.5281/zenodo.19737993)
|
|
@@ -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.28.
|
|
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.2.md](docs/analysis_v0.28.2.md), produced (of course) by `pycodekg analyze` against this very repo.
|
|
204
204
|
|
|
205
205
|
---
|
|
206
206
|
|
|
@@ -227,13 +227,13 @@ If you use PyCodeKG in your research or project, please cite it:
|
|
|
227
227
|
|
|
228
228
|
[](https://doi.org/10.5281/zenodo.19737993)
|
|
229
229
|
|
|
230
|
-
> Suchanek, E. G. (2026). *PyCodeKG: A Knowledge Graph for Python Codebases* (Version 0.28.
|
|
230
|
+
> Suchanek, E. G. (2026). *PyCodeKG: A Knowledge Graph for Python Codebases* (Version 0.28.2) [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.28.
|
|
236
|
+
version = {0.28.2},
|
|
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.28.
|
|
73
|
+
version = "0.28.2"
|
|
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"
|
|
@@ -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.27.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.27.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.16.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.16.0",
|
|
233
233
|
"streamlit>=1.56.0",
|
|
234
234
|
"trame-vtk>=2.0.0",
|
|
235
235
|
]
|
|
@@ -27,6 +27,7 @@ Author: Eric G. Suchanek, PhD
|
|
|
27
27
|
License: Elastic 2.0
|
|
28
28
|
"""
|
|
29
29
|
|
|
30
|
+
import ast
|
|
30
31
|
import datetime
|
|
31
32
|
import importlib
|
|
32
33
|
import inspect
|
|
@@ -43,6 +44,7 @@ from collections.abc import Callable
|
|
|
43
44
|
from dataclasses import asdict, dataclass
|
|
44
45
|
from pathlib import Path
|
|
45
46
|
|
|
47
|
+
from kg_utils.vector_backend import VectorStoreNotFoundError
|
|
46
48
|
from rich.columns import Columns
|
|
47
49
|
from rich.console import Console
|
|
48
50
|
from rich.panel import Panel
|
|
@@ -135,6 +137,28 @@ _PROPERTY_DECORATOR_RE = re.compile(
|
|
|
135
137
|
r"@(?:functools\.)?(?:cached_)?property\b|@\w[\w.]*\.(?:setter|getter|deleter)\b"
|
|
136
138
|
)
|
|
137
139
|
|
|
140
|
+
# Route-registration decorator attributes on a web-framework app or router:
|
|
141
|
+
# ``@app.get(...)``, ``@router.post(...)``, ``@app.middleware("http")``
|
|
142
|
+
# (FastAPI, Starlette) and ``@app.route(...)`` (Flask). The framework calls
|
|
143
|
+
# the handler, so the graph never sees a CALLS edge to it.
|
|
144
|
+
_ROUTE_DECORATOR_ATTRS = frozenset(
|
|
145
|
+
{
|
|
146
|
+
"get",
|
|
147
|
+
"post",
|
|
148
|
+
"put",
|
|
149
|
+
"patch",
|
|
150
|
+
"delete",
|
|
151
|
+
"head",
|
|
152
|
+
"options",
|
|
153
|
+
"api_route",
|
|
154
|
+
"route",
|
|
155
|
+
"middleware",
|
|
156
|
+
"websocket",
|
|
157
|
+
"exception_handler",
|
|
158
|
+
"on_event",
|
|
159
|
+
}
|
|
160
|
+
)
|
|
161
|
+
|
|
138
162
|
# Builtin container/str method names live in pycode_kg.resolution, where the
|
|
139
163
|
# post-build hook prunes their bogus RESOLVES_TO edges at the source. The
|
|
140
164
|
# analyzer still guards its own reads (chain tracing, fan-out) so it stays
|
|
@@ -355,6 +379,33 @@ def _has_property_decorator(source_lines: list[str], def_lineno: int) -> bool:
|
|
|
355
379
|
return False
|
|
356
380
|
|
|
357
381
|
|
|
382
|
+
def _has_route_decorator(source: str, def_lineno: int) -> bool:
|
|
383
|
+
"""Check whether the ``def`` at *def_lineno* is registered as a web route.
|
|
384
|
+
|
|
385
|
+
Matches a decorator of the form ``@<obj>.<attr>`` or ``@<obj>.<attr>(...)``
|
|
386
|
+
with ``attr`` in :data:`_ROUTE_DECORATOR_ATTRS`. The module is parsed
|
|
387
|
+
rather than scanned line by line because route decorators often span
|
|
388
|
+
several lines (``@app.post(\n "/x",\n response_model=M,\n)``).
|
|
389
|
+
|
|
390
|
+
:param source: Full module source text.
|
|
391
|
+
:param def_lineno: 1-based line number of the ``def`` statement.
|
|
392
|
+
:return: True when a route-registration decorator precedes the definition;
|
|
393
|
+
False when it does not, or the source does not parse.
|
|
394
|
+
"""
|
|
395
|
+
try:
|
|
396
|
+
tree = ast.parse(source)
|
|
397
|
+
except SyntaxError:
|
|
398
|
+
return False
|
|
399
|
+
for node in ast.walk(tree):
|
|
400
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.lineno == def_lineno:
|
|
401
|
+
for dec in node.decorator_list:
|
|
402
|
+
target = dec.func if isinstance(dec, ast.Call) else dec
|
|
403
|
+
if isinstance(target, ast.Attribute) and target.attr in _ROUTE_DECORATOR_ATTRS:
|
|
404
|
+
return True
|
|
405
|
+
return False
|
|
406
|
+
return False
|
|
407
|
+
|
|
408
|
+
|
|
358
409
|
def _dotted_module(module_path: str) -> str:
|
|
359
410
|
"""Convert a repo-relative module path to its dotted import path.
|
|
360
411
|
|
|
@@ -444,6 +495,8 @@ class PyCodeKGAnalyzer:
|
|
|
444
495
|
self.concern_analysis: list[dict] = [] # populated by _analyze_concerns
|
|
445
496
|
# Single-line phase output: each phase fn writes its summary here
|
|
446
497
|
self._phase_result: str = ""
|
|
498
|
+
# Phases that raised; rendered in the report (see _run_phase)
|
|
499
|
+
self.phase_failures: list[dict] = []
|
|
447
500
|
|
|
448
501
|
# ── total phase count (update if phases are added/removed) ────────────────
|
|
449
502
|
_TOTAL_PHASES = 15
|
|
@@ -455,13 +508,39 @@ class PyCodeKGAnalyzer:
|
|
|
455
508
|
instead of printing directly, keeping the terminal output to one line
|
|
456
509
|
per phase.
|
|
457
510
|
|
|
511
|
+
A phase that cannot run must not take the others and the report with
|
|
512
|
+
it. The concrete case is ``pycodekg build-sqlite``, a supported command
|
|
513
|
+
that builds the graph without the vector index: the fan-out and
|
|
514
|
+
concern-ranking phases seed on a semantic query and raise
|
|
515
|
+
``VectorStoreNotFoundError``, while every other phase reads the graph
|
|
516
|
+
only. The failure is recorded in :attr:`phase_failures` and rendered in
|
|
517
|
+
the report, so a degraded run names its missing sections instead of
|
|
518
|
+
quietly omitting them.
|
|
519
|
+
|
|
458
520
|
:param num: Phase number (1-based).
|
|
459
521
|
:param name: Human-readable phase label.
|
|
460
522
|
:param fn: Zero-argument callable to execute.
|
|
461
523
|
"""
|
|
462
524
|
self._phase_result = ""
|
|
463
525
|
t0 = time.monotonic()
|
|
464
|
-
|
|
526
|
+
try:
|
|
527
|
+
fn()
|
|
528
|
+
except Exception as exc: # noqa: BLE001 -- see docstring
|
|
529
|
+
elapsed = time.monotonic() - t0
|
|
530
|
+
self.phase_failures.append(
|
|
531
|
+
{
|
|
532
|
+
"phase": num,
|
|
533
|
+
"name": name,
|
|
534
|
+
"error": str(exc),
|
|
535
|
+
"missing_index": isinstance(exc, VectorStoreNotFoundError),
|
|
536
|
+
}
|
|
537
|
+
)
|
|
538
|
+
logger.warning("Phase %d (%s) failed: %s", num, name, exc)
|
|
539
|
+
self.console.print(
|
|
540
|
+
f" [cyan]▶ Phase {num:2d}/{self._TOTAL_PHASES}:[/cyan]"
|
|
541
|
+
f" {name} [yellow]skipped: {exc}[/yellow] [green]({elapsed:.1f}s)[/green]"
|
|
542
|
+
)
|
|
543
|
+
return
|
|
465
544
|
elapsed = time.monotonic() - t0
|
|
466
545
|
result = f" {self._phase_result}" if self._phase_result else ""
|
|
467
546
|
self.console.print(
|
|
@@ -899,6 +978,8 @@ class PyCodeKGAnalyzer:
|
|
|
899
978
|
- Property-family methods — accessed as attributes, not calls
|
|
900
979
|
- MCP tools in ``mcp_server.py`` — dispatched by the MCP router
|
|
901
980
|
- Click handlers in CLI modules — dispatched by Click
|
|
981
|
+
- Web route handlers (``@app.get``, ``@router.post``,
|
|
982
|
+
``@app.middleware``, ...) -- dispatched by the web framework
|
|
902
983
|
- ``visit_*`` on ``ast.NodeVisitor`` subclasses — ``getattr`` dispatch
|
|
903
984
|
- Overrides of SDK protocol methods on classes with external bases —
|
|
904
985
|
invoked by the kg_utils pipeline
|
|
@@ -985,6 +1066,14 @@ class PyCodeKGAnalyzer:
|
|
|
985
1066
|
if len(re.findall(rf"\b{re.escape(name)}\b", source)) > 1:
|
|
986
1067
|
return True
|
|
987
1068
|
|
|
1069
|
+
# Web route handlers: registered by decorator and called by the
|
|
1070
|
+
# framework's router, so no CALLS edge reaches them.
|
|
1071
|
+
if kind in ("function", "method"):
|
|
1072
|
+
source = self._module_source(module)
|
|
1073
|
+
lineno = node.get("lineno")
|
|
1074
|
+
if source and lineno and _has_route_decorator(source, lineno):
|
|
1075
|
+
return True
|
|
1076
|
+
|
|
988
1077
|
return False
|
|
989
1078
|
|
|
990
1079
|
def _is_unverifiable_override(self, node: dict, ctx: dict) -> bool:
|
|
@@ -2505,6 +2594,9 @@ class PyCodeKGAnalyzer:
|
|
|
2505
2594
|
"coderank_top_nodes": self.coderank_top_nodes,
|
|
2506
2595
|
# Option D: concern-based hybrid ranking
|
|
2507
2596
|
"concern_analysis": self.concern_analysis,
|
|
2597
|
+
# An empty section above may mean a phase did not run, not that
|
|
2598
|
+
# the codebase has nothing to show.
|
|
2599
|
+
"phase_failures": self.phase_failures,
|
|
2508
2600
|
}
|
|
2509
2601
|
|
|
2510
2602
|
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
|
|
File without changes
|
|
File without changes
|