pycode-kg 0.26.0__tar.gz → 0.27.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 (62) hide show
  1. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/PKG-INFO +7 -7
  2. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/README.md +4 -4
  3. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/pyproject.toml +17 -3
  4. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/__init__.py +1 -1
  5. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/cli/cmd_init.py +1 -1
  6. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/cli/cmd_snapshot.py +30 -2
  7. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/snapshots.py +44 -128
  8. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/LICENSE +0 -0
  9. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/.DS_Store +0 -0
  10. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/__main__.py +0 -0
  11. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/analysis/__init__.py +0 -0
  12. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/analysis/bridge.py +0 -0
  13. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/analysis/centrality.py +0 -0
  14. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/analysis/framework_detector.py +0 -0
  15. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/app.py +0 -0
  16. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/architecture.py +0 -0
  17. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/build_pycodekg_sqlite.py +0 -0
  18. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/cli/__init__.py +0 -0
  19. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/cli/cmd_analyze.py +0 -0
  20. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/cli/cmd_architecture.py +0 -0
  21. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/cli/cmd_bridges.py +0 -0
  22. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/cli/cmd_build.py +0 -0
  23. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/cli/cmd_build_full.py +0 -0
  24. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/cli/cmd_centrality.py +0 -0
  25. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/cli/cmd_explain.py +0 -0
  26. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/cli/cmd_framework_nodes.py +0 -0
  27. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/cli/cmd_hooks.py +0 -0
  28. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/cli/cmd_mcp.py +0 -0
  29. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/cli/cmd_model.py +0 -0
  30. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/cli/cmd_query.py +0 -0
  31. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/cli/cmd_quilt.py +0 -0
  32. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/cli/cmd_viz.py +0 -0
  33. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/cli/main.py +0 -0
  34. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/cli/options.py +0 -0
  35. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/config.py +0 -0
  36. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/explain.py +0 -0
  37. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/graph.py +0 -0
  38. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/graph_html.py +0 -0
  39. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/index.py +0 -0
  40. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/kg.py +0 -0
  41. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/layout3d.py +0 -0
  42. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/mcp_server.py +0 -0
  43. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/module/__init__.py +0 -0
  44. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/module/base.py +0 -0
  45. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/module/extractor.py +0 -0
  46. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/module/types.py +0 -0
  47. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/pycodekg.py +0 -0
  48. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/pycodekg_thorough_analysis.py +0 -0
  49. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/ranking/__init__.py +0 -0
  50. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/ranking/cli_rank.py +0 -0
  51. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/ranking/coderank.py +0 -0
  52. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/render.py +0 -0
  53. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/report.py +0 -0
  54. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/resolution.py +0 -0
  55. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/scene3d.py +0 -0
  56. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/sql/004_add_centrality_table.sql +0 -0
  57. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/store.py +0 -0
  58. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/theme.py +0 -0
  59. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/utils.py +0 -0
  60. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/visitor.py +0 -0
  61. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/src/pycode_kg/viz3d.py +0 -0
  62. {pycode_kg-0.26.0 → pycode_kg-0.27.0}/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.26.0
3
+ Version: 0.27.0
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.19.0)
25
- Requires-Dist: kgmodule-utils[viz3d-qt,viz3d-render] (>=0.18.1) ; extra == "viz3d"
24
+ Requires-Dist: kgmodule-utils[semantic,viz3d] (>=0.20.0)
25
+ Requires-Dist: kgmodule-utils[viz3d-qt,viz3d-render] (>=0.20.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.0.0,<2)
@@ -60,7 +60,7 @@ Description-Content-Type: text/markdown
60
60
 
61
61
  [![Python](https://img.shields.io/badge/python-3.12%20%7C%203.13-blue.svg)](https://www.python.org/)
62
62
  [![License: Elastic-2.0](https://img.shields.io/badge/License-Elastic%202.0-blue.svg)](https://www.elastic.co/licensing/elastic-license)
63
- [![Version](https://img.shields.io/badge/version-0.26.0-blue.svg)](https://github.com/Flux-Frontiers/pycode_kg/releases)
63
+ [![Version](https://img.shields.io/badge/version-0.27.0-blue.svg)](https://github.com/Flux-Frontiers/pycode_kg/releases)
64
64
  [![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)
65
65
  [![Poetry](https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json)](https://python-poetry.org/)
66
66
  [![DOI](https://zenodo.org/badge/1202379010.svg)](https://zenodo.org/badge/latestdoi/1202379010)
@@ -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.26.0.md](docs/analysis_v0.26.0.md), produced (of course) by `pycodekg analyze` against this very repo.
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.27.0.md](docs/analysis_v0.27.0.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
  [![DOI](https://zenodo.org/badge/1202379010.svg)](https://zenodo.org/badge/latestdoi/1202379010)
284
284
 
285
- > Suchanek, E. G. (2026). *PyCodeKG: A Knowledge Graph for Python Codebases* (Version 0.26.0) [Software]. Flux-Frontiers. https://doi.org/10.5281/zenodo.19737993
285
+ > Suchanek, E. G. (2026). *PyCodeKG: A Knowledge Graph for Python Codebases* (Version 0.27.0) [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.26.0},
291
+ version = {0.27.0},
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
  [![Python](https://img.shields.io/badge/python-3.12%20%7C%203.13-blue.svg)](https://www.python.org/)
7
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.26.0-blue.svg)](https://github.com/Flux-Frontiers/pycode_kg/releases)
8
+ [![Version](https://img.shields.io/badge/version-0.27.0-blue.svg)](https://github.com/Flux-Frontiers/pycode_kg/releases)
9
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
10
  [![Poetry](https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json)](https://python-poetry.org/)
11
11
  [![DOI](https://zenodo.org/badge/1202379010.svg)](https://zenodo.org/badge/latestdoi/1202379010)
@@ -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.26.0.md](docs/analysis_v0.26.0.md), produced (of course) by `pycodekg analyze` against this very repo.
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.27.0.md](docs/analysis_v0.27.0.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
  [![DOI](https://zenodo.org/badge/1202379010.svg)](https://zenodo.org/badge/latestdoi/1202379010)
229
229
 
230
- > Suchanek, E. G. (2026). *PyCodeKG: A Knowledge Graph for Python Codebases* (Version 0.26.0) [Software]. Flux-Frontiers. https://doi.org/10.5281/zenodo.19737993
230
+ > Suchanek, E. G. (2026). *PyCodeKG: A Knowledge Graph for Python Codebases* (Version 0.27.0) [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.26.0},
236
+ version = {0.27.0},
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.26.0"
73
+ version = "0.27.0"
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"
@@ -155,7 +155,21 @@ dependencies = [
155
155
  # this repo's visitor writes as of 0.24.1 -- against 0.18.0 the stubs are
156
156
  # tagged correctly but the tag is silently ignored, so resolution stays on
157
157
  # the old untyped trailing-name match with no error to say so.
158
- "kgmodule-utils[semantic,viz3d]>=0.19.0",
158
+ #
159
+ # 0.19.1 because SnapshotManager.load_snapshot back-fills a missing
160
+ # vs_previous through _compute_delta_from_metrics as of that release.
161
+ # Against 0.19.0 the backfill uses a hardcoded nodes/edges dict, so
162
+ # coverage_delta and critical_issues_delta read as zero in `snapshot show`
163
+ # for every snapshot whose deltas were not persisted at capture -- which is
164
+ # the normal path, not a legacy one.
165
+ #
166
+ # 0.20.0 is a hard requirement, not a preference: snapshots.py has no
167
+ # __init__, capture or diff_snapshots of its own any more. It configures
168
+ # the base through the package_name and dict_metric_deltas class attributes
169
+ # and the _domain_metrics() hook, none of which exist before 0.20.0.
170
+ # Against 0.19.x the manager silently reports itself as "kg-utils" and
171
+ # drops module_node_counts entirely.
172
+ "kgmodule-utils[semantic,viz3d]>=0.20.0",
159
173
  ]
160
174
 
161
175
  [project.optional-dependencies]
@@ -180,7 +194,7 @@ viz3d = [
180
194
  # the four-step build/render/write/cast itself. It subsumes [viz3d-render]
181
195
  # (it declares pyvista too), but both are named so the dependency reads as
182
196
  # what it is rather than as a side effect.
183
- "kgmodule-utils[viz3d-render,viz3d-qt]>=0.18.1",
197
+ "kgmodule-utils[viz3d-render,viz3d-qt]>=0.20.0",
184
198
  "pyvistaqt>=0.11.0",
185
199
  "trame-vtk>=2.0.0",
186
200
  # Looking Glass quilts. 0.4.0 supplies depth_report() and widened
@@ -36,7 +36,7 @@ Author: Eric G. Suchanek, PhD
36
36
  License: Elastic 2.0
37
37
  """
38
38
 
39
- __version__ = "0.26.0"
39
+ __version__ = "0.27.0"
40
40
  __author__ = "Eric G. Suchanek, PhD"
41
41
 
42
42
  # Low-level primitives (locked v0 contract)
@@ -277,7 +277,7 @@ def init(
277
277
  version=version,
278
278
  branch=branch,
279
279
  graph_stats_dict=stats,
280
- coverage=coverage,
280
+ docstring_coverage=coverage,
281
281
  coverage_documented=coverage_documented,
282
282
  coverage_total=coverage_total,
283
283
  critical_issues=critical_issues,
@@ -19,6 +19,7 @@ import json
19
19
  from pathlib import Path
20
20
 
21
21
  import click
22
+ from click.core import ParameterSource
22
23
 
23
24
  from pycode_kg.cli.main import cli
24
25
  from pycode_kg.cli.options import sqlite_option
@@ -34,6 +35,33 @@ from pycode_kg.snapshots import (
34
35
  from pycode_kg.store import GraphStore
35
36
 
36
37
 
38
+ def _graph_for_repo(sqlite: str, repo_root: Path) -> Path:
39
+ """Resolve the graph path, anchoring the default to ``--repo``.
40
+
41
+ ``--sqlite`` defaults to the relative ``.pycodekg/graph.sqlite``, which
42
+ click resolves against the *current working directory*, while
43
+ ``--repo`` decides where the snapshot is filed. Left alone, the two
44
+ disagree: ``pycodekg snapshot save 1.0.0 --repo /other/project`` run from
45
+ inside this repo reads *this* repo's graph and writes the result into
46
+ */other/project*'s snapshots directory -- a snapshot whose metrics belong
47
+ to one project and whose provenance claims another, with no error.
48
+
49
+ An explicitly passed ``--sqlite`` is always honoured as given; only the
50
+ default is re-anchored. The other commands sharing ``sqlite_option``
51
+ (``query``, ``explain``) take no ``--repo``, so cwd is the right base for
52
+ them and they are unaffected.
53
+
54
+ :param sqlite: The ``--sqlite`` value, explicit or default.
55
+ :param repo_root: The resolved ``--repo`` path.
56
+ :return: The graph database path to read.
57
+ """
58
+ ctx = click.get_current_context(silent=True)
59
+ source = ctx.get_parameter_source("sqlite") if ctx is not None else None
60
+ if source is not None and source is not ParameterSource.DEFAULT:
61
+ return Path(sqlite)
62
+ return repo_root / ".pycodekg" / "graph.sqlite"
63
+
64
+
37
65
  @cli.group("snapshot")
38
66
  def snapshot() -> None:
39
67
  """Manage temporal snapshots of PyCodeKG metrics."""
@@ -105,7 +133,7 @@ def save_snapshot(
105
133
  pycodekg snapshot save 0.5.1 --repo .
106
134
  """
107
135
  repo_root = Path(repo).resolve()
108
- db_path = Path(sqlite)
136
+ db_path = _graph_for_repo(sqlite, repo_root)
109
137
  snapshots_path = (
110
138
  Path(snapshots_dir).resolve() if snapshots_dir else (repo_root / ".pycodekg" / "snapshots")
111
139
  )
@@ -161,7 +189,7 @@ def save_snapshot(
161
189
  version=version,
162
190
  branch=branch,
163
191
  graph_stats_dict=stats,
164
- coverage=coverage,
192
+ docstring_coverage=coverage,
165
193
  coverage_documented=coverage_documented,
166
194
  coverage_total=coverage_total,
167
195
  critical_issues=critical_issues,
@@ -15,7 +15,7 @@ This module adds:
15
15
  ``metrics_from_dict`` / ``metrics_to_dict`` and ``delta_from_dict`` /
16
16
  ``delta_to_dict``; a ``Snapshot`` never holds one.
17
17
  - a ``SnapshotManager`` subclass that sets ``package_name="pycode-kg"``,
18
- names the pycode-kg metric fields in ``capture()``, adds
18
+ collects the pycode-kg metric fields in ``_domain_metrics()``, adds
19
19
  ``coverage_delta`` and ``critical_issues_delta`` to deltas, collects
20
20
  per-module node counts from SQLite, and extends a diff with
21
21
  ``module_node_counts_delta``, ``issues_delta`` and ``timestamp``.
@@ -44,7 +44,6 @@ from __future__ import annotations
44
44
 
45
45
  import sqlite3
46
46
  from dataclasses import dataclass, field
47
- from pathlib import Path
48
47
  from typing import Any
49
48
 
50
49
  # ---------------------------------------------------------------------------
@@ -174,147 +173,64 @@ def delta_from_dict(d: dict[str, Any] | None) -> SnapshotDelta | None:
174
173
  class SnapshotManager(_BaseSnapshotManager):
175
174
  """pycode-kg snapshot manager.
176
175
 
177
- Subclasses the shared ``kg_utils.snapshots.SnapshotManager`` and adds:
176
+ Subclasses the shared ``kg_utils.snapshots.SnapshotManager``. Three of the
177
+ four additions are class attributes rather than methods, because the base
178
+ supplies the behaviour and this module only names the domain values:
178
179
 
179
- * ``package_name="pycode-kg"`` default for version detection.
180
- * A ``capture()`` naming the pycode-kg metric fields (``coverage``,
181
- ``critical_issues``, ``complexity_median`` and the coverage numerator
182
- and denominator) and collecting per-module node counts.
180
+ * ``package_name = "pycode-kg"`` for version detection.
181
+ * ``dict_metric_deltas`` naming ``module_node_counts``, which the base
182
+ turns into ``module_node_counts_delta`` in ``diff_snapshots``.
183
+ * ``_domain_metrics()`` collecting per-module node counts at capture time.
183
184
  * ``_compute_delta_from_metrics`` extended with ``coverage_delta`` and
184
- ``critical_issues_delta``.
185
- * ``diff_snapshots`` extended with ``module_node_counts_delta``,
186
- ``issues_delta`` and ``timestamp``.
185
+ ``critical_issues_delta`` — the one genuinely domain-specific method.
187
186
  * ``_collect_module_node_counts()`` — SQLite per-module node counts.
188
187
 
189
- Everything else -- saving, loading, listing, pruning, key handling -- is
190
- inherited unchanged. Overriding those to convert between dicts and the
191
- domain dataclasses is what this module used to do, and is what let the
192
- 0.25.0 snapshot key regression through.
188
+ Everything else -- capture, saving, loading, listing, pruning, diffing, key
189
+ handling -- is inherited unchanged. Overriding those is what this module
190
+ used to do, and is what let the 0.25.0 snapshot key regression through.
191
+
192
+ Note that ``capture()`` takes the coverage fraction as
193
+ ``docstring_coverage``, the name it is stored under. Until 0.27.0 this
194
+ class overrode ``capture()`` to accept it as ``coverage`` and rename it on
195
+ the way through; that override is gone, and with it the signature-restating
196
+ pattern that let a ``key=`` go missing. ``coverage=`` still works and warns
197
+ -- see ``capture_aliases`` below.
193
198
  """
194
199
 
195
- def __init__(
196
- self,
197
- snapshots_dir: Path | str,
198
- *,
199
- db_path: Path | str | None = None,
200
- package_name: str = "pycode-kg",
201
- ) -> None:
202
- """Initialize the manager rooted at ``snapshots_dir``.
203
-
204
- :param snapshots_dir: Directory where snapshot JSON files live; created
205
- on first save if it does not exist.
206
- :param db_path: PyCodeKG SQLite graph path; required for collecting
207
- per-module node counts during ``capture()``. Optional otherwise.
208
- :param package_name: Package name used for version detection in saved
209
- snapshots. Defaults to ``"pycode-kg"``.
210
- """
211
- super().__init__(snapshots_dir, package_name=package_name, db_path=db_path)
200
+ #: Version detection reads this; the base uses it as the snapshot's ``tool``.
201
+ package_name = "pycode-kg"
212
202
 
213
- # ------------------------------------------------------------------
214
- # capture name the pycode-kg metric fields
215
- # ------------------------------------------------------------------
203
+ #: ``diff_snapshots`` emits ``module_node_counts_delta`` from this, holding
204
+ #: only the modules whose node count actually changed.
205
+ dict_metric_deltas = ("module_node_counts",)
216
206
 
217
- def capture( # ty: ignore[invalid-method-override]
218
- self,
219
- version: str | None = None,
220
- branch: str | None = None,
221
- graph_stats_dict: dict[str, Any] | None = None,
222
- coverage: float = 0.0,
223
- coverage_documented: int = 0,
224
- coverage_total: int = 0,
225
- critical_issues: int = 0,
226
- complexity_median: float = 0.0,
227
- hotspots: list[dict[str, Any]] | None = None,
228
- issues: list[str] | None = None,
229
- tree_hash: str = "",
230
- key: str = "",
231
- subject: str = "",
232
- ) -> Snapshot:
233
- """Capture a pycode-kg snapshot.
234
-
235
- Names the pycode-kg metric fields explicitly rather than taking them
236
- through ``**extra_metrics``, and adds per-module node counts, then
237
- delegates to the shared implementation.
238
-
239
- :param version: Version string (e.g., "0.5.1").
240
- :param branch: Git branch name; auto-detected if None.
241
- :param graph_stats_dict: Output from ``graph_stats()`` / ``store.stats()``.
242
- :param coverage: Docstring coverage fraction (0.0-1.0).
243
- :param coverage_documented: Nodes with a non-empty docstring — the
244
- numerator behind ``coverage``. Shown beside the percentage in
245
- Snapshot History so a coverage drop caused by adding undocumented
246
- nodes reads differently from one caused by removing docstrings.
247
- :param coverage_total: Nodes eligible for docstring coverage — the
248
- denominator behind ``coverage``.
249
- :param critical_issues: Number of critical issues detected.
250
- :param complexity_median: Median fan-in across functions.
251
- :param hotspots: Top hotspot entries.
252
- :param issues: Issue description strings.
253
- :param tree_hash: Git tree hash, recorded as provenance; auto-detected
254
- if not provided. It is not the snapshot's key.
255
- :param key: Snapshot identifier. Pass the release tag at release time;
256
- omit it and the base assigns a UTC timestamp.
257
- :param subject: What was measured, e.g. ``repo:pycode-kg``.
258
- :return: New :class:`~kg_utils.snapshots.Snapshot` (not yet persisted).
259
- """
260
- return super().capture(
261
- version=version,
262
- branch=branch,
263
- graph_stats_dict=graph_stats_dict,
264
- tree_hash=tree_hash,
265
- key=key,
266
- subject=subject,
267
- hotspots=hotspots,
268
- issues=issues,
269
- docstring_coverage=coverage,
270
- coverage_documented=coverage_documented,
271
- coverage_total=coverage_total,
272
- critical_issues=critical_issues,
273
- complexity_median=complexity_median,
274
- module_node_counts=self._collect_module_node_counts(),
275
- )
207
+ #: Until 0.27.0 this class overrode ``capture()`` to accept the docstring
208
+ #: coverage fraction as ``coverage`` and rename it on the way through. The
209
+ #: override is gone, and the stored name is the only name. Without this
210
+ #: entry a caller still passing ``coverage=`` would get no error: the base
211
+ #: ``**extra_metrics`` would record a ``coverage`` metric nobody reads and
212
+ #: leave ``docstring_coverage`` absent.
213
+ capture_aliases = {"coverage": "docstring_coverage"}
276
214
 
277
215
  # ------------------------------------------------------------------
278
- # diff_snapshots adds module_node_counts_delta, issues_delta, timestamp
216
+ # Capture-time metrics collected by this module
279
217
  # ------------------------------------------------------------------
280
218
 
281
- def diff_snapshots(self, key_a: str, key_b: str) -> dict[str, Any]:
282
- """Compare two snapshots side-by-side.
219
+ def _domain_metrics(self, stats: dict[str, Any]) -> dict[str, Any]:
220
+ """Collect the metrics this module gathers for itself.
283
221
 
284
- Extends the shared diff with ``module_node_counts_delta``,
285
- ``issues_delta`` (introduced / resolved issue strings) and the
286
- ``timestamp`` of each side, which the CLI prints.
222
+ Called by the inherited ``capture()``. Overriding this rather than
223
+ ``capture()`` is deliberate: a ``capture()`` override has to restate the
224
+ base signature, and restating it is what let ``key=`` fall into
225
+ ``**extra_metrics`` and ship 0.25.0 with every snapshot keyed on a tree
226
+ hash.
287
227
 
288
- :param key_a: Earlier snapshot key.
289
- :param key_b: Later snapshot key.
290
- :return: The shared diff result with the pycode-kg additions.
228
+ :param stats: Graph stats passed to ``capture()``; unused here, the
229
+ counts come from SQLite.
230
+ :return: ``{"module_node_counts": {module_path: node_count}}``, empty
231
+ if no SQLite graph is configured.
291
232
  """
292
- result = super().diff_snapshots(key_a, key_b)
293
- if "error" in result:
294
- return result
295
-
296
- for side, key in (("a", key_a), ("b", key_b)):
297
- snap = self.load_snapshot(key)
298
- if snap is not None:
299
- result[side]["timestamp"] = snap.timestamp
300
-
301
- m_a: dict[str, Any] = result["a"]["metrics"]
302
- m_b: dict[str, Any] = result["b"]["metrics"]
303
- counts_a: dict[str, int] = m_a.get("module_node_counts", {})
304
- counts_b: dict[str, int] = m_b.get("module_node_counts", {})
305
- result["module_node_counts_delta"] = {
306
- mod: counts_b.get(mod, 0) - counts_a.get(mod, 0)
307
- for mod in set(counts_a) | set(counts_b)
308
- if counts_b.get(mod, 0) != counts_a.get(mod, 0)
309
- }
310
-
311
- issues_a = set(result["a"]["issues"])
312
- issues_b = set(result["b"]["issues"])
313
- result["issues_delta"] = {
314
- "introduced": list(issues_b - issues_a),
315
- "resolved": list(issues_a - issues_b),
316
- }
317
- return result
233
+ return {"module_node_counts": self._collect_module_node_counts()}
318
234
 
319
235
  # ------------------------------------------------------------------
320
236
  # Delta computation — adds coverage_delta and critical_issues_delta
File without changes