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.
Files changed (63) hide show
  1. pycode_kg/.DS_Store +0 -0
  2. pycode_kg/__init__.py +91 -0
  3. pycode_kg/__main__.py +11 -0
  4. pycode_kg/analysis/__init__.py +15 -0
  5. pycode_kg/analysis/bridge.py +108 -0
  6. pycode_kg/analysis/centrality.py +412 -0
  7. pycode_kg/analysis/framework_detector.py +103 -0
  8. pycode_kg/analysis/hybrid_rank.py +53 -0
  9. pycode_kg/app.py +1335 -0
  10. pycode_kg/architecture.py +624 -0
  11. pycode_kg/build_pycodekg_lancedb.py +15 -0
  12. pycode_kg/build_pycodekg_sqlite.py +15 -0
  13. pycode_kg/cli/__init__.py +29 -0
  14. pycode_kg/cli/cmd_analyze.py +96 -0
  15. pycode_kg/cli/cmd_architecture.py +150 -0
  16. pycode_kg/cli/cmd_bridges.py +26 -0
  17. pycode_kg/cli/cmd_build.py +154 -0
  18. pycode_kg/cli/cmd_build_full.py +242 -0
  19. pycode_kg/cli/cmd_centrality.py +131 -0
  20. pycode_kg/cli/cmd_explain.py +180 -0
  21. pycode_kg/cli/cmd_framework_nodes.py +18 -0
  22. pycode_kg/cli/cmd_hooks.py +137 -0
  23. pycode_kg/cli/cmd_init.py +312 -0
  24. pycode_kg/cli/cmd_mcp.py +71 -0
  25. pycode_kg/cli/cmd_model.py +53 -0
  26. pycode_kg/cli/cmd_query.py +211 -0
  27. pycode_kg/cli/cmd_snapshot.py +421 -0
  28. pycode_kg/cli/cmd_viz.py +180 -0
  29. pycode_kg/cli/main.py +23 -0
  30. pycode_kg/cli/options.py +63 -0
  31. pycode_kg/config.py +78 -0
  32. pycode_kg/graph.py +125 -0
  33. pycode_kg/index.py +542 -0
  34. pycode_kg/kg.py +220 -0
  35. pycode_kg/layout3d.py +470 -0
  36. pycode_kg/mcp/bridge_tools.py +19 -0
  37. pycode_kg/mcp/framework_tools.py +18 -0
  38. pycode_kg/mcp_server.py +1965 -0
  39. pycode_kg/module/__init__.py +83 -0
  40. pycode_kg/module/base.py +720 -0
  41. pycode_kg/module/extractor.py +276 -0
  42. pycode_kg/module/types.py +532 -0
  43. pycode_kg/pycodekg.py +543 -0
  44. pycode_kg/pycodekg_query.py +1 -0
  45. pycode_kg/pycodekg_snippet_packer.py +1 -0
  46. pycode_kg/pycodekg_thorough_analysis.py +2751 -0
  47. pycode_kg/pycodekg_viz.py +1 -0
  48. pycode_kg/pycodekg_viz3d.py +1 -0
  49. pycode_kg/ranking/__init__.py +1 -0
  50. pycode_kg/ranking/cli_rank.py +92 -0
  51. pycode_kg/ranking/coderank.py +555 -0
  52. pycode_kg/snapshots.py +612 -0
  53. pycode_kg/sql/004_add_centrality_table.sql +12 -0
  54. pycode_kg/store.py +766 -0
  55. pycode_kg/utils.py +39 -0
  56. pycode_kg/visitor.py +413 -0
  57. pycode_kg/viz3d.py +1353 -0
  58. pycode_kg/viz3d_timeline.py +364 -0
  59. pycode_kg-0.16.0.dist-info/METADATA +305 -0
  60. pycode_kg-0.16.0.dist-info/RECORD +63 -0
  61. pycode_kg-0.16.0.dist-info/WHEEL +4 -0
  62. pycode_kg-0.16.0.dist-info/entry_points.txt +19 -0
  63. 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)"