pycode-kg 0.16.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pycode_kg/.DS_Store +0 -0
- pycode_kg/__init__.py +91 -0
- pycode_kg/__main__.py +11 -0
- pycode_kg/analysis/__init__.py +15 -0
- pycode_kg/analysis/bridge.py +108 -0
- pycode_kg/analysis/centrality.py +412 -0
- pycode_kg/analysis/framework_detector.py +103 -0
- pycode_kg/analysis/hybrid_rank.py +53 -0
- pycode_kg/app.py +1335 -0
- pycode_kg/architecture.py +624 -0
- pycode_kg/build_pycodekg_lancedb.py +15 -0
- pycode_kg/build_pycodekg_sqlite.py +15 -0
- pycode_kg/cli/__init__.py +29 -0
- pycode_kg/cli/cmd_analyze.py +96 -0
- pycode_kg/cli/cmd_architecture.py +150 -0
- pycode_kg/cli/cmd_bridges.py +26 -0
- pycode_kg/cli/cmd_build.py +154 -0
- pycode_kg/cli/cmd_build_full.py +242 -0
- pycode_kg/cli/cmd_centrality.py +131 -0
- pycode_kg/cli/cmd_explain.py +180 -0
- pycode_kg/cli/cmd_framework_nodes.py +18 -0
- pycode_kg/cli/cmd_hooks.py +137 -0
- pycode_kg/cli/cmd_init.py +312 -0
- pycode_kg/cli/cmd_mcp.py +71 -0
- pycode_kg/cli/cmd_model.py +53 -0
- pycode_kg/cli/cmd_query.py +211 -0
- pycode_kg/cli/cmd_snapshot.py +421 -0
- pycode_kg/cli/cmd_viz.py +180 -0
- pycode_kg/cli/main.py +23 -0
- pycode_kg/cli/options.py +63 -0
- pycode_kg/config.py +78 -0
- pycode_kg/graph.py +125 -0
- pycode_kg/index.py +542 -0
- pycode_kg/kg.py +220 -0
- pycode_kg/layout3d.py +470 -0
- pycode_kg/mcp/bridge_tools.py +19 -0
- pycode_kg/mcp/framework_tools.py +18 -0
- pycode_kg/mcp_server.py +1965 -0
- pycode_kg/module/__init__.py +83 -0
- pycode_kg/module/base.py +720 -0
- pycode_kg/module/extractor.py +276 -0
- pycode_kg/module/types.py +532 -0
- pycode_kg/pycodekg.py +543 -0
- pycode_kg/pycodekg_query.py +1 -0
- pycode_kg/pycodekg_snippet_packer.py +1 -0
- pycode_kg/pycodekg_thorough_analysis.py +2751 -0
- pycode_kg/pycodekg_viz.py +1 -0
- pycode_kg/pycodekg_viz3d.py +1 -0
- pycode_kg/ranking/__init__.py +1 -0
- pycode_kg/ranking/cli_rank.py +92 -0
- pycode_kg/ranking/coderank.py +555 -0
- pycode_kg/snapshots.py +612 -0
- pycode_kg/sql/004_add_centrality_table.sql +12 -0
- pycode_kg/store.py +766 -0
- pycode_kg/utils.py +39 -0
- pycode_kg/visitor.py +413 -0
- pycode_kg/viz3d.py +1353 -0
- pycode_kg/viz3d_timeline.py +364 -0
- pycode_kg-0.16.0.dist-info/METADATA +305 -0
- pycode_kg-0.16.0.dist-info/RECORD +63 -0
- pycode_kg-0.16.0.dist-info/WHEEL +4 -0
- pycode_kg-0.16.0.dist-info/entry_points.txt +19 -0
- pycode_kg-0.16.0.dist-info/licenses/LICENSE +94 -0
pycode_kg/config.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""
|
|
2
|
+
config.py — Configuration utilities for PyCodeKG.
|
|
3
|
+
|
|
4
|
+
Reads and parses PyCodeKG configuration from pyproject.toml.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import tomllib
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _load_dir_list(repo_root: Path | str, key: str) -> set[str]:
|
|
14
|
+
"""Load a directory name list from ``[tool.pycodekg].<key>`` in pyproject.toml.
|
|
15
|
+
|
|
16
|
+
:param repo_root: Repository root directory.
|
|
17
|
+
:param key: Key name under ``[tool.pycodekg]`` (e.g. ``"include"`` or ``"exclude"``).
|
|
18
|
+
:return: Set of directory names, or an empty set if not found.
|
|
19
|
+
"""
|
|
20
|
+
repo_root = Path(repo_root)
|
|
21
|
+
pyproject_path = repo_root / "pyproject.toml"
|
|
22
|
+
|
|
23
|
+
if not pyproject_path.exists():
|
|
24
|
+
return set()
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
with open(pyproject_path, "rb") as f:
|
|
28
|
+
data = tomllib.load(f)
|
|
29
|
+
except (OSError, ValueError):
|
|
30
|
+
# OSError: file read error, ValueError: invalid TOML
|
|
31
|
+
return set()
|
|
32
|
+
|
|
33
|
+
value = data.get("tool", {}).get("pycodekg", {}).get(key, [])
|
|
34
|
+
if isinstance(value, list):
|
|
35
|
+
return {d.rstrip("/") for d in value if isinstance(d, str)}
|
|
36
|
+
return set()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def load_include_dirs(repo_root: Path | str) -> set[str]:
|
|
40
|
+
"""
|
|
41
|
+
Load include directory patterns from pyproject.toml.
|
|
42
|
+
|
|
43
|
+
Looks for [tool.pycodekg].include in pyproject.toml at repo_root.
|
|
44
|
+
If not found, returns an empty set (meaning all directories are indexed).
|
|
45
|
+
|
|
46
|
+
Example::
|
|
47
|
+
|
|
48
|
+
# pyproject.toml
|
|
49
|
+
[tool.pycodekg]
|
|
50
|
+
include = ["src", "lib"]
|
|
51
|
+
|
|
52
|
+
:param repo_root: Repository root directory.
|
|
53
|
+
:return: Set of directory names to include (e.g., {"src", "lib"}).
|
|
54
|
+
An empty set means no filter — all directories are indexed.
|
|
55
|
+
"""
|
|
56
|
+
return _load_dir_list(repo_root, "include")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def load_exclude_dirs(repo_root: Path | str) -> set[str]:
|
|
60
|
+
"""
|
|
61
|
+
Load exclude directory patterns from pyproject.toml.
|
|
62
|
+
|
|
63
|
+
Looks for [tool.pycodekg].exclude in pyproject.toml at repo_root.
|
|
64
|
+
Excluded directory names are pruned at every level during the file walk,
|
|
65
|
+
just like the built-in ``SKIP_DIRS`` constant.
|
|
66
|
+
|
|
67
|
+
Example::
|
|
68
|
+
|
|
69
|
+
# pyproject.toml
|
|
70
|
+
[tool.pycodekg]
|
|
71
|
+
include = ["numpy"]
|
|
72
|
+
exclude = ["tests", "benchmarks"]
|
|
73
|
+
|
|
74
|
+
:param repo_root: Repository root directory.
|
|
75
|
+
:return: Set of directory names to exclude (e.g., {"tests", "benchmarks"}).
|
|
76
|
+
An empty set means no extra exclusions beyond ``SKIP_DIRS``.
|
|
77
|
+
"""
|
|
78
|
+
return _load_dir_list(repo_root, "exclude")
|
pycode_kg/graph.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
graph.py
|
|
4
|
+
|
|
5
|
+
CodeGraph — pure AST extraction class.
|
|
6
|
+
|
|
7
|
+
Wraps extract_repo() with a clean object interface.
|
|
8
|
+
No I/O, no persistence, no embeddings.
|
|
9
|
+
|
|
10
|
+
Author: Eric G. Suchanek, PhD
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from collections import Counter
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from pycode_kg.pycodekg import Edge, Node, extract_repo
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CodeGraph:
|
|
22
|
+
"""
|
|
23
|
+
Pure, deterministic AST extraction from a Python repository.
|
|
24
|
+
|
|
25
|
+
Wraps the low-level ``extract_repo`` function with a cached,
|
|
26
|
+
object-oriented interface. No side effects; calling :meth:`extract`
|
|
27
|
+
twice on the same root returns the same result.
|
|
28
|
+
|
|
29
|
+
Example::
|
|
30
|
+
|
|
31
|
+
graph = CodeGraph("/path/to/repo")
|
|
32
|
+
graph.extract()
|
|
33
|
+
print(f"{len(graph.nodes)} nodes, {len(graph.edges)} edges")
|
|
34
|
+
|
|
35
|
+
:param repo_root: Path to the repository root directory.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
repo_root: str | Path,
|
|
41
|
+
include: set[str] | None = None,
|
|
42
|
+
exclude: set[str] | None = None,
|
|
43
|
+
) -> None:
|
|
44
|
+
"""Initialise the graph for a given repository root.
|
|
45
|
+
|
|
46
|
+
:param repo_root: Path to the repository root directory.
|
|
47
|
+
:param include: Set of top-level directory names to include in extraction.
|
|
48
|
+
When non-empty, only these directories are indexed.
|
|
49
|
+
When empty/None, all directories are indexed.
|
|
50
|
+
:param exclude: Set of directory names to prune at every walk depth
|
|
51
|
+
(e.g., ``{"tests", "benchmarks"}``).
|
|
52
|
+
"""
|
|
53
|
+
self.repo_root: Path = Path(repo_root).resolve()
|
|
54
|
+
self.include: set[str] = include or set()
|
|
55
|
+
self.exclude: set[str] = exclude or set()
|
|
56
|
+
self._nodes: list[Node] | None = None
|
|
57
|
+
self._edges: list[Edge] | None = None
|
|
58
|
+
|
|
59
|
+
# ------------------------------------------------------------------
|
|
60
|
+
# Public API
|
|
61
|
+
# ------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
def extract(self, *, force: bool = False) -> CodeGraph:
|
|
64
|
+
"""
|
|
65
|
+
Run AST extraction (cached after first call).
|
|
66
|
+
|
|
67
|
+
:param force: Re-extract even if already cached.
|
|
68
|
+
:return: self (for chaining)
|
|
69
|
+
"""
|
|
70
|
+
if self._nodes is None or force:
|
|
71
|
+
self._nodes, self._edges = extract_repo(
|
|
72
|
+
self.repo_root, include=self.include, exclude=self.exclude
|
|
73
|
+
)
|
|
74
|
+
return self
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def nodes(self) -> list[Node]:
|
|
78
|
+
"""Extracted nodes (calls :meth:`extract` if needed)."""
|
|
79
|
+
if self._nodes is None:
|
|
80
|
+
self.extract()
|
|
81
|
+
return self._nodes # type: ignore[return-value]
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def edges(self) -> list[Edge]:
|
|
85
|
+
"""Extracted edges (calls :meth:`extract` if needed)."""
|
|
86
|
+
if self._edges is None:
|
|
87
|
+
self.extract()
|
|
88
|
+
return self._edges # type: ignore[return-value]
|
|
89
|
+
|
|
90
|
+
def result(self) -> tuple[list[Node], list[Edge]]:
|
|
91
|
+
"""Return the extracted nodes and edges as a tuple.
|
|
92
|
+
|
|
93
|
+
:return: ``(nodes, edges)`` tuple, triggering extraction if not yet done.
|
|
94
|
+
"""
|
|
95
|
+
return self.nodes, self.edges
|
|
96
|
+
|
|
97
|
+
def stats(self) -> dict:
|
|
98
|
+
"""
|
|
99
|
+
Return a summary of extracted nodes and edges by kind/relation.
|
|
100
|
+
|
|
101
|
+
:return: dict with ``node_counts``, ``edge_counts``, ``total_nodes``,
|
|
102
|
+
``total_edges``.
|
|
103
|
+
"""
|
|
104
|
+
node_counts: Counter = Counter(n.kind for n in self.nodes)
|
|
105
|
+
edge_counts: Counter = Counter(e.rel for e in self.edges)
|
|
106
|
+
return {
|
|
107
|
+
"repo_root": str(self.repo_root),
|
|
108
|
+
"total_nodes": len(self.nodes),
|
|
109
|
+
"total_edges": len(self.edges),
|
|
110
|
+
"node_counts": dict(node_counts),
|
|
111
|
+
"edge_counts": dict(edge_counts),
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
def __repr__(self) -> str:
|
|
115
|
+
"""Return a developer-readable representation of this CodeGraph.
|
|
116
|
+
|
|
117
|
+
:return: String including repo root, and node/edge counts if already extracted.
|
|
118
|
+
"""
|
|
119
|
+
extracted = self._nodes is not None
|
|
120
|
+
if extracted:
|
|
121
|
+
return (
|
|
122
|
+
f"CodeGraph(repo_root={self.repo_root!r}, "
|
|
123
|
+
f"nodes={len(self._nodes)}, edges={len(self._edges)})" # type: ignore[arg-type]
|
|
124
|
+
)
|
|
125
|
+
return f"CodeGraph(repo_root={self.repo_root!r}, not yet extracted)"
|