codegraph-engine 2.3.0__py3-none-any.whl → 3.0.0__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 (80) hide show
  1. codegraph/__init__.py +1 -1
  2. codegraph/agent_brain.py +172 -2
  3. codegraph/agent_capabilities.py +320 -3
  4. codegraph/agent_rules.py +1 -1
  5. codegraph/api_drift.py +455 -0
  6. codegraph/async_queue/__init__.py +50 -0
  7. codegraph/async_queue/detectors/__init__.py +48 -0
  8. codegraph/async_queue/detectors/celery.py +160 -0
  9. codegraph/async_queue/detectors/kafka.py +168 -0
  10. codegraph/async_queue/detectors/rabbitmq.py +116 -0
  11. codegraph/async_queue/detectors/redis.py +130 -0
  12. codegraph/async_queue/detectors/sqs.py +135 -0
  13. codegraph/async_queue/models.py +161 -0
  14. codegraph/async_queue/otel_models.py +74 -0
  15. codegraph/async_queue/otel_parser.py +337 -0
  16. codegraph/async_queue/reconciler.py +427 -0
  17. codegraph/async_queue/scanner.py +291 -0
  18. codegraph/async_queue/traversal.py +447 -0
  19. codegraph/change_impact.py +320 -110
  20. codegraph/cli.py +776 -25
  21. codegraph/cli_output.py +17 -2
  22. codegraph/context.py +38 -11
  23. codegraph/context_freshness.py +2 -0
  24. codegraph/database/__init__.py +14 -0
  25. codegraph/database/extractor.py +712 -17
  26. codegraph/database/interrogation.py +388 -2
  27. codegraph/database/schema_drift.py +862 -0
  28. codegraph/epistemic.py +7 -0
  29. codegraph/evidence_contract.py +21 -0
  30. codegraph/frameworks.py +171 -6
  31. codegraph/frameworks_nestjs.py +502 -0
  32. codegraph/frameworks_nextjs.py +337 -0
  33. codegraph/frameworks_react.py +457 -0
  34. codegraph/freshness.py +2 -0
  35. codegraph/frontend_html.py +109 -0
  36. codegraph/git.py +2 -0
  37. codegraph/git_state.py +2 -0
  38. codegraph/indexing/__init__.py +7 -1
  39. codegraph/indexing/indexer.py +291 -38
  40. codegraph/indexing/models.py +6 -0
  41. codegraph/indexing/parser.py +239 -0
  42. codegraph/indexing/pool.py +136 -0
  43. codegraph/indexing/scanner.py +6 -0
  44. codegraph/indexing/templates.py +418 -0
  45. codegraph/interrogation.py +128 -20
  46. codegraph/mcp/server.py +190 -28
  47. codegraph/models.py +7 -0
  48. codegraph/monorepo.py +3 -0
  49. codegraph/optimizer.py +42 -5
  50. codegraph/process_lifecycle.py +31 -13
  51. codegraph/refactor/__init__.py +36 -0
  52. codegraph/refactor/models.py +114 -0
  53. codegraph/refactor/renamer.py +844 -0
  54. codegraph/refactor/transactions.py +403 -0
  55. codegraph/resolver.py +1202 -159
  56. codegraph/retrieval_policy.py +6 -0
  57. codegraph/route_topology.py +172 -0
  58. codegraph/runtime/__init__.py +19 -0
  59. codegraph/runtime/collector.py +155 -0
  60. codegraph/runtime/db_watcher.py +191 -0
  61. codegraph/runtime/ingestor.py +30 -10
  62. codegraph/runtime/port_inspector.py +304 -0
  63. codegraph/runtime/runner.py +155 -6
  64. codegraph/runtime/trace_linker.py +175 -0
  65. codegraph/search/hybrid.py +26 -0
  66. codegraph/semantic_decorators.py +34 -3
  67. codegraph/structural_diff.py +2 -0
  68. codegraph/styles.py +144 -0
  69. codegraph/symbol_history.py +2 -0
  70. codegraph/tsconfig.py +236 -0
  71. codegraph/ui/__init__.py +6 -0
  72. codegraph/ui/server.py +715 -0
  73. codegraph/watcher.py +323 -0
  74. {codegraph_engine-2.3.0.dist-info → codegraph_engine-3.0.0.dist-info}/METADATA +150 -38
  75. codegraph_engine-3.0.0.dist-info/RECORD +129 -0
  76. codegraph_engine-2.3.0.dist-info/RECORD +0 -94
  77. {codegraph_engine-2.3.0.dist-info → codegraph_engine-3.0.0.dist-info}/WHEEL +0 -0
  78. {codegraph_engine-2.3.0.dist-info → codegraph_engine-3.0.0.dist-info}/entry_points.txt +0 -0
  79. {codegraph_engine-2.3.0.dist-info → codegraph_engine-3.0.0.dist-info}/licenses/LICENSE +0 -0
  80. {codegraph_engine-2.3.0.dist-info → codegraph_engine-3.0.0.dist-info}/top_level.txt +0 -0
@@ -485,7 +485,7 @@ def find_db_writers(
485
485
  res = _find_table_accessors(
486
486
  con,
487
487
  target,
488
- ("WRITES_TABLE", "WRITES_COLUMN"),
488
+ ("WRITES_TABLE", "WRITES_COLUMN", "WRITES_COLLECTION"),
489
489
  include_upstream_routes=True,
490
490
  )
491
491
  res["writers"] = res["direct_accessors"]
@@ -505,7 +505,7 @@ def find_db_readers(
505
505
  res = _find_table_accessors(
506
506
  con,
507
507
  target,
508
- ("READS_TABLE", "READS_COLUMN"),
508
+ ("READS_TABLE", "READS_COLUMN", "READS_COLLECTION"),
509
509
  include_upstream_routes=True,
510
510
  )
511
511
  res["readers"] = res["direct_accessors"]
@@ -1106,3 +1106,389 @@ def get_db_impact(
1106
1106
  "impacts": impact_items,
1107
1107
  "results": impact_items,
1108
1108
  })
1109
+
1110
+
1111
+ def _normalize_table_name(raw: str) -> str:
1112
+ if not raw:
1113
+ return ""
1114
+ clean = raw.strip().strip('"`')
1115
+ if clean.startswith("db."):
1116
+ parts = clean.split(".")
1117
+ cand = parts[-1].strip('"`')
1118
+ return "UNKNOWN" if cand.upper() == "UNKNOWN" else cand.lower()
1119
+ cand = clean.split(".")[-1].strip('"`')
1120
+ return "UNKNOWN" if cand.upper() == "UNKNOWN" else cand.lower()
1121
+
1122
+
1123
+ def _normalize_col_name(raw: str) -> str:
1124
+ clean = raw.strip().strip('"`')
1125
+ return clean.split(".")[-1].strip('"`').lower()
1126
+
1127
+
1128
+ def _infer_table_from_column_target(tgt: str) -> str:
1129
+ clean = tgt.strip().strip('"`')
1130
+ parts = clean.split(".")
1131
+ if len(parts) >= 2:
1132
+ cand = parts[-2].strip('"`')
1133
+ return "UNKNOWN" if cand.upper() == "UNKNOWN" else cand.lower()
1134
+ return ""
1135
+
1136
+
1137
+ def _aggregate_table_access(raw_items: list[dict[str, Any]], max_cols: int) -> list[dict[str, Any]]:
1138
+ by_table: dict[str, dict[str, Any]] = {}
1139
+ for item in raw_items:
1140
+ t = item["table"]
1141
+ if not t:
1142
+ continue
1143
+ if t not in by_table:
1144
+ by_table[t] = {
1145
+ "table": t,
1146
+ "columns": set(),
1147
+ "operations": set(),
1148
+ "evidence_classes": set(),
1149
+ "confidences": set(),
1150
+ "call_chains": [],
1151
+ "evidence": [],
1152
+ }
1153
+ entry = by_table[t]
1154
+ for c in item.get("columns", []):
1155
+ if c:
1156
+ entry["columns"].add(c)
1157
+ if item.get("operation"):
1158
+ entry["operations"].add(item["operation"])
1159
+ entry["evidence_classes"].add(item.get("evidence_class", "AST_VERIFIED"))
1160
+ entry["confidences"].add(item.get("confidence", "HIGH"))
1161
+ if item.get("call_chain"):
1162
+ entry["call_chains"].append(item["call_chain"])
1163
+ if item.get("evidence"):
1164
+ entry["evidence"].append({
1165
+ "file": item.get("file", ""),
1166
+ "line": item.get("line", 0),
1167
+ "snippet": item.get("evidence", ""),
1168
+ })
1169
+
1170
+ results: list[dict[str, Any]] = []
1171
+ for t_name in sorted(by_table.keys()):
1172
+ data = by_table[t_name]
1173
+ sorted_cols = sorted(list(data["columns"]))[:max_cols]
1174
+ ops = set(data["operations"])
1175
+ # If specific mutation operations exist, drop generic "WRITE"
1176
+ if len(ops) > 1 and "WRITE" in ops and any(x in ops for x in ("INSERT", "UPDATE", "DELETE", "CREATE", "DROP", "ALTER")):
1177
+ ops.remove("WRITE")
1178
+ # If specific read operations exist, drop generic "READ"
1179
+ if len(ops) > 1 and "READ" in ops and "SELECT" in ops:
1180
+ ops.remove("READ")
1181
+
1182
+ sorted_ops = sorted(list(ops))
1183
+ ev_classes = data["evidence_classes"]
1184
+
1185
+ if "UNKNOWN" in ev_classes:
1186
+ synth_ev = "UNKNOWN"
1187
+ synth_conf = "UNKNOWN"
1188
+ elif "POSSIBLE" in ev_classes or "AMBIGUOUS" in ev_classes:
1189
+ synth_ev = "POSSIBLE"
1190
+ synth_conf = "LOW"
1191
+ elif "DATAFLOW_VERIFIED" in ev_classes:
1192
+ synth_ev = "DATAFLOW_VERIFIED"
1193
+ synth_conf = "HIGH"
1194
+ elif "FRAMEWORK_VERIFIED" in ev_classes:
1195
+ synth_ev = "FRAMEWORK_VERIFIED"
1196
+ synth_conf = "HIGH"
1197
+ else:
1198
+ synth_ev = "AST_VERIFIED"
1199
+ synth_conf = "HIGH"
1200
+
1201
+ shortest_chain = min(data["call_chains"], key=len) if data["call_chains"] else []
1202
+
1203
+ results.append({
1204
+ "table": t_name,
1205
+ "columns": sorted_cols,
1206
+ "operations": sorted_ops,
1207
+ "evidence_class": synth_ev,
1208
+ "confidence": synth_conf,
1209
+ "call_chain": shortest_chain,
1210
+ "evidence": data["evidence"][:10],
1211
+ })
1212
+
1213
+ return results
1214
+
1215
+
1216
+ def get_route_db_lineage(
1217
+ con: sqlite3.Connection,
1218
+ route_or_handler: str,
1219
+ method: str | None = None,
1220
+ *,
1221
+ max_depth: int = 4,
1222
+ max_tables: int = 25,
1223
+ max_columns_per_table: int = 50,
1224
+ ) -> dict[str, Any]:
1225
+ """Deterministically trace route or handler through services, repositories, and ORM/SQL down to database mutations and reads.
1226
+
1227
+ Enforces bounded BFS (max_depth=4), cycle protection via visited set, deterministic ordering (table ASC, column ASC),
1228
+ strict output budgets, and preserves epistemic states (AST_VERIFIED, STATIC_VERIFIED, DATAFLOW_VERIFIED, FRAMEWORK_VERIFIED, POSSIBLE, UNKNOWN).
1229
+ """
1230
+ raw = (route_or_handler or "").strip()
1231
+ if not raw:
1232
+ return {
1233
+ "status": "ok",
1234
+ "route": "",
1235
+ "method": (method or "").upper() or "ANY",
1236
+ "handler": "",
1237
+ "db_reads": [],
1238
+ "db_writes": [],
1239
+ "tables": [],
1240
+ "evidence_class": "STATIC_VERIFIED",
1241
+ }
1242
+
1243
+ clean_target = raw
1244
+ req_method = (method or "").strip().upper() or None
1245
+
1246
+ if " " in clean_target:
1247
+ parts = clean_target.split(" ", 1)
1248
+ if parts[0].upper() in ("GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"):
1249
+ req_method = req_method or parts[0].upper()
1250
+ clean_target = parts[1].strip()
1251
+
1252
+ route_query = (
1253
+ "SELECT route_path, http_method, handler_name, handler_canonical_id, file_path, line "
1254
+ "FROM framework_routes WHERE route_path = ? OR endpoint_id = ?"
1255
+ )
1256
+ params: list[Any] = [clean_target, clean_target]
1257
+ if req_method:
1258
+ route_query += " AND UPPER(http_method) = ?"
1259
+ params.append(req_method)
1260
+
1261
+ try:
1262
+ route_rows = con.execute(route_query, params).fetchall()
1263
+ except sqlite3.OperationalError:
1264
+ route_rows = []
1265
+
1266
+ if not route_rows and req_method:
1267
+ try:
1268
+ route_rows = con.execute(
1269
+ "SELECT route_path, http_method, handler_name, handler_canonical_id, file_path, line "
1270
+ "FROM framework_routes WHERE route_path = ? OR endpoint_id = ?",
1271
+ (clean_target, clean_target),
1272
+ ).fetchall()
1273
+ except sqlite3.OperationalError:
1274
+ route_rows = []
1275
+
1276
+ seeds: list[tuple[str, str]] = []
1277
+ route_info: dict[str, Any] = {}
1278
+
1279
+ if route_rows:
1280
+ primary_r = route_rows[0]
1281
+ route_info = {
1282
+ "route": str(primary_r["route_path"]),
1283
+ "method": str(primary_r["http_method"] or req_method or "").upper(),
1284
+ "file": str(primary_r["file_path"]),
1285
+ "line": int(primary_r["line"]),
1286
+ }
1287
+ for rr in route_rows:
1288
+ h_cid = str(rr["handler_canonical_id"] or "")
1289
+ h_name = str(rr["handler_name"] or "")
1290
+ if h_cid:
1291
+ seeds.append((h_cid, h_name or h_cid.split(".")[-1]))
1292
+ elif h_name:
1293
+ seeds.append((h_name, h_name))
1294
+ else:
1295
+ try:
1296
+ sym_rows = con.execute(
1297
+ "SELECT canonical_id, qualified_name, name, file_path, start_line FROM symbols "
1298
+ "WHERE canonical_id = ? OR qualified_name = ? OR name = ?",
1299
+ (clean_target, clean_target, clean_target),
1300
+ ).fetchall()
1301
+ except sqlite3.OperationalError:
1302
+ sym_rows = []
1303
+ if sym_rows:
1304
+ for sr in sym_rows:
1305
+ cid = str(sr["canonical_id"] or sr["qualified_name"] or sr["name"])
1306
+ seeds.append((cid, str(sr["name"])))
1307
+ else:
1308
+ seeds.append((clean_target, clean_target))
1309
+
1310
+ visited: set[str] = set()
1311
+ queue: deque[tuple[str, int, list[str]]] = deque()
1312
+ for s_id, s_disp in seeds:
1313
+ if s_id not in visited:
1314
+ visited.add(s_id)
1315
+ queue.append((s_id, 0, [s_disp]))
1316
+
1317
+ raw_reads: list[dict[str, Any]] = []
1318
+ raw_writes: list[dict[str, Any]] = []
1319
+
1320
+ while queue:
1321
+ curr_sym, depth, chain = queue.popleft()
1322
+ if depth > max_depth:
1323
+ continue
1324
+
1325
+ curr_bare = curr_sym.split(".")[-1]
1326
+
1327
+ # 1. Query db_queries table
1328
+ try:
1329
+ q_rows = con.execute(
1330
+ "SELECT query_id, caller_symbol_id, operation, relationship, table_name, table_canonical_id, "
1331
+ "columns_json, file_path, start_line, end_line, evidence, confidence, evidence_class, status "
1332
+ "FROM db_queries "
1333
+ "WHERE caller_symbol_id = ? OR caller_symbol_id LIKE ? OR caller_symbol_id LIKE ? OR caller_symbol_id = ?",
1334
+ (curr_sym, f"%.{curr_sym}", f"{curr_sym}.%", curr_bare),
1335
+ ).fetchall()
1336
+ except sqlite3.OperationalError:
1337
+ q_rows = []
1338
+
1339
+ for qr in q_rows:
1340
+ op = str(qr["operation"] or "").upper()
1341
+ rel = str(qr["relationship"] or "")
1342
+ tbl_raw = str(qr["table_name"] or qr["table_canonical_id"] or "")
1343
+ tbl_name = _normalize_table_name(tbl_raw)
1344
+ if not tbl_name:
1345
+ continue
1346
+
1347
+ cols_raw: list[Any] = []
1348
+ try:
1349
+ cols_raw = json.loads(qr["columns_json"] or "[]")
1350
+ except Exception:
1351
+ cols_raw = []
1352
+ cols = [_normalize_col_name(str(c)) for c in cols_raw if str(c).strip()]
1353
+
1354
+ ev_cls = str(qr["evidence_class"] or "AST_VERIFIED")
1355
+ conf = str(qr["confidence"] or "HIGH")
1356
+ item = {
1357
+ "table": tbl_name,
1358
+ "columns": cols,
1359
+ "operation": op,
1360
+ "evidence_class": ev_cls,
1361
+ "confidence": conf,
1362
+ "call_chain": list(chain),
1363
+ "file": str(qr["file_path"]),
1364
+ "line": int(qr["start_line"]),
1365
+ "evidence": redact_secrets(str(qr["evidence"])),
1366
+ }
1367
+
1368
+ if op in ("SELECT", "FETCH", "READ", "FIND") or rel in ("READS_TABLE", "READS_COLUMN"):
1369
+ raw_reads.append(item)
1370
+ elif op in ("INSERT", "UPDATE", "DELETE", "WRITE", "SAVE", "CREATE", "DROP", "ALTER") or rel in ("WRITES_TABLE", "WRITES_COLUMN"):
1371
+ raw_writes.append(item)
1372
+ elif op == "UNKNOWN" or rel == "UNKNOWN_TABLE" or ev_cls == "UNKNOWN":
1373
+ raw_writes.append(item)
1374
+ raw_reads.append(item)
1375
+
1376
+ # 2. Query graph_edges table for outgoing edges from curr_sym
1377
+ try:
1378
+ edge_rows = con.execute(
1379
+ "SELECT source, target, relationship, confidence, file, start_line, end_line, evidence, evidence_class, reason "
1380
+ "FROM graph_edges WHERE source = ? OR source = ?",
1381
+ (curr_sym, curr_bare),
1382
+ ).fetchall()
1383
+ except sqlite3.OperationalError:
1384
+ edge_rows = []
1385
+
1386
+ for er in edge_rows:
1387
+ rel = str(er["relationship"])
1388
+ tgt = str(er["target"])
1389
+ ev_cls = str(er["evidence_class"] or "AST_VERIFIED")
1390
+ conf = str(er["confidence"] or "HIGH")
1391
+ ev_str = redact_secrets(str(er["evidence"] or ""))
1392
+
1393
+ if rel in ("READS_TABLE", "READS_COLUMN"):
1394
+ tbl_name = _normalize_table_name(tgt) if rel == "READS_TABLE" else _infer_table_from_column_target(tgt)
1395
+ col_name = _normalize_col_name(tgt) if rel == "READS_COLUMN" else None
1396
+ if tbl_name:
1397
+ raw_reads.append({
1398
+ "table": tbl_name,
1399
+ "columns": [col_name] if col_name else [],
1400
+ "operation": "SELECT",
1401
+ "evidence_class": ev_cls,
1402
+ "confidence": conf,
1403
+ "call_chain": list(chain),
1404
+ "file": str(er["file"]),
1405
+ "line": int(er["start_line"]),
1406
+ "evidence": ev_str,
1407
+ })
1408
+ elif rel in ("WRITES_TABLE", "WRITES_COLUMN"):
1409
+ tbl_name = _normalize_table_name(tgt) if rel == "WRITES_TABLE" else _infer_table_from_column_target(tgt)
1410
+ col_name = _normalize_col_name(tgt) if rel == "WRITES_COLUMN" else None
1411
+ if tbl_name:
1412
+ raw_writes.append({
1413
+ "table": tbl_name,
1414
+ "columns": [col_name] if col_name else [],
1415
+ "operation": "WRITE",
1416
+ "evidence_class": ev_cls,
1417
+ "confidence": conf,
1418
+ "call_chain": list(chain),
1419
+ "file": str(er["file"]),
1420
+ "line": int(er["start_line"]),
1421
+ "evidence": ev_str,
1422
+ })
1423
+ elif rel in ("UNKNOWN_TABLE", "POSSIBLE_TABLE"):
1424
+ tbl_name = _normalize_table_name(tgt)
1425
+ if tbl_name:
1426
+ u_item = {
1427
+ "table": tbl_name,
1428
+ "columns": [],
1429
+ "operation": "UNKNOWN",
1430
+ "evidence_class": "UNKNOWN" if rel == "UNKNOWN_TABLE" else "POSSIBLE",
1431
+ "confidence": "UNKNOWN" if rel == "UNKNOWN_TABLE" else "LOW",
1432
+ "call_chain": list(chain),
1433
+ "file": str(er["file"]),
1434
+ "line": int(er["start_line"]),
1435
+ "evidence": ev_str,
1436
+ }
1437
+ raw_writes.append(u_item)
1438
+ raw_reads.append(u_item)
1439
+ elif rel in (
1440
+ "CALLS",
1441
+ "INJECTS",
1442
+ "PROVIDES",
1443
+ "RESOLVES_DEPENDENCY",
1444
+ "DISPATCHES_TO",
1445
+ "ROUTES_TO",
1446
+ "HANDLED_BY",
1447
+ "CALLS_CELERY_TASK",
1448
+ "DISPATCHES_SIGNAL",
1449
+ ):
1450
+ if depth + 1 <= max_depth and tgt not in visited:
1451
+ visited.add(tgt)
1452
+ next_disp = tgt.split(".")[-1]
1453
+ queue.append((tgt, depth + 1, chain + [next_disp]))
1454
+
1455
+ # 3. Class method expansion
1456
+ try:
1457
+ def_rows = con.execute(
1458
+ "SELECT target FROM graph_edges WHERE (source = ? OR source = ?) AND relationship = 'DEFINES'",
1459
+ (curr_sym, curr_bare),
1460
+ ).fetchall()
1461
+ for dr in def_rows:
1462
+ dtgt = str(dr["target"])
1463
+ if depth + 1 <= max_depth and dtgt not in visited:
1464
+ visited.add(dtgt)
1465
+ queue.append((dtgt, depth + 1, chain + [dtgt.split(".")[-1]]))
1466
+ except sqlite3.OperationalError:
1467
+ pass
1468
+
1469
+ aggregated_reads = _aggregate_table_access(raw_reads, max_columns_per_table)[:max_tables]
1470
+ aggregated_writes = _aggregate_table_access(raw_writes, max_columns_per_table)[:max_tables]
1471
+
1472
+ all_tables = sorted(list(set(r["table"] for r in aggregated_reads) | set(w["table"] for w in aggregated_writes)))
1473
+
1474
+ all_ev_classes = set(r["evidence_class"] for r in aggregated_reads) | set(w["evidence_class"] for w in aggregated_writes)
1475
+ if "UNKNOWN" in all_ev_classes:
1476
+ overall_ev = "UNKNOWN"
1477
+ elif "POSSIBLE" in all_ev_classes:
1478
+ overall_ev = "POSSIBLE"
1479
+ elif all_ev_classes:
1480
+ overall_ev = "AST_VERIFIED"
1481
+ else:
1482
+ overall_ev = "STATIC_VERIFIED"
1483
+
1484
+ return {
1485
+ "status": "ok",
1486
+ "route": route_info.get("route", clean_target),
1487
+ "method": route_info.get("method", req_method or "ANY"),
1488
+ "handler": seeds[0][0] if seeds else clean_target,
1489
+ "db_reads": aggregated_reads,
1490
+ "db_writes": aggregated_writes,
1491
+ "tables": all_tables,
1492
+ "evidence_class": overall_ev,
1493
+ }
1494
+