codegraph-engine 2.1.7__py3-none-any.whl → 2.1.8__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.
- codegraph/__init__.py +1 -1
- codegraph/cli.py +124 -47
- codegraph/cli_output.py +190 -0
- codegraph/context.py +116 -3
- codegraph/indexing/indexer.py +7 -3
- codegraph/installer.py +72 -22
- codegraph/mcp/server.py +89 -5
- codegraph/mcp_diagnostics.py +5 -2
- codegraph/monorepo.py +1 -1
- codegraph/process_lifecycle.py +1115 -0
- {codegraph_engine-2.1.7.dist-info → codegraph_engine-2.1.8.dist-info}/METADATA +44 -19
- {codegraph_engine-2.1.7.dist-info → codegraph_engine-2.1.8.dist-info}/RECORD +16 -14
- {codegraph_engine-2.1.7.dist-info → codegraph_engine-2.1.8.dist-info}/WHEEL +0 -0
- {codegraph_engine-2.1.7.dist-info → codegraph_engine-2.1.8.dist-info}/entry_points.txt +0 -0
- {codegraph_engine-2.1.7.dist-info → codegraph_engine-2.1.8.dist-info}/licenses/LICENSE +0 -0
- {codegraph_engine-2.1.7.dist-info → codegraph_engine-2.1.8.dist-info}/top_level.txt +0 -0
codegraph/indexing/indexer.py
CHANGED
|
@@ -538,9 +538,13 @@ class Indexer:
|
|
|
538
538
|
pass
|
|
539
539
|
con.commit()
|
|
540
540
|
else:
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
541
|
+
existing_core = con.execute(
|
|
542
|
+
"SELECT count(*) FROM sqlite_master WHERE type='table' "
|
|
543
|
+
"AND name IN ('files', 'symbols', 'graph_edges', 'db_tables', 'runtime_traces')"
|
|
544
|
+
).fetchone()[0]
|
|
545
|
+
if existing_core < 5:
|
|
546
|
+
con.executescript(SCHEMA)
|
|
547
|
+
con.commit()
|
|
544
548
|
|
|
545
549
|
def _migrate_schema(
|
|
546
550
|
self, con: sqlite3.Connection, from_version: int, to_version: int
|
codegraph/installer.py
CHANGED
|
@@ -201,12 +201,15 @@ class InstallerReport:
|
|
|
201
201
|
out["error"] = self.error
|
|
202
202
|
return out
|
|
203
203
|
|
|
204
|
-
def format_preview_human(self) -> str:
|
|
204
|
+
def format_preview_human(self, *, unicode_override: bool | None = None) -> str:
|
|
205
205
|
"""Render the interactive preview banner before confirmation."""
|
|
206
|
+
from codegraph.cli_output import get_cli_symbols
|
|
207
|
+
|
|
208
|
+
sym = get_cli_symbols(unicode_override=unicode_override)
|
|
206
209
|
lines: list[str] = ["CodeGraph Installer", ""]
|
|
207
210
|
lines.append("Detected agents:")
|
|
208
211
|
for da in self.detected_agents:
|
|
209
|
-
mark =
|
|
212
|
+
mark = sym.ok if da.detected else sym.error
|
|
210
213
|
lines.append(f" {mark} {da.display_name}")
|
|
211
214
|
|
|
212
215
|
lines.append("")
|
|
@@ -221,11 +224,11 @@ class InstallerReport:
|
|
|
221
224
|
lines.append("")
|
|
222
225
|
lines.append("Installation scope:")
|
|
223
226
|
if self.location == "global":
|
|
224
|
-
lines.append("
|
|
225
|
-
lines.append("
|
|
227
|
+
lines.append(f" {sym.radio_on} Global")
|
|
228
|
+
lines.append(f" {sym.radio_off} Current project")
|
|
226
229
|
else:
|
|
227
|
-
lines.append("
|
|
228
|
-
lines.append("
|
|
230
|
+
lines.append(f" {sym.radio_off} Global")
|
|
231
|
+
lines.append(f" {sym.radio_on} Current project")
|
|
229
232
|
|
|
230
233
|
lines.append("")
|
|
231
234
|
lines.append("Changes to be made:")
|
|
@@ -234,8 +237,11 @@ class InstallerReport:
|
|
|
234
237
|
lines.append(" + CodeGraph skill/rules")
|
|
235
238
|
return "\n".join(lines)
|
|
236
239
|
|
|
237
|
-
def format_human(self) -> str:
|
|
240
|
+
def format_human(self, *, unicode_override: bool | None = None) -> str:
|
|
238
241
|
"""Render concise, professional human-readable output."""
|
|
242
|
+
from codegraph.cli_output import get_cli_symbols
|
|
243
|
+
|
|
244
|
+
sym = get_cli_symbols(unicode_override=unicode_override)
|
|
239
245
|
if self.error is not None:
|
|
240
246
|
err_lines = [
|
|
241
247
|
f"Error [{self.error.get('code', 'ERROR')}]: {self.error.get('message', '')}",
|
|
@@ -256,10 +262,14 @@ class InstallerReport:
|
|
|
256
262
|
|
|
257
263
|
if self.status == "dry_run":
|
|
258
264
|
if self.operation == "install":
|
|
259
|
-
lines.append(self.format_preview_human())
|
|
265
|
+
lines.append(self.format_preview_human(unicode_override=unicode_override))
|
|
260
266
|
lines.append("")
|
|
261
267
|
else:
|
|
262
|
-
title =
|
|
268
|
+
title = (
|
|
269
|
+
"CodeGraph Uninstaller (Dry Run)"
|
|
270
|
+
if self.operation == "uninstall"
|
|
271
|
+
else "CodeGraph Uninit (Dry Run)"
|
|
272
|
+
)
|
|
263
273
|
lines.append(title)
|
|
264
274
|
lines.append("")
|
|
265
275
|
|
|
@@ -295,15 +305,15 @@ class InstallerReport:
|
|
|
295
305
|
|
|
296
306
|
if self.operation == "install":
|
|
297
307
|
for r_msg in self.repairs:
|
|
298
|
-
lines.append(f"
|
|
308
|
+
lines.append(f"{sym.repair} Repaired: {r_msg}")
|
|
299
309
|
for w_msg in self.warnings:
|
|
300
|
-
lines.append(f"
|
|
310
|
+
lines.append(f"{sym.warn} Warning: {w_msg}")
|
|
301
311
|
|
|
302
|
-
lines.append("
|
|
312
|
+
lines.append(f"{sym.ok} CodeGraph installed")
|
|
303
313
|
if self.verification is not None:
|
|
304
|
-
v_mcp =
|
|
305
|
-
v_inst =
|
|
306
|
-
v_srv =
|
|
314
|
+
v_mcp = sym.ok if self.verification.mcp_config_verified else sym.error
|
|
315
|
+
v_inst = sym.ok if self.verification.instructions_verified else sym.error
|
|
316
|
+
v_srv = sym.ok if self.verification.mcp_server_health_verified else sym.error
|
|
307
317
|
lines.append(f"{v_mcp} MCP configuration verified")
|
|
308
318
|
lines.append(f"{v_inst} Agent instructions verified")
|
|
309
319
|
lines.append(f"{v_srv} MCP server health verified")
|
|
@@ -319,23 +329,23 @@ class InstallerReport:
|
|
|
319
329
|
|
|
320
330
|
if self.operation == "uninstall":
|
|
321
331
|
for w_msg in self.warnings:
|
|
322
|
-
lines.append(f"
|
|
332
|
+
lines.append(f"{sym.warn} Warning: {w_msg}")
|
|
323
333
|
if removed or modified:
|
|
324
334
|
for c in [*removed, *modified]:
|
|
325
335
|
verb = "Removed" if c.action == "remove" else "Updated"
|
|
326
|
-
lines.append(f"
|
|
336
|
+
lines.append(f"{sym.ok} {verb} {c.display_path}")
|
|
327
337
|
else:
|
|
328
|
-
lines.append("
|
|
329
|
-
lines.append("
|
|
338
|
+
lines.append(f"{sym.ok} No CodeGraph agent integrations needed removal")
|
|
339
|
+
lines.append(f"{sym.ok} CodeGraph agent integration uninstalled")
|
|
330
340
|
return "\n".join(lines)
|
|
331
341
|
|
|
332
342
|
# uninit
|
|
333
343
|
if removed:
|
|
334
344
|
for c in removed:
|
|
335
|
-
lines.append(f"
|
|
345
|
+
lines.append(f"{sym.ok} Removed {c.display_path}")
|
|
336
346
|
else:
|
|
337
|
-
lines.append("
|
|
338
|
-
lines.append("
|
|
347
|
+
lines.append(f"{sym.ok} No CodeGraph index files present")
|
|
348
|
+
lines.append(f"{sym.ok} Project CodeGraph index removed")
|
|
339
349
|
return "\n".join(lines)
|
|
340
350
|
|
|
341
351
|
|
|
@@ -1351,6 +1361,33 @@ def run_install(
|
|
|
1351
1361
|
allow_malformed_repair=allow_malformed_repair,
|
|
1352
1362
|
)
|
|
1353
1363
|
|
|
1364
|
+
from codegraph.process_lifecycle import (
|
|
1365
|
+
discover_codegraph_processes,
|
|
1366
|
+
stop_codegraph_processes,
|
|
1367
|
+
)
|
|
1368
|
+
|
|
1369
|
+
proc_discovery = discover_codegraph_processes(clean_stale=not dry_run)
|
|
1370
|
+
current_pid = os.getpid()
|
|
1371
|
+
other_active = [
|
|
1372
|
+
p for p in proc_discovery.active_processes if p.record.pid != current_pid
|
|
1373
|
+
]
|
|
1374
|
+
orphaned_procs = [p for p in other_active if p.orphaned]
|
|
1375
|
+
live_procs = [p for p in other_active if not p.orphaned]
|
|
1376
|
+
|
|
1377
|
+
if orphaned_procs and dry_run:
|
|
1378
|
+
pids_str = ", ".join(str(p.record.pid) for p in orphaned_procs)
|
|
1379
|
+
warnings.append(
|
|
1380
|
+
f"Orphaned CodeGraph process(es) detected (PID: {pids_str}); "
|
|
1381
|
+
"run 'codegraph stop' or 'codegraph install' to clean up."
|
|
1382
|
+
)
|
|
1383
|
+
if live_procs:
|
|
1384
|
+
pids_str = ", ".join(str(p.record.pid) for p in live_procs)
|
|
1385
|
+
warnings.append(
|
|
1386
|
+
f"Active CodeGraph process(es) running (PID: {pids_str}). "
|
|
1387
|
+
"Before upgrading ('pip install --upgrade codegraph-engine'), run 'codegraph stop' "
|
|
1388
|
+
"to prevent executable file lock errors (WinError 32)."
|
|
1389
|
+
)
|
|
1390
|
+
|
|
1354
1391
|
preview_report = InstallerReport(
|
|
1355
1392
|
status="dry_run" if dry_run else "ok",
|
|
1356
1393
|
operation="install",
|
|
@@ -1383,6 +1420,12 @@ def run_install(
|
|
|
1383
1420
|
files_changed=False,
|
|
1384
1421
|
)
|
|
1385
1422
|
|
|
1423
|
+
if orphaned_procs:
|
|
1424
|
+
stop_rep = stop_codegraph_processes(only_orphaned=True)
|
|
1425
|
+
stopped_cnt = sum(1 for item in stop_rep.stopped_processes if item.stopped)
|
|
1426
|
+
if stopped_cnt > 0:
|
|
1427
|
+
repairs.append(f"Stopped {stopped_cnt} orphaned CodeGraph background process(es).")
|
|
1428
|
+
|
|
1386
1429
|
applied = _apply_planned_changes_atomically(changes)
|
|
1387
1430
|
ver = verify_installation(
|
|
1388
1431
|
base_root=base_root,
|
|
@@ -1545,6 +1588,13 @@ def run_uninstall(
|
|
|
1545
1588
|
files_changed=False,
|
|
1546
1589
|
)
|
|
1547
1590
|
|
|
1591
|
+
from codegraph.process_lifecycle import stop_codegraph_processes
|
|
1592
|
+
|
|
1593
|
+
stop_codegraph_processes(
|
|
1594
|
+
repository=None if scope == "global" else effective_ws,
|
|
1595
|
+
stop_all=(scope == "global"),
|
|
1596
|
+
)
|
|
1597
|
+
|
|
1548
1598
|
applied = _apply_planned_changes_atomically(planned)
|
|
1549
1599
|
return InstallerReport(
|
|
1550
1600
|
status="ok",
|
codegraph/mcp/server.py
CHANGED
|
@@ -486,25 +486,79 @@ def create_server(
|
|
|
486
486
|
return git_analyze_change_impact(con, indexer.repository, since=since, until=until)
|
|
487
487
|
|
|
488
488
|
def get_repository_status() -> dict[str, object]:
|
|
489
|
-
"""Get repository indexing status, freshness, symbol counts, and health."""
|
|
489
|
+
"""Get repository indexing status, freshness, symbol counts, and health (automatically hot-reloads in-memory caches when `.codegraph.sqlite3` is created or updated by `codegraph init` / `codegraph index`)."""
|
|
490
|
+
from codegraph.resources.cache import get_graph_cache, get_parse_cache
|
|
491
|
+
|
|
492
|
+
get_graph_cache().clear()
|
|
493
|
+
get_parse_cache().clear()
|
|
494
|
+
if not indexer.db_path.exists():
|
|
495
|
+
return {
|
|
496
|
+
"status": "error",
|
|
497
|
+
"repository": str(indexer.repository),
|
|
498
|
+
"generation": 0,
|
|
499
|
+
"freshness": "NOT_INDEXED",
|
|
500
|
+
"freshness_detail": "No CodeGraph index exists for this repository.",
|
|
501
|
+
"files": 0,
|
|
502
|
+
"files_indexed": 0,
|
|
503
|
+
"symbols": 0,
|
|
504
|
+
"symbols_indexed": 0,
|
|
505
|
+
"graph_edges": 0,
|
|
506
|
+
"framework_routes": 0,
|
|
507
|
+
"reloaded": True,
|
|
508
|
+
"error": {
|
|
509
|
+
"code": "INDEX_NOT_FOUND",
|
|
510
|
+
"message": "No CodeGraph index exists for this repository.",
|
|
511
|
+
"next_action": {
|
|
512
|
+
"command": "codegraph init",
|
|
513
|
+
"reason": "Initialize the repository before querying it.",
|
|
514
|
+
},
|
|
515
|
+
},
|
|
516
|
+
}
|
|
490
517
|
with indexer.session() as con:
|
|
491
518
|
report = check_freshness(indexer.repository, con)
|
|
492
|
-
file_count = con.execute("SELECT count(*) FROM files").fetchone()[0]
|
|
493
|
-
symbol_count = con.execute("SELECT count(*) FROM symbols").fetchone()[0]
|
|
494
|
-
edge_count = con.execute("SELECT count(*) FROM graph_edges").fetchone()[0]
|
|
495
|
-
route_count = con.execute("SELECT count(*) FROM framework_routes").fetchone()[0]
|
|
519
|
+
file_count = int(con.execute("SELECT count(*) FROM files").fetchone()[0])
|
|
520
|
+
symbol_count = int(con.execute("SELECT count(*) FROM symbols").fetchone()[0])
|
|
521
|
+
edge_count = int(con.execute("SELECT count(*) FROM graph_edges").fetchone()[0])
|
|
522
|
+
route_count = int(con.execute("SELECT count(*) FROM framework_routes").fetchone()[0])
|
|
523
|
+
if file_count == 0:
|
|
524
|
+
return {
|
|
525
|
+
"status": "error",
|
|
526
|
+
"repository": str(indexer.repository),
|
|
527
|
+
"generation": report.index_generation,
|
|
528
|
+
"freshness": "NOT_INDEXED",
|
|
529
|
+
"freshness_detail": "No files are indexed in this repository.",
|
|
530
|
+
"files": 0,
|
|
531
|
+
"files_indexed": 0,
|
|
532
|
+
"symbols": 0,
|
|
533
|
+
"symbols_indexed": 0,
|
|
534
|
+
"graph_edges": 0,
|
|
535
|
+
"framework_routes": 0,
|
|
536
|
+
"reloaded": True,
|
|
537
|
+
"error": {
|
|
538
|
+
"code": "INDEX_NOT_FOUND",
|
|
539
|
+
"message": "No CodeGraph index exists for this repository.",
|
|
540
|
+
"next_action": {
|
|
541
|
+
"command": "codegraph init",
|
|
542
|
+
"reason": "Initialize the repository before querying it.",
|
|
543
|
+
},
|
|
544
|
+
},
|
|
545
|
+
}
|
|
496
546
|
return {
|
|
547
|
+
"status": "ok",
|
|
497
548
|
"repository": str(indexer.repository),
|
|
498
549
|
"generation": report.index_generation,
|
|
499
550
|
"freshness": report.status.value,
|
|
500
551
|
"freshness_detail": report.detail,
|
|
552
|
+
"files": file_count,
|
|
501
553
|
"files_indexed": file_count,
|
|
554
|
+
"symbols": symbol_count,
|
|
502
555
|
"symbols_indexed": symbol_count,
|
|
503
556
|
"graph_edges": edge_count,
|
|
504
557
|
"framework_routes": route_count,
|
|
505
558
|
"modified_files": report.modified_files,
|
|
506
559
|
"deleted_files": report.deleted_files,
|
|
507
560
|
"parse_failed_files": report.parse_failed_files,
|
|
561
|
+
"reloaded": True,
|
|
508
562
|
}
|
|
509
563
|
|
|
510
564
|
def trace_call(
|
|
@@ -1024,12 +1078,41 @@ def create_server(
|
|
|
1024
1078
|
else:
|
|
1025
1079
|
selected_names = set(all_tools.keys())
|
|
1026
1080
|
|
|
1081
|
+
_last_db_sig: list[tuple[int, int, int, int] | None] = [None]
|
|
1082
|
+
|
|
1083
|
+
def _compute_db_sig() -> tuple[int, int, int, int] | None:
|
|
1084
|
+
db_file = indexer.db_path
|
|
1085
|
+
if not db_file.exists():
|
|
1086
|
+
return None
|
|
1087
|
+
try:
|
|
1088
|
+
st = db_file.stat()
|
|
1089
|
+
wal_file = Path(f"{db_file}-wal")
|
|
1090
|
+
wal_mtime = wal_file.stat().st_mtime_ns if wal_file.exists() else 0
|
|
1091
|
+
return (st.st_ino, st.st_mtime_ns, st.st_size, wal_mtime)
|
|
1092
|
+
except OSError:
|
|
1093
|
+
return None
|
|
1094
|
+
|
|
1095
|
+
_last_db_sig[0] = _compute_db_sig()
|
|
1096
|
+
|
|
1097
|
+
def _ensure_hot_reloaded() -> bool:
|
|
1098
|
+
"""Detect if `.codegraph.sqlite3` was created, replaced, or updated on disk (e.g. via `codegraph init`)."""
|
|
1099
|
+
cur_sig = _compute_db_sig()
|
|
1100
|
+
if cur_sig != _last_db_sig[0]:
|
|
1101
|
+
_last_db_sig[0] = cur_sig
|
|
1102
|
+
from codegraph.resources.cache import get_graph_cache, get_parse_cache
|
|
1103
|
+
|
|
1104
|
+
get_graph_cache().clear()
|
|
1105
|
+
get_parse_cache().clear()
|
|
1106
|
+
return True
|
|
1107
|
+
return False
|
|
1108
|
+
|
|
1027
1109
|
for tool_name in sorted(selected_names):
|
|
1028
1110
|
target_fn: Any = all_tools[tool_name]
|
|
1029
1111
|
|
|
1030
1112
|
def _make_guarded(fn: Any) -> Any:
|
|
1031
1113
|
@functools.wraps(fn)
|
|
1032
1114
|
def _guarded(*args: Any, **kwargs: Any) -> Any:
|
|
1115
|
+
_ensure_hot_reloaded()
|
|
1033
1116
|
with indexer.governor.task_scope(TaskPriority.INTERACTIVE_HIGH):
|
|
1034
1117
|
return fn(*args, **kwargs)
|
|
1035
1118
|
|
|
@@ -1151,4 +1234,5 @@ def create_server(
|
|
|
1151
1234
|
"2. Run `get_context` with intent='REVIEW'."
|
|
1152
1235
|
)
|
|
1153
1236
|
|
|
1237
|
+
app._codegraph_indexer = indexer # type: ignore[attr-defined]
|
|
1154
1238
|
return app
|
codegraph/mcp_diagnostics.py
CHANGED
|
@@ -111,14 +111,17 @@ class MCPDoctorReport:
|
|
|
111
111
|
"discovered_tools": list(self.discovered_tools),
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
def format_human(self) -> str:
|
|
114
|
+
def format_human(self, *, unicode_override: bool | None = None) -> str:
|
|
115
|
+
from codegraph.cli_output import get_cli_symbols
|
|
116
|
+
|
|
117
|
+
sym = get_cli_symbols(unicode_override=unicode_override)
|
|
115
118
|
lines = [
|
|
116
119
|
"CodeGraph MCP Doctor",
|
|
117
120
|
"--------------------",
|
|
118
121
|
"",
|
|
119
122
|
]
|
|
120
123
|
for chk in self.checks:
|
|
121
|
-
mark =
|
|
124
|
+
mark = sym.ok if chk.passed else sym.error
|
|
122
125
|
lines.append(f"{mark} {chk.name}")
|
|
123
126
|
return "\n".join(lines)
|
|
124
127
|
|
codegraph/monorepo.py
CHANGED
|
@@ -29,7 +29,7 @@ from codegraph.indexing.classifier import FileCategory, classify_file
|
|
|
29
29
|
from codegraph.security import is_sensitive, safe_path
|
|
30
30
|
|
|
31
31
|
try:
|
|
32
|
-
import yaml
|
|
32
|
+
import yaml
|
|
33
33
|
_HAS_YAML = True
|
|
34
34
|
except ImportError:
|
|
35
35
|
_HAS_YAML = False
|