tscode-kg 0.2.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.
- tscode_kg/__init__.py +42 -0
- tscode_kg/__main__.py +6 -0
- tscode_kg/analysis.py +1829 -0
- tscode_kg/app.py +1355 -0
- tscode_kg/bridge.py +114 -0
- tscode_kg/centrality.py +434 -0
- tscode_kg/cli/__init__.py +1 -0
- tscode_kg/cli/cmd_analyze.py +69 -0
- tscode_kg/cli/cmd_bridges.py +38 -0
- tscode_kg/cli/cmd_build.py +86 -0
- tscode_kg/cli/cmd_centrality.py +124 -0
- tscode_kg/cli/cmd_explain.py +58 -0
- tscode_kg/cli/cmd_framework_nodes.py +43 -0
- tscode_kg/cli/cmd_hooks.py +125 -0
- tscode_kg/cli/cmd_init.py +234 -0
- tscode_kg/cli/cmd_mcp.py +35 -0
- tscode_kg/cli/cmd_model.py +52 -0
- tscode_kg/cli/cmd_query.py +75 -0
- tscode_kg/cli/cmd_snapshot.py +431 -0
- tscode_kg/cli/cmd_viz.py +175 -0
- tscode_kg/cli/main.py +56 -0
- tscode_kg/coderank.py +564 -0
- tscode_kg/config.py +36 -0
- tscode_kg/explain.py +270 -0
- tscode_kg/extractor.py +827 -0
- tscode_kg/framework_detector.py +106 -0
- tscode_kg/kg.py +193 -0
- tscode_kg/layout3d.py +492 -0
- tscode_kg/mcp_server.py +1412 -0
- tscode_kg/snapshots.py +64 -0
- tscode_kg/viz3d.py +1457 -0
- tscode_kg/viz3d_timeline.py +369 -0
- tscode_kg-0.2.0.dist-info/METADATA +196 -0
- tscode_kg-0.2.0.dist-info/RECORD +37 -0
- tscode_kg-0.2.0.dist-info/WHEEL +4 -0
- tscode_kg-0.2.0.dist-info/entry_points.txt +15 -0
- tscode_kg-0.2.0.dist-info/licenses/LICENSE +24 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Framework Detector for TypeScriptKG.
|
|
3
|
+
Identifies repo-defining abstractions using centrality and cross-module signals.
|
|
4
|
+
|
|
5
|
+
Ported from PyCodeKG's ``analysis/framework_detector.py`` with the boilerplate
|
|
6
|
+
exclusion list adapted to TS/JS lifecycle and protocol members.
|
|
7
|
+
|
|
8
|
+
Author: Eric G. Suchanek, PhD
|
|
9
|
+
License: Elastic 2.0
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import sqlite3
|
|
13
|
+
|
|
14
|
+
# Short member names that are structurally high fan-in but architecturally trivial.
|
|
15
|
+
# Excluded from framework-node ranking so hubs surface over lifecycle boilerplate.
|
|
16
|
+
_BOILERPLATE_NAMES: frozenset[str] = frozenset(
|
|
17
|
+
{
|
|
18
|
+
"constructor",
|
|
19
|
+
"toString",
|
|
20
|
+
"valueOf",
|
|
21
|
+
"toJSON",
|
|
22
|
+
"hasOwnProperty",
|
|
23
|
+
"close",
|
|
24
|
+
"dispose",
|
|
25
|
+
"destroy",
|
|
26
|
+
"next",
|
|
27
|
+
"return",
|
|
28
|
+
"throw",
|
|
29
|
+
"then",
|
|
30
|
+
"catch",
|
|
31
|
+
"finally",
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def detect_framework_nodes(
|
|
37
|
+
limit: int = 25, db_path: str = "tscodekg.sqlite"
|
|
38
|
+
) -> list[tuple[str, float, str]]:
|
|
39
|
+
"""
|
|
40
|
+
Detect framework-like nodes using SIR and module connectivity.
|
|
41
|
+
|
|
42
|
+
Combines Structural Importance Ranking (SIR — importance within the graph)
|
|
43
|
+
with module connectivity (interaction complexity) to identify modules that are
|
|
44
|
+
both architecturally central AND highly connected to other modules.
|
|
45
|
+
|
|
46
|
+
Framework score = 0.6 × normalized SIR + 0.4 × normalized connectivity.
|
|
47
|
+
High-scoring modules are critical hubs: important AND complex.
|
|
48
|
+
|
|
49
|
+
Lifecycle / protocol boilerplate (``constructor``, ``toString``, ``close``,
|
|
50
|
+
etc.) is excluded so the ranking surfaces meaningful orchestrators rather
|
|
51
|
+
than the most-called teardown methods.
|
|
52
|
+
|
|
53
|
+
:param limit: Number of top framework nodes to return (default 25).
|
|
54
|
+
:param db_path: Path to SQLite database (default "tscodekg.sqlite").
|
|
55
|
+
:return: List of (node_id, framework_score, label) tuples, sorted by score descending.
|
|
56
|
+
"""
|
|
57
|
+
# Aggregate node-level SIR up to module level so we compare like-for-like
|
|
58
|
+
# against the module_connectivity metric. Both signals end up keyed by
|
|
59
|
+
# bare module path (e.g. "src/auth/middleware.ts"), which we map to "mod:..."
|
|
60
|
+
# IDs for output.
|
|
61
|
+
from tscode_kg.centrality import ( # noqa: PLC0415
|
|
62
|
+
StructuralImportanceRanker,
|
|
63
|
+
aggregate_module_scores,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
with sqlite3.connect(db_path) as con:
|
|
67
|
+
connectivity = dict(
|
|
68
|
+
con.execute(
|
|
69
|
+
"SELECT node_id, score FROM centrality_scores WHERE metric = 'module_connectivity'"
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
module_id_by_path: dict[str, str] = dict(
|
|
73
|
+
con.execute("SELECT module_path, id FROM nodes WHERE kind = 'module'")
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
ranker = StructuralImportanceRanker(db_path)
|
|
77
|
+
sir_records = ranker.compute()
|
|
78
|
+
sir_by_path: dict[str, float] = {
|
|
79
|
+
row["module_path"]: row["score"] for row in aggregate_module_scores(sir_records)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
def norm(d: dict) -> dict:
|
|
83
|
+
if not d:
|
|
84
|
+
return {}
|
|
85
|
+
vals = list(d.values())
|
|
86
|
+
mn, mx = min(vals), max(vals)
|
|
87
|
+
return {k: (v - mn) / (mx - mn) if mx > mn else 0.0 for k, v in d.items()}
|
|
88
|
+
|
|
89
|
+
nsir = norm(sir_by_path)
|
|
90
|
+
nconnectivity = norm(connectivity)
|
|
91
|
+
|
|
92
|
+
# Framework score: weighted sum (SIR 60% — importance, connectivity 40% — coupling).
|
|
93
|
+
# Restrict to module paths only — the tool advertises "Framework-like Modules",
|
|
94
|
+
# so methods/functions must not leak into the ranking.
|
|
95
|
+
framework: dict[str, float] = {}
|
|
96
|
+
for path in set(nsir) | set(nconnectivity):
|
|
97
|
+
framework[path] = 0.6 * nsir.get(path, 0.0) + 0.4 * nconnectivity.get(path, 0.0)
|
|
98
|
+
|
|
99
|
+
ranked = sorted(framework.items(), key=lambda x: x[1], reverse=True)
|
|
100
|
+
result: list[tuple[str, float, str]] = []
|
|
101
|
+
for path, score in ranked[:limit]:
|
|
102
|
+
node_id = module_id_by_path.get(path, f"mod:{path}")
|
|
103
|
+
result.append((node_id, score, path))
|
|
104
|
+
# _BOILERPLATE_NAMES retained for backward-compat at the import site.
|
|
105
|
+
_ = _BOILERPLATE_NAMES
|
|
106
|
+
return result
|
tscode_kg/kg.py
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
kg.py — TypeScriptKG: concrete KGModule for TypeScript/JavaScript codebases.
|
|
4
|
+
|
|
5
|
+
Owns the TS/JS-specific extraction layer (tree-sitter AST) and delegates all
|
|
6
|
+
generic infrastructure (SQLite, sqlite-vec, hybrid query, snippet packing,
|
|
7
|
+
snapshots) to the KGModule base class from kg_utils.pipeline.
|
|
8
|
+
|
|
9
|
+
Author: Eric G. Suchanek, PhD
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
from kg_utils.extractor import KGExtractor
|
|
18
|
+
from kg_utils.pipeline import KGModule
|
|
19
|
+
from kg_utils.semantic import DEFAULT_MODEL
|
|
20
|
+
from kg_utils.specs import BuildStats, QueryResult, SnippetPack
|
|
21
|
+
from kg_utils.store import GraphStore # noqa: F401
|
|
22
|
+
except ImportError as _e:
|
|
23
|
+
raise ImportError(
|
|
24
|
+
"TypeScriptKG requires kgmodule-utils[semantic] for its graph infrastructure.\n"
|
|
25
|
+
"Install with: pip install 'tscode-kg[kg]'\n"
|
|
26
|
+
f"Original error: {_e}"
|
|
27
|
+
) from _e
|
|
28
|
+
|
|
29
|
+
from tscode_kg.config import load_exclude_dirs, load_include_dirs
|
|
30
|
+
from tscode_kg.extractor import TSCodeExtractor
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"TypeScriptKG",
|
|
34
|
+
"BuildStats",
|
|
35
|
+
"QueryResult",
|
|
36
|
+
"SnippetPack",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
# TypeScript/JavaScript node kind priority for ranking
|
|
40
|
+
_TS_KIND_PRIORITY: dict[str, int] = {
|
|
41
|
+
"function": 0,
|
|
42
|
+
"method": 1,
|
|
43
|
+
"class": 2,
|
|
44
|
+
"interface": 3,
|
|
45
|
+
"type_alias": 4,
|
|
46
|
+
"enum": 5,
|
|
47
|
+
"namespace": 6,
|
|
48
|
+
"module": 7,
|
|
49
|
+
"symbol": 8,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class TypeScriptKG(KGModule):
|
|
54
|
+
"""
|
|
55
|
+
Top-level orchestrator for the TypeScript/JavaScript Knowledge Graph.
|
|
56
|
+
|
|
57
|
+
Subclasses :class:`~kg_utils.pipeline.KGModule` and provides the
|
|
58
|
+
TypeScript/JS-specific extraction layer via :class:`~tscode_kg.extractor.TSCodeExtractor`.
|
|
59
|
+
All generic infrastructure — SQLite persistence, sqlite-vec indexing,
|
|
60
|
+
hybrid query, snippet packing — is inherited from KGModule.
|
|
61
|
+
|
|
62
|
+
Typical usage::
|
|
63
|
+
|
|
64
|
+
kg = TypeScriptKG(repo_root="/path/to/ts-repo")
|
|
65
|
+
stats = kg.build(wipe=True)
|
|
66
|
+
print(stats)
|
|
67
|
+
|
|
68
|
+
result = kg.query("authentication middleware", k=8)
|
|
69
|
+
pack = kg.pack("API error handling")
|
|
70
|
+
pack.save("context.md")
|
|
71
|
+
|
|
72
|
+
:param repo_root: Repository root directory.
|
|
73
|
+
:param db_path: SQLite database path (defaults to ``<repo_root>/.tscodekg/graph.sqlite``).
|
|
74
|
+
:param vectors_path: sqlite-vec store path (defaults to ``<repo_root>/.tscodekg/vectors.sqlite``).
|
|
75
|
+
:param model: Sentence-transformer model name.
|
|
76
|
+
:param table: sqlite-vec table name.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
_default_dir = ".tscodekg"
|
|
80
|
+
|
|
81
|
+
def __init__(
|
|
82
|
+
self,
|
|
83
|
+
repo_root: str | Path,
|
|
84
|
+
db_path: str | Path | None = None,
|
|
85
|
+
vectors_path: str | Path | None = None,
|
|
86
|
+
*,
|
|
87
|
+
model: str = DEFAULT_MODEL,
|
|
88
|
+
table: str = "tscodekg_nodes",
|
|
89
|
+
) -> None:
|
|
90
|
+
super().__init__(
|
|
91
|
+
repo_root,
|
|
92
|
+
db_path=db_path,
|
|
93
|
+
model=model,
|
|
94
|
+
table=table,
|
|
95
|
+
vector_backend="sqlite-vec",
|
|
96
|
+
)
|
|
97
|
+
if vectors_path is not None:
|
|
98
|
+
self.vectors_path = Path(vectors_path)
|
|
99
|
+
|
|
100
|
+
# ------------------------------------------------------------------
|
|
101
|
+
# KGModule abstract interface
|
|
102
|
+
# ------------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
def make_extractor(self) -> KGExtractor:
|
|
105
|
+
include = load_include_dirs(self.repo_root)
|
|
106
|
+
exclude = load_exclude_dirs(self.repo_root)
|
|
107
|
+
return TSCodeExtractor(self.repo_root, include=include, exclude=exclude)
|
|
108
|
+
|
|
109
|
+
def kind(self) -> str:
|
|
110
|
+
return "code"
|
|
111
|
+
|
|
112
|
+
def analyze(self) -> str:
|
|
113
|
+
"""Run thorough structural analysis and return a Markdown report.
|
|
114
|
+
|
|
115
|
+
Uses :class:`~tscode_kg.analysis.TSCodeKGAnalyzer` for a full 14-phase
|
|
116
|
+
analysis (fan-in, fan-out, module coupling, JSDoc coverage, hierarchy, …).
|
|
117
|
+
Falls back to a lightweight summary when the KG has not been built yet.
|
|
118
|
+
"""
|
|
119
|
+
try:
|
|
120
|
+
from tscode_kg.analysis import TSCodeKGAnalyzer # noqa: PLC0415
|
|
121
|
+
|
|
122
|
+
analyzer = TSCodeKGAnalyzer(self)
|
|
123
|
+
analyzer.run_analysis()
|
|
124
|
+
return analyzer.to_markdown()
|
|
125
|
+
except Exception as exc: # noqa: BLE001
|
|
126
|
+
try:
|
|
127
|
+
return _render_analysis(str(self.repo_root), self.store.stats())
|
|
128
|
+
except Exception: # noqa: BLE001
|
|
129
|
+
return f"# TypeScriptKG Analysis\n\nAnalysis failed: {exc}\n"
|
|
130
|
+
|
|
131
|
+
# ------------------------------------------------------------------
|
|
132
|
+
# TS-specific overrides
|
|
133
|
+
# ------------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
def _kind_priority(self, kind: str) -> int:
|
|
136
|
+
return _TS_KIND_PRIORITY.get(kind, 99)
|
|
137
|
+
|
|
138
|
+
# ------------------------------------------------------------------
|
|
139
|
+
# Repr
|
|
140
|
+
# ------------------------------------------------------------------
|
|
141
|
+
|
|
142
|
+
def __repr__(self) -> str:
|
|
143
|
+
return (
|
|
144
|
+
f"TypeScriptKG(repo_root={self.repo_root!r}, "
|
|
145
|
+
f"db_path={self.db_path!r}, "
|
|
146
|
+
f"vectors_path={self.vectors_path!r}, "
|
|
147
|
+
f"model={self.model_name!r})"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
# ---------------------------------------------------------------------------
|
|
152
|
+
# Analysis renderer
|
|
153
|
+
# ---------------------------------------------------------------------------
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _render_analysis(repo_root: str, stats: dict) -> str:
|
|
157
|
+
"""Render a Markdown analysis report from store stats."""
|
|
158
|
+
lines: list[str] = [
|
|
159
|
+
"# TypeScriptKG Analysis Report\n",
|
|
160
|
+
f"**Repository:** `{repo_root}`\n",
|
|
161
|
+
"---\n",
|
|
162
|
+
"## Structural Metrics\n",
|
|
163
|
+
f"- **Total nodes:** {stats.get('total_nodes', 0):,}",
|
|
164
|
+
f"- **Meaningful nodes:** {stats.get('meaningful_nodes', 0):,}",
|
|
165
|
+
f"- **Total edges:** {stats.get('total_edges', 0):,}",
|
|
166
|
+
]
|
|
167
|
+
|
|
168
|
+
cov = stats.get("docstring_coverage")
|
|
169
|
+
if cov is not None:
|
|
170
|
+
lines.append(f"- **JSDoc coverage:** {cov:.1%} *(functions + methods)*")
|
|
171
|
+
|
|
172
|
+
node_counts: dict = stats.get("node_counts", {})
|
|
173
|
+
if node_counts:
|
|
174
|
+
lines.append("\n### Nodes by Kind\n")
|
|
175
|
+
lines.append("| Kind | Count |")
|
|
176
|
+
lines.append("|------|------:|")
|
|
177
|
+
for kind, count in sorted(node_counts.items(), key=lambda x: -x[1]):
|
|
178
|
+
lines.append(f"| {kind} | {count:,} |")
|
|
179
|
+
|
|
180
|
+
edge_counts: dict = stats.get("edge_counts", {})
|
|
181
|
+
if edge_counts:
|
|
182
|
+
lines.append("\n### Edges by Relation\n")
|
|
183
|
+
lines.append("| Relation | Count |")
|
|
184
|
+
lines.append("|----------|------:|")
|
|
185
|
+
for rel, count in sorted(edge_counts.items(), key=lambda x: -x[1]):
|
|
186
|
+
lines.append(f"| {rel} | {count:,} |")
|
|
187
|
+
|
|
188
|
+
lines.append("\n---\n")
|
|
189
|
+
lines.append(
|
|
190
|
+
"> Graph built by deterministic tree-sitter AST extraction. "
|
|
191
|
+
"No LLM inference used in indexing."
|
|
192
|
+
)
|
|
193
|
+
return "\n".join(lines)
|