codegraph-engine 2.3.0__py3-none-any.whl → 3.0.1__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.
Files changed (118) hide show
  1. codegraph/__init__.py +1 -1
  2. codegraph/__main__.py +7 -0
  3. codegraph/agent_brain.py +253 -2
  4. codegraph/agent_capabilities.py +1348 -67
  5. codegraph/agent_rules.py +2 -1
  6. codegraph/api_drift.py +455 -0
  7. codegraph/architecture_rules.py +123 -0
  8. codegraph/async_queue/__init__.py +50 -0
  9. codegraph/async_queue/detectors/__init__.py +48 -0
  10. codegraph/async_queue/detectors/celery.py +160 -0
  11. codegraph/async_queue/detectors/kafka.py +168 -0
  12. codegraph/async_queue/detectors/rabbitmq.py +116 -0
  13. codegraph/async_queue/detectors/redis.py +130 -0
  14. codegraph/async_queue/detectors/sqs.py +135 -0
  15. codegraph/async_queue/models.py +161 -0
  16. codegraph/async_queue/otel_models.py +74 -0
  17. codegraph/async_queue/otel_parser.py +337 -0
  18. codegraph/async_queue/reconciler.py +427 -0
  19. codegraph/async_queue/scanner.py +291 -0
  20. codegraph/async_queue/traversal.py +447 -0
  21. codegraph/change_impact.py +320 -110
  22. codegraph/changelog.py +170 -0
  23. codegraph/cli.py +1605 -62
  24. codegraph/cli_output.py +17 -2
  25. codegraph/context.py +135 -24
  26. codegraph/context_freshness.py +2 -0
  27. codegraph/database/__init__.py +14 -0
  28. codegraph/database/extractor.py +712 -17
  29. codegraph/database/interrogation.py +388 -2
  30. codegraph/database/schema_drift.py +862 -0
  31. codegraph/dataflow_propagation.py +118 -0
  32. codegraph/dead_code.py +387 -0
  33. codegraph/epistemic.py +15 -0
  34. codegraph/evidence_contract.py +34 -3
  35. codegraph/federation.py +245 -0
  36. codegraph/frameworks.py +234 -12
  37. codegraph/frameworks_nestjs.py +502 -0
  38. codegraph/frameworks_nextjs.py +337 -0
  39. codegraph/frameworks_react.py +457 -0
  40. codegraph/freshness.py +2 -0
  41. codegraph/frontend_html.py +109 -0
  42. codegraph/git.py +2 -0
  43. codegraph/git_state.py +2 -0
  44. codegraph/graph/traversal.py +150 -7
  45. codegraph/graph_csr.py +218 -0
  46. codegraph/incremental.py +150 -0
  47. codegraph/indexing/__init__.py +7 -1
  48. codegraph/indexing/classifier.py +5 -0
  49. codegraph/indexing/indexer.py +292 -38
  50. codegraph/indexing/languages/__init__.py +1 -0
  51. codegraph/indexing/languages/cpp.py +457 -0
  52. codegraph/indexing/languages/go.py +459 -0
  53. codegraph/indexing/languages/jvm.py +583 -0
  54. codegraph/indexing/languages/rust.py +507 -0
  55. codegraph/indexing/models.py +15 -1
  56. codegraph/indexing/parser.py +385 -9
  57. codegraph/indexing/pool.py +136 -0
  58. codegraph/indexing/protocols.py +220 -0
  59. codegraph/indexing/scanner.py +23 -0
  60. codegraph/indexing/templates.py +418 -0
  61. codegraph/interrogation.py +207 -39
  62. codegraph/lsp.py +318 -0
  63. codegraph/lsp_bridge/__init__.py +16 -0
  64. codegraph/lsp_bridge/bridge.py +102 -0
  65. codegraph/lsp_bridge/client.py +207 -0
  66. codegraph/lsp_bridge/models.py +71 -0
  67. codegraph/mcp/server.py +502 -29
  68. codegraph/memory/trajectory.py +282 -0
  69. codegraph/models.py +7 -0
  70. codegraph/monorepo.py +3 -0
  71. codegraph/multi_agent/__init__.py +13 -0
  72. codegraph/multi_agent/coordinator.py +229 -0
  73. codegraph/multi_agent/models.py +76 -0
  74. codegraph/optimizer.py +42 -5
  75. codegraph/polymorphism.py +95 -0
  76. codegraph/pr_review.py +296 -0
  77. codegraph/process_lifecycle.py +50 -12
  78. codegraph/reachability.py +198 -0
  79. codegraph/reachability_index.py +74 -0
  80. codegraph/refactor/__init__.py +48 -0
  81. codegraph/refactor/models.py +114 -0
  82. codegraph/refactor/patcher.py +348 -0
  83. codegraph/refactor/renamer.py +844 -0
  84. codegraph/refactor/transactions.py +414 -0
  85. codegraph/repair.py +233 -0
  86. codegraph/resolver.py +1357 -161
  87. codegraph/retrieval_policy.py +22 -1
  88. codegraph/roblox.py +2174 -0
  89. codegraph/route_composer.py +7 -0
  90. codegraph/route_topology.py +172 -0
  91. codegraph/runtime/__init__.py +25 -0
  92. codegraph/runtime/collector.py +155 -0
  93. codegraph/runtime/db_watcher.py +191 -0
  94. codegraph/runtime/ingestor.py +30 -10
  95. codegraph/runtime/otel_receiver.py +208 -0
  96. codegraph/runtime/port_inspector.py +304 -0
  97. codegraph/runtime/runner.py +155 -6
  98. codegraph/runtime/trace_linker.py +175 -0
  99. codegraph/sandbox.py +215 -0
  100. codegraph/search/hybrid.py +26 -0
  101. codegraph/security/supply_chain.py +188 -0
  102. codegraph/security/taint.py +421 -0
  103. codegraph/semantic_decorators.py +34 -3
  104. codegraph/structural_diff.py +2 -0
  105. codegraph/styles.py +144 -0
  106. codegraph/symbol_history.py +2 -0
  107. codegraph/test_gen.py +437 -0
  108. codegraph/tsconfig.py +236 -0
  109. codegraph/ui/__init__.py +6 -0
  110. codegraph/ui/server.py +876 -0
  111. codegraph/watcher.py +323 -0
  112. {codegraph_engine-2.3.0.dist-info → codegraph_engine-3.0.1.dist-info}/METADATA +365 -67
  113. codegraph_engine-3.0.1.dist-info/RECORD +164 -0
  114. {codegraph_engine-2.3.0.dist-info → codegraph_engine-3.0.1.dist-info}/WHEEL +1 -1
  115. codegraph_engine-2.3.0.dist-info/RECORD +0 -94
  116. {codegraph_engine-2.3.0.dist-info → codegraph_engine-3.0.1.dist-info}/entry_points.txt +0 -0
  117. {codegraph_engine-2.3.0.dist-info → codegraph_engine-3.0.1.dist-info}/licenses/LICENSE +0 -0
  118. {codegraph_engine-2.3.0.dist-info → codegraph_engine-3.0.1.dist-info}/top_level.txt +0 -0
codegraph/mcp/server.py CHANGED
@@ -1,10 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import asyncio
3
4
  import functools
4
5
  import json
5
6
  from pathlib import Path
6
7
  from typing import Any
7
8
 
9
+ import anyio
10
+
8
11
  from codegraph.architecture import get_architecture as arch_get_architecture
9
12
  from codegraph.config import Settings
10
13
  from codegraph.context import _resolve_task_or_query_input
@@ -442,9 +445,9 @@ def create_server(
442
445
  task: dict[str, Any] | str = "",
443
446
  intent: str | None = None,
444
447
  max_tokens: int = 4000,
445
- max_files: int = 15,
448
+ max_files: int = 25,
446
449
  max_lines: int = 500,
447
- top_k: int = 15,
450
+ top_k: int = 25,
448
451
  plan: dict[str, Any] | None = None,
449
452
  mode: str = "BALANCED",
450
453
  explain: bool = False,
@@ -609,7 +612,7 @@ def create_server(
609
612
  return [
610
613
  dict(r)
611
614
  for r in con.execute(
612
- "SELECT source_path,imported FROM imports ORDER BY source_path,imported LIMIT 500"
615
+ "SELECT source_path, COALESCE(NULLIF(imported_module, ''), module) AS imported FROM imports ORDER BY source_path, imported LIMIT 500"
613
616
  )
614
617
  ]
615
618
 
@@ -956,6 +959,64 @@ def create_server(
956
959
  with indexer.session() as con:
957
960
  return runtime_reconcile_static_runtime(con, indexer.repository, symbol=symbol, route=route, table=table)
958
961
 
962
+ def get_live_services() -> dict[str, Any]:
963
+ """Detect and attribute active localhost services, ports, PIDs, frameworks, and workspace sub-packages with zero idle CPU. Use when diagnosing running dev servers or multi-service architectures. Does not prove live HTTP response payloads or application layer health."""
964
+ from codegraph.runtime.port_inspector import (
965
+ discover_live_listening_ports,
966
+ discover_static_configured_ports,
967
+ )
968
+ live = [s.as_dict() for s in discover_live_listening_ports(indexer.repository)]
969
+ static = [s.as_dict() for s in discover_static_configured_ports(indexer.repository).values()]
970
+ return {"live_services": live, "configured_services": static, "count": len(live)}
971
+
972
+ def check_db_drift() -> dict[str, Any]:
973
+ """Detect database schema drift between migration files, live database state, and code ORM models with zero idle CPU. Flags unmapped tables, missing columns, and migration version state. Use when verifying whether database migrations are up to date with code models. Does not execute SQL data assertions or validate column constraints against live row data."""
974
+ from codegraph.runtime.db_watcher import detect_schema_drift
975
+ with indexer.session() as con:
976
+ return detect_schema_drift(con, indexer.repository).as_dict()
977
+
978
+ def get_distributed_trace(trace_id: str) -> dict[str, Any]:
979
+ """Stitch and reconstruct multi-service distributed execution trees across frontend, backend, and database boundaries for a specific W3C trace ID. Use when tracing cross-service requests or debugging multi-service latency bottlenecks. Does not capture unobserved code paths or untraced sidecar requests."""
980
+ from codegraph.runtime.trace_linker import link_distributed_trace
981
+ with indexer.session() as con:
982
+ res = link_distributed_trace(con, indexer.repository, trace_id)
983
+ return res.as_dict() if res else {"status": "not_found", "trace_id": trace_id}
984
+
985
+ def check_route_schema_drift(
986
+ route: str = "",
987
+ handler: str = "",
988
+ table: str | None = None,
989
+ schema: str | None = None,
990
+ ) -> dict[str, Any]:
991
+ """Detect schema validation drift between a route/handler input schema (Pydantic BaseModel, Django Form, DRF Serializer) and destination database table columns. Flags MISSING_REQUIRED_COLUMN, NULLABILITY_MISMATCH, TYPE_INCOMPATIBILITY, LENGTH_CONSTRAINT_DRIFT, and UNUSED_SCHEMA_FIELD. Does not execute live requests."""
992
+ from codegraph.database.schema_drift import detect_schema_drift as impl_detect_drift
993
+ target = route or handler
994
+ with indexer.session() as con:
995
+ return impl_detect_drift(
996
+ con,
997
+ indexer.repository,
998
+ route_or_handler=target,
999
+ target_table=table,
1000
+ schema_name=schema,
1001
+ )
1002
+
1003
+ def check_api_drift() -> dict[str, Any]:
1004
+ """Detect client-server API contract drift between frontend fetch or axios calls and backend route registrations. Flags orphaned client endpoints (404s), HTTP method mismatches (405s), and cross-language TypeScript-to-backend model drift. Use when validating frontend API calls against backend endpoints before deployment. Does not execute live HTTP requests or validate dynamic URL strings constructed via runtime interpolation."""
1005
+ from codegraph.api_drift import detect_api_contract_drift
1006
+ with indexer.session() as con:
1007
+ return detect_api_contract_drift(con, indexer.repository).as_dict()
1008
+
1009
+ def get_monorepo_packages() -> dict[str, Any]:
1010
+ """Catalog monorepo workspace packages, tools (pnpm, Turborepo, npm/yarn workspaces), entry points, and inter-package dependencies. Use when mapping multi-package repository structure and package boundaries. Does not prove build system execution order or runtime dependency loading."""
1011
+ from codegraph.monorepo import discover_workspace
1012
+ return discover_workspace(indexer.repository).as_dict()
1013
+
1014
+ def find_dead_code(max_symbols: int = 50) -> dict[str, Any]:
1015
+ """Detect zero-caller functions, classes, uncalled zombie HTTP routes, and orphaned database tables by cross-referencing AST graphs with runtime traces. Use when identifying unused code, zombie endpoints, or unread/unwritten database tables. Does not prove unobserved paths are impossible when runtime traces are absent."""
1016
+ from codegraph.dead_code import analyze_dead_code
1017
+ with indexer.session() as con:
1018
+ return analyze_dead_code(con, indexer.repository, max_symbols=max_symbols).as_dict()
1019
+
959
1020
  # -----------------------------------------------------------------------
960
1021
  # Git Intelligence Tools (5 Unified Capabilities)
961
1022
  # -----------------------------------------------------------------------
@@ -980,8 +1041,12 @@ def create_server(
980
1041
  def get_change_impact(base: str = "HEAD~1", head: str = "HEAD", max_depth: int = 2, max_results: int = 50) -> dict[str, Any]:
981
1042
  """Compute deep downstream change impact across callers, callees, framework routes, covering tests, mutating database queries, and monorepo packages. Use when analyzing blast radius and ripple effects of git commits. Does not assume unobserved runtime paths are impossible."""
982
1043
  from codegraph.change_impact import get_deep_change_impact
1044
+ target_sym = None
1045
+ clean_base = base.strip()
1046
+ if clean_base.startswith("symbol:"):
1047
+ target_sym = clean_base.split("symbol:", 1)[1]
983
1048
  with indexer.session() as con:
984
- return get_deep_change_impact(indexer.repository, con, base=base, head=head, max_depth=max_depth, max_results=max_results).as_dict()
1049
+ return get_deep_change_impact(indexer.repository, con, base=base, head=head, max_depth=max_depth, max_results=max_results, symbol=target_sym).as_dict()
985
1050
 
986
1051
  def check_context_freshness(task: str = "", context_packet: dict[str, Any] | None = None) -> dict[str, Any]:
987
1052
  """Validate whether a previously compiled context packet or task context is still VALID, PARTIALLY_STALE, or STALE. Use when checking if cached context is valid after edits. Does not invalidate context when only unrelated files change."""
@@ -1015,6 +1080,323 @@ def create_server(
1015
1080
  with indexer.session() as con:
1016
1081
  return impl_detect_semantic_conflicts(indexer.repository, base_branch=base_branch, head_branch=head_branch, con=con).as_dict()
1017
1082
 
1083
+ def safe_rename(
1084
+ target: str,
1085
+ new_name: str,
1086
+ dry_run: bool = True,
1087
+ force_uncertain: bool = False,
1088
+ ) -> dict[str, Any]:
1089
+ """Perform a deterministic, syntax-validated, transaction-safe AST rename of a symbol across the repository.
1090
+ Follows the core invariant: DISCOVER -> PLAN -> PREVIEW -> VALIDATE -> COMMIT -> REINDEX -> VERIFY.
1091
+ When dry_run=True, performs zero disk mutation and returns preview diffs, risk assessment, affected tests, and affected routes.
1092
+ When dry_run=False, validates all preconditions and syntax in memory, commits atomically using sibling temp files and os.replace, persists a rollback manifest, and triggers an incremental index update.
1093
+ If risk != LOW (due to UNKNOWN/POSSIBLE/AMBIGUOUS references), the rename is BLOCKED unless force_uncertain=True."""
1094
+ from codegraph.refactor.renamer import execute_safe_rename
1095
+
1096
+ with indexer.session() as con:
1097
+ result = execute_safe_rename(
1098
+ repository=indexer.repository,
1099
+ con=con,
1100
+ target=target,
1101
+ new_name=new_name,
1102
+ dry_run=dry_run,
1103
+ force_uncertain=force_uncertain,
1104
+ )
1105
+ return result.to_dict()
1106
+
1107
+ def rollback_refactor(
1108
+ transaction_id: str,
1109
+ ) -> dict[str, Any]:
1110
+ """Atomically rollback an applied refactoring transaction using its rollback manifest ID.
1111
+ Verifies that files on disk have not been modified externally since the refactor before restoring original contents."""
1112
+ from codegraph.refactor.models import RefactorStatus
1113
+ from codegraph.refactor.transactions import rollback_refactor as do_rollback
1114
+
1115
+ result = do_rollback(
1116
+ repository=indexer.repository,
1117
+ transaction_id=transaction_id,
1118
+ )
1119
+ if result.status == RefactorStatus.ROLLED_BACK:
1120
+ try:
1121
+ indexer.index()
1122
+ except Exception:
1123
+ pass
1124
+ return result.to_dict()
1125
+
1126
+ def batch_get_symbols(symbols: list[str]) -> dict[str, Any]:
1127
+ """Retrieve declaration metadata, signature, kind, and source snippet for multiple symbols in a single round-trip.
1128
+ Reduces LLM latency and tool call overhead when inspecting multiple related symbols. Bounded to 50 symbols max."""
1129
+ bounded_symbols = symbols[:50]
1130
+ results: dict[str, Any] = {}
1131
+ with indexer.session() as con:
1132
+ for s in bounded_symbols:
1133
+ s_str = str(s).strip()
1134
+ if not s_str:
1135
+ continue
1136
+ results[s_str] = interrogation_get_symbol(con, indexer.repository, symbol=s_str)
1137
+ return {
1138
+ "results": results,
1139
+ "count": len(results),
1140
+ "found_count": sum(1 for r in results.values() if r.get("status") != "NOT_FOUND"),
1141
+ }
1142
+
1143
+ def batch_find_callers(symbols: list[str], max_results_per_symbol: int = 10) -> dict[str, Any]:
1144
+ """Find functions and methods calling each of the specified symbols in a single round-trip.
1145
+ Reduces multi-hop turn count for AI coding agents. Bounded to 30 symbols max."""
1146
+ bounded_symbols = symbols[:30]
1147
+ results: dict[str, Any] = {}
1148
+ with indexer.session() as con:
1149
+ for s in bounded_symbols:
1150
+ s_str = str(s).strip()
1151
+ if not s_str:
1152
+ continue
1153
+ results[s_str] = [dict(r) for r in graph_find_callers(con, s_str, max_results=max_results_per_symbol)]
1154
+ return {
1155
+ "results": results,
1156
+ "count": len(results),
1157
+ "total_callers": sum(len(c) for c in results.values()),
1158
+ }
1159
+
1160
+ def batch_get_context(
1161
+ tasks: list[dict[str, Any]] | list[str],
1162
+ max_tokens_per_task: int = 2000,
1163
+ ) -> dict[str, Any]:
1164
+ """Compile token-bounded, coverage-optimized ContextPackets for multiple task prompts or symbol targets in a single call.
1165
+ Enables agents to retrieve separate contexts for different sub-tasks in one request. Bounded to 5 tasks max."""
1166
+ bounded_tasks = tasks[:5]
1167
+ results: list[dict[str, Any]] = []
1168
+ with indexer.session() as con:
1169
+ for t in bounded_tasks:
1170
+ effective_task = t if isinstance(t, str) else t.get("query") or t.get("task") or str(t)
1171
+ effective_intent = t.get("intent") if isinstance(t, dict) else None
1172
+ packet = compile_context(
1173
+ con,
1174
+ indexer.repository,
1175
+ task=effective_task,
1176
+ intent=effective_intent,
1177
+ max_tokens=max_tokens_per_task,
1178
+ mode="FAST",
1179
+ )
1180
+ results.append(packet.as_dict())
1181
+ return {
1182
+ "results": results,
1183
+ "count": len(results),
1184
+ }
1185
+
1186
+ def preview_semantic_patch(
1187
+ patches: list[dict[str, Any]],
1188
+ ) -> dict[str, Any]:
1189
+ """Dry-run preview a multi-file semantic patch across repository files with syntax validation and unified diffs.
1190
+ Invariants: Zero disk mutation; pre-flight AST validation for Python, Luau, JSON, and JS/TS; boundary checks. Does not commit modifications to disk."""
1191
+ from codegraph.refactor.patcher import preview_semantic_patch as do_preview
1192
+
1193
+ result = do_preview(
1194
+ repository=indexer.repository,
1195
+ patch_specs=patches,
1196
+ )
1197
+ return result.to_dict()
1198
+
1199
+ def apply_semantic_patch(
1200
+ patches: list[dict[str, Any]],
1201
+ dry_run: bool = False,
1202
+ ) -> dict[str, Any]:
1203
+ """Atomically apply a multi-file semantic patch with pre-flight syntax validation and rollback support.
1204
+ When dry_run=False: checks preconditions and hashes, validates syntax in memory, commits atomically using sibling temp files and os.replace under RefactorLock, persists a rollback manifest, and triggers an incremental index update."""
1205
+ from codegraph.refactor.models import RefactorStatus
1206
+ from codegraph.refactor.patcher import apply_semantic_patch as do_apply
1207
+
1208
+ result = do_apply(
1209
+ repository=indexer.repository,
1210
+ patch_specs=patches,
1211
+ dry_run=dry_run,
1212
+ )
1213
+ if result.status == RefactorStatus.APPLIED:
1214
+ try:
1215
+ indexer.index()
1216
+ except Exception:
1217
+ pass
1218
+ return result.to_dict()
1219
+
1220
+ def generate_pr_review(base_ref: str = "main", head_ref: str = "HEAD") -> dict[str, Any]:
1221
+ """Generate comprehensive semantic PR review evaluating structural diffs, caller contract breaks, affected routes, and test gaps. Use when reviewing pull requests or evaluating branch changes before merging. Does not execute repository code."""
1222
+ from codegraph.pr_review import generate_pr_review as impl_pr_review
1223
+ with indexer.session() as con:
1224
+ return impl_pr_review(indexer.repository, base_ref=base_ref, head_ref=head_ref, con=con).as_dict()
1225
+
1226
+ def generate_test_scaffolding(target: str, framework: str | None = None) -> dict[str, Any]:
1227
+ """Generate production-ready test scaffolding with fixtures, dependency mocks (DB sessions, HTTP clients), and boundary test cases for a symbol or route. Use when writing tests for untested functions or HTTP endpoints. Does not execute the generated test code."""
1228
+ from codegraph.test_gen import generate_test_scaffolding as impl_gen_test
1229
+ with indexer.session() as con:
1230
+ return impl_gen_test(con, target=target, repository=indexer.repository, framework=framework).as_dict()
1231
+
1232
+ def federate_repositories(workspace_root: str | None = None) -> dict[str, Any]:
1233
+ """Discover and catalog federated repositories in a multi-repo workspace. Use when mapping microservice boundaries and cross-service index sizes. Does not modify child repository indices."""
1234
+ from codegraph.federation import federate_workspace
1235
+ root = Path(workspace_root) if workspace_root else indexer.repository.parent
1236
+ return federate_workspace(root).as_dict()
1237
+
1238
+ def find_cross_repo_routes(workspace_root: str | None = None, query: str | None = None) -> dict[str, Any]:
1239
+ """Resolve cross-repository HTTP and RPC service-to-service links across microservices. Use when tracing requests across separate repositories. Does not guess unobserved endpoints without route evidence."""
1240
+ from codegraph.federation import federate_workspace
1241
+ root = Path(workspace_root) if workspace_root else indexer.repository.parent
1242
+ rep = federate_workspace(root)
1243
+ links = rep.cross_repo_links
1244
+ if query:
1245
+ clean_q = query.lower()
1246
+ links = [item for item in links if clean_q in item.route_path.lower() or clean_q in item.target_repo.lower() or clean_q in item.source_repo.lower()]
1247
+ return {
1248
+ "workspace_root": rep.workspace_root,
1249
+ "total_links": len(links),
1250
+ "cross_repo_links": [item.as_dict() for item in links],
1251
+ }
1252
+
1253
+ def store_agent_memory(
1254
+ title: str,
1255
+ resolution: str,
1256
+ category: str = "BUG_FIX",
1257
+ symptoms: str = "",
1258
+ root_cause: str = "",
1259
+ ) -> dict[str, Any]:
1260
+ """Store an agent debugging trajectory, test repair pattern, or architectural decision into persistent memory. Use when recording successful bug resolutions or architecture decisions for future recall. Does not share memory outside the local repository index."""
1261
+ from codegraph.memory.trajectory import get_trajectory_memory_store
1262
+ store = get_trajectory_memory_store(indexer.repository)
1263
+ return store.store(
1264
+ title=title,
1265
+ resolution=resolution,
1266
+ category=category,
1267
+ symptoms=symptoms,
1268
+ root_cause=root_cause,
1269
+ ).as_dict()
1270
+
1271
+ def query_agent_memory(
1272
+ query: str,
1273
+ category: str | None = None,
1274
+ limit: int = 5,
1275
+ max_tokens: int = 1500,
1276
+ ) -> dict[str, Any]:
1277
+ """Search past agent debugging memories and architectural decisions using FTS5 BM25 ranking. Use when investigating recurring bugs or checking past resolutions. Does not execute stored resolution code."""
1278
+ from codegraph.memory.trajectory import get_trajectory_memory_store
1279
+ store = get_trajectory_memory_store(indexer.repository)
1280
+ records = store.search(query=query, category=category, limit=limit, max_tokens=max_tokens)
1281
+ return {
1282
+ "query": query,
1283
+ "count": len(records),
1284
+ "memories": [r.as_dict() for r in records],
1285
+ }
1286
+
1287
+ def analyze_dataflow_taint(
1288
+ entrypoint_symbol: str | None = None,
1289
+ filter_cwe: str | None = None,
1290
+ max_depth: int = 12,
1291
+ ) -> dict[str, Any]:
1292
+ """Analyze source-to-sink dataflow taint paths for injection and security vulnerabilities (SQL injection, command execution, path traversal, SSRF, insecure deserialization) using AST dataflow tracking. Use when auditing endpoint security, sanitization gaps, or vulnerable data sinks. Does not execute repository code."""
1293
+ from codegraph.security.taint import audit_repository_taint
1294
+ with indexer.session() as con:
1295
+ return audit_repository_taint(indexer.repository, con=con, entrypoint_symbol=entrypoint_symbol, filter_cwe=filter_cwe, max_depth=max_depth).as_dict()
1296
+
1297
+ def repair_repository_index(
1298
+ deep: bool = True,
1299
+ vacuum: bool = False,
1300
+ ) -> dict[str, Any]:
1301
+ """Perform automated self-healing, SQLite integrity verification, rebuilding corrupted FTS5 indices, pruning dangling FK references, clearing stale locks, and truncating WAL logs. Use when the repository index encounters lock contention, corrupted FTS entries, or orphaned edges. Does not delete source repository code."""
1302
+ from codegraph.repair import repair_repository_index as impl_repair
1303
+ return impl_repair(indexer.repository, deep=deep, vacuum=vacuum).as_dict()
1304
+
1305
+ def get_lsp_diagnostics(file_path: str | None = None) -> dict[str, Any]:
1306
+ """Collect live compiler diagnostics, type errors, and lint warnings via Language Server Protocol (LSP) daemons (pyright, gopls, rust-analyzer, clangd). Use when diagnosing compile errors or verifying code correctness. Does not modify source files."""
1307
+ from codegraph.lsp_bridge.bridge import collect_lsp_diagnostics
1308
+ return collect_lsp_diagnostics(indexer.repository, file_path=file_path).as_dict()
1309
+
1310
+ def register_agent_session(agent_id: str, name: str, task: str) -> dict[str, Any]:
1311
+ """Register an active AI coding agent session in the multi-agent shared workspace. Use when joining a swarm or announcing an active coding task to coordinate with other agents. Does not lock files."""
1312
+ from codegraph.multi_agent.coordinator import MultiAgentCoordinator
1313
+ coord = MultiAgentCoordinator(indexer.repository)
1314
+ return coord.register_session(agent_id, name, task).as_dict()
1315
+
1316
+ def claim_file_lease(
1317
+ agent_id: str,
1318
+ file_path: str,
1319
+ lease_type: str = "EXCLUSIVE_WRITE",
1320
+ ttl_seconds: float = 60.0,
1321
+ ) -> dict[str, Any]:
1322
+ """Claim a lease lock on a file path to prevent concurrent edit clobbering by other AI agents. Use before modifying a file in a multi-agent environment. Does not modify file content."""
1323
+ from codegraph.multi_agent.coordinator import MultiAgentCoordinator
1324
+ coord = MultiAgentCoordinator(indexer.repository)
1325
+ ok, msg = coord.claim_lease(agent_id, file_path, lease_type=lease_type, ttl_seconds=ttl_seconds)
1326
+ return {"success": ok, "message": msg, "file_path": file_path, "agent_id": agent_id, "lease_type": lease_type}
1327
+
1328
+ def release_file_lease(agent_id: str, file_path: str | None = None) -> dict[str, Any]:
1329
+ """Release one or all file leases held by an AI agent upon completing task edits. Use after committing or discarding file edits. Does not delete source files."""
1330
+ from codegraph.multi_agent.coordinator import MultiAgentCoordinator
1331
+ coord = MultiAgentCoordinator(indexer.repository)
1332
+ released_count = coord.release_lease(agent_id, file_path)
1333
+ return {"released_count": released_count, "agent_id": agent_id, "file_path": file_path}
1334
+
1335
+ def get_multi_agent_status() -> dict[str, Any]:
1336
+ """Return consolidated multi-agent status report including active agent sessions, held file leases, and predicted AST conflicts. Use to check if other agents are working on related files. Does not acquire locks."""
1337
+ from codegraph.multi_agent.coordinator import MultiAgentCoordinator
1338
+ coord = MultiAgentCoordinator(indexer.repository)
1339
+ with indexer.session() as con:
1340
+ conflicts = coord.check_conflicts(con=con)
1341
+ rep = coord.get_status_report()
1342
+ rep.conflicts = conflicts
1343
+ rep.conflicts_count = len(conflicts)
1344
+ return rep.as_dict()
1345
+
1346
+ def evaluate_speculative_patch(patches: dict[str, str]) -> dict[str, Any]:
1347
+ """Project an in-memory virtual AST branch in RAM to evaluate caller breakages, parameter mismatches, and route mutations without modifying disk files. Use before writing refactoring patches. Does not modify disk files."""
1348
+ from codegraph.sandbox import evaluate_speculative_patch as impl_sandbox
1349
+ with indexer.session() as con:
1350
+ return impl_sandbox(indexer.repository, file_patches=patches, con=con).as_dict()
1351
+
1352
+ def verify_architectural_rules(rules: list[dict[str, Any]] | None = None) -> dict[str, Any]:
1353
+ """Evaluate declarative architectural boundaries, forbidden cross-module dependencies, and layer rules against the repository index. Use when checking pull request compliance with enterprise architecture. Does not modify code."""
1354
+ from codegraph.architecture_rules import evaluate_architecture_rules as impl_arch
1355
+ with indexer.session() as con:
1356
+ return impl_arch(indexer.repository, con=con, rules=rules).as_dict()
1357
+
1358
+ def find_unreachable_code() -> dict[str, Any]:
1359
+ """Perform DI-aware root-to-leaf reachability analysis from HTTP routes, CLIs, and crons to detect true dead code with STATIC_VERIFIED confidence. Use when pruning dead code. Does not delete code."""
1360
+ from codegraph.reachability import compute_di_aware_reachability as impl_reach
1361
+ with indexer.session() as con:
1362
+ return impl_reach(indexer.repository, con=con).as_dict()
1363
+
1364
+ def generate_semantic_changelog(base_ref: str = "HEAD~1", head_ref: str = "HEAD") -> dict[str, Any]:
1365
+ """Synthesize human-readable release changelog categorizing breaking changes, new endpoints, and database mutations from AST diffs. Use when writing release notes. Does not modify git repository."""
1366
+ from codegraph.changelog import generate_semantic_changelog as impl_changelog
1367
+ return impl_changelog(indexer.repository, base_ref=base_ref, head_ref=head_ref).as_dict()
1368
+
1369
+ def fast_graph_path(start_node: str, end_node: str) -> dict[str, Any]:
1370
+ """Compute microsecond BFS shortest path between two symbols using the Compressed Sparse Row (CSR) binary graph. Use for instant path tracing between caller and callee. Does not modify graph."""
1371
+ from codegraph.graph_csr import CSRGraph
1372
+ with indexer.session() as con:
1373
+ cur = con.cursor()
1374
+ cur.execute("SELECT source, target, relationship FROM graph_edges LIMIT 5000;")
1375
+ edges = [(str(r[0]), str(r[1]), str(r[2])) for r in cur.fetchall()]
1376
+ csr = CSRGraph.build_from_edges(edges)
1377
+ path = csr.shortest_path(start_node, end_node)
1378
+ return {"start_node": start_node, "end_node": end_node, "path": path, "length": len(path) if path else 0}
1379
+
1380
+ def resolve_polymorphic_callees(interface_symbol: str, method_name: str) -> dict[str, Any]:
1381
+ """Resolve an abstract interface method or trait invocation to all concrete implementing classes in the workspace. Use when tracing dynamic dispatch or trait method implementations. Does not execute code."""
1382
+ from codegraph.polymorphism import resolve_polymorphic_callees as impl_poly
1383
+ with indexer.session() as con:
1384
+ return impl_poly(con, interface_symbol=interface_symbol, method_name=method_name).as_dict()
1385
+
1386
+ def propagate_interprocedural_dataflow(target_symbol: str) -> dict[str, Any]:
1387
+ """Trace inter-procedural dataflow, return type inference, and unguarded None/null dereferences across caller-callee boundaries. Use when investigating NullPointerExceptions or type contract violations. Does not execute code."""
1388
+ from codegraph.dataflow_propagation import (
1389
+ propagate_interprocedural_dataflow as impl_dataflow,
1390
+ )
1391
+ with indexer.session() as con:
1392
+ return impl_dataflow(con, target_symbol=target_symbol).as_dict()
1393
+
1394
+ def analyze_supply_chain_cve_impact(target_package: str | None = None) -> dict[str, Any]:
1395
+ """Inspect third-party dependency manifests (package.json, go.mod, Cargo.toml, requirements.txt) and trace actual internal call site reachability. Use when assessing CVE vulnerability blast radius. Does not execute external dependencies."""
1396
+ from codegraph.security.supply_chain import analyze_supply_chain as impl_supply
1397
+ with indexer.session() as con:
1398
+ return impl_supply(indexer.repository, con=con, target_package=target_package).as_dict()
1399
+
1018
1400
  core_tools: dict[str, Any] = {
1019
1401
  "resolve_symbol": resolve_symbol,
1020
1402
  "search_symbols": search_symbols,
@@ -1040,10 +1422,25 @@ def create_server(
1040
1422
  "detect_semantic_conflicts": detect_semantic_conflicts,
1041
1423
  }
1042
1424
 
1425
+ refactor_tools: dict[str, Any] = {
1426
+ "safe_rename": safe_rename,
1427
+ "rollback_refactor": rollback_refactor,
1428
+ "preview_semantic_patch": preview_semantic_patch,
1429
+ "apply_semantic_patch": apply_semantic_patch,
1430
+ }
1431
+
1432
+ batch_tools: dict[str, Any] = {
1433
+ "batch_get_symbols": batch_get_symbols,
1434
+ "batch_find_callers": batch_find_callers,
1435
+ "batch_get_context": batch_get_context,
1436
+ }
1437
+
1043
1438
  # Map of all available tools
1044
1439
  all_tools: dict[str, Any] = {
1045
1440
  **core_tools,
1046
1441
  **git_intelligence_tools,
1442
+ **refactor_tools,
1443
+ **batch_tools,
1047
1444
  "search_code": search_code,
1048
1445
  "read_file": read_file,
1049
1446
  "find_symbol": find_symbol,
@@ -1087,6 +1484,34 @@ def create_server(
1087
1484
  "ingest_runtime_traces": ingest_runtime_traces,
1088
1485
  "get_runtime_trace": get_runtime_trace,
1089
1486
  "reconcile_static_runtime": reconcile_static_runtime,
1487
+ "get_live_services": get_live_services,
1488
+ "check_db_drift": check_db_drift,
1489
+ "check_route_schema_drift": check_route_schema_drift,
1490
+ "get_distributed_trace": get_distributed_trace,
1491
+ "check_api_drift": check_api_drift,
1492
+ "get_monorepo_packages": get_monorepo_packages,
1493
+ "find_dead_code": find_dead_code,
1494
+ "generate_pr_review": generate_pr_review,
1495
+ "generate_test_scaffolding": generate_test_scaffolding,
1496
+ "federate_repositories": federate_repositories,
1497
+ "find_cross_repo_routes": find_cross_repo_routes,
1498
+ "store_agent_memory": store_agent_memory,
1499
+ "query_agent_memory": query_agent_memory,
1500
+ "analyze_dataflow_taint": analyze_dataflow_taint,
1501
+ "repair_repository_index": repair_repository_index,
1502
+ "get_lsp_diagnostics": get_lsp_diagnostics,
1503
+ "register_agent_session": register_agent_session,
1504
+ "claim_file_lease": claim_file_lease,
1505
+ "release_file_lease": release_file_lease,
1506
+ "get_multi_agent_status": get_multi_agent_status,
1507
+ "evaluate_speculative_patch": evaluate_speculative_patch,
1508
+ "verify_architectural_rules": verify_architectural_rules,
1509
+ "find_unreachable_code": find_unreachable_code,
1510
+ "generate_semantic_changelog": generate_semantic_changelog,
1511
+ "fast_graph_path": fast_graph_path,
1512
+ "resolve_polymorphic_callees": resolve_polymorphic_callees,
1513
+ "propagate_interprocedural_dataflow": propagate_interprocedural_dataflow,
1514
+ "analyze_supply_chain_cve_impact": analyze_supply_chain_cve_impact,
1090
1515
  }
1091
1516
 
1092
1517
  agent_names = {
@@ -1179,6 +1604,7 @@ def create_server(
1179
1604
 
1180
1605
  get_graph_cache().clear()
1181
1606
  get_parse_cache().clear()
1607
+ indexer.reset_pool()
1182
1608
  return True
1183
1609
  return False
1184
1610
 
@@ -1196,6 +1622,40 @@ def create_server(
1196
1622
 
1197
1623
  app.tool(name=tool_name)(_make_guarded(target_fn))
1198
1624
 
1625
+ # FastMCP Event Loop Offload:
1626
+ # Ensure synchronous tools execute in worker threads so MCP asyncio loop is never blocked
1627
+ orig_call_tool = app._tool_manager.call_tool
1628
+
1629
+ async def _concurrent_call_tool(
1630
+ name: str,
1631
+ arguments: dict[str, Any],
1632
+ context: Any = None,
1633
+ convert_result: bool = False,
1634
+ ) -> Any:
1635
+ tool = app._tool_manager.get_tool(name)
1636
+ if tool is not None and not tool.is_async:
1637
+ def _invoke_in_thread() -> Any:
1638
+ return asyncio.run(
1639
+ orig_call_tool(
1640
+ name,
1641
+ arguments,
1642
+ context=context,
1643
+ convert_result=convert_result,
1644
+ )
1645
+ )
1646
+
1647
+ return await anyio.to_thread.run_sync(_invoke_in_thread)
1648
+
1649
+ return await orig_call_tool(
1650
+ name,
1651
+ arguments,
1652
+ context=context,
1653
+ convert_result=convert_result,
1654
+ )
1655
+
1656
+ app._tool_manager.call_tool = _concurrent_call_tool # type: ignore[method-assign]
1657
+
1658
+
1199
1659
  # -----------------------------------------------------------------------
1200
1660
  # MCP Resources
1201
1661
  # -----------------------------------------------------------------------
@@ -1208,47 +1668,60 @@ def create_server(
1208
1668
  return json.dumps(get_capability_manifest(), indent=2)
1209
1669
 
1210
1670
  @app.resource("codebase://status")
1211
- def resource_status() -> str:
1671
+ async def resource_status() -> str:
1212
1672
  """Repository index status, generation, and file counts."""
1213
- with indexer.session() as con:
1214
- report = check_freshness(indexer.repository, con)
1215
- return json.dumps(
1216
- {
1217
- "repository": str(indexer.repository),
1218
- "generation": report.index_generation,
1219
- "freshness": report.status.value,
1220
- "files": con.execute("SELECT count(*) FROM files").fetchone()[0],
1221
- "symbols": con.execute("SELECT count(*) FROM symbols").fetchone()[0],
1222
- },
1223
- indent=2,
1224
- )
1673
+ def _get_status() -> str:
1674
+ with indexer.session() as con:
1675
+ report = check_freshness(indexer.repository, con)
1676
+ return json.dumps(
1677
+ {
1678
+ "repository": str(indexer.repository),
1679
+ "generation": report.index_generation,
1680
+ "freshness": report.status.value,
1681
+ "files": con.execute("SELECT count(*) FROM files").fetchone()[0],
1682
+ "symbols": con.execute("SELECT count(*) FROM symbols").fetchone()[0],
1683
+ },
1684
+ indent=2,
1685
+ )
1686
+
1687
+ return await anyio.to_thread.run_sync(_get_status)
1225
1688
 
1226
1689
  @app.resource("codebase://architecture")
1227
- def resource_architecture() -> str:
1690
+ async def resource_architecture() -> str:
1228
1691
  """High-level architectural overview."""
1229
- with indexer.session() as con:
1230
- arch = arch_get_architecture(con, indexer.repository)
1231
- return json.dumps(arch, indent=2)
1692
+ def _get_arch() -> str:
1693
+ with indexer.session() as con:
1694
+ arch = arch_get_architecture(con, indexer.repository)
1695
+ return json.dumps(arch, indent=2)
1696
+
1697
+ return await anyio.to_thread.run_sync(_get_arch)
1232
1698
 
1233
1699
  @app.resource("codebase://modules")
1234
- def resource_modules() -> str:
1700
+ async def resource_modules() -> str:
1235
1701
  """List of indexed repository module paths."""
1236
- with indexer.session() as con:
1237
- rows = con.execute("SELECT path FROM files ORDER BY path LIMIT 500").fetchall()
1238
- return "\n".join(r[0] for r in rows)
1702
+ def _get_modules() -> str:
1703
+ with indexer.session() as con:
1704
+ rows = con.execute("SELECT path FROM files ORDER BY path LIMIT 500").fetchall()
1705
+ return "\n".join(r[0] for r in rows)
1706
+
1707
+ return await anyio.to_thread.run_sync(_get_modules)
1239
1708
 
1240
1709
  @app.resource("codebase://health")
1241
- def resource_health() -> str:
1710
+ async def resource_health() -> str:
1242
1711
  """Database health and integrity report."""
1243
- with indexer.session() as con:
1244
- health = check_database_health(con)
1245
- return json.dumps(health, indent=2)
1712
+ def _get_health() -> str:
1713
+ with indexer.session() as con:
1714
+ health = check_database_health(con)
1715
+ return json.dumps(health, indent=2)
1716
+
1717
+ return await anyio.to_thread.run_sync(_get_health)
1246
1718
 
1247
1719
  @app.resource("codebase://resources")
1248
1720
  def resource_resources() -> str:
1249
1721
  """Resource governor state, memory bounds, pressure, and activity mode."""
1250
1722
  return json.dumps(indexer.governor.get_state().as_dict(), indent=2)
1251
1723
 
1724
+
1252
1725
  # -----------------------------------------------------------------------
1253
1726
  # MCP Prompts
1254
1727
  # -----------------------------------------------------------------------