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.
Files changed (60) hide show
  1. package/.pantheon/code-mode/README.md +4 -2
  2. package/.pantheon/code-mode/checkpoint_session.py +138 -102
  3. package/.pantheon/code-mode/compress-inline.py +102 -57
  4. package/.pantheon/code-mode/manifest.json +2 -8
  5. package/CHANGELOG.md +17 -0
  6. package/README.md +86 -52
  7. package/README.pt-BR.md +63 -43
  8. package/commands/pantheon-optimize.md +2 -2
  9. package/package.json +13 -1
  10. package/plugin.json +1 -1
  11. package/pyproject.toml +8 -15
  12. package/scripts/build-agents-md.mjs +2 -2
  13. package/scripts/check-plugin-v2-coverage.mjs +213 -0
  14. package/scripts/ci-validate-yaml.py +7 -5
  15. package/scripts/code_mode_server.py +116 -24
  16. package/scripts/doctor.mjs +85 -28
  17. package/scripts/install/cli-ui.mjs +1 -1
  18. package/scripts/install/config-migration.mjs +2 -2
  19. package/scripts/install/health-check.mjs +6 -19
  20. package/scripts/install/interactive.mjs +6 -6
  21. package/scripts/install/migrate.mjs +1 -1
  22. package/scripts/mcp_persistence_server.py +390 -76
  23. package/scripts/memory_mcp_server.py +370 -196
  24. package/scripts/postinstall.mjs +0 -7
  25. package/scripts/probe-context-rehydrate.mjs +28 -11
  26. package/scripts/prune.mjs +2 -2
  27. package/scripts/test-opencode-v2-sandbox.sh +1759 -0
  28. package/scripts/validate-routing.mjs +40 -0
  29. package/skills-lock.json +9 -10
  30. package/src/agents/mnemosyne.md +15 -16
  31. package/src/agents/zeus.md +1 -2
  32. package/src/mcp/code_mode_server.py +116 -24
  33. package/src/mcp/mcp_codemap_module.py +559 -129
  34. package/src/mcp/mcp_persistence_server.py +390 -76
  35. package/src/mcp/memory_mcp_server.py +371 -248
  36. package/src/mcp/pantheon_vision_server.py +136 -24
  37. package/src/mcp/requirements-mcp.txt +5 -3
  38. package/src/mcp/requirements-vision.txt +2 -0
  39. package/src/pantheon/model-command.ts +18 -7
  40. package/src/pantheon/todo-enforcer.ts +1 -1
  41. package/src/pantheon/vision.ts +1 -1
  42. package/src/plugin-v2.ts +134 -104
  43. package/src/plugins/tui/dist/tui.js +1 -1
  44. package/src/plugins/tui/package-lock.json +20 -20
  45. package/src/plugins/tui/package.json +12 -12
  46. package/src/routing.yml +0 -1
  47. package/src/skills/README.md +31 -36
  48. package/src/skills/auto-continue/SKILL.md +25 -75
  49. package/.pantheon/code-mode/eval-llm-judge.py +0 -255
  50. package/.pantheon/code-mode/eval-monte-carlo.py +0 -368
  51. package/.pantheon/code-mode/eval-run.py +0 -197
  52. package/.pantheon/code-mode/eval-static.py +0 -321
  53. package/.pantheon/code-mode/session-end-save.py +0 -186
  54. package/.pantheon/code-mode/session-end-save.sh +0 -18
  55. package/scripts/recover-zenodo-deposition.mjs +0 -146
  56. package/scripts/test-opencode-v1-v2-sandbox.sh +0 -862
  57. package/scripts/validate-zenodo-release.mjs +0 -154
  58. package/scripts/zenodo-artifact.mjs +0 -94
  59. package/scripts/zenodo-release-state.mjs +0 -53
  60. 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] = {".py":"python",".ts":"typescript",".tsx":"typescript",".js":"typescript",".jsx":"typescript",".mts":"typescript",".cts":"typescript"}
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 (id TEXT PRIMARY KEY,file_path TEXT NOT NULL,name TEXT NOT NULL,type TEXT NOT NULL,language TEXT NOT NULL,start_line INTEGER,end_line INTEGER,signature TEXT,docstring TEXT);
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 (id INTEGER PRIMARY KEY AUTOINCREMENT,source_id TEXT NOT NULL,target_id TEXT NOT NULL,type TEXT NOT NULL,FOREIGN KEY(source_id) REFERENCES code_entities(id),FOREIGN KEY(target_id) REFERENCES code_entities(id));
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 (path TEXT PRIMARY KEY,hash TEXT NOT NULL,language TEXT NOT NULL,indexed_at TEXT NOT NULL DEFAULT (datetime('now')));
18
- CREATE VIRTUAL TABLE IF NOT EXISTS code_entities_fts USING fts5(name,signature,docstring,content='code_entities',content_rowid='rowid',tokenize='porter unicode61');
19
- CREATE TRIGGER IF NOT EXISTS code_entities_ai AFTER INSERT ON code_entities BEGIN INSERT INTO code_entities_fts(rowid,name,signature,docstring) VALUES (new.rowid,new.name,new.signature,new.docstring); END;
20
- CREATE TRIGGER IF NOT EXISTS code_entities_ad AFTER DELETE ON code_entities BEGIN INSERT INTO code_entities_fts(code_entities_fts,rowid,name,signature,docstring) VALUES ('delete',old.rowid,old.name,old.signature,old.docstring); END;
21
- CREATE TRIGGER IF NOT EXISTS code_entities_au AFTER UPDATE ON code_entities BEGIN INSERT INTO code_entities_fts(code_entities_fts,rowid,name,signature,docstring) VALUES ('delete',old.rowid,old.name,old.signature,old.docstring); INSERT INTO code_entities_fts(rowid,name,signature,docstring) VALUES (new.rowid,new.name,new.signature,new.docstring); END;
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); conn.commit()
67
+ conn.executescript(CODEMAP_SCHEMA_SQL)
68
+ conn.commit()
69
+
70
+
25
71
  def drop_codemap_schema(conn: sqlite3.Connection) -> None:
26
- conn.executescript("DROP TRIGGER IF EXISTS code_entities_ai;DROP TRIGGER IF EXISTS code_entities_ad;DROP TRIGGER IF EXISTS code_entities_au;DROP TABLE IF EXISTS code_entities_fts;DROP TABLE IF EXISTS code_relations;DROP TABLE IF EXISTS code_entities;DROP TABLE IF EXISTS code_files;"); conn.commit()
27
- def _hash_content(c: str) -> str: return hashlib.sha256(c.encode()).hexdigest()
28
- def _entity_id(fp: str, name: str, tp: str) -> str: return hashlib.sha256(f"{fp}:{name}:{tp}".encode()).hexdigest()[:16]
29
- def _parse_python_entities(fp: str, content: str) -> tuple[list[dict[str,Any]], list[dict[str,Any]]]: # noqa: C901, PLR0912, PLR0915
30
- ents: list[dict[str,Any]] = []; rels: list[dict[str,Any]] = []; ph: dict[str,dict[str,Any]] = {}
31
- try: tree = ast.parse(content, filename=fp)
32
- except SyntaxError: return [], []
33
- file_mod: str|None = None
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; fid=_entity_id(fp,stem,"module")
38
- if fid not in ph: ph[fid]={"id":fid,"file_path":fp,"name":stem,"type":"module","language":"python","start_line":1,"end_line":1,"signature":f"module {stem}","docstring":""}
39
- file_mod=fid
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"); doc=ast.get_docstring(node) or ""
44
- bases=[]
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): bases.append(b.id)
47
- elif isinstance(b, ast.Attribute): bases.append(b.attr)
48
- sig=f"class {node.name}({', '.join(bases)})" if bases else f"class {node.name}"
49
- ents.append({"id":cid,"file_path":fp,"name":node.name,"type":"class","language":"python","start_line":getattr(node,"lineno",0),"end_line":getattr(node,"end_lineno",0) or getattr(node,"lineno",0),"signature":sig,"docstring":doc})
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: ph[tid]={"id":tid,"file_path":f"external:{base}","name":base,"type":"class","language":"python","start_line":0,"end_line":0,"signature":f"class {base}","docstring":""}
53
- rels.append({"source_id":cid,"target_id":tid,"type":"inherits"})
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"); docm=ast.get_docstring(item) or ""; args=", ".join(a.arg for a in item.args.args); sig_m=f"def {item.name}({args})"
57
- ents.append({"id":mid,"file_path":fp,"name":f"{node.name}.{item.name}","type":"method","language":"python","start_line":getattr(item,"lineno",0),"end_line":getattr(item,"end_lineno",0) or getattr(item,"lineno",0),"signature":sig_m,"docstring":docm})
58
- elif isinstance(node,(ast.FunctionDef,ast.AsyncFunctionDef)):
59
- fid=_entity_id(fp,node.name,"function"); doc=ast.get_docstring(node) or ""; args=", ".join(a.arg for a in node.args.args); sig=f"def {node.name}({args})"
60
- ents.append({"id":fid,"file_path":fp,"name":node.name,"type":"function","language":"python","start_line":getattr(node,"lineno",0),"end_line":getattr(node,"end_lineno",0) or getattr(node,"lineno",0),"signature":sig,"docstring":doc})
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]; tid=_entity_id(f"external:{mod}",mod,"module")
64
- if tid not in ph: ph[tid]={"id":tid,"file_path":f"external:{mod}","name":mod,"type":"module","language":"python","start_line":0,"end_line":0,"signature":f"import {mod}","docstring":""}
65
- src=_file_mod(); rels.append({"source_id":src,"target_id":tid,"type":"imports"})
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: ph[tid]={"id":tid,"file_path":f"external:{mod}","name":mod,"type":"module","language":"python","start_line":0,"end_line":0,"signature":f"from {mod} import ...","docstring":""}
71
- src=_file_mod(); rels.append({"source_id":src,"target_id":tid,"type":"imports"})
72
- eids={e["id"] for e in ents}
73
- for pid,pent in ph.items():
74
- if pid not in eids: ents.append(pent)
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
- _TS_CLASS_RE=re.compile(r"^\s*(?:export\s+)?class\s+(\w+)(?:\s+extends\s+(\w+))?",re.MULTILINE)
77
- _TS_INTERFACE_RE=re.compile(r"^\s*(?:export\s+)?interface\s+(\w+)",re.MULTILINE)
78
- _TS_FUNC_RE=re.compile(r"^\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(",re.MULTILINE)
79
- _TS_CONST_RE=re.compile(r"^\s*(?:export\s+)?const\s+(\w+)\s*(?:[:=])",re.MULTILINE)
80
- _TS_IMPORT_RE=re.compile(r"^\s*import\s+.*?from\s+['\"]([^'\"]+)['\"]",re.MULTILINE)
81
- def _parse_typescript_entities(fp: str, content: str) -> tuple[list[dict[str,Any]], list[dict[str,Any]]]: # noqa: C901
82
- ents: list[dict[str,Any]]=[]; rels: list[dict[str,Any]]=[]; ph: dict[str,dict[str,Any]]={}
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: ph[tid]={"id":tid,"file_path":f"external:{name}","name":name,"type":tp,"language":"typescript","start_line":0,"end_line":0,"signature":sig,"docstring":""}
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
- lines=content.splitlines()
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: return i
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); base=m.group(2); cid=_entity_id(fp,name,"class"); sig=f"class {name}"+(f" extends {base}" if base else "")
94
- ents.append({"id":cid,"file_path":fp,"name":name,"type":"class","language":"typescript","start_line":_line("class",name),"end_line":_line("class",name),"signature":sig,"docstring":""})
95
- if base: tid=_ph(base,"class",f"class {base}"); rels.append({"source_id":cid,"target_id":tid,"type":"inherits"})
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); iid=_entity_id(fp,name,"interface")
98
- ents.append({"id":iid,"file_path":fp,"name":name,"type":"interface","language":"typescript","start_line":_line("interface",name),"end_line":_line("interface",name),"signature":f"interface {name}","docstring":""})
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); fid=_entity_id(fp,name,"function")
101
- ents.append({"id":fid,"file_path":fp,"name":name,"type":"function","language":"typescript","start_line":_line("function",name),"end_line":_line("function",name),"signature":f"function {name}()","docstring":""})
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); cid=_entity_id(fp,name,"const")
104
- ents.append({"id":cid,"file_path":fp,"name":name,"type":"const","language":"typescript","start_line":_line("const",name),"end_line":_line("const",name),"signature":f"const {name}","docstring":""})
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); mod=raw.split("/")[-1].replace(".js","").replace(".ts","") or raw; tid=_ph(mod,"module",f"import {raw}"); stem=Path(fp).stem; src=_entity_id(fp,stem,"module")
107
- if src not in {e["id"] for e in ents} and src not in ph: ph[src]={"id":src,"file_path":fp,"name":stem,"type":"module","language":"typescript","start_line":1,"end_line":1,"signature":f"module {stem}","docstring":""}
108
- rels.append({"source_id":src,"target_id":tid,"type":"imports"})
109
- eids={e["id"] for e in ents}
110
- for pid,pent in ph.items():
111
- if pid not in eids: ents.append(pent)
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
- def _insert_entities_and_relations(conn: sqlite3.Connection, ents: list[dict[str,Any]], rels: list[dict[str,Any]]) -> None:
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("INSERT OR REPLACE INTO code_entities (id,file_path,name,type,language,start_line,end_line,signature,docstring) VALUES (?,?,?,?,?,?,?,?,?)",[e["id"],e["file_path"],e["name"],e["type"],e["language"],e["start_line"],e["end_line"],e["signature"],e["docstring"]])
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("SELECT 1 FROM code_relations WHERE source_id=? AND target_id=? AND type=?", [r["source_id"],r["target_id"],r["type"]]).fetchone():
118
- conn.execute("INSERT INTO code_relations (source_id,target_id,type) VALUES (?,?,?)", [r["source_id"],r["target_id"],r["type"]])
119
- def code_index(conn: sqlite3.Connection, path: str|Path|None=None, force: bool=False) -> dict[str,Any]: # noqa: C901, PLR0912, PLR0915
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(): return {"error":f"Path not found: {base}","indexed":0,"skipped":0}
123
- files: list[Path]=[base] if base.is_file() else [p for ext in SUPPORTED_EXTS for p in base.rglob(f"*{ext}")]
124
- indexed=skipped=errors=large_skipped=unsupported=0
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 any(p.startswith(".") for p in f.parts) and ".pantheon" not in str(f) and (".git" in f.parts or "__pycache__" in f.parts): continue
127
- if "node_modules" in f.parts or ".venv" in f.parts or "venv" in f.parts: continue
128
- ext=f.suffix.lower(); lang=SUPPORTED_EXTS.get(ext)
129
- if not lang: unsupported+=1; continue
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: large_skipped+=1; continue
132
- content=f.read_text(encoding="utf-8",errors="ignore")
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); conn.execute("INSERT OR REPLACE INTO code_files (path,hash,language) VALUES (?,?,?)",[str(f),h,lang]); conn.commit(); skipped+=1; continue
135
- h=_hash_content(content)
136
- row=conn.execute("SELECT hash FROM code_files WHERE path=?",[str(f)]).fetchone()
137
- if row and row[0]==h and not force: skipped+=1; continue
138
- ents,rels=_parse_python_entities(str(f),content) if lang=="python" else _parse_typescript_entities(str(f),content)
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("INSERT OR REPLACE INTO code_files (path,hash,language) VALUES (?,?,?)",[str(f),h,lang]); conn.commit()
141
- try: ast.parse(content)
142
- except SyntaxError: errors+=1; continue
143
- skipped+=1; continue
144
- _insert_entities_and_relations(conn,ents,rels)
145
- conn.execute("INSERT OR REPLACE INTO code_files (path,hash,language) VALUES (?,?,?)",[str(f),h,lang]); conn.commit(); indexed+=1
146
- except Exception: errors+=1; continue
147
- return {"indexed":indexed,"skipped":skipped,"errors":errors,"large_skipped":large_skipped,"unsupported":unsupported}
148
- def code_query(conn: sqlite3.Connection, query: str, type_filter: str|None=None, limit: int=10) -> list[dict[str,Any]]:
149
- if not query or not query.strip(): return []
150
- limit=max(1,min(50,int(limit))); q=query.strip(); ensure_codemap_schema(conn)
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: raise ValueError
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("SELECT e.id,e.file_path,e.name,e.type,e.language,e.start_line,e.end_line,e.signature,e.docstring FROM code_entities_fts f JOIN code_entities e ON e.rowid=f.rowid WHERE code_entities_fts MATCH ? AND e.type=? ORDER BY rank LIMIT ?",[fts_q,type_filter,limit]).fetchall()
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("SELECT e.id,e.file_path,e.name,e.type,e.language,e.start_line,e.end_line,e.signature,e.docstring FROM code_entities_fts f JOIN code_entities e ON e.rowid=f.rowid WHERE code_entities_fts MATCH ? ORDER BY rank LIMIT ?",[fts_q,limit]).fetchall()
158
- if rows: return [dict(r) for r in rows]
159
- except Exception: pass
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("SELECT id,file_path,name,type,language,start_line,end_line,signature,docstring FROM code_entities WHERE (name LIKE ? OR signature LIKE ? OR docstring LIKE ?) AND type=? LIMIT ?",[like,like,like,type_filter,limit]).fetchall()
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("SELECT id,file_path,name,type,language,start_line,end_line,signature,docstring FROM code_entities WHERE name LIKE ? OR signature LIKE ? OR docstring LIKE ? LIMIT ?",[like,like,like,limit]).fetchall()
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: return []
168
- def code_neighbors(conn: sqlite3.Connection, entity_id: str, depth: int=1) -> dict[str,Any]:
169
- ensure_codemap_schema(conn); depth=max(1,min(3,int(depth)))
170
- row=conn.execute("SELECT * FROM code_entities WHERE id=?",[entity_id]).fetchone()
171
- if not row: return {"error":"Entity not found","entity_id":entity_id}
172
- start=dict(row); visited: dict[str,dict[str,Any]]={entity_id:start}; queue=[(entity_id,0)]; edges=[]; seen=set()
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: continue
176
- for r in conn.execute("SELECT * FROM code_relations WHERE source_id=? OR target_id=?",[cur,cur]).fetchall():
177
- rel=dict(r); key=(rel["source_id"],rel["target_id"],rel["type"])
178
- if key in seen: continue
179
- seen.add(key); edges.append(rel)
180
- nid=rel["target_id"] if rel["source_id"]==cur else rel["source_id"]
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("SELECT * FROM code_entities WHERE id=?",[nid]).fetchone()
183
- visited[nid]=dict(nrow) if nrow else {"id":nid,"file_path":"external:unknown","name":nid,"type":"placeholder","language":"","start_line":0,"end_line":0,"signature":"","docstring":""}
184
- if d+1<depth: queue.append((nid,d+1))
185
- return {"entity":start,"neighbors":[v for k,v in visited.items() if k!=entity_id],"relations":edges,"depth":depth}
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
+ }