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
@@ -0,0 +1,103 @@
1
+ """
2
+ Framework Detector for PyCodeKG.
3
+ Identifies repo-defining abstractions using centrality and cross-module signals.
4
+ """
5
+
6
+ import sqlite3
7
+
8
+ # Short method names that are structurally high fan-in but architecturally trivial.
9
+ # Excluded from framework-node ranking so hubs surface over lifecycle boilerplate.
10
+ _BOILERPLATE_NAMES: frozenset[str] = frozenset(
11
+ {
12
+ "close",
13
+ "__init__",
14
+ "__exit__",
15
+ "__enter__",
16
+ "__del__",
17
+ "__repr__",
18
+ "__str__",
19
+ "__len__",
20
+ "__iter__",
21
+ "__next__",
22
+ "__contains__",
23
+ "__bool__",
24
+ "__hash__",
25
+ "__eq__",
26
+ "__ne__",
27
+ "__lt__",
28
+ "__gt__",
29
+ "__le__",
30
+ "__ge__",
31
+ "__call__",
32
+ }
33
+ )
34
+
35
+
36
+ def detect_framework_nodes(limit=25, db_path="pycodekg.sqlite"):
37
+ """
38
+ Detect framework-like nodes using SIR and module connectivity.
39
+
40
+ Combines Structural Importance Ranking (SIR — importance within the graph)
41
+ with module connectivity (interaction complexity) to identify modules that are
42
+ both architecturally central AND highly connected to other modules.
43
+
44
+ Framework score = 0.6 × normalized SIR + 0.4 × normalized connectivity.
45
+ High-scoring modules are critical hubs: important AND complex.
46
+
47
+ Lifecycle / protocol boilerplate (``close``, ``__init__``, ``__exit__``, etc.)
48
+ is excluded so the ranking surfaces meaningful orchestrators rather than
49
+ the most-called teardown methods.
50
+
51
+ :param limit: Number of top framework nodes to return (default 25).
52
+ :param db_path: Path to SQLite database (default "pycodekg.sqlite").
53
+ :return: List of (node_id, framework_score, label) tuples, sorted by score descending.
54
+ """
55
+ with sqlite3.connect(db_path) as con:
56
+ # Get SIR (structural importance) and connectivity (interaction complexity) scores
57
+ sir = dict(
58
+ con.execute(
59
+ "SELECT node_id, score FROM centrality_scores WHERE metric = 'sir_pagerank'"
60
+ )
61
+ )
62
+ connectivity = dict(
63
+ con.execute(
64
+ "SELECT node_id, score FROM centrality_scores WHERE metric = 'module_connectivity'"
65
+ )
66
+ )
67
+ # Get module names (for labelling)
68
+ names = dict(con.execute("SELECT id, name FROM nodes WHERE kind = 'module'"))
69
+ # Fetch qualnames for boilerplate filtering across all node kinds
70
+ qualnames: dict[str, str] = dict(
71
+ con.execute("SELECT id, COALESCE(qualname, name) FROM nodes")
72
+ )
73
+
74
+ def _is_boilerplate(node_id: str) -> bool:
75
+ qn = qualnames.get(node_id, node_id)
76
+ short = qn.split(".")[-1] if "." in qn else qn
77
+ return short in _BOILERPLATE_NAMES
78
+
79
+ # Normalize scores to [0, 1]
80
+ def norm(d: dict) -> dict:
81
+ if not d:
82
+ return {}
83
+ vals = list(d.values())
84
+ mn, mx = min(vals), max(vals)
85
+ return {k: (v - mn) / (mx - mn) if mx > mn else 0.0 for k, v in d.items()}
86
+
87
+ nsir = norm(sir)
88
+ nconnectivity = norm(connectivity)
89
+
90
+ # Framework score: weighted sum (SIR 60% — importance, connectivity 40% — coupling)
91
+ # Skip boilerplate methods that score high purely due to lifecycle call frequency.
92
+ framework = {}
93
+ for k in set(nsir) | set(nconnectivity):
94
+ if _is_boilerplate(k):
95
+ continue
96
+ framework[k] = 0.6 * nsir.get(k, 0.0) + 0.4 * nconnectivity.get(k, 0.0)
97
+
98
+ ranked = sorted(framework.items(), key=lambda x: x[1], reverse=True)
99
+ result = []
100
+ for node_id, score in ranked[:limit]:
101
+ label = names.get(node_id, node_id)
102
+ result.append((node_id, score, label))
103
+ return result
@@ -0,0 +1,53 @@
1
+ """
2
+ Hybrid Search Ranking for PyCodeKG.
3
+ Combines semantic relevance with structural importance (SIR).
4
+ """
5
+
6
+ import math
7
+ import sqlite3
8
+
9
+
10
+ def get_sir_scores(metric="sir_pagerank", db_path="pycodekg.sqlite"):
11
+ """Fetch SIR centrality scores from the database."""
12
+ with sqlite3.connect(db_path) as con:
13
+ rows = con.execute(
14
+ "SELECT node_id, score FROM centrality_scores WHERE metric = ?", (metric,)
15
+ ).fetchall()
16
+ scores = {node_id: score for node_id, score in rows}
17
+ if not scores:
18
+ return {}
19
+ max_score = max(scores.values())
20
+ min_score = min(scores.values())
21
+
22
+ # Normalize to [0,1]
23
+ def norm(s):
24
+ return (s - min_score) / (max_score - min_score) if max_score > min_score else 0.0
25
+
26
+ return {k: norm(v) for k, v in scores.items()}
27
+
28
+
29
+ def rerank_hybrid(
30
+ results, centrality_metric="sir_pagerank", lambda_weight=0.15, db_path="pycodekg.sqlite"
31
+ ):
32
+ """
33
+ Rerank search results using hybrid score.
34
+ :param results: List of dicts with 'node_id' and 'semantic_score'
35
+ :param centrality_metric: Centrality metric to use (default 'sir_pagerank')
36
+ :param lambda_weight: Weight for structural term (default 0.15)
37
+ :param db_path: Path to SQLite database
38
+ :return: List of dicts with hybrid scores
39
+ """
40
+ sir_scores = get_sir_scores(centrality_metric, db_path)
41
+ reranked = []
42
+ for r in results:
43
+ node_id = r.get("node_id")
44
+ sem = r.get("semantic_score", 0.0)
45
+ struct = sir_scores.get(node_id, 0.0)
46
+ # Hybrid score: semantic + lambda * log(1 + struct)
47
+ hybrid = sem + lambda_weight * math.log(1 + struct)
48
+ out = dict(r)
49
+ out["structural_score"] = struct
50
+ out["hybrid_score"] = hybrid
51
+ reranked.append(out)
52
+ reranked.sort(key=lambda x: x["hybrid_score"], reverse=True)
53
+ return reranked