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.
Files changed (62) hide show
  1. codegraph/__init__.py +37 -0
  2. codegraph/agent.py +26 -0
  3. codegraph/architecture.py +328 -0
  4. codegraph/audit.py +106 -0
  5. codegraph/cache.py +95 -0
  6. codegraph/cli.py +854 -0
  7. codegraph/config.py +43 -0
  8. codegraph/constraints.py +238 -0
  9. codegraph/context.py +1228 -0
  10. codegraph/epistemic.py +90 -0
  11. codegraph/errors.py +275 -0
  12. codegraph/evidence/__init__.py +15 -0
  13. codegraph/evidence/citations.py +397 -0
  14. codegraph/frameworks.py +434 -0
  15. codegraph/freshness.py +295 -0
  16. codegraph/git.py +278 -0
  17. codegraph/graph/__init__.py +46 -0
  18. codegraph/graph/models.py +41 -0
  19. codegraph/graph/traversal.py +1291 -0
  20. codegraph/indexing/__init__.py +4 -0
  21. codegraph/indexing/classifier.py +274 -0
  22. codegraph/indexing/indexer.py +943 -0
  23. codegraph/indexing/models.py +338 -0
  24. codegraph/indexing/parser.py +1240 -0
  25. codegraph/indexing/scanner.py +200 -0
  26. codegraph/indexing/test_framework.py +116 -0
  27. codegraph/interrogation.py +1582 -0
  28. codegraph/llm/__init__.py +3 -0
  29. codegraph/llm/base.py +15 -0
  30. codegraph/llm/context.py +20 -0
  31. codegraph/mcp/__init__.py +3 -0
  32. codegraph/mcp/server.py +736 -0
  33. codegraph/memory/__init__.py +3 -0
  34. codegraph/memory/store.py +46 -0
  35. codegraph/models.py +289 -0
  36. codegraph/observability.py +151 -0
  37. codegraph/optimizer.py +372 -0
  38. codegraph/planner.py +417 -0
  39. codegraph/py.typed +1 -0
  40. codegraph/query_expansion.py +199 -0
  41. codegraph/ranking.py +363 -0
  42. codegraph/resolver.py +843 -0
  43. codegraph/resources/__init__.py +45 -0
  44. codegraph/resources/cache.py +117 -0
  45. codegraph/resources/coalescer.py +83 -0
  46. codegraph/resources/debouncer.py +98 -0
  47. codegraph/resources/governor.py +232 -0
  48. codegraph/resources/policy.py +123 -0
  49. codegraph/retrieval_policy.py +220 -0
  50. codegraph/search/__init__.py +23 -0
  51. codegraph/search/hybrid.py +301 -0
  52. codegraph/search/semantic.py +28 -0
  53. codegraph/security/__init__.py +3 -0
  54. codegraph/security/paths.py +35 -0
  55. codegraph/target_resolver.py +348 -0
  56. codegraph/task.py +637 -0
  57. codegraph_engine-2.1.1.dist-info/METADATA +334 -0
  58. codegraph_engine-2.1.1.dist-info/RECORD +62 -0
  59. codegraph_engine-2.1.1.dist-info/WHEEL +5 -0
  60. codegraph_engine-2.1.1.dist-info/entry_points.txt +2 -0
  61. codegraph_engine-2.1.1.dist-info/licenses/LICENSE +21 -0
  62. 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
+ )
@@ -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