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.
@@ -538,9 +538,13 @@ class Indexer:
538
538
  pass
539
539
  con.commit()
540
540
  else:
541
- # Idempotently ensure all tables from SCHEMA exist
542
- con.executescript(SCHEMA)
543
- con.commit()
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 = "✓" if da.detected else "✗"
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(" (•) Global")
225
- lines.append(" ( ) Current project")
227
+ lines.append(f" {sym.radio_on} Global")
228
+ lines.append(f" {sym.radio_off} Current project")
226
229
  else:
227
- lines.append(" ( ) Global")
228
- lines.append(" (•) Current project")
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 = "CodeGraph Uninstaller (Dry Run)" if self.operation == "uninstall" else "CodeGraph Uninit (Dry Run)"
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"↻ Repaired: {r_msg}")
308
+ lines.append(f"{sym.repair} Repaired: {r_msg}")
299
309
  for w_msg in self.warnings:
300
- lines.append(f"! Warning: {w_msg}")
310
+ lines.append(f"{sym.warn} Warning: {w_msg}")
301
311
 
302
- lines.append("✓ CodeGraph installed")
312
+ lines.append(f"{sym.ok} CodeGraph installed")
303
313
  if self.verification is not None:
304
- v_mcp = "✓" if self.verification.mcp_config_verified else "✗"
305
- v_inst = "✓" if self.verification.instructions_verified else "✗"
306
- v_srv = "✓" if self.verification.mcp_server_health_verified else "✗"
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"! Warning: {w_msg}")
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"✓ {verb} {c.display_path}")
336
+ lines.append(f"{sym.ok} {verb} {c.display_path}")
327
337
  else:
328
- lines.append("✓ No CodeGraph agent integrations needed removal")
329
- lines.append("✓ CodeGraph agent integration uninstalled")
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"✓ Removed {c.display_path}")
345
+ lines.append(f"{sym.ok} Removed {c.display_path}")
336
346
  else:
337
- lines.append("✓ No CodeGraph index files present")
338
- lines.append("✓ Project CodeGraph index removed")
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
@@ -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 = "✓" if chk.passed else "✗"
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 # type: ignore[import-untyped]
32
+ import yaml
33
33
  _HAS_YAML = True
34
34
  except ImportError:
35
35
  _HAS_YAML = False