codegraph-engine 2.1.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.
- codegraph/__init__.py +37 -0
- codegraph/agent.py +26 -0
- codegraph/architecture.py +328 -0
- codegraph/audit.py +106 -0
- codegraph/cache.py +95 -0
- codegraph/cli.py +854 -0
- codegraph/config.py +43 -0
- codegraph/constraints.py +238 -0
- codegraph/context.py +1228 -0
- codegraph/epistemic.py +90 -0
- codegraph/errors.py +275 -0
- codegraph/evidence/__init__.py +15 -0
- codegraph/evidence/citations.py +397 -0
- codegraph/frameworks.py +434 -0
- codegraph/freshness.py +295 -0
- codegraph/git.py +278 -0
- codegraph/graph/__init__.py +46 -0
- codegraph/graph/models.py +41 -0
- codegraph/graph/traversal.py +1291 -0
- codegraph/indexing/__init__.py +4 -0
- codegraph/indexing/classifier.py +274 -0
- codegraph/indexing/indexer.py +943 -0
- codegraph/indexing/models.py +338 -0
- codegraph/indexing/parser.py +1240 -0
- codegraph/indexing/scanner.py +200 -0
- codegraph/indexing/test_framework.py +116 -0
- codegraph/interrogation.py +1582 -0
- codegraph/llm/__init__.py +3 -0
- codegraph/llm/base.py +15 -0
- codegraph/llm/context.py +20 -0
- codegraph/mcp/__init__.py +3 -0
- codegraph/mcp/server.py +736 -0
- codegraph/memory/__init__.py +3 -0
- codegraph/memory/store.py +46 -0
- codegraph/models.py +289 -0
- codegraph/observability.py +151 -0
- codegraph/optimizer.py +372 -0
- codegraph/planner.py +417 -0
- codegraph/py.typed +1 -0
- codegraph/query_expansion.py +199 -0
- codegraph/ranking.py +363 -0
- codegraph/resolver.py +843 -0
- codegraph/resources/__init__.py +45 -0
- codegraph/resources/cache.py +117 -0
- codegraph/resources/coalescer.py +83 -0
- codegraph/resources/debouncer.py +98 -0
- codegraph/resources/governor.py +232 -0
- codegraph/resources/policy.py +123 -0
- codegraph/retrieval_policy.py +220 -0
- codegraph/search/__init__.py +23 -0
- codegraph/search/hybrid.py +301 -0
- codegraph/search/semantic.py +28 -0
- codegraph/security/__init__.py +3 -0
- codegraph/security/paths.py +35 -0
- codegraph/target_resolver.py +348 -0
- codegraph/task.py +637 -0
- codegraph_engine-2.1.1.dist-info/METADATA +334 -0
- codegraph_engine-2.1.1.dist-info/RECORD +62 -0
- codegraph_engine-2.1.1.dist-info/WHEEL +5 -0
- codegraph_engine-2.1.1.dist-info/entry_points.txt +2 -0
- codegraph_engine-2.1.1.dist-info/licenses/LICENSE +21 -0
- codegraph_engine-2.1.1.dist-info/top_level.txt +1 -0
codegraph/epistemic.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Epistemic classification for CodeGraph findings.
|
|
2
|
+
|
|
3
|
+
Every relationship or claim is classified as:
|
|
4
|
+
FACT — parser-confirmed, source-backed.
|
|
5
|
+
INFERENCE — statically plausible but not directly confirmed.
|
|
6
|
+
UNKNOWN — not determinable from static analysis.
|
|
7
|
+
CONFLICT — two sources contradict each other.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import asdict, dataclass
|
|
12
|
+
from enum import StrEnum
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class EpistemicStatus(StrEnum):
|
|
16
|
+
FACT = "FACT"
|
|
17
|
+
ASSUMPTION = "ASSUMPTION"
|
|
18
|
+
INFERENCE = "INFERENCE"
|
|
19
|
+
UNKNOWN = "UNKNOWN"
|
|
20
|
+
CONFLICT = "CONFLICT"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ConfidenceLevel(StrEnum):
|
|
24
|
+
HIGH = "HIGH"
|
|
25
|
+
MEDIUM = "MEDIUM"
|
|
26
|
+
LOW = "LOW"
|
|
27
|
+
UNKNOWN = "UNKNOWN"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class FreshnessLevel(StrEnum):
|
|
31
|
+
FRESH = "FRESH"
|
|
32
|
+
STALE = "STALE"
|
|
33
|
+
UNKNOWN = "UNKNOWN"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class Finding:
|
|
38
|
+
status: EpistemicStatus
|
|
39
|
+
statement: str
|
|
40
|
+
file: str | None = None
|
|
41
|
+
start_line: int | None = None
|
|
42
|
+
end_line: int | None = None
|
|
43
|
+
symbol: str | None = None
|
|
44
|
+
evidence: str = ""
|
|
45
|
+
confidence: str = "HIGH" # HIGH | MEDIUM | LOW | UNKNOWN
|
|
46
|
+
freshness: str = "FRESH" # FRESH | STALE | UNKNOWN
|
|
47
|
+
|
|
48
|
+
def as_dict(self) -> dict[str, object]:
|
|
49
|
+
d = asdict(self)
|
|
50
|
+
d["status"] = self.status.value
|
|
51
|
+
return d
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def fact(
|
|
55
|
+
statement: str,
|
|
56
|
+
file: str | None = None,
|
|
57
|
+
start_line: int | None = None,
|
|
58
|
+
end_line: int | None = None,
|
|
59
|
+
symbol: str | None = None,
|
|
60
|
+
evidence: str = "",
|
|
61
|
+
confidence: str = "HIGH",
|
|
62
|
+
) -> Finding:
|
|
63
|
+
return Finding(EpistemicStatus.FACT, statement, file, start_line, end_line, symbol, evidence, confidence)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def inference(
|
|
67
|
+
statement: str,
|
|
68
|
+
file: str | None = None,
|
|
69
|
+
symbol: str | None = None,
|
|
70
|
+
evidence: str = "",
|
|
71
|
+
) -> Finding:
|
|
72
|
+
return Finding(EpistemicStatus.INFERENCE, statement, file, symbol=symbol, evidence=evidence, confidence="MEDIUM")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def unknown(statement: str, evidence: str = "") -> Finding:
|
|
76
|
+
return Finding(EpistemicStatus.UNKNOWN, statement, evidence=evidence, confidence="LOW")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def assumption(
|
|
80
|
+
statement: str,
|
|
81
|
+
file: str | None = None,
|
|
82
|
+
symbol: str | None = None,
|
|
83
|
+
evidence: str = "",
|
|
84
|
+
confidence: str = "MEDIUM",
|
|
85
|
+
) -> Finding:
|
|
86
|
+
return Finding(EpistemicStatus.ASSUMPTION, statement, file, symbol=symbol, evidence=evidence, confidence=confidence)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def conflict(statement: str, evidence: str = "") -> Finding:
|
|
90
|
+
return Finding(EpistemicStatus.CONFLICT, statement, evidence=evidence, confidence="LOW")
|
codegraph/errors.py
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"""Centralized error models, stable error codes, and structured exception handling for CodeGraph MCP.
|
|
2
|
+
|
|
3
|
+
Principle:
|
|
4
|
+
Expected operational failures must produce machine-readable, deterministic error contracts
|
|
5
|
+
with actionable next-step recovery instructions for AI agents, avoiding raw tracebacks.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from enum import StrEnum
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ErrorCode(StrEnum):
|
|
14
|
+
"""Stable, centralized machine-readable error codes for CodeGraph MCP."""
|
|
15
|
+
INDEX_NOT_FOUND = "INDEX_NOT_FOUND"
|
|
16
|
+
INDEX_STALE = "INDEX_STALE"
|
|
17
|
+
REPOSITORY_NOT_INITIALIZED = "REPOSITORY_NOT_INITIALIZED"
|
|
18
|
+
INVALID_PATH = "INVALID_PATH"
|
|
19
|
+
PATH_OUTSIDE_REPOSITORY = "PATH_OUTSIDE_REPOSITORY"
|
|
20
|
+
SYMBOL_NOT_FOUND = "SYMBOL_NOT_FOUND"
|
|
21
|
+
SYMBOL_AMBIGUOUS = "SYMBOL_AMBIGUOUS"
|
|
22
|
+
INVALID_ARGUMENT = "INVALID_ARGUMENT"
|
|
23
|
+
UNSUPPORTED_LANGUAGE = "UNSUPPORTED_LANGUAGE"
|
|
24
|
+
PARSE_FAILURE = "PARSE_FAILURE"
|
|
25
|
+
INVALID_DEPTH = "INVALID_DEPTH"
|
|
26
|
+
INVALID_MODULE = "INVALID_MODULE"
|
|
27
|
+
INTERNAL_ERROR = "INTERNAL_ERROR"
|
|
28
|
+
EMPTY_SYMBOL_NAME = "EMPTY_SYMBOL_NAME"
|
|
29
|
+
SENSITIVE_FILE_ACCESS_DENIED = "SENSITIVE_FILE_ACCESS_DENIED"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class CodeGraphError(Exception):
|
|
33
|
+
"""Base error that is safe to display to users and AI agents."""
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
message: str,
|
|
38
|
+
code: ErrorCode | str = ErrorCode.INTERNAL_ERROR,
|
|
39
|
+
next_action: dict[str, Any] | None = None,
|
|
40
|
+
) -> None:
|
|
41
|
+
super().__init__(message)
|
|
42
|
+
self.message = message
|
|
43
|
+
self.code = code.value if isinstance(code, ErrorCode) else str(code)
|
|
44
|
+
self.next_action = next_action
|
|
45
|
+
|
|
46
|
+
def to_dict(self) -> dict[str, Any]:
|
|
47
|
+
"""Convert error to machine-readable dictionary representation."""
|
|
48
|
+
data: dict[str, Any] = {
|
|
49
|
+
"code": self.code,
|
|
50
|
+
"message": self.message,
|
|
51
|
+
}
|
|
52
|
+
if self.next_action:
|
|
53
|
+
data["next_action"] = self.next_action
|
|
54
|
+
return data
|
|
55
|
+
|
|
56
|
+
def to_response(self, status: str = "error") -> dict[str, Any]:
|
|
57
|
+
"""Wrap error inside canonical machine-readable response envelope."""
|
|
58
|
+
return {
|
|
59
|
+
"status": status,
|
|
60
|
+
"error": self.to_dict(),
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class SecurityError(CodeGraphError):
|
|
65
|
+
"""A request attempted to access something outside the repository or a protected file."""
|
|
66
|
+
|
|
67
|
+
def __init__(
|
|
68
|
+
self,
|
|
69
|
+
message: str = "Access denied: path is outside repository or sensitive.",
|
|
70
|
+
code: ErrorCode | str = ErrorCode.PATH_OUTSIDE_REPOSITORY,
|
|
71
|
+
next_action: dict[str, Any] | None = None,
|
|
72
|
+
) -> None:
|
|
73
|
+
if next_action is None:
|
|
74
|
+
next_action = {
|
|
75
|
+
"command": "codegraph status",
|
|
76
|
+
"reason": "Ensure all queried paths reside within the repository root.",
|
|
77
|
+
}
|
|
78
|
+
super().__init__(message, code=code, next_action=next_action)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class NotIndexedError(CodeGraphError):
|
|
82
|
+
"""The repository has no usable CodeGraph index."""
|
|
83
|
+
|
|
84
|
+
def __init__(
|
|
85
|
+
self,
|
|
86
|
+
message: str = "No CodeGraph index exists for this repository.",
|
|
87
|
+
code: ErrorCode | str = ErrorCode.INDEX_NOT_FOUND,
|
|
88
|
+
next_action: dict[str, Any] | None = None,
|
|
89
|
+
) -> None:
|
|
90
|
+
if next_action is None:
|
|
91
|
+
next_action = {
|
|
92
|
+
"command": "codegraph init",
|
|
93
|
+
"reason": "Initialize the repository before querying it.",
|
|
94
|
+
}
|
|
95
|
+
super().__init__(message, code=code, next_action=next_action)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class IndexStaleError(CodeGraphError):
|
|
99
|
+
"""The repository changed after the current index was generated."""
|
|
100
|
+
|
|
101
|
+
def __init__(
|
|
102
|
+
self,
|
|
103
|
+
message: str = "The repository changed after the current index was generated.",
|
|
104
|
+
code: ErrorCode | str = ErrorCode.INDEX_STALE,
|
|
105
|
+
next_action: dict[str, Any] | None = None,
|
|
106
|
+
) -> None:
|
|
107
|
+
if next_action is None:
|
|
108
|
+
next_action = {
|
|
109
|
+
"command": "codegraph index",
|
|
110
|
+
"reason": "Re-index the repository to update stale evidence.",
|
|
111
|
+
}
|
|
112
|
+
super().__init__(message, code=code, next_action=next_action)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class RepositoryNotInitializedError(CodeGraphError):
|
|
116
|
+
"""The repository has not been initialized with CodeGraph configuration."""
|
|
117
|
+
|
|
118
|
+
def __init__(
|
|
119
|
+
self,
|
|
120
|
+
message: str = "Repository is not initialized for CodeGraph.",
|
|
121
|
+
code: ErrorCode | str = ErrorCode.REPOSITORY_NOT_INITIALIZED,
|
|
122
|
+
next_action: dict[str, Any] | None = None,
|
|
123
|
+
) -> None:
|
|
124
|
+
if next_action is None:
|
|
125
|
+
next_action = {
|
|
126
|
+
"command": "codegraph init",
|
|
127
|
+
"reason": "Initialize the repository with CodeGraph schema and metadata.",
|
|
128
|
+
}
|
|
129
|
+
super().__init__(message, code=code, next_action=next_action)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class InvalidPathError(CodeGraphError):
|
|
133
|
+
"""A file or directory path is invalid, malformed, or does not exist."""
|
|
134
|
+
|
|
135
|
+
def __init__(
|
|
136
|
+
self,
|
|
137
|
+
path: str,
|
|
138
|
+
message: str | None = None,
|
|
139
|
+
next_action: dict[str, Any] | None = None,
|
|
140
|
+
) -> None:
|
|
141
|
+
msg = message or f"Invalid or non-existent path: {path}"
|
|
142
|
+
if next_action is None:
|
|
143
|
+
next_action = {
|
|
144
|
+
"command": "codegraph status",
|
|
145
|
+
"reason": "Check repository files and paths.",
|
|
146
|
+
}
|
|
147
|
+
super().__init__(msg, code=ErrorCode.INVALID_PATH, next_action=next_action)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class SymbolNotFoundError(CodeGraphError):
|
|
151
|
+
"""A requested symbol cannot be found in the repository index."""
|
|
152
|
+
|
|
153
|
+
def __init__(
|
|
154
|
+
self,
|
|
155
|
+
symbol: str,
|
|
156
|
+
message: str | None = None,
|
|
157
|
+
next_action: dict[str, Any] | None = None,
|
|
158
|
+
) -> None:
|
|
159
|
+
msg = message or f"No matching symbol was found for '{symbol}'."
|
|
160
|
+
if next_action is None:
|
|
161
|
+
next_action = {
|
|
162
|
+
"command": f"codegraph search {symbol}",
|
|
163
|
+
"reason": "Search with a broader query term.",
|
|
164
|
+
}
|
|
165
|
+
super().__init__(msg, code=ErrorCode.SYMBOL_NOT_FOUND, next_action=next_action)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class SymbolAmbiguousError(CodeGraphError):
|
|
169
|
+
"""Multiple symbols match a short or un-qualified name."""
|
|
170
|
+
|
|
171
|
+
def __init__(
|
|
172
|
+
self,
|
|
173
|
+
symbol: str,
|
|
174
|
+
candidates: list[dict[str, Any]],
|
|
175
|
+
message: str | None = None,
|
|
176
|
+
next_action: dict[str, Any] | None = None,
|
|
177
|
+
) -> None:
|
|
178
|
+
msg = message or f"Multiple symbols match '{symbol}'. Provide a qualified name or canonical ID."
|
|
179
|
+
if next_action is None:
|
|
180
|
+
next_action = {
|
|
181
|
+
"command": "codegraph resolve <canonical_id>",
|
|
182
|
+
"reason": "Select an explicit canonical ID from candidates.",
|
|
183
|
+
}
|
|
184
|
+
super().__init__(msg, code=ErrorCode.SYMBOL_AMBIGUOUS, next_action=next_action)
|
|
185
|
+
self.candidates = candidates
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class InvalidArgumentError(CodeGraphError):
|
|
189
|
+
"""An interrogation or CLI argument was missing, empty, or malformed."""
|
|
190
|
+
|
|
191
|
+
def __init__(
|
|
192
|
+
self,
|
|
193
|
+
argument: str,
|
|
194
|
+
message: str | None = None,
|
|
195
|
+
next_action: dict[str, Any] | None = None,
|
|
196
|
+
) -> None:
|
|
197
|
+
msg = message or f"Invalid or empty argument: {argument}"
|
|
198
|
+
if next_action is None:
|
|
199
|
+
next_action = {
|
|
200
|
+
"command": "codegraph --help",
|
|
201
|
+
"reason": "Inspect accepted command parameters and arguments.",
|
|
202
|
+
}
|
|
203
|
+
super().__init__(msg, code=ErrorCode.INVALID_ARGUMENT, next_action=next_action)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class UnsupportedLanguageError(CodeGraphError):
|
|
207
|
+
"""The requested file language is not supported by CodeGraph AST parsers."""
|
|
208
|
+
|
|
209
|
+
def __init__(
|
|
210
|
+
self,
|
|
211
|
+
language_or_ext: str,
|
|
212
|
+
message: str | None = None,
|
|
213
|
+
next_action: dict[str, Any] | None = None,
|
|
214
|
+
) -> None:
|
|
215
|
+
msg = message or f"Unsupported source language or extension: {language_or_ext}"
|
|
216
|
+
if next_action is None:
|
|
217
|
+
next_action = {
|
|
218
|
+
"command": "codegraph doctor",
|
|
219
|
+
"reason": "Check supported languages (Python, TypeScript, JavaScript).",
|
|
220
|
+
}
|
|
221
|
+
super().__init__(msg, code=ErrorCode.UNSUPPORTED_LANGUAGE, next_action=next_action)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class ParseFailureError(CodeGraphError):
|
|
225
|
+
"""A source file failed AST parsing due to syntax errors."""
|
|
226
|
+
|
|
227
|
+
def __init__(
|
|
228
|
+
self,
|
|
229
|
+
file_path: str,
|
|
230
|
+
message: str | None = None,
|
|
231
|
+
next_action: dict[str, Any] | None = None,
|
|
232
|
+
) -> None:
|
|
233
|
+
msg = message or f"Failed to parse source file due to syntax error: {file_path}"
|
|
234
|
+
if next_action is None:
|
|
235
|
+
next_action = {
|
|
236
|
+
"command": f"python3 -m py_compile {file_path}",
|
|
237
|
+
"reason": "Fix syntax errors in the source file and re-index.",
|
|
238
|
+
}
|
|
239
|
+
super().__init__(msg, code=ErrorCode.PARSE_FAILURE, next_action=next_action)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class InvalidDepthError(CodeGraphError):
|
|
243
|
+
"""Graph traversal depth argument is out of bounds (allowed range: 1..5)."""
|
|
244
|
+
|
|
245
|
+
def __init__(
|
|
246
|
+
self,
|
|
247
|
+
depth: int,
|
|
248
|
+
message: str | None = None,
|
|
249
|
+
next_action: dict[str, Any] | None = None,
|
|
250
|
+
) -> None:
|
|
251
|
+
msg = message or f"Invalid graph traversal depth {depth}; allowed range is 1..5."
|
|
252
|
+
if next_action is None:
|
|
253
|
+
next_action = {
|
|
254
|
+
"command": "codegraph trace --depth 2 <symbol>",
|
|
255
|
+
"reason": "Specify a traversal depth between 1 and 5.",
|
|
256
|
+
}
|
|
257
|
+
super().__init__(msg, code=ErrorCode.INVALID_DEPTH, next_action=next_action)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class InvalidModuleError(CodeGraphError):
|
|
261
|
+
"""The specified module does not exist or has not been indexed."""
|
|
262
|
+
|
|
263
|
+
def __init__(
|
|
264
|
+
self,
|
|
265
|
+
module: str,
|
|
266
|
+
message: str | None = None,
|
|
267
|
+
next_action: dict[str, Any] | None = None,
|
|
268
|
+
) -> None:
|
|
269
|
+
msg = message or f"Unknown or unindexed module: '{module}'."
|
|
270
|
+
if next_action is None:
|
|
271
|
+
next_action = {
|
|
272
|
+
"command": "codegraph architecture",
|
|
273
|
+
"reason": "Inspect indexed repository modules.",
|
|
274
|
+
}
|
|
275
|
+
super().__init__(msg, code=ErrorCode.INVALID_MODULE, next_action=next_action)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from .citations import (
|
|
2
|
+
Evidence,
|
|
3
|
+
build_evidence,
|
|
4
|
+
get_repository_from_con,
|
|
5
|
+
verify_evidence,
|
|
6
|
+
verify_source_hash,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"Evidence",
|
|
11
|
+
"build_evidence",
|
|
12
|
+
"get_repository_from_con",
|
|
13
|
+
"verify_evidence",
|
|
14
|
+
"verify_source_hash",
|
|
15
|
+
]
|