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/config.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Settings(BaseModel):
|
|
10
|
+
"""Runtime settings. Environment values deliberately never include API keys."""
|
|
11
|
+
|
|
12
|
+
repository: Path | None = None
|
|
13
|
+
db_path: Path | None = None
|
|
14
|
+
max_file_size: int = Field(default=1_000_000, ge=1)
|
|
15
|
+
max_results: int = Field(default=10, ge=1, le=100)
|
|
16
|
+
max_context: int = Field(default=20_000, ge=100)
|
|
17
|
+
max_tool_calls: int = Field(default=12, ge=1)
|
|
18
|
+
exclude: tuple[str, ...] = ()
|
|
19
|
+
resource_profile: str = "BALANCED"
|
|
20
|
+
max_interactive_workers: int = Field(default=2, ge=1)
|
|
21
|
+
max_background_workers: int = Field(default=1, ge=0)
|
|
22
|
+
max_memory_mb: int = Field(default=1024, ge=128)
|
|
23
|
+
max_graph_nodes: int = Field(default=300, ge=10)
|
|
24
|
+
max_graph_edges: int = Field(default=1200, ge=10)
|
|
25
|
+
max_git_commits: int = Field(default=15, ge=1)
|
|
26
|
+
background_indexing: bool = True
|
|
27
|
+
debounce_ms: int = Field(default=500, ge=50)
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def from_env(cls) -> Settings:
|
|
31
|
+
repo = os.getenv("CODEGRAPH_REPOSITORY")
|
|
32
|
+
db = os.getenv("CODEGRAPH_DB_PATH")
|
|
33
|
+
return cls(
|
|
34
|
+
repository=Path(repo) if repo else None,
|
|
35
|
+
db_path=Path(db) if db else None,
|
|
36
|
+
max_file_size=int(os.getenv("CODEGRAPH_MAX_FILE_SIZE", "1000000")),
|
|
37
|
+
max_context=int(os.getenv("CODEGRAPH_MAX_CONTEXT", "20000")),
|
|
38
|
+
max_tool_calls=int(os.getenv("CODEGRAPH_MAX_TOOL_CALLS", "12")),
|
|
39
|
+
resource_profile=os.getenv("CODEGRAPH_RESOURCE_PROFILE", "BALANCED"),
|
|
40
|
+
max_memory_mb=int(os.getenv("CODEGRAPH_MAX_MEMORY_MB", "1024")),
|
|
41
|
+
background_indexing=os.getenv("CODEGRAPH_BACKGROUND_INDEXING", "true").lower() in ("true", "1"),
|
|
42
|
+
debounce_ms=int(os.getenv("CODEGRAPH_DEBOUNCE_MS", "500")),
|
|
43
|
+
)
|
codegraph/constraints.py
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"""Constraint Guard for CodeGraph MCP v2.1.
|
|
2
|
+
|
|
3
|
+
Enforces deterministic hard constraints across every retrieval and compilation stage:
|
|
4
|
+
- Explicit excluded symbols, hierarchical descendants, and canonical IDs
|
|
5
|
+
- Explicit excluded files, directories, and path-component matching
|
|
6
|
+
- Excluded modules and namespaces
|
|
7
|
+
- Scope paths and scope modules
|
|
8
|
+
- Repository and security boundary protection
|
|
9
|
+
|
|
10
|
+
Architectural Invariant:
|
|
11
|
+
Hard constraints are HARD FILTERS, never ranking penalties.
|
|
12
|
+
No entity violating a hard constraint may participate in candidate generation,
|
|
13
|
+
graph traversal, ranking, coverage, or appear in the final ContextPacket.
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from pathlib import PurePosixPath
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _normalize_path(p: str | None) -> str:
|
|
23
|
+
if not p:
|
|
24
|
+
return ""
|
|
25
|
+
norm = p.replace("\\", "/").strip()
|
|
26
|
+
while norm.startswith("./"):
|
|
27
|
+
norm = norm[2:]
|
|
28
|
+
return norm.strip("/")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _is_hierarchical_symbol_match(sym: str, excl: str) -> bool:
|
|
32
|
+
"""Return True if `sym` matches `excl` or is a hierarchical descendant of `excl`.
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
- excl="AuthService", sym="AuthService" -> True
|
|
36
|
+
- excl="AuthService", sym="AuthService.login" -> True
|
|
37
|
+
- excl="AuthService", sym="auth.AuthService.login" -> True
|
|
38
|
+
- excl="AuthService", sym="AuthServiceHelper" -> False (distinct symbol)
|
|
39
|
+
- excl="AuthService", sym="UserAuthService" -> False (distinct symbol)
|
|
40
|
+
"""
|
|
41
|
+
if not sym or not excl:
|
|
42
|
+
return False
|
|
43
|
+
if sym == excl:
|
|
44
|
+
return True
|
|
45
|
+
if sym.startswith(f"{excl}.") or sym.startswith(f"{excl}:"):
|
|
46
|
+
return True
|
|
47
|
+
|
|
48
|
+
# Check dot-separated hierarchy components
|
|
49
|
+
parts = sym.split(".")
|
|
50
|
+
for part in parts:
|
|
51
|
+
if part == excl:
|
|
52
|
+
return True
|
|
53
|
+
|
|
54
|
+
# Check colon-separated hierarchy components (e.g. file:Class.method)
|
|
55
|
+
col_parts = sym.split(":")
|
|
56
|
+
for part in col_parts:
|
|
57
|
+
if part == excl or part.startswith(f"{excl}."):
|
|
58
|
+
return True
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True)
|
|
63
|
+
class ConstraintGuard:
|
|
64
|
+
"""Central hard-constraint enforcement guard."""
|
|
65
|
+
|
|
66
|
+
excluded_symbols: frozenset[str] = field(default_factory=frozenset)
|
|
67
|
+
excluded_files: frozenset[str] = field(default_factory=frozenset)
|
|
68
|
+
excluded_modules: frozenset[str] = field(default_factory=frozenset)
|
|
69
|
+
scope_paths: frozenset[str] = field(default_factory=frozenset)
|
|
70
|
+
scope_modules: frozenset[str] = field(default_factory=frozenset)
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def from_task_spec(cls, task_spec: Any) -> ConstraintGuard:
|
|
74
|
+
"""Construct a ConstraintGuard from a TaskSpec or dictionary."""
|
|
75
|
+
if not task_spec:
|
|
76
|
+
return cls()
|
|
77
|
+
|
|
78
|
+
excl_syms: set[str] = set()
|
|
79
|
+
excl_files: set[str] = set()
|
|
80
|
+
excl_mods: set[str] = set()
|
|
81
|
+
|
|
82
|
+
raw_exclusions = getattr(task_spec, "exclusions", ()) or ()
|
|
83
|
+
if isinstance(raw_exclusions, (list, tuple, set)):
|
|
84
|
+
for item in raw_exclusions:
|
|
85
|
+
if not item:
|
|
86
|
+
continue
|
|
87
|
+
s = str(item).strip()
|
|
88
|
+
if "/" in s or "\\" in s or s.endswith(".py") or s.endswith(".ts") or s.endswith(".js"):
|
|
89
|
+
excl_files.add(_normalize_path(s))
|
|
90
|
+
else:
|
|
91
|
+
excl_syms.add(s)
|
|
92
|
+
|
|
93
|
+
raw_scope_paths = getattr(task_spec, "scope_paths", ()) or ()
|
|
94
|
+
scope_p = {_normalize_path(str(p)) for p in raw_scope_paths if p}
|
|
95
|
+
|
|
96
|
+
raw_scope_mods = getattr(task_spec, "scope_modules", ()) or ()
|
|
97
|
+
scope_m = {str(m).strip() for m in raw_scope_mods if m}
|
|
98
|
+
|
|
99
|
+
return cls(
|
|
100
|
+
excluded_symbols=frozenset(excl_syms),
|
|
101
|
+
excluded_files=frozenset(excl_files),
|
|
102
|
+
excluded_modules=frozenset(excl_mods),
|
|
103
|
+
scope_paths=frozenset(scope_p),
|
|
104
|
+
scope_modules=frozenset(scope_m),
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
def allows_symbol(self, symbol: str | None) -> bool:
|
|
108
|
+
"""Check if symbol is permitted under exclusion and scope constraints."""
|
|
109
|
+
if not symbol:
|
|
110
|
+
return True
|
|
111
|
+
s = symbol.strip()
|
|
112
|
+
for excl in self.excluded_symbols:
|
|
113
|
+
if _is_hierarchical_symbol_match(s, excl):
|
|
114
|
+
return False
|
|
115
|
+
return True
|
|
116
|
+
|
|
117
|
+
def allows_canonical_id(self, canonical_id: str | None) -> bool:
|
|
118
|
+
"""Check if canonical symbol ID is permitted."""
|
|
119
|
+
if not canonical_id:
|
|
120
|
+
return True
|
|
121
|
+
cid = canonical_id.strip()
|
|
122
|
+
if not self.allows_symbol(cid):
|
|
123
|
+
return False
|
|
124
|
+
# If canonical_id encodes a file path, verify file is allowed
|
|
125
|
+
if ":" in cid:
|
|
126
|
+
path_part = cid.split(":", 1)[0]
|
|
127
|
+
if "/" in path_part or path_part.endswith((".py", ".ts", ".js")):
|
|
128
|
+
if not self.allows_file(path_part):
|
|
129
|
+
return False
|
|
130
|
+
return True
|
|
131
|
+
|
|
132
|
+
def allows_file(self, file_path: str | None) -> bool:
|
|
133
|
+
"""Check if file path is permitted under exclusion, path-component, and scope constraints."""
|
|
134
|
+
if not file_path:
|
|
135
|
+
return True
|
|
136
|
+
path_norm = _normalize_path(file_path)
|
|
137
|
+
|
|
138
|
+
# 1. Security check: reject path traversal
|
|
139
|
+
path_obj = PurePosixPath(path_norm)
|
|
140
|
+
if ".." in path_obj.parts:
|
|
141
|
+
return False
|
|
142
|
+
|
|
143
|
+
# 2. Hard exclusions
|
|
144
|
+
for excl in self.excluded_files:
|
|
145
|
+
excl_norm = _normalize_path(excl)
|
|
146
|
+
if not excl_norm:
|
|
147
|
+
continue
|
|
148
|
+
# Exact path match
|
|
149
|
+
if path_norm == excl_norm:
|
|
150
|
+
return False
|
|
151
|
+
# Directory prefix match (e.g. excl="src/admin_panel", path="src/admin_panel/views.py")
|
|
152
|
+
if path_norm.startswith(f"{excl_norm}/"):
|
|
153
|
+
return False
|
|
154
|
+
# Suffix / component match if exclusion is just a filename
|
|
155
|
+
if "/" not in excl_norm and path_obj.name == excl_norm:
|
|
156
|
+
return False
|
|
157
|
+
|
|
158
|
+
# 3. Scope constraints (if scope paths specified, path must match at least one)
|
|
159
|
+
if self.scope_paths:
|
|
160
|
+
in_scope = False
|
|
161
|
+
for sp in self.scope_paths:
|
|
162
|
+
if path_norm == sp or path_norm.startswith(f"{sp}/"):
|
|
163
|
+
in_scope = True
|
|
164
|
+
break
|
|
165
|
+
if not in_scope:
|
|
166
|
+
return False
|
|
167
|
+
|
|
168
|
+
return True
|
|
169
|
+
|
|
170
|
+
def allows_module(self, module_name: str | None) -> bool:
|
|
171
|
+
"""Check if module is permitted."""
|
|
172
|
+
if not module_name:
|
|
173
|
+
return True
|
|
174
|
+
m = module_name.strip()
|
|
175
|
+
for excl in self.excluded_modules:
|
|
176
|
+
if m == excl or m.startswith(f"{excl}."):
|
|
177
|
+
return False
|
|
178
|
+
if self.scope_modules:
|
|
179
|
+
in_scope = False
|
|
180
|
+
for sm in self.scope_modules:
|
|
181
|
+
if m == sm or m.startswith(f"{sm}."):
|
|
182
|
+
in_scope = True
|
|
183
|
+
break
|
|
184
|
+
if not in_scope:
|
|
185
|
+
return False
|
|
186
|
+
return True
|
|
187
|
+
|
|
188
|
+
def allows_relationship(
|
|
189
|
+
self,
|
|
190
|
+
source_id: str | None,
|
|
191
|
+
rel_type: str | None,
|
|
192
|
+
target_id: str | None,
|
|
193
|
+
) -> bool:
|
|
194
|
+
"""Check if relationship endpoints are permitted."""
|
|
195
|
+
if source_id and not self.allows_canonical_id(source_id) and not self.allows_symbol(source_id):
|
|
196
|
+
return False
|
|
197
|
+
if target_id and not self.allows_canonical_id(target_id) and not self.allows_symbol(target_id):
|
|
198
|
+
return False
|
|
199
|
+
return True
|
|
200
|
+
|
|
201
|
+
def allows_test(
|
|
202
|
+
self,
|
|
203
|
+
test_file: str | None,
|
|
204
|
+
test_symbol: str | None = None,
|
|
205
|
+
) -> bool:
|
|
206
|
+
"""Check if test artifact is permitted."""
|
|
207
|
+
if test_file and not self.allows_file(test_file):
|
|
208
|
+
return False
|
|
209
|
+
if test_symbol and not self.allows_symbol(test_symbol):
|
|
210
|
+
return False
|
|
211
|
+
return True
|
|
212
|
+
|
|
213
|
+
def allows_endpoint(
|
|
214
|
+
self,
|
|
215
|
+
route_path: str | None,
|
|
216
|
+
handler_name: str | None = None,
|
|
217
|
+
file_path: str | None = None,
|
|
218
|
+
) -> bool:
|
|
219
|
+
"""Check if framework route is permitted."""
|
|
220
|
+
if file_path and not self.allows_file(file_path):
|
|
221
|
+
return False
|
|
222
|
+
if handler_name and not self.allows_symbol(handler_name):
|
|
223
|
+
return False
|
|
224
|
+
if route_path and route_path in self.excluded_symbols:
|
|
225
|
+
return False
|
|
226
|
+
return True
|
|
227
|
+
|
|
228
|
+
def allows_evidence(
|
|
229
|
+
self,
|
|
230
|
+
file_path: str | None,
|
|
231
|
+
symbol: str | None = None,
|
|
232
|
+
) -> bool:
|
|
233
|
+
"""Check if evidence item is permitted."""
|
|
234
|
+
if file_path and not self.allows_file(file_path):
|
|
235
|
+
return False
|
|
236
|
+
if symbol and not self.allows_symbol(symbol):
|
|
237
|
+
return False
|
|
238
|
+
return True
|