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
@@ -0,0 +1,135 @@
1
+ """AWS SQS message producer and consumer detector.
2
+
3
+ Detects:
4
+ - Publishers / Producers: sqs.send_message(QueueUrl=...), sqs.send_message_batch()
5
+ - Consumers / Workers: sqs.receive_message(QueueUrl=...), @sqs_handler()
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import ast
10
+ from typing import Any
11
+
12
+ from codegraph.async_queue.models import (
13
+ AsyncConsumerRecord,
14
+ AsyncMessagingSystem,
15
+ AsyncProducerRecord,
16
+ )
17
+
18
+
19
+ def _extract_queue_name(url_or_name: str) -> str:
20
+ """Extract queue name from SQS URL or name string."""
21
+ clean = url_or_name.strip().rstrip("/")
22
+ if "/" in clean:
23
+ return clean.split("/")[-1]
24
+ return clean
25
+
26
+
27
+ def detect_sqs(
28
+ tree: ast.AST,
29
+ content: str,
30
+ file_path: str,
31
+ module_name: str,
32
+ ) -> tuple[list[AsyncProducerRecord], list[AsyncConsumerRecord]]:
33
+ producers: list[AsyncProducerRecord] = []
34
+ consumers: list[AsyncConsumerRecord] = []
35
+ lines = content.splitlines()
36
+
37
+ def get_line_snippet(lineno: int) -> str:
38
+ if 1 <= lineno <= len(lines):
39
+ return lines[lineno - 1].strip()
40
+ return ""
41
+
42
+ class SQSVisitor(ast.NodeVisitor):
43
+ def __init__(self) -> None:
44
+ self.current_function: str = f"{module_name}.<module>"
45
+
46
+ def visit_FunctionDef(self, fn: ast.FunctionDef) -> None:
47
+ prev = self.current_function
48
+ self.current_function = f"{module_name}.{fn.name}"
49
+ # Check for decorator like @sqs_handler
50
+ for dec in fn.decorator_list:
51
+ dec_func = dec.func if isinstance(dec, ast.Call) else dec
52
+ dec_name = getattr(dec_func, "id", "") or getattr(dec_func, "attr", "")
53
+ if "sqs" in dec_name.lower():
54
+ q_name = ""
55
+ if isinstance(dec, ast.Call):
56
+ for kw in dec.keywords:
57
+ if kw.arg in ("queue", "queue_name", "queue_url") and isinstance(kw.value, ast.Constant) and isinstance(kw.value.value, str):
58
+ q_name = _extract_queue_name(kw.value.value)
59
+ if q_name:
60
+ consumers.append(
61
+ AsyncConsumerRecord(
62
+ system=AsyncMessagingSystem.AWS_SQS,
63
+ destination=q_name,
64
+ queue_id=f"aws_sqs:{q_name}",
65
+ handler_canonical_id=f"{module_name}.{fn.name}",
66
+ file_path=file_path,
67
+ line=fn.lineno,
68
+ handler_snippet=get_line_snippet(fn.lineno),
69
+ confidence="HIGH",
70
+ metadata={"decorator": dec_name},
71
+ )
72
+ )
73
+ self.generic_visit(fn)
74
+ self.current_function = prev
75
+
76
+ def visit_AsyncFunctionDef(self, fn: ast.AsyncFunctionDef) -> None:
77
+ prev = self.current_function
78
+ self.current_function = f"{module_name}.{fn.name}"
79
+ self.generic_visit(fn)
80
+ self.current_function = prev
81
+
82
+ def visit_Call(self, node: ast.Call) -> None:
83
+ if isinstance(node.func, ast.Attribute):
84
+ attr = node.func.attr
85
+ # Producer: send_message, send_message_batch
86
+ if attr in ("send_message", "send_message_batch"):
87
+ q_name = ""
88
+ meta: dict[str, Any] = {"method": attr}
89
+ for kw in node.keywords:
90
+ if kw.arg == "QueueUrl" and isinstance(kw.value, ast.Constant) and isinstance(kw.value.value, str):
91
+ q_name = _extract_queue_name(kw.value.value)
92
+ elif kw.arg in ("DelaySeconds", "MessageDeduplicationId", "MessageGroupId"):
93
+ meta[kw.arg] = True
94
+
95
+ if q_name:
96
+ producers.append(
97
+ AsyncProducerRecord(
98
+ system=AsyncMessagingSystem.AWS_SQS,
99
+ destination=q_name,
100
+ queue_id=f"aws_sqs:{q_name}",
101
+ caller_canonical_id=self.current_function,
102
+ file_path=file_path,
103
+ line=node.lineno,
104
+ call_snippet=get_line_snippet(node.lineno),
105
+ confidence="HIGH",
106
+ metadata=meta,
107
+ )
108
+ )
109
+
110
+ # Consumer: receive_message
111
+ elif attr == "receive_message":
112
+ q_name = ""
113
+ for kw in node.keywords:
114
+ if kw.arg == "QueueUrl" and isinstance(kw.value, ast.Constant) and isinstance(kw.value.value, str):
115
+ q_name = _extract_queue_name(kw.value.value)
116
+ if q_name:
117
+ consumers.append(
118
+ AsyncConsumerRecord(
119
+ system=AsyncMessagingSystem.AWS_SQS,
120
+ destination=q_name,
121
+ queue_id=f"aws_sqs:{q_name}",
122
+ handler_canonical_id=self.current_function,
123
+ file_path=file_path,
124
+ line=node.lineno,
125
+ handler_snippet=get_line_snippet(node.lineno),
126
+ confidence="HIGH",
127
+ metadata={"method": "receive_message"},
128
+ )
129
+ )
130
+
131
+ self.generic_visit(node)
132
+
133
+ SQSVisitor().visit(tree)
134
+
135
+ return producers, consumers
@@ -0,0 +1,161 @@
1
+ """Data models for asynchronous message queues, task dispatches, and cross-service traces.
2
+
3
+ Epistemic Invariants:
4
+ 1. Never collapse distributed message passing into a generic synchronous CALLS edge.
5
+ 2. Distinct semantic relationships:
6
+ - PRODUCES_TO_QUEUE: Producer AST call site -> Queue/Topic entity
7
+ - CONSUMES_FROM_QUEUE: Queue/Topic entity -> Consumer Worker AST handler
8
+ - STATIC_QUEUE_LINKED: Same-repository static topic match (static inference, not runtime proof)
9
+ - OTEL_DISTRIBUTED_VERIFIED: OpenTelemetry runtime-confirmed trace/span/link proof
10
+ 3. Queue identity: `f"{system}:{destination}"` (system + topic/queue name).
11
+ 4. Never persist raw message bodies or unredacted keys.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ from dataclasses import asdict, dataclass, field
16
+ from enum import StrEnum
17
+ from typing import Any
18
+
19
+
20
+ class AsyncMessagingSystem(StrEnum):
21
+ """Supported message brokers and asynchronous execution systems."""
22
+
23
+ KAFKA = "kafka"
24
+ CELERY = "celery"
25
+ AWS_SQS = "aws_sqs"
26
+ RABBITMQ = "rabbitmq"
27
+ REDIS = "redis"
28
+ GENERIC = "generic"
29
+
30
+
31
+ class AsyncQueueEvidenceClass(StrEnum):
32
+ """Epistemic evidence classification for asynchronous queue links."""
33
+
34
+ STATIC_QUEUE_LINKED = "STATIC_QUEUE_LINKED"
35
+ OTEL_DISTRIBUTED_VERIFIED = "OTEL_DISTRIBUTED_VERIFIED"
36
+
37
+
38
+ class AsyncOperationType(StrEnum):
39
+ """Operation performed on the message broker."""
40
+
41
+ PUBLISH = "publish"
42
+ CONSUME = "consume"
43
+
44
+
45
+ @dataclass(frozen=True)
46
+ class AsyncQueueEntity:
47
+ """Logical queue, topic, or task channel."""
48
+
49
+ queue_id: str # f"{system}:{destination}"
50
+ system: AsyncMessagingSystem
51
+ destination: str # topic name, queue URL, or celery task name
52
+ service: str = ""
53
+ environment: str = ""
54
+ is_dead_letter_queue: bool = False
55
+
56
+ def to_dict(self) -> dict[str, Any]:
57
+ return asdict(self)
58
+
59
+
60
+ @dataclass(frozen=True)
61
+ class AsyncProducerRecord:
62
+ """Static or runtime-observed message publisher/producer call site."""
63
+
64
+ system: AsyncMessagingSystem
65
+ destination: str
66
+ queue_id: str
67
+ caller_canonical_id: str
68
+ file_path: str
69
+ line: int
70
+ call_snippet: str
71
+ confidence: str = "HIGH"
72
+ metadata: dict[str, Any] = field(default_factory=dict)
73
+
74
+ def to_dict(self) -> dict[str, Any]:
75
+ return {
76
+ "system": str(self.system),
77
+ "destination": self.destination,
78
+ "queue_id": self.queue_id,
79
+ "caller_canonical_id": self.caller_canonical_id,
80
+ "file_path": self.file_path,
81
+ "line": self.line,
82
+ "call_snippet": self.call_snippet,
83
+ "confidence": self.confidence,
84
+ "metadata": dict(self.metadata),
85
+ }
86
+
87
+
88
+ @dataclass(frozen=True)
89
+ class AsyncConsumerRecord:
90
+ """Static or runtime-observed message subscriber/consumer worker handler."""
91
+
92
+ system: AsyncMessagingSystem
93
+ destination: str
94
+ queue_id: str
95
+ handler_canonical_id: str
96
+ file_path: str
97
+ line: int
98
+ handler_snippet: str
99
+ confidence: str = "HIGH"
100
+ metadata: dict[str, Any] = field(default_factory=dict)
101
+
102
+ def to_dict(self) -> dict[str, Any]:
103
+ return {
104
+ "system": str(self.system),
105
+ "destination": self.destination,
106
+ "queue_id": self.queue_id,
107
+ "handler_canonical_id": self.handler_canonical_id,
108
+ "file_path": self.file_path,
109
+ "line": self.line,
110
+ "handler_snippet": self.handler_snippet,
111
+ "confidence": self.confidence,
112
+ "metadata": dict(self.metadata),
113
+ }
114
+
115
+
116
+ @dataclass(frozen=True)
117
+ class AsyncQueueLink:
118
+ """Bridged relationship between a producer and a consumer across an async boundary."""
119
+
120
+ link_id: str
121
+ queue_id: str
122
+ system: AsyncMessagingSystem
123
+ destination: str
124
+ producer_id: str
125
+ producer_file: str
126
+ producer_line: int
127
+ consumer_id: str
128
+ consumer_file: str
129
+ consumer_line: int
130
+ evidence_class: AsyncQueueEvidenceClass
131
+ confidence: str = "HIGH"
132
+ trace_count: int = 1
133
+ avg_latency_ms: float = 0.0
134
+ p50_ms: float = 0.0
135
+ p95_ms: float = 0.0
136
+ p99_ms: float = 0.0
137
+ error_count: int = 0
138
+ metadata: dict[str, Any] = field(default_factory=dict)
139
+
140
+ def to_dict(self) -> dict[str, Any]:
141
+ return {
142
+ "link_id": self.link_id,
143
+ "queue_id": self.queue_id,
144
+ "system": str(self.system),
145
+ "destination": self.destination,
146
+ "producer_id": self.producer_id,
147
+ "producer_file": self.producer_file,
148
+ "producer_line": self.producer_line,
149
+ "consumer_id": self.consumer_id,
150
+ "consumer_file": self.consumer_file,
151
+ "consumer_line": self.consumer_line,
152
+ "evidence_class": str(self.evidence_class),
153
+ "confidence": self.confidence,
154
+ "trace_count": self.trace_count,
155
+ "avg_latency_ms": self.avg_latency_ms,
156
+ "p50_ms": self.p50_ms,
157
+ "p95_ms": self.p95_ms,
158
+ "p99_ms": self.p99_ms,
159
+ "error_count": self.error_count,
160
+ "metadata": dict(self.metadata),
161
+ }
@@ -0,0 +1,74 @@
1
+ """Canonical OpenTelemetry models and reconciliation classifications.
2
+
3
+ Epistemic Invariant:
4
+ 1. Static and runtime evidence are distinct:
5
+ - STATIC_QUEUE_LINKED: Same-repo static topic match.
6
+ - OTEL_DISTRIBUTED_VERIFIED: Real empirical telemetry links producer and consumer.
7
+ - RUNTIME_ONLY_OBSERVED: Observed in telemetry, but consumer or producer lives in another repository/service.
8
+ - STATIC_RUNTIME_CONFLICT: Static code points to queue X, but runtime points to queue Y or reports persistent failure.
9
+ - UNKNOWN: Inconclusive / unobserved.
10
+ 2. Zero payload retention: Never persist message bodies, tokens, or raw sensitive headers.
11
+ 3. Correlation IDs are stored strictly as salted SHA-256 digests.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ from dataclasses import asdict, dataclass, field
16
+ from enum import StrEnum
17
+ from typing import Any
18
+
19
+
20
+ class OTelEvidenceStatus(StrEnum):
21
+ """Epistemic evidence classification for static and distributed links."""
22
+
23
+ STATIC_QUEUE_LINKED = "STATIC_QUEUE_LINKED"
24
+ OTEL_DISTRIBUTED_VERIFIED = "OTEL_DISTRIBUTED_VERIFIED"
25
+ RUNTIME_ONLY_OBSERVED = "RUNTIME_ONLY_OBSERVED"
26
+ STATIC_RUNTIME_CONFLICT = "STATIC_RUNTIME_CONFLICT"
27
+ UNKNOWN = "UNKNOWN"
28
+
29
+
30
+ @dataclass(frozen=True)
31
+ class OTelSpanRecord:
32
+ """Sanitized, canonical representation of an ingested OpenTelemetry span."""
33
+
34
+ trace_id: str
35
+ span_id: str
36
+ parent_span_id: str = ""
37
+ service_name: str = ""
38
+ name: str = ""
39
+ kind: str = "INTERNAL" # PRODUCER, CONSUMER, SERVER, CLIENT, INTERNAL
40
+ start_time_unix_nano: int = 0
41
+ end_time_unix_nano: int = 0
42
+ duration_ms: float = 0.0
43
+ status_code: str = "OK" # OK, ERROR, UNSET
44
+ messaging_system: str = ""
45
+ messaging_destination: str = ""
46
+ messaging_operation: str = "" # publish, process, receive
47
+ correlation_id_hash: str = "" # Salted SHA-256 digest
48
+ attributes: dict[str, Any] = field(default_factory=dict)
49
+ links: tuple[tuple[str, str], ...] = () # ((trace_id, span_id), ...)
50
+
51
+ @property
52
+ def composite_id(self) -> str:
53
+ """Stable idempotent key for deduplication."""
54
+ return f"{self.trace_id}:{self.span_id}"
55
+
56
+ def to_dict(self) -> dict[str, Any]:
57
+ d = asdict(self)
58
+ d["links"] = [{"trace_id": t, "span_id": s} for t, s in self.links]
59
+ return d
60
+
61
+
62
+ @dataclass(frozen=True)
63
+ class TraceReconciliationResult:
64
+ """Summary of an idempotent OTel trace ingestion and reconciliation pass."""
65
+
66
+ spans_ingested: int
67
+ spans_deduplicated: int
68
+ links_upgraded: int
69
+ links_created_runtime_only: int
70
+ conflicts_detected: int
71
+ details: list[dict[str, Any]] = field(default_factory=list)
72
+
73
+ def to_dict(self) -> dict[str, Any]:
74
+ return asdict(self)