codegraph-engine 3.0.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 (130) hide show
  1. codegraph/__init__.py +37 -0
  2. codegraph/agent.py +26 -0
  3. codegraph/agent_brain.py +2212 -0
  4. codegraph/agent_capabilities.py +4058 -0
  5. codegraph/agent_rules.py +531 -0
  6. codegraph/api_drift.py +455 -0
  7. codegraph/architecture.py +371 -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/audit.py +106 -0
  22. codegraph/binding_resolver.py +616 -0
  23. codegraph/branch_comparison.py +197 -0
  24. codegraph/cache.py +95 -0
  25. codegraph/change_impact.py +547 -0
  26. codegraph/cli.py +2436 -0
  27. codegraph/cli_output.py +205 -0
  28. codegraph/config.py +45 -0
  29. codegraph/constraints.py +238 -0
  30. codegraph/context.py +2182 -0
  31. codegraph/context_freshness.py +351 -0
  32. codegraph/database/__init__.py +72 -0
  33. codegraph/database/extractor.py +3651 -0
  34. codegraph/database/interrogation.py +1494 -0
  35. codegraph/database/models.py +310 -0
  36. codegraph/database/schema_drift.py +862 -0
  37. codegraph/epistemic.py +200 -0
  38. codegraph/errors.py +282 -0
  39. codegraph/evidence/__init__.py +15 -0
  40. codegraph/evidence/citations.py +418 -0
  41. codegraph/evidence_contract.py +486 -0
  42. codegraph/frameworks.py +960 -0
  43. codegraph/frameworks_nestjs.py +502 -0
  44. codegraph/frameworks_nextjs.py +337 -0
  45. codegraph/frameworks_react.py +457 -0
  46. codegraph/freshness.py +335 -0
  47. codegraph/frontend_html.py +109 -0
  48. codegraph/git.py +362 -0
  49. codegraph/git_state.py +545 -0
  50. codegraph/graph/__init__.py +46 -0
  51. codegraph/graph/models.py +56 -0
  52. codegraph/graph/traversal.py +1993 -0
  53. codegraph/indexing/__init__.py +24 -0
  54. codegraph/indexing/classifier.py +564 -0
  55. codegraph/indexing/indexer.py +2578 -0
  56. codegraph/indexing/models.py +427 -0
  57. codegraph/indexing/parser.py +3404 -0
  58. codegraph/indexing/pool.py +136 -0
  59. codegraph/indexing/scanner.py +211 -0
  60. codegraph/indexing/telemetry.py +326 -0
  61. codegraph/indexing/templates.py +418 -0
  62. codegraph/indexing/test_framework.py +116 -0
  63. codegraph/installer.py +1702 -0
  64. codegraph/interrogation.py +2355 -0
  65. codegraph/llm/__init__.py +3 -0
  66. codegraph/llm/base.py +15 -0
  67. codegraph/llm/context.py +20 -0
  68. codegraph/mcp/__init__.py +3 -0
  69. codegraph/mcp/server.py +1476 -0
  70. codegraph/mcp_diagnostics.py +882 -0
  71. codegraph/memory/__init__.py +3 -0
  72. codegraph/memory/store.py +46 -0
  73. codegraph/models.py +310 -0
  74. codegraph/monorepo.py +925 -0
  75. codegraph/observability.py +262 -0
  76. codegraph/optimizer.py +1014 -0
  77. codegraph/planner.py +417 -0
  78. codegraph/process_lifecycle.py +1153 -0
  79. codegraph/py.typed +1 -0
  80. codegraph/query_expansion.py +199 -0
  81. codegraph/ranking.py +408 -0
  82. codegraph/refactor/__init__.py +36 -0
  83. codegraph/refactor/models.py +114 -0
  84. codegraph/refactor/renamer.py +844 -0
  85. codegraph/refactor/transactions.py +414 -0
  86. codegraph/resolver.py +3968 -0
  87. codegraph/resources/__init__.py +46 -0
  88. codegraph/resources/cache.py +117 -0
  89. codegraph/resources/coalescer.py +83 -0
  90. codegraph/resources/debouncer.py +98 -0
  91. codegraph/resources/governor.py +219 -0
  92. codegraph/resources/monitor.py +102 -0
  93. codegraph/resources/policy.py +123 -0
  94. codegraph/retrieval_policy.py +320 -0
  95. codegraph/roblox.py +2174 -0
  96. codegraph/route_composer.py +363 -0
  97. codegraph/route_topology.py +172 -0
  98. codegraph/runtime/__init__.py +51 -0
  99. codegraph/runtime/collector.py +155 -0
  100. codegraph/runtime/db_watcher.py +191 -0
  101. codegraph/runtime/ingestor.py +692 -0
  102. codegraph/runtime/models.py +130 -0
  103. codegraph/runtime/port_inspector.py +304 -0
  104. codegraph/runtime/reconciliation.py +492 -0
  105. codegraph/runtime/runner.py +353 -0
  106. codegraph/runtime/trace_linker.py +175 -0
  107. codegraph/search/__init__.py +25 -0
  108. codegraph/search/hybrid.py +846 -0
  109. codegraph/search/semantic.py +28 -0
  110. codegraph/security/__init__.py +51 -0
  111. codegraph/security/paths.py +189 -0
  112. codegraph/security/redaction.py +633 -0
  113. codegraph/semantic_conflicts.py +270 -0
  114. codegraph/semantic_decorators.py +372 -0
  115. codegraph/structural_diff.py +542 -0
  116. codegraph/styles.py +144 -0
  117. codegraph/symbol_history.py +277 -0
  118. codegraph/target_resolver.py +348 -0
  119. codegraph/task.py +637 -0
  120. codegraph/tool_selection_eval.py +1158 -0
  121. codegraph/tsconfig.py +236 -0
  122. codegraph/ui/__init__.py +6 -0
  123. codegraph/ui/server.py +755 -0
  124. codegraph/watcher.py +323 -0
  125. codegraph_engine-3.0.0.dist-info/METADATA +859 -0
  126. codegraph_engine-3.0.0.dist-info/RECORD +130 -0
  127. codegraph_engine-3.0.0.dist-info/WHEEL +6 -0
  128. codegraph_engine-3.0.0.dist-info/entry_points.txt +2 -0
  129. codegraph_engine-3.0.0.dist-info/licenses/LICENSE +21 -0
  130. codegraph_engine-3.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,418 @@
1
+ """First-class Evidence Engine with deterministic source-hash verification.
2
+
3
+ Every evidence item contains:
4
+ evidence_id, repository, file, start_line, end_line, symbol, relationship,
5
+ source_hash, indexed_commit, confidence, evidence_type, evidence_status.
6
+
7
+ Before returning evidence, current file content is hashed and compared against
8
+ the stored source_hash. If modified or deleted since indexing, evidence_status
9
+ is set to 'stale' (never silently returning obsolete citations as current).
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import hashlib
14
+ import sqlite3
15
+ from dataclasses import asdict, dataclass
16
+ from pathlib import Path
17
+ from typing import Any
18
+
19
+ from codegraph.freshness import index_generation
20
+ from codegraph.security import DEFAULT_MAX_READ_BYTES, is_sensitive_path, safe_path
21
+
22
+ PARSER_VERSION = "3.0"
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class Evidence:
27
+ file: str
28
+ start_line: int
29
+ end_line: int
30
+ symbol: str | None
31
+ snippet: str
32
+ evidence_type: str = "source"
33
+ confidence: str = "HIGH"
34
+ source_hash: str | None = None
35
+ indexed_commit: str | None = None
36
+ evidence_status: str = "current" # current | stale | deleted | unknown
37
+ relationship: str = "DEFINES"
38
+ repository: str = ""
39
+ evidence_id: str = ""
40
+ start_column: int | None = None
41
+ end_column: int | None = None
42
+ index_generation: int = 0
43
+ parser_version: str = PARSER_VERSION
44
+
45
+ def __post_init__(self) -> None:
46
+ if not self.evidence_id:
47
+ raw = f"{self.file}:{self.start_line}:{self.end_line}:{self.symbol or ''}:{self.relationship}"
48
+ eid = "ev_" + hashlib.sha256(raw.encode("utf-8")).hexdigest()[:12]
49
+ object.__setattr__(self, "evidence_id", eid)
50
+
51
+ def as_dict(self) -> dict[str, object]:
52
+ return asdict(self)
53
+
54
+ def __getitem__(self, key: str) -> object:
55
+ return self.as_dict()[key]
56
+
57
+ def get(self, key: str, default: object = None) -> object:
58
+ return self.as_dict().get(key, default)
59
+
60
+
61
+ def get_repository_from_con(con: sqlite3.Connection) -> Path | None:
62
+ """Retrieve the repository root path stored in the index metadata table."""
63
+ try:
64
+ row = con.execute(
65
+ "SELECT value FROM metadata WHERE key='repository'"
66
+ ).fetchone()
67
+ if row and row[0]:
68
+ p = Path(str(row[0]))
69
+ if p.exists():
70
+ return p
71
+ except sqlite3.OperationalError:
72
+ pass
73
+ return None
74
+
75
+
76
+ def verify_source_hash(
77
+ repository: Path | None,
78
+ file_path: str,
79
+ stored_hash: str | None,
80
+ ) -> str:
81
+ """Verify whether the current file on disk matches `stored_hash`.
82
+
83
+ Returns 'current', 'stale', or 'deleted'.
84
+ """
85
+ if is_sensitive_path(file_path):
86
+ return "deleted"
87
+ if not stored_hash or repository is None:
88
+ return "current"
89
+ abs_path = repository / file_path
90
+ if not abs_path.exists():
91
+ return "stale"
92
+ try:
93
+ if abs_path.stat().st_size > DEFAULT_MAX_READ_BYTES * 4:
94
+ return "stale"
95
+ current_digest = hashlib.sha256(
96
+ abs_path.read_text(encoding="utf-8", errors="replace").encode()
97
+ ).hexdigest()
98
+ return "current" if current_digest == stored_hash else "stale"
99
+ except OSError:
100
+ return "stale"
101
+
102
+
103
+ def build_evidence(
104
+ con: sqlite3.Connection,
105
+ repository: Path | None,
106
+ path: str,
107
+ symbol: str | None = None,
108
+ limit: int = 50,
109
+ ) -> list[Evidence]:
110
+ """Build verified evidence list for a file/symbol, checking source hash freshness."""
111
+ from codegraph.freshness import indexed_commit as get_indexed_commit
112
+
113
+ if is_sensitive_path(path):
114
+ return []
115
+
116
+ repo = repository or get_repository_from_con(con)
117
+ idx_commit = get_indexed_commit(con)
118
+ repo_str = str(repo) if repo else ""
119
+
120
+ # Look up the file-level hash in `files` table for source verification
121
+ file_row = con.execute(
122
+ "SELECT hash FROM files WHERE path=?", (path,)
123
+ ).fetchone()
124
+ file_hash = str(file_row["hash"]) if file_row else None
125
+
126
+ if symbol:
127
+ short = symbol.split(".")[-1]
128
+ rows = con.execute(
129
+ "SELECT symbol, start_line, end_line, content, hash FROM chunks "
130
+ "WHERE path=? AND (symbol=? OR symbol LIKE ? OR symbol=?) LIMIT ?",
131
+ (path, symbol, f"%.{symbol}", short, limit),
132
+ ).fetchall()
133
+ else:
134
+ rows = con.execute(
135
+ "SELECT symbol, start_line, end_line, content, hash FROM chunks "
136
+ "WHERE path=? ORDER BY start_line LIMIT ?",
137
+ (path, limit),
138
+ ).fetchall()
139
+
140
+ results: list[Evidence] = []
141
+ for row in rows:
142
+ expected_hash = file_hash or str(row["hash"])
143
+ status = verify_source_hash(repo, path, expected_hash)
144
+ confidence = "HIGH" if status == "current" else "LOW"
145
+ results.append(
146
+ Evidence(
147
+ file=path,
148
+ start_line=int(row["start_line"]),
149
+ end_line=int(row["end_line"]),
150
+ symbol=row["symbol"],
151
+ snippet=str(row["content"])[:900],
152
+ evidence_type="source",
153
+ confidence=confidence,
154
+ source_hash=expected_hash,
155
+ indexed_commit=idx_commit,
156
+ evidence_status=status,
157
+ relationship="DEFINES",
158
+ repository=repo_str,
159
+ )
160
+ )
161
+ return results
162
+
163
+
164
+ @dataclass
165
+ class VerificationReport:
166
+ valid: bool
167
+ exists: bool
168
+ hash_matches: bool
169
+ lines_valid: bool
170
+ status: str # FACT | CONFLICT | UNKNOWN
171
+ confidence: str # HIGH | MEDIUM | LOW | UNKNOWN
172
+ freshness: str # FRESH | STALE | UNKNOWN
173
+ reason: str
174
+ file: str
175
+ start_line: int | None = None
176
+ end_line: int | None = None
177
+ symbol: str | None = None
178
+ evidence_id: str | None = None
179
+ index_generation: int = 0
180
+ parser_version: str = PARSER_VERSION
181
+
182
+ def as_dict(self) -> dict[str, object]:
183
+ d: dict[str, object] = asdict(self)
184
+ d["epistemic_status"] = self.status
185
+ return d
186
+
187
+ def __getitem__(self, key: str) -> Any:
188
+ return self.as_dict()[key]
189
+
190
+ def get(self, key: str, default: Any = None) -> Any:
191
+ return self.as_dict().get(key, default)
192
+
193
+
194
+ def verify_evidence(
195
+ con: sqlite3.Connection,
196
+ repository: Path,
197
+ evidence: Evidence | dict[str, Any] | str | None = None,
198
+ evidence_id: str | None = None,
199
+ file_path: str | None = None,
200
+ start_line: int | None = None,
201
+ end_line: int | None = None,
202
+ symbol: str | None = None,
203
+ expected_content_hash: str | None = None,
204
+ expected_hash: str | None = None,
205
+ ) -> VerificationReport:
206
+ """Verify validity and freshness of an evidence reference against current repository disk state."""
207
+ gen = index_generation(con)
208
+
209
+ resolved_file = file_path
210
+ resolved_start = start_line
211
+ resolved_end = end_line
212
+ resolved_symbol = symbol
213
+ resolved_id = evidence_id
214
+ expected_h = expected_content_hash or expected_hash
215
+
216
+ if isinstance(evidence, Evidence):
217
+ resolved_file = evidence.file
218
+ resolved_start = evidence.start_line
219
+ resolved_end = evidence.end_line
220
+ resolved_symbol = evidence.symbol
221
+ resolved_id = evidence.evidence_id
222
+ elif isinstance(evidence, dict):
223
+ resolved_file = str(evidence.get("file") or evidence.get("file_path") or "")
224
+ resolved_start = evidence.get("start_line")
225
+ resolved_end = evidence.get("end_line")
226
+ resolved_symbol = evidence.get("symbol")
227
+ resolved_id = evidence.get("evidence_id")
228
+ if not expected_h:
229
+ expected_h = evidence.get("source_hash")
230
+ elif isinstance(evidence, str):
231
+ if "/" in evidence or evidence.endswith(".py") or evidence.endswith(".ts") or evidence.endswith(".js"):
232
+ resolved_file = evidence
233
+ else:
234
+ resolved_id = evidence
235
+
236
+ if resolved_id and not resolved_file:
237
+ edge_row = con.execute(
238
+ "SELECT file, start_line, end_line, source, target, relationship FROM graph_edges WHERE evidence_id=?",
239
+ (resolved_id,),
240
+ ).fetchone()
241
+ if edge_row:
242
+ resolved_file = str(edge_row["file"])
243
+ resolved_start = int(edge_row["start_line"])
244
+ resolved_end = int(edge_row["end_line"])
245
+ if not resolved_symbol:
246
+ resolved_symbol = str(edge_row["target"])
247
+
248
+ if not resolved_file:
249
+ return VerificationReport(
250
+ valid=False,
251
+ exists=False,
252
+ hash_matches=False,
253
+ lines_valid=False,
254
+ status="UNKNOWN",
255
+ confidence="UNKNOWN",
256
+ freshness="UNKNOWN",
257
+ reason="Missing file path and evidence_id not found in index",
258
+ file="",
259
+ index_generation=gen,
260
+ )
261
+
262
+ if is_sensitive_path(resolved_file):
263
+ return VerificationReport(
264
+ valid=False,
265
+ exists=False,
266
+ hash_matches=False,
267
+ lines_valid=False,
268
+ status="UNKNOWN",
269
+ confidence="UNKNOWN",
270
+ freshness="UNKNOWN",
271
+ reason=f"Access to sensitive file '{resolved_file}' is blocked",
272
+ file=resolved_file,
273
+ index_generation=gen,
274
+ )
275
+
276
+ try:
277
+ abs_path = safe_path(repository, resolved_file)
278
+ except Exception as e:
279
+ return VerificationReport(
280
+ valid=False,
281
+ exists=False,
282
+ hash_matches=False,
283
+ lines_valid=False,
284
+ status="UNKNOWN",
285
+ confidence="UNKNOWN",
286
+ freshness="UNKNOWN",
287
+ reason=f"Repository boundary violation: {e}",
288
+ file=resolved_file,
289
+ index_generation=gen,
290
+ )
291
+
292
+ if not abs_path.exists() or not abs_path.is_file():
293
+ return VerificationReport(
294
+ valid=False,
295
+ exists=False,
296
+ hash_matches=False,
297
+ lines_valid=False,
298
+ status="UNKNOWN",
299
+ confidence="LOW",
300
+ freshness="STALE",
301
+ reason=f"File not found on disk: {resolved_file}",
302
+ file=resolved_file,
303
+ index_generation=gen,
304
+ )
305
+
306
+ try:
307
+ content_bytes = abs_path.read_bytes()
308
+ current_hash = hashlib.sha256(content_bytes).hexdigest()
309
+ except OSError as e:
310
+ return VerificationReport(
311
+ valid=False,
312
+ exists=True,
313
+ hash_matches=False,
314
+ lines_valid=False,
315
+ status="CONFLICT",
316
+ confidence="LOW",
317
+ freshness="STALE",
318
+ reason=f"Could not read file from disk: {e}",
319
+ file=resolved_file,
320
+ index_generation=gen,
321
+ )
322
+
323
+ # Check against expected hash if supplied
324
+ if expected_h and current_hash != expected_h:
325
+ return VerificationReport(
326
+ valid=False,
327
+ exists=True,
328
+ hash_matches=False,
329
+ lines_valid=False,
330
+ status="CONFLICT",
331
+ confidence="LOW",
332
+ freshness="STALE",
333
+ reason=f"Source hash mismatch (expected {expected_h[:8]}..., found {current_hash[:8]}...)",
334
+ file=resolved_file,
335
+ start_line=resolved_start,
336
+ end_line=resolved_end,
337
+ symbol=resolved_symbol,
338
+ index_generation=gen,
339
+ )
340
+
341
+ # Check against indexed database hash if available
342
+ file_row = con.execute("SELECT hash FROM files WHERE path=?", (resolved_file,)).fetchone()
343
+ if file_row:
344
+ stored_hash = str(file_row["hash"])
345
+ if current_hash != stored_hash:
346
+ return VerificationReport(
347
+ valid=False,
348
+ exists=True,
349
+ hash_matches=False,
350
+ lines_valid=False,
351
+ status="CONFLICT",
352
+ confidence="LOW",
353
+ freshness="STALE",
354
+ reason="Source hash changed (file modified on disk after indexing)",
355
+ file=resolved_file,
356
+ start_line=resolved_start,
357
+ end_line=resolved_end,
358
+ symbol=resolved_symbol,
359
+ index_generation=gen,
360
+ )
361
+
362
+ lines = content_bytes.splitlines()
363
+ line_count = len(lines)
364
+ if resolved_start is not None and resolved_end is not None:
365
+ if resolved_start < 1 or resolved_end > line_count or resolved_start > resolved_end:
366
+ return VerificationReport(
367
+ valid=False,
368
+ exists=True,
369
+ hash_matches=True,
370
+ lines_valid=False,
371
+ status="CONFLICT",
372
+ confidence="LOW",
373
+ freshness="STALE",
374
+ reason=f"Referenced line range {resolved_start}-{resolved_end} exceeds current file length ({line_count} lines)",
375
+ file=resolved_file,
376
+ start_line=resolved_start,
377
+ end_line=resolved_end,
378
+ symbol=resolved_symbol,
379
+ index_generation=gen,
380
+ )
381
+
382
+ if resolved_symbol:
383
+ sym_row = con.execute(
384
+ "SELECT canonical_id FROM symbols WHERE path=? AND (name=? OR qualified_name=? OR canonical_id=?)",
385
+ (resolved_file, resolved_symbol, resolved_symbol, resolved_symbol),
386
+ ).fetchone()
387
+ short_sym = resolved_symbol.split(".")[-1]
388
+ if not sym_row and short_sym not in abs_path.read_text(encoding="utf-8", errors="replace"):
389
+ return VerificationReport(
390
+ valid=False,
391
+ exists=True,
392
+ hash_matches=True,
393
+ lines_valid=True,
394
+ status="UNKNOWN",
395
+ confidence="LOW",
396
+ freshness="STALE",
397
+ reason=f"Referenced symbol '{resolved_symbol}' no longer found in file",
398
+ file=resolved_file,
399
+ symbol=resolved_symbol,
400
+ index_generation=gen,
401
+ )
402
+
403
+ return VerificationReport(
404
+ valid=True,
405
+ exists=True,
406
+ hash_matches=True,
407
+ lines_valid=True,
408
+ status="FACT",
409
+ confidence="HIGH",
410
+ freshness="FRESH",
411
+ reason="Evidence verified against source file and disk hash",
412
+ file=resolved_file,
413
+ start_line=resolved_start,
414
+ end_line=resolved_end,
415
+ symbol=resolved_symbol,
416
+ evidence_id=resolved_id,
417
+ index_generation=gen,
418
+ )