pantheon-opencode 1.5.2 → 1.6.0-beta.1
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.
- package/.pantheon/code-mode/README.md +4 -2
- package/.pantheon/code-mode/checkpoint_session.py +138 -102
- package/.pantheon/code-mode/compress-inline.py +102 -57
- package/.pantheon/code-mode/manifest.json +2 -8
- package/CHANGELOG.md +17 -0
- package/README.md +86 -52
- package/README.pt-BR.md +63 -43
- package/commands/pantheon-optimize.md +2 -2
- package/package.json +13 -1
- package/plugin.json +1 -1
- package/pyproject.toml +8 -15
- package/scripts/build-agents-md.mjs +2 -2
- package/scripts/check-plugin-v2-coverage.mjs +213 -0
- package/scripts/ci-validate-yaml.py +7 -5
- package/scripts/code_mode_server.py +116 -24
- package/scripts/doctor.mjs +85 -28
- package/scripts/install/cli-ui.mjs +1 -1
- package/scripts/install/config-migration.mjs +2 -2
- package/scripts/install/health-check.mjs +6 -19
- package/scripts/install/interactive.mjs +6 -6
- package/scripts/install/migrate.mjs +1 -1
- package/scripts/mcp_persistence_server.py +390 -76
- package/scripts/memory_mcp_server.py +370 -196
- package/scripts/postinstall.mjs +0 -7
- package/scripts/probe-context-rehydrate.mjs +28 -11
- package/scripts/prune.mjs +2 -2
- package/scripts/test-opencode-v2-sandbox.sh +1759 -0
- package/scripts/validate-routing.mjs +40 -0
- package/skills-lock.json +9 -10
- package/src/agents/mnemosyne.md +15 -16
- package/src/agents/zeus.md +1 -2
- package/src/mcp/code_mode_server.py +116 -24
- package/src/mcp/mcp_codemap_module.py +559 -129
- package/src/mcp/mcp_persistence_server.py +390 -76
- package/src/mcp/memory_mcp_server.py +371 -248
- package/src/mcp/pantheon_vision_server.py +136 -24
- package/src/mcp/requirements-mcp.txt +5 -3
- package/src/mcp/requirements-vision.txt +2 -0
- package/src/pantheon/model-command.ts +18 -7
- package/src/pantheon/todo-enforcer.ts +1 -1
- package/src/pantheon/vision.ts +1 -1
- package/src/plugin-v2.ts +134 -104
- package/src/plugins/tui/dist/tui.js +1 -1
- package/src/plugins/tui/package-lock.json +20 -20
- package/src/plugins/tui/package.json +12 -12
- package/src/routing.yml +0 -1
- package/src/skills/README.md +31 -36
- package/src/skills/auto-continue/SKILL.md +25 -75
- package/.pantheon/code-mode/eval-llm-judge.py +0 -255
- package/.pantheon/code-mode/eval-monte-carlo.py +0 -368
- package/.pantheon/code-mode/eval-run.py +0 -197
- package/.pantheon/code-mode/eval-static.py +0 -321
- package/.pantheon/code-mode/session-end-save.py +0 -186
- package/.pantheon/code-mode/session-end-save.sh +0 -18
- package/scripts/recover-zenodo-deposition.mjs +0 -146
- package/scripts/test-opencode-v1-v2-sandbox.sh +0 -862
- package/scripts/validate-zenodo-release.mjs +0 -154
- package/scripts/zenodo-artifact.mjs +0 -94
- package/scripts/zenodo-release-state.mjs +0 -53
- package/src/skills/orchestration-workflow/SKILL.md +0 -311
|
@@ -1,185 +1,615 @@
|
|
|
1
1
|
# ruff: noqa: E701,E702,E741,I001,E401,RUF100,W292
|
|
2
2
|
"""Pantheon Codemap — lean KG for Python/TS (ast+regex), 3 tables+FTS5."""
|
|
3
|
+
|
|
3
4
|
from __future__ import annotations
|
|
4
5
|
import ast, hashlib, re, sqlite3
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from typing import Any
|
|
8
|
+
|
|
7
9
|
LARGE_FILE_THRESHOLD = 512 * 1024
|
|
8
|
-
SUPPORTED_EXTS: dict[str, str] = {
|
|
10
|
+
SUPPORTED_EXTS: dict[str, str] = {
|
|
11
|
+
".py": "python",
|
|
12
|
+
".ts": "typescript",
|
|
13
|
+
".tsx": "typescript",
|
|
14
|
+
".js": "typescript",
|
|
15
|
+
".jsx": "typescript",
|
|
16
|
+
".mts": "typescript",
|
|
17
|
+
".cts": "typescript",
|
|
18
|
+
}
|
|
9
19
|
CODEMAP_SCHEMA_SQL = """
|
|
10
|
-
CREATE TABLE IF NOT EXISTS code_entities (
|
|
20
|
+
CREATE TABLE IF NOT EXISTS code_entities (
|
|
21
|
+
id TEXT PRIMARY KEY, file_path TEXT NOT NULL, name TEXT NOT NULL,
|
|
22
|
+
type TEXT NOT NULL, language TEXT NOT NULL, start_line INTEGER,
|
|
23
|
+
end_line INTEGER, signature TEXT, docstring TEXT
|
|
24
|
+
);
|
|
11
25
|
CREATE INDEX IF NOT EXISTS idx_code_entities_type ON code_entities(type);
|
|
12
26
|
CREATE INDEX IF NOT EXISTS idx_code_entities_name ON code_entities(name);
|
|
13
27
|
CREATE INDEX IF NOT EXISTS idx_code_entities_path ON code_entities(file_path);
|
|
14
|
-
CREATE TABLE IF NOT EXISTS code_relations (
|
|
28
|
+
CREATE TABLE IF NOT EXISTS code_relations (
|
|
29
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT, source_id TEXT NOT NULL,
|
|
30
|
+
target_id TEXT NOT NULL, type TEXT NOT NULL,
|
|
31
|
+
FOREIGN KEY(source_id) REFERENCES code_entities(id),
|
|
32
|
+
FOREIGN KEY(target_id) REFERENCES code_entities(id)
|
|
33
|
+
);
|
|
15
34
|
CREATE INDEX IF NOT EXISTS idx_code_relations_source ON code_relations(source_id);
|
|
16
35
|
CREATE INDEX IF NOT EXISTS idx_code_relations_target ON code_relations(target_id);
|
|
17
|
-
CREATE TABLE IF NOT EXISTS code_files (
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
CREATE
|
|
36
|
+
CREATE TABLE IF NOT EXISTS code_files (
|
|
37
|
+
path TEXT PRIMARY KEY, hash TEXT NOT NULL, language TEXT NOT NULL,
|
|
38
|
+
indexed_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
39
|
+
);
|
|
40
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS code_entities_fts USING fts5(
|
|
41
|
+
name, signature, docstring, content='code_entities', content_rowid='rowid',
|
|
42
|
+
tokenize='porter unicode61'
|
|
43
|
+
);
|
|
44
|
+
CREATE TRIGGER IF NOT EXISTS code_entities_ai AFTER INSERT ON code_entities
|
|
45
|
+
BEGIN
|
|
46
|
+
INSERT INTO code_entities_fts(rowid, name, signature, docstring)
|
|
47
|
+
VALUES (new.rowid, new.name, new.signature, new.docstring);
|
|
48
|
+
END;
|
|
49
|
+
CREATE TRIGGER IF NOT EXISTS code_entities_ad AFTER DELETE ON code_entities
|
|
50
|
+
BEGIN
|
|
51
|
+
INSERT INTO code_entities_fts(
|
|
52
|
+
code_entities_fts, rowid, name, signature, docstring
|
|
53
|
+
) VALUES ('delete', old.rowid, old.name, old.signature, old.docstring);
|
|
54
|
+
END;
|
|
55
|
+
CREATE TRIGGER IF NOT EXISTS code_entities_au AFTER UPDATE ON code_entities
|
|
56
|
+
BEGIN
|
|
57
|
+
INSERT INTO code_entities_fts(
|
|
58
|
+
code_entities_fts, rowid, name, signature, docstring
|
|
59
|
+
) VALUES ('delete', old.rowid, old.name, old.signature, old.docstring);
|
|
60
|
+
INSERT INTO code_entities_fts(rowid, name, signature, docstring)
|
|
61
|
+
VALUES (new.rowid, new.name, new.signature, new.docstring);
|
|
62
|
+
END;
|
|
22
63
|
"""
|
|
64
|
+
|
|
65
|
+
|
|
23
66
|
def ensure_codemap_schema(conn: sqlite3.Connection) -> None:
|
|
24
|
-
conn.executescript(CODEMAP_SCHEMA_SQL)
|
|
67
|
+
conn.executescript(CODEMAP_SCHEMA_SQL)
|
|
68
|
+
conn.commit()
|
|
69
|
+
|
|
70
|
+
|
|
25
71
|
def drop_codemap_schema(conn: sqlite3.Connection) -> None:
|
|
26
|
-
conn.executescript(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
72
|
+
conn.executescript(
|
|
73
|
+
"DROP TRIGGER IF EXISTS code_entities_ai; "
|
|
74
|
+
"DROP TRIGGER IF EXISTS code_entities_ad; "
|
|
75
|
+
"DROP TRIGGER IF EXISTS code_entities_au; "
|
|
76
|
+
"DROP TABLE IF EXISTS code_entities_fts; "
|
|
77
|
+
"DROP TABLE IF EXISTS code_relations; "
|
|
78
|
+
"DROP TABLE IF EXISTS code_entities; "
|
|
79
|
+
"DROP TABLE IF EXISTS code_files;"
|
|
80
|
+
)
|
|
81
|
+
conn.commit()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _hash_content(c: str) -> str:
|
|
85
|
+
return hashlib.sha256(c.encode()).hexdigest()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _entity_id(fp: str, name: str, tp: str) -> str:
|
|
89
|
+
return hashlib.sha256(f"{fp}:{name}:{tp}".encode()).hexdigest()[:16]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _parse_python_entities( # noqa: C901, PLR0912, PLR0915
|
|
93
|
+
fp: str, content: str
|
|
94
|
+
) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
|
|
95
|
+
ents: list[dict[str, Any]] = []
|
|
96
|
+
rels: list[dict[str, Any]] = []
|
|
97
|
+
ph: dict[str, dict[str, Any]] = {}
|
|
98
|
+
try:
|
|
99
|
+
tree = ast.parse(content, filename=fp)
|
|
100
|
+
except SyntaxError:
|
|
101
|
+
return [], []
|
|
102
|
+
file_mod: str | None = None
|
|
103
|
+
|
|
34
104
|
def _file_mod() -> str:
|
|
35
105
|
nonlocal file_mod
|
|
36
106
|
if file_mod is None:
|
|
37
|
-
stem = Path(fp).stem
|
|
38
|
-
|
|
39
|
-
|
|
107
|
+
stem = Path(fp).stem
|
|
108
|
+
fid = _entity_id(fp, stem, "module")
|
|
109
|
+
if fid not in ph:
|
|
110
|
+
ph[fid] = {
|
|
111
|
+
"id": fid,
|
|
112
|
+
"file_path": fp,
|
|
113
|
+
"name": stem,
|
|
114
|
+
"type": "module",
|
|
115
|
+
"language": "python",
|
|
116
|
+
"start_line": 1,
|
|
117
|
+
"end_line": 1,
|
|
118
|
+
"signature": f"module {stem}",
|
|
119
|
+
"docstring": "",
|
|
120
|
+
}
|
|
121
|
+
file_mod = fid
|
|
40
122
|
return file_mod
|
|
123
|
+
|
|
41
124
|
for node in tree.body:
|
|
42
125
|
if isinstance(node, ast.ClassDef):
|
|
43
|
-
cid=_entity_id(fp,node.name,"class")
|
|
44
|
-
|
|
126
|
+
cid = _entity_id(fp, node.name, "class")
|
|
127
|
+
doc = ast.get_docstring(node) or ""
|
|
128
|
+
bases = []
|
|
45
129
|
for b in node.bases:
|
|
46
|
-
if isinstance(b, ast.Name):
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
130
|
+
if isinstance(b, ast.Name):
|
|
131
|
+
bases.append(b.id)
|
|
132
|
+
elif isinstance(b, ast.Attribute):
|
|
133
|
+
bases.append(b.attr)
|
|
134
|
+
sig = (
|
|
135
|
+
f"class {node.name}({', '.join(bases)})"
|
|
136
|
+
if bases
|
|
137
|
+
else f"class {node.name}"
|
|
138
|
+
)
|
|
139
|
+
ents.append(
|
|
140
|
+
{
|
|
141
|
+
"id": cid,
|
|
142
|
+
"file_path": fp,
|
|
143
|
+
"name": node.name,
|
|
144
|
+
"type": "class",
|
|
145
|
+
"language": "python",
|
|
146
|
+
"start_line": getattr(node, "lineno", 0),
|
|
147
|
+
"end_line": getattr(node, "end_lineno", 0)
|
|
148
|
+
or getattr(node, "lineno", 0),
|
|
149
|
+
"signature": sig,
|
|
150
|
+
"docstring": doc,
|
|
151
|
+
}
|
|
152
|
+
)
|
|
50
153
|
for base in bases:
|
|
51
|
-
tid=_entity_id(f"external:{base}",base,"class")
|
|
52
|
-
if tid not in ph:
|
|
53
|
-
|
|
154
|
+
tid = _entity_id(f"external:{base}", base, "class")
|
|
155
|
+
if tid not in ph:
|
|
156
|
+
ph[tid] = {
|
|
157
|
+
"id": tid,
|
|
158
|
+
"file_path": f"external:{base}",
|
|
159
|
+
"name": base,
|
|
160
|
+
"type": "class",
|
|
161
|
+
"language": "python",
|
|
162
|
+
"start_line": 0,
|
|
163
|
+
"end_line": 0,
|
|
164
|
+
"signature": f"class {base}",
|
|
165
|
+
"docstring": "",
|
|
166
|
+
}
|
|
167
|
+
rels.append({"source_id": cid, "target_id": tid, "type": "inherits"})
|
|
54
168
|
for item in node.body:
|
|
55
|
-
if isinstance(item,(ast.FunctionDef,ast.AsyncFunctionDef)):
|
|
56
|
-
mid=_entity_id(fp,f"{node.name}.{item.name}","method")
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
169
|
+
if isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
170
|
+
mid = _entity_id(fp, f"{node.name}.{item.name}", "method")
|
|
171
|
+
docm = ast.get_docstring(item) or ""
|
|
172
|
+
args = ", ".join(a.arg for a in item.args.args)
|
|
173
|
+
sig_m = f"def {item.name}({args})"
|
|
174
|
+
ents.append(
|
|
175
|
+
{
|
|
176
|
+
"id": mid,
|
|
177
|
+
"file_path": fp,
|
|
178
|
+
"name": f"{node.name}.{item.name}",
|
|
179
|
+
"type": "method",
|
|
180
|
+
"language": "python",
|
|
181
|
+
"start_line": getattr(item, "lineno", 0),
|
|
182
|
+
"end_line": getattr(item, "end_lineno", 0)
|
|
183
|
+
or getattr(item, "lineno", 0),
|
|
184
|
+
"signature": sig_m,
|
|
185
|
+
"docstring": docm,
|
|
186
|
+
}
|
|
187
|
+
)
|
|
188
|
+
elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
189
|
+
fid = _entity_id(fp, node.name, "function")
|
|
190
|
+
doc = ast.get_docstring(node) or ""
|
|
191
|
+
args = ", ".join(a.arg for a in node.args.args)
|
|
192
|
+
sig = f"def {node.name}({args})"
|
|
193
|
+
ents.append(
|
|
194
|
+
{
|
|
195
|
+
"id": fid,
|
|
196
|
+
"file_path": fp,
|
|
197
|
+
"name": node.name,
|
|
198
|
+
"type": "function",
|
|
199
|
+
"language": "python",
|
|
200
|
+
"start_line": getattr(node, "lineno", 0),
|
|
201
|
+
"end_line": getattr(node, "end_lineno", 0)
|
|
202
|
+
or getattr(node, "lineno", 0),
|
|
203
|
+
"signature": sig,
|
|
204
|
+
"docstring": doc,
|
|
205
|
+
}
|
|
206
|
+
)
|
|
61
207
|
elif isinstance(node, ast.Import):
|
|
62
208
|
for alias in node.names:
|
|
63
|
-
mod=alias.name.split(".")[0]
|
|
64
|
-
|
|
65
|
-
|
|
209
|
+
mod = alias.name.split(".")[0]
|
|
210
|
+
tid = _entity_id(f"external:{mod}", mod, "module")
|
|
211
|
+
if tid not in ph:
|
|
212
|
+
ph[tid] = {
|
|
213
|
+
"id": tid,
|
|
214
|
+
"file_path": f"external:{mod}",
|
|
215
|
+
"name": mod,
|
|
216
|
+
"type": "module",
|
|
217
|
+
"language": "python",
|
|
218
|
+
"start_line": 0,
|
|
219
|
+
"end_line": 0,
|
|
220
|
+
"signature": f"import {mod}",
|
|
221
|
+
"docstring": "",
|
|
222
|
+
}
|
|
223
|
+
src = _file_mod()
|
|
224
|
+
rels.append({"source_id": src, "target_id": tid, "type": "imports"})
|
|
66
225
|
elif isinstance(node, ast.ImportFrom):
|
|
67
|
-
mod=(node.module or "").split(".")[0]
|
|
226
|
+
mod = (node.module or "").split(".")[0]
|
|
68
227
|
if mod:
|
|
69
|
-
tid=_entity_id(f"external:{mod}",mod,"module")
|
|
70
|
-
if tid not in ph:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
228
|
+
tid = _entity_id(f"external:{mod}", mod, "module")
|
|
229
|
+
if tid not in ph:
|
|
230
|
+
ph[tid] = {
|
|
231
|
+
"id": tid,
|
|
232
|
+
"file_path": f"external:{mod}",
|
|
233
|
+
"name": mod,
|
|
234
|
+
"type": "module",
|
|
235
|
+
"language": "python",
|
|
236
|
+
"start_line": 0,
|
|
237
|
+
"end_line": 0,
|
|
238
|
+
"signature": f"from {mod} import ...",
|
|
239
|
+
"docstring": "",
|
|
240
|
+
}
|
|
241
|
+
src = _file_mod()
|
|
242
|
+
rels.append({"source_id": src, "target_id": tid, "type": "imports"})
|
|
243
|
+
eids = {e["id"] for e in ents}
|
|
244
|
+
for pid, pent in ph.items():
|
|
245
|
+
if pid not in eids:
|
|
246
|
+
ents.append(pent)
|
|
75
247
|
return ents, rels
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
_TS_CLASS_RE = re.compile(
|
|
251
|
+
r"^\s*(?:export\s+)?class\s+(\w+)(?:\s+extends\s+(\w+))?", re.MULTILINE
|
|
252
|
+
)
|
|
253
|
+
_TS_INTERFACE_RE = re.compile(r"^\s*(?:export\s+)?interface\s+(\w+)", re.MULTILINE)
|
|
254
|
+
_TS_FUNC_RE = re.compile(
|
|
255
|
+
r"^\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(", re.MULTILINE
|
|
256
|
+
)
|
|
257
|
+
_TS_CONST_RE = re.compile(r"^\s*(?:export\s+)?const\s+(\w+)\s*(?:[:=])", re.MULTILINE)
|
|
258
|
+
_TS_IMPORT_RE = re.compile(r"^\s*import\s+.*?from\s+['\"]([^'\"]+)['\"]", re.MULTILINE)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _parse_typescript_entities( # noqa: C901
|
|
262
|
+
fp: str, content: str
|
|
263
|
+
) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
|
|
264
|
+
ents: list[dict[str, Any]] = []
|
|
265
|
+
rels: list[dict[str, Any]] = []
|
|
266
|
+
ph: dict[str, dict[str, Any]] = {}
|
|
267
|
+
|
|
83
268
|
def _ph(name: str, tp: str, sig: str) -> str:
|
|
84
|
-
tid=_entity_id(f"external:{name}",name,tp)
|
|
85
|
-
if tid not in ph:
|
|
269
|
+
tid = _entity_id(f"external:{name}", name, tp)
|
|
270
|
+
if tid not in ph:
|
|
271
|
+
ph[tid] = {
|
|
272
|
+
"id": tid,
|
|
273
|
+
"file_path": f"external:{name}",
|
|
274
|
+
"name": name,
|
|
275
|
+
"type": tp,
|
|
276
|
+
"language": "typescript",
|
|
277
|
+
"start_line": 0,
|
|
278
|
+
"end_line": 0,
|
|
279
|
+
"signature": sig,
|
|
280
|
+
"docstring": "",
|
|
281
|
+
}
|
|
86
282
|
return tid
|
|
87
|
-
|
|
283
|
+
|
|
284
|
+
lines = content.splitlines()
|
|
285
|
+
|
|
88
286
|
def _line(pat: str, name: str) -> int:
|
|
89
|
-
for i,l in enumerate(lines, start=1):
|
|
90
|
-
if pat in l and name in l:
|
|
287
|
+
for i, l in enumerate(lines, start=1):
|
|
288
|
+
if pat in l and name in l:
|
|
289
|
+
return i
|
|
91
290
|
return 1
|
|
291
|
+
|
|
92
292
|
for m in _TS_CLASS_RE.finditer(content):
|
|
93
|
-
name=m.group(1)
|
|
94
|
-
|
|
95
|
-
|
|
293
|
+
name = m.group(1)
|
|
294
|
+
base = m.group(2)
|
|
295
|
+
cid = _entity_id(fp, name, "class")
|
|
296
|
+
sig = f"class {name}" + (f" extends {base}" if base else "")
|
|
297
|
+
ents.append(
|
|
298
|
+
{
|
|
299
|
+
"id": cid,
|
|
300
|
+
"file_path": fp,
|
|
301
|
+
"name": name,
|
|
302
|
+
"type": "class",
|
|
303
|
+
"language": "typescript",
|
|
304
|
+
"start_line": _line("class", name),
|
|
305
|
+
"end_line": _line("class", name),
|
|
306
|
+
"signature": sig,
|
|
307
|
+
"docstring": "",
|
|
308
|
+
}
|
|
309
|
+
)
|
|
310
|
+
if base:
|
|
311
|
+
tid = _ph(base, "class", f"class {base}")
|
|
312
|
+
rels.append({"source_id": cid, "target_id": tid, "type": "inherits"})
|
|
96
313
|
for m in _TS_INTERFACE_RE.finditer(content):
|
|
97
|
-
name=m.group(1)
|
|
98
|
-
|
|
314
|
+
name = m.group(1)
|
|
315
|
+
iid = _entity_id(fp, name, "interface")
|
|
316
|
+
ents.append(
|
|
317
|
+
{
|
|
318
|
+
"id": iid,
|
|
319
|
+
"file_path": fp,
|
|
320
|
+
"name": name,
|
|
321
|
+
"type": "interface",
|
|
322
|
+
"language": "typescript",
|
|
323
|
+
"start_line": _line("interface", name),
|
|
324
|
+
"end_line": _line("interface", name),
|
|
325
|
+
"signature": f"interface {name}",
|
|
326
|
+
"docstring": "",
|
|
327
|
+
}
|
|
328
|
+
)
|
|
99
329
|
for m in _TS_FUNC_RE.finditer(content):
|
|
100
|
-
name=m.group(1)
|
|
101
|
-
|
|
330
|
+
name = m.group(1)
|
|
331
|
+
fid = _entity_id(fp, name, "function")
|
|
332
|
+
ents.append(
|
|
333
|
+
{
|
|
334
|
+
"id": fid,
|
|
335
|
+
"file_path": fp,
|
|
336
|
+
"name": name,
|
|
337
|
+
"type": "function",
|
|
338
|
+
"language": "typescript",
|
|
339
|
+
"start_line": _line("function", name),
|
|
340
|
+
"end_line": _line("function", name),
|
|
341
|
+
"signature": f"function {name}()",
|
|
342
|
+
"docstring": "",
|
|
343
|
+
}
|
|
344
|
+
)
|
|
102
345
|
for m in _TS_CONST_RE.finditer(content):
|
|
103
|
-
name=m.group(1)
|
|
104
|
-
|
|
346
|
+
name = m.group(1)
|
|
347
|
+
cid = _entity_id(fp, name, "const")
|
|
348
|
+
ents.append(
|
|
349
|
+
{
|
|
350
|
+
"id": cid,
|
|
351
|
+
"file_path": fp,
|
|
352
|
+
"name": name,
|
|
353
|
+
"type": "const",
|
|
354
|
+
"language": "typescript",
|
|
355
|
+
"start_line": _line("const", name),
|
|
356
|
+
"end_line": _line("const", name),
|
|
357
|
+
"signature": f"const {name}",
|
|
358
|
+
"docstring": "",
|
|
359
|
+
}
|
|
360
|
+
)
|
|
105
361
|
for m in _TS_IMPORT_RE.finditer(content):
|
|
106
|
-
raw=m.group(1)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if
|
|
362
|
+
raw = m.group(1)
|
|
363
|
+
mod = raw.split("/")[-1].replace(".js", "").replace(".ts", "") or raw
|
|
364
|
+
tid = _ph(mod, "module", f"import {raw}")
|
|
365
|
+
stem = Path(fp).stem
|
|
366
|
+
src = _entity_id(fp, stem, "module")
|
|
367
|
+
if src not in {e["id"] for e in ents} and src not in ph:
|
|
368
|
+
ph[src] = {
|
|
369
|
+
"id": src,
|
|
370
|
+
"file_path": fp,
|
|
371
|
+
"name": stem,
|
|
372
|
+
"type": "module",
|
|
373
|
+
"language": "typescript",
|
|
374
|
+
"start_line": 1,
|
|
375
|
+
"end_line": 1,
|
|
376
|
+
"signature": f"module {stem}",
|
|
377
|
+
"docstring": "",
|
|
378
|
+
}
|
|
379
|
+
rels.append({"source_id": src, "target_id": tid, "type": "imports"})
|
|
380
|
+
eids = {e["id"] for e in ents}
|
|
381
|
+
for pid, pent in ph.items():
|
|
382
|
+
if pid not in eids:
|
|
383
|
+
ents.append(pent)
|
|
112
384
|
return ents, rels
|
|
113
|
-
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def _insert_entities_and_relations(
|
|
388
|
+
conn: sqlite3.Connection, ents: list[dict[str, Any]], rels: list[dict[str, Any]]
|
|
389
|
+
) -> None:
|
|
114
390
|
for e in ents:
|
|
115
|
-
conn.execute(
|
|
391
|
+
conn.execute(
|
|
392
|
+
"INSERT OR REPLACE INTO code_entities "
|
|
393
|
+
"(id, file_path, name, type, language, start_line, end_line, "
|
|
394
|
+
"signature, docstring) VALUES (?,?,?,?,?,?,?,?,?)",
|
|
395
|
+
[
|
|
396
|
+
e["id"],
|
|
397
|
+
e["file_path"],
|
|
398
|
+
e["name"],
|
|
399
|
+
e["type"],
|
|
400
|
+
e["language"],
|
|
401
|
+
e["start_line"],
|
|
402
|
+
e["end_line"],
|
|
403
|
+
e["signature"],
|
|
404
|
+
e["docstring"],
|
|
405
|
+
],
|
|
406
|
+
)
|
|
116
407
|
for r in rels:
|
|
117
|
-
if not conn.execute(
|
|
118
|
-
|
|
119
|
-
|
|
408
|
+
if not conn.execute(
|
|
409
|
+
"SELECT 1 FROM code_relations WHERE source_id=? AND target_id=? AND type=?",
|
|
410
|
+
[r["source_id"], r["target_id"], r["type"]],
|
|
411
|
+
).fetchone():
|
|
412
|
+
conn.execute(
|
|
413
|
+
"INSERT INTO code_relations (source_id,target_id,type) VALUES (?,?,?)",
|
|
414
|
+
[r["source_id"], r["target_id"], r["type"]],
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def code_index( # noqa: C901, PLR0912, PLR0915
|
|
419
|
+
conn: sqlite3.Connection, path: str | Path | None = None, force: bool = False
|
|
420
|
+
) -> dict[str, Any]:
|
|
120
421
|
ensure_codemap_schema(conn)
|
|
121
|
-
base=Path(path) if path else Path.cwd()
|
|
122
|
-
if not base.exists():
|
|
123
|
-
|
|
124
|
-
|
|
422
|
+
base = Path(path) if path else Path.cwd()
|
|
423
|
+
if not base.exists():
|
|
424
|
+
return {"error": f"Path not found: {base}", "indexed": 0, "skipped": 0}
|
|
425
|
+
files: list[Path] = (
|
|
426
|
+
[base]
|
|
427
|
+
if base.is_file()
|
|
428
|
+
else [p for ext in SUPPORTED_EXTS for p in base.rglob(f"*{ext}")]
|
|
429
|
+
)
|
|
430
|
+
indexed = skipped = errors = large_skipped = unsupported = 0
|
|
125
431
|
for f in files:
|
|
126
|
-
if
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
432
|
+
if (
|
|
433
|
+
any(p.startswith(".") for p in f.parts)
|
|
434
|
+
and ".pantheon" not in str(f)
|
|
435
|
+
and (".git" in f.parts or "__pycache__" in f.parts)
|
|
436
|
+
):
|
|
437
|
+
continue
|
|
438
|
+
if "node_modules" in f.parts or ".venv" in f.parts or "venv" in f.parts:
|
|
439
|
+
continue
|
|
440
|
+
ext = f.suffix.lower()
|
|
441
|
+
lang = SUPPORTED_EXTS.get(ext)
|
|
442
|
+
if not lang:
|
|
443
|
+
unsupported += 1
|
|
444
|
+
continue
|
|
130
445
|
try:
|
|
131
|
-
if f.stat().st_size > LARGE_FILE_THRESHOLD:
|
|
132
|
-
|
|
446
|
+
if f.stat().st_size > LARGE_FILE_THRESHOLD:
|
|
447
|
+
large_skipped += 1
|
|
448
|
+
continue
|
|
449
|
+
content = f.read_text(encoding="utf-8", errors="ignore")
|
|
133
450
|
if not content.strip():
|
|
134
|
-
h=_hash_content(content)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
451
|
+
h = _hash_content(content)
|
|
452
|
+
conn.execute(
|
|
453
|
+
"INSERT OR REPLACE INTO code_files "
|
|
454
|
+
"(path, hash, language) VALUES (?,?,?)",
|
|
455
|
+
[str(f), h, lang],
|
|
456
|
+
)
|
|
457
|
+
conn.commit()
|
|
458
|
+
skipped += 1
|
|
459
|
+
continue
|
|
460
|
+
h = _hash_content(content)
|
|
461
|
+
row = conn.execute(
|
|
462
|
+
"SELECT hash FROM code_files WHERE path=?", [str(f)]
|
|
463
|
+
).fetchone()
|
|
464
|
+
if row and row[0] == h and not force:
|
|
465
|
+
skipped += 1
|
|
466
|
+
continue
|
|
467
|
+
ents, rels = (
|
|
468
|
+
_parse_python_entities(str(f), content)
|
|
469
|
+
if lang == "python"
|
|
470
|
+
else _parse_typescript_entities(str(f), content)
|
|
471
|
+
)
|
|
139
472
|
if not ents and not rels:
|
|
140
|
-
conn.execute(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
473
|
+
conn.execute(
|
|
474
|
+
"INSERT OR REPLACE INTO code_files "
|
|
475
|
+
"(path, hash, language) VALUES (?,?,?)",
|
|
476
|
+
[str(f), h, lang],
|
|
477
|
+
)
|
|
478
|
+
conn.commit()
|
|
479
|
+
try:
|
|
480
|
+
ast.parse(content)
|
|
481
|
+
except SyntaxError:
|
|
482
|
+
errors += 1
|
|
483
|
+
continue
|
|
484
|
+
skipped += 1
|
|
485
|
+
continue
|
|
486
|
+
_insert_entities_and_relations(conn, ents, rels)
|
|
487
|
+
conn.execute(
|
|
488
|
+
"INSERT OR REPLACE INTO code_files (path,hash,language) VALUES (?,?,?)",
|
|
489
|
+
[str(f), h, lang],
|
|
490
|
+
)
|
|
491
|
+
conn.commit()
|
|
492
|
+
indexed += 1
|
|
493
|
+
except Exception:
|
|
494
|
+
errors += 1
|
|
495
|
+
continue
|
|
496
|
+
return {
|
|
497
|
+
"indexed": indexed,
|
|
498
|
+
"skipped": skipped,
|
|
499
|
+
"errors": errors,
|
|
500
|
+
"large_skipped": large_skipped,
|
|
501
|
+
"unsupported": unsupported,
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def code_query(
|
|
506
|
+
conn: sqlite3.Connection,
|
|
507
|
+
query: str,
|
|
508
|
+
type_filter: str | None = None,
|
|
509
|
+
limit: int = 10,
|
|
510
|
+
) -> list[dict[str, Any]]:
|
|
511
|
+
if not query or not query.strip():
|
|
512
|
+
return []
|
|
513
|
+
limit = max(1, min(50, int(limit)))
|
|
514
|
+
q = query.strip()
|
|
515
|
+
ensure_codemap_schema(conn)
|
|
151
516
|
try:
|
|
152
|
-
fts_q=" OR ".join(f'"{w}"*' for w in q.split() if w)
|
|
153
|
-
if not fts_q:
|
|
517
|
+
fts_q = " OR ".join(f'"{w}"*' for w in q.split() if w)
|
|
518
|
+
if not fts_q:
|
|
519
|
+
raise ValueError
|
|
154
520
|
if type_filter:
|
|
155
|
-
rows=conn.execute(
|
|
521
|
+
rows = conn.execute(
|
|
522
|
+
"SELECT e.id, e.file_path, e.name, e.type, e.language, "
|
|
523
|
+
"e.start_line, e.end_line, e.signature, e.docstring "
|
|
524
|
+
"FROM code_entities_fts f JOIN code_entities e ON e.rowid=f.rowid "
|
|
525
|
+
"WHERE code_entities_fts MATCH ? AND e.type=? ORDER BY rank LIMIT ?",
|
|
526
|
+
[fts_q, type_filter, limit],
|
|
527
|
+
).fetchall()
|
|
156
528
|
else:
|
|
157
|
-
rows=conn.execute(
|
|
158
|
-
|
|
159
|
-
|
|
529
|
+
rows = conn.execute(
|
|
530
|
+
"SELECT e.id, e.file_path, e.name, e.type, e.language, "
|
|
531
|
+
"e.start_line, e.end_line, e.signature, e.docstring "
|
|
532
|
+
"FROM code_entities_fts f JOIN code_entities e ON e.rowid=f.rowid "
|
|
533
|
+
"WHERE code_entities_fts MATCH ? ORDER BY rank LIMIT ?",
|
|
534
|
+
[fts_q, limit],
|
|
535
|
+
).fetchall()
|
|
536
|
+
if rows:
|
|
537
|
+
return [dict(r) for r in rows]
|
|
538
|
+
except Exception:
|
|
539
|
+
pass
|
|
160
540
|
try:
|
|
161
|
-
like=f"%{q}%"
|
|
541
|
+
like = f"%{q}%"
|
|
162
542
|
if type_filter:
|
|
163
|
-
rows=conn.execute(
|
|
543
|
+
rows = conn.execute(
|
|
544
|
+
"SELECT id, file_path, name, type, language, start_line, end_line, "
|
|
545
|
+
"signature, docstring FROM code_entities "
|
|
546
|
+
"WHERE (name LIKE ? OR signature LIKE ? OR docstring LIKE ?) "
|
|
547
|
+
"AND type=? LIMIT ?",
|
|
548
|
+
[like, like, like, type_filter, limit],
|
|
549
|
+
).fetchall()
|
|
164
550
|
else:
|
|
165
|
-
rows=conn.execute(
|
|
551
|
+
rows = conn.execute(
|
|
552
|
+
"SELECT id, file_path, name, type, language, start_line, end_line, "
|
|
553
|
+
"signature, docstring FROM code_entities "
|
|
554
|
+
"WHERE name LIKE ? OR signature LIKE ? OR docstring LIKE ? LIMIT ?",
|
|
555
|
+
[like, like, like, limit],
|
|
556
|
+
).fetchall()
|
|
166
557
|
return [dict(r) for r in rows]
|
|
167
|
-
except Exception:
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
558
|
+
except Exception:
|
|
559
|
+
return []
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
def code_neighbors(
|
|
563
|
+
conn: sqlite3.Connection, entity_id: str, depth: int = 1
|
|
564
|
+
) -> dict[str, Any]:
|
|
565
|
+
ensure_codemap_schema(conn)
|
|
566
|
+
depth = max(1, min(3, int(depth)))
|
|
567
|
+
row = conn.execute("SELECT * FROM code_entities WHERE id=?", [entity_id]).fetchone()
|
|
568
|
+
if not row:
|
|
569
|
+
return {"error": "Entity not found", "entity_id": entity_id}
|
|
570
|
+
start = dict(row)
|
|
571
|
+
visited: dict[str, dict[str, Any]] = {entity_id: start}
|
|
572
|
+
queue = [(entity_id, 0)]
|
|
573
|
+
edges = []
|
|
574
|
+
seen = set()
|
|
173
575
|
while queue:
|
|
174
|
-
cur,d=queue.pop(0)
|
|
175
|
-
if d>=depth:
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
576
|
+
cur, d = queue.pop(0)
|
|
577
|
+
if d >= depth:
|
|
578
|
+
continue
|
|
579
|
+
for r in conn.execute(
|
|
580
|
+
"SELECT * FROM code_relations WHERE source_id=? OR target_id=?", [cur, cur]
|
|
581
|
+
).fetchall():
|
|
582
|
+
rel = dict(r)
|
|
583
|
+
key = (rel["source_id"], rel["target_id"], rel["type"])
|
|
584
|
+
if key in seen:
|
|
585
|
+
continue
|
|
586
|
+
seen.add(key)
|
|
587
|
+
edges.append(rel)
|
|
588
|
+
nid = rel["target_id"] if rel["source_id"] == cur else rel["source_id"]
|
|
181
589
|
if nid not in visited:
|
|
182
|
-
nrow=conn.execute(
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
590
|
+
nrow = conn.execute(
|
|
591
|
+
"SELECT * FROM code_entities WHERE id=?", [nid]
|
|
592
|
+
).fetchone()
|
|
593
|
+
visited[nid] = (
|
|
594
|
+
dict(nrow)
|
|
595
|
+
if nrow
|
|
596
|
+
else {
|
|
597
|
+
"id": nid,
|
|
598
|
+
"file_path": "external:unknown",
|
|
599
|
+
"name": nid,
|
|
600
|
+
"type": "placeholder",
|
|
601
|
+
"language": "",
|
|
602
|
+
"start_line": 0,
|
|
603
|
+
"end_line": 0,
|
|
604
|
+
"signature": "",
|
|
605
|
+
"docstring": "",
|
|
606
|
+
}
|
|
607
|
+
)
|
|
608
|
+
if d + 1 < depth:
|
|
609
|
+
queue.append((nid, d + 1))
|
|
610
|
+
return {
|
|
611
|
+
"entity": start,
|
|
612
|
+
"neighbors": [v for k, v in visited.items() if k != entity_id],
|
|
613
|
+
"relations": edges,
|
|
614
|
+
"depth": depth,
|
|
615
|
+
}
|