cortex-loop 0.1.0a1__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.
- cortex/__init__.py +7 -0
- cortex/adapters.py +339 -0
- cortex/blocklist.py +51 -0
- cortex/challenges.py +210 -0
- cortex/cli.py +7 -0
- cortex/core.py +601 -0
- cortex/core_helpers.py +190 -0
- cortex/data/identity_preamble.md +5 -0
- cortex/data/layer1_part_a.md +65 -0
- cortex/data/layer1_part_b.md +17 -0
- cortex/executive.py +295 -0
- cortex/foundation.py +185 -0
- cortex/genome.py +348 -0
- cortex/graveyard.py +226 -0
- cortex/hooks/__init__.py +27 -0
- cortex/hooks/_shared.py +167 -0
- cortex/hooks/post_tool_use.py +13 -0
- cortex/hooks/pre_tool_use.py +13 -0
- cortex/hooks/session_start.py +13 -0
- cortex/hooks/stop.py +13 -0
- cortex/invariants.py +258 -0
- cortex/packs.py +118 -0
- cortex/repomap.py +6 -0
- cortex/requirements.py +497 -0
- cortex/retry.py +312 -0
- cortex/stop_contract.py +217 -0
- cortex/stop_payload.py +122 -0
- cortex/stop_policy.py +100 -0
- cortex/stop_runtime.py +400 -0
- cortex/stop_signals.py +75 -0
- cortex/store.py +793 -0
- cortex/templates/__init__.py +10 -0
- cortex/utils.py +58 -0
- cortex_loop-0.1.0a1.dist-info/METADATA +121 -0
- cortex_loop-0.1.0a1.dist-info/RECORD +52 -0
- cortex_loop-0.1.0a1.dist-info/WHEEL +5 -0
- cortex_loop-0.1.0a1.dist-info/entry_points.txt +3 -0
- cortex_loop-0.1.0a1.dist-info/licenses/LICENSE +21 -0
- cortex_loop-0.1.0a1.dist-info/top_level.txt +3 -0
- cortex_ops_cli/__init__.py +3 -0
- cortex_ops_cli/_adapter_validation.py +119 -0
- cortex_ops_cli/_check_report.py +454 -0
- cortex_ops_cli/_check_report_output.py +270 -0
- cortex_ops_cli/_openai_bridge_probe.py +241 -0
- cortex_ops_cli/_openai_bridge_protocol.py +469 -0
- cortex_ops_cli/_runtime_profile_templates.py +341 -0
- cortex_ops_cli/_runtime_profiles.py +445 -0
- cortex_ops_cli/gemini_hooks.py +301 -0
- cortex_ops_cli/main.py +911 -0
- cortex_ops_cli/openai_app_server_bridge.py +375 -0
- cortex_repomap/__init__.py +1 -0
- cortex_repomap/engine.py +1201 -0
cortex/invariants.py
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import importlib.util
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Iterable
|
|
12
|
+
|
|
13
|
+
from .genome import HooksConfig, InvariantsConfig
|
|
14
|
+
from .store import SQLiteStore
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(slots=True)
|
|
18
|
+
class InvariantCaseResult:
|
|
19
|
+
test_path: str
|
|
20
|
+
status: str
|
|
21
|
+
duration_ms: int
|
|
22
|
+
stdout: str
|
|
23
|
+
stderr: str
|
|
24
|
+
|
|
25
|
+
def to_dict(self) -> dict[str, Any]:
|
|
26
|
+
return {
|
|
27
|
+
"test_path": self.test_path,
|
|
28
|
+
"status": self.status,
|
|
29
|
+
"duration_ms": self.duration_ms,
|
|
30
|
+
"stdout": self.stdout,
|
|
31
|
+
"stderr": self.stderr,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(slots=True)
|
|
36
|
+
class InvariantReport:
|
|
37
|
+
configured_paths: list[str]
|
|
38
|
+
results: list[InvariantCaseResult] = field(default_factory=list)
|
|
39
|
+
diagnostics: list[dict[str, Any]] = field(default_factory=list)
|
|
40
|
+
ok: bool = True
|
|
41
|
+
had_errors: bool = False
|
|
42
|
+
recommend_revert: bool = False
|
|
43
|
+
|
|
44
|
+
def to_dict(self) -> dict[str, Any]:
|
|
45
|
+
return {
|
|
46
|
+
"configured_paths": self.configured_paths,
|
|
47
|
+
"results": [r.to_dict() for r in self.results],
|
|
48
|
+
"diagnostics": self.diagnostics,
|
|
49
|
+
"ok": self.ok,
|
|
50
|
+
"had_errors": self.had_errors,
|
|
51
|
+
"recommend_revert": self.recommend_revert,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class InvariantRunner:
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
repo_root: Path,
|
|
59
|
+
store: SQLiteStore,
|
|
60
|
+
config: InvariantsConfig,
|
|
61
|
+
hooks_config: HooksConfig,
|
|
62
|
+
*,
|
|
63
|
+
trust_profile: str = "trusted",
|
|
64
|
+
) -> None:
|
|
65
|
+
self.repo_root = repo_root.resolve()
|
|
66
|
+
self.store = store
|
|
67
|
+
self.config = config
|
|
68
|
+
self.hooks_config = hooks_config
|
|
69
|
+
self.trust_profile = trust_profile if trust_profile in {"trusted", "untrusted"} else "untrusted"
|
|
70
|
+
|
|
71
|
+
def run(self, session_id: str, extra_pytest_args: Iterable[str] | None = None) -> InvariantReport:
|
|
72
|
+
report = InvariantReport(configured_paths=list(self.config.suite_paths))
|
|
73
|
+
args = list(extra_pytest_args or [])
|
|
74
|
+
if self.trust_profile == "untrusted" and self.config.execution_mode == "host":
|
|
75
|
+
result = InvariantCaseResult(
|
|
76
|
+
test_path="__policy__",
|
|
77
|
+
status="error",
|
|
78
|
+
duration_ms=0,
|
|
79
|
+
stdout="",
|
|
80
|
+
stderr=(
|
|
81
|
+
"Host invariant execution is blocked for trust_profile='untrusted'. "
|
|
82
|
+
"Set [invariants].execution_mode='container' or [project].trust_profile='trusted'."
|
|
83
|
+
),
|
|
84
|
+
)
|
|
85
|
+
report.results.append(result)
|
|
86
|
+
report.diagnostics.append(_invariant_diagnostic(result))
|
|
87
|
+
report.ok = False
|
|
88
|
+
report.had_errors = True
|
|
89
|
+
self.store.record_invariant_result(
|
|
90
|
+
session_id=session_id,
|
|
91
|
+
test_path=result.test_path,
|
|
92
|
+
status=result.status,
|
|
93
|
+
duration_ms=result.duration_ms,
|
|
94
|
+
stdout=result.stdout,
|
|
95
|
+
stderr=result.stderr,
|
|
96
|
+
)
|
|
97
|
+
report.recommend_revert = self.hooks_config.recommend_revert_on_invariant_failure
|
|
98
|
+
return report
|
|
99
|
+
|
|
100
|
+
for suite_path in self.config.suite_paths:
|
|
101
|
+
result = self._run_one(session_id=session_id, suite_path=suite_path, extra_args=args)
|
|
102
|
+
report.results.append(result)
|
|
103
|
+
if result.status in {"fail", "error", "missing"}:
|
|
104
|
+
report.diagnostics.append(_invariant_diagnostic(result))
|
|
105
|
+
self.store.record_invariant_result(
|
|
106
|
+
session_id=session_id,
|
|
107
|
+
test_path=result.test_path,
|
|
108
|
+
status=result.status,
|
|
109
|
+
duration_ms=result.duration_ms,
|
|
110
|
+
stdout=result.stdout,
|
|
111
|
+
stderr=result.stderr,
|
|
112
|
+
)
|
|
113
|
+
if result.status in {"fail", "error", "missing"}:
|
|
114
|
+
report.ok = False
|
|
115
|
+
if result.status == "error":
|
|
116
|
+
report.had_errors = True
|
|
117
|
+
|
|
118
|
+
if not report.ok:
|
|
119
|
+
report.recommend_revert = self.hooks_config.recommend_revert_on_invariant_failure
|
|
120
|
+
return report
|
|
121
|
+
|
|
122
|
+
def promote_session_test(self, session_id: str, source_path: str | Path) -> Path:
|
|
123
|
+
source = (self.repo_root / source_path).resolve() if not Path(source_path).is_absolute() else Path(source_path)
|
|
124
|
+
target_dir = self.repo_root / self.config.graduation.target_dir
|
|
125
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
126
|
+
target = target_dir / source.name
|
|
127
|
+
shutil.copy2(source, target)
|
|
128
|
+
self.store.record_invariant_result(
|
|
129
|
+
session_id=session_id,
|
|
130
|
+
test_path=str(target.relative_to(self.repo_root)),
|
|
131
|
+
status="graduated",
|
|
132
|
+
duration_ms=0,
|
|
133
|
+
stdout="",
|
|
134
|
+
stderr="",
|
|
135
|
+
graduated_from=str(source),
|
|
136
|
+
)
|
|
137
|
+
return target
|
|
138
|
+
|
|
139
|
+
def _run_one(self, *, session_id: str, suite_path: str, extra_args: list[str]) -> InvariantCaseResult:
|
|
140
|
+
path = self.repo_root / suite_path
|
|
141
|
+
if not path.exists():
|
|
142
|
+
return InvariantCaseResult(
|
|
143
|
+
test_path=suite_path,
|
|
144
|
+
status="missing",
|
|
145
|
+
duration_ms=0,
|
|
146
|
+
stdout="",
|
|
147
|
+
stderr=f"Invariant path not found: {suite_path}",
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
started = time.perf_counter()
|
|
151
|
+
try:
|
|
152
|
+
cmd = self._pytest_command(path=path, suite_path=suite_path, extra_args=extra_args, session_id=session_id)
|
|
153
|
+
except ValueError as exc:
|
|
154
|
+
return InvariantCaseResult(
|
|
155
|
+
test_path=suite_path,
|
|
156
|
+
status="error",
|
|
157
|
+
duration_ms=int((time.perf_counter() - started) * 1000),
|
|
158
|
+
stdout="",
|
|
159
|
+
stderr=str(exc),
|
|
160
|
+
)
|
|
161
|
+
env = None
|
|
162
|
+
if self.config.execution_mode != "container":
|
|
163
|
+
env = os.environ.copy()
|
|
164
|
+
env["CORTEX_SESSION_ID"] = str(session_id)
|
|
165
|
+
env["CORTEX_PROJECT_ROOT"] = str(self.repo_root)
|
|
166
|
+
try:
|
|
167
|
+
proc = subprocess.run(
|
|
168
|
+
cmd,
|
|
169
|
+
cwd=self.repo_root,
|
|
170
|
+
env=env,
|
|
171
|
+
capture_output=True,
|
|
172
|
+
text=True,
|
|
173
|
+
check=False,
|
|
174
|
+
)
|
|
175
|
+
except FileNotFoundError as exc:
|
|
176
|
+
return InvariantCaseResult(
|
|
177
|
+
test_path=suite_path,
|
|
178
|
+
status="error",
|
|
179
|
+
duration_ms=int((time.perf_counter() - started) * 1000),
|
|
180
|
+
stdout="",
|
|
181
|
+
stderr=str(exc),
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
duration_ms = int((time.perf_counter() - started) * 1000)
|
|
185
|
+
status = "pass" if proc.returncode == 0 else "fail"
|
|
186
|
+
return InvariantCaseResult(
|
|
187
|
+
test_path=suite_path,
|
|
188
|
+
status=status,
|
|
189
|
+
duration_ms=duration_ms,
|
|
190
|
+
stdout=proc.stdout.strip(),
|
|
191
|
+
stderr=proc.stderr.strip(),
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def _pytest_command(
|
|
195
|
+
self,
|
|
196
|
+
*,
|
|
197
|
+
path: Path,
|
|
198
|
+
suite_path: str,
|
|
199
|
+
extra_args: list[str],
|
|
200
|
+
session_id: str,
|
|
201
|
+
) -> list[str]:
|
|
202
|
+
if self.config.execution_mode != "container":
|
|
203
|
+
return self._host_pytest_command(path=path, extra_args=extra_args)
|
|
204
|
+
target = self._container_suite_path(path, suite_path)
|
|
205
|
+
return [
|
|
206
|
+
self.config.container_engine,
|
|
207
|
+
"run",
|
|
208
|
+
"--rm",
|
|
209
|
+
"-e",
|
|
210
|
+
f"CORTEX_SESSION_ID={session_id}",
|
|
211
|
+
"-e",
|
|
212
|
+
f"CORTEX_PROJECT_ROOT={self.config.container_workdir}",
|
|
213
|
+
"-v",
|
|
214
|
+
f"{self.repo_root}:{self.config.container_workdir}",
|
|
215
|
+
"-w",
|
|
216
|
+
self.config.container_workdir,
|
|
217
|
+
self.config.container_image,
|
|
218
|
+
"python",
|
|
219
|
+
"-m",
|
|
220
|
+
"pytest",
|
|
221
|
+
target,
|
|
222
|
+
*extra_args,
|
|
223
|
+
]
|
|
224
|
+
|
|
225
|
+
def _host_pytest_command(self, *, path: Path, extra_args: list[str]) -> list[str]:
|
|
226
|
+
configured = str(self.config.pytest_bin).strip() or "pytest"
|
|
227
|
+
configured_path = Path(configured).expanduser()
|
|
228
|
+
if configured_path.is_absolute() and configured_path.exists():
|
|
229
|
+
return [str(configured_path), str(path), *extra_args]
|
|
230
|
+
if any(sep in configured for sep in ("/", "\\")):
|
|
231
|
+
repo_relative = (self.repo_root / configured_path).resolve()
|
|
232
|
+
if repo_relative.exists():
|
|
233
|
+
return [str(repo_relative), str(path), *extra_args]
|
|
234
|
+
if configured_path.exists():
|
|
235
|
+
return [str(configured_path.resolve()), str(path), *extra_args]
|
|
236
|
+
elif shutil.which(configured):
|
|
237
|
+
return [configured, str(path), *extra_args]
|
|
238
|
+
if importlib.util.find_spec("pytest") is not None:
|
|
239
|
+
return [sys.executable, "-m", "pytest", str(path), *extra_args]
|
|
240
|
+
raise ValueError(
|
|
241
|
+
f"Configured pytest_bin '{configured}' is unavailable and fallback 'python -m pytest' is not installed."
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
def _container_suite_path(self, path: Path, suite_path: str) -> str:
|
|
245
|
+
try:
|
|
246
|
+
return str(path.resolve().relative_to(self.repo_root))
|
|
247
|
+
except ValueError as exc:
|
|
248
|
+
raise ValueError(
|
|
249
|
+
f"Container invariant path is outside repo root: {path.resolve()} (root: {self.repo_root})"
|
|
250
|
+
) from exc
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _invariant_diagnostic(result: InvariantCaseResult) -> dict[str, Any]:
|
|
254
|
+
if result.status == "missing":
|
|
255
|
+
return {"evidence_found": [result.test_path], "evidence_expected": ["configured invariant test path to exist"], "gap_description": f"Invariant path '{result.test_path}' was missing.", "gap_characterization": "comprehension_gap", "distance_signal": "far"}
|
|
256
|
+
if result.status == "error":
|
|
257
|
+
return {"evidence_found": [result.stderr[:200]] if result.stderr else [result.test_path], "evidence_expected": [f"invariant '{result.test_path}' to run successfully"], "gap_description": f"Invariant '{result.test_path}' could not execute cleanly.", "gap_characterization": "execution_gap", "distance_signal": "moderate"}
|
|
258
|
+
return {"evidence_found": [result.test_path], "evidence_expected": [f"invariant '{result.test_path}' to pass"], "gap_description": f"Invariant '{result.test_path}' failed.", "gap_characterization": "execution_gap", "distance_signal": "close"}
|
cortex/packs.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import tomllib
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
PACK_SCHEMA_VERSION = "cortex_pack_v1"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(slots=True)
|
|
12
|
+
class PackOverlay:
|
|
13
|
+
challenge_categories: list[str] = field(default_factory=list)
|
|
14
|
+
invariant_paths: list[str] = field(default_factory=list)
|
|
15
|
+
blocked_tools: list[str] = field(default_factory=list)
|
|
16
|
+
allowed_tools: list[str] = field(default_factory=list)
|
|
17
|
+
warnings: list[str] = field(default_factory=list)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def load_pack_overlay(*, root: Path, pack_paths: list[str]) -> PackOverlay:
|
|
21
|
+
overlay = PackOverlay()
|
|
22
|
+
for raw_path in pack_paths:
|
|
23
|
+
declared_path = str(raw_path).strip()
|
|
24
|
+
if not declared_path:
|
|
25
|
+
continue
|
|
26
|
+
manifest_path = (
|
|
27
|
+
Path(declared_path).expanduser()
|
|
28
|
+
if Path(declared_path).is_absolute()
|
|
29
|
+
else (root / declared_path).expanduser()
|
|
30
|
+
).resolve()
|
|
31
|
+
if not manifest_path.exists():
|
|
32
|
+
overlay.warnings.append(f"Pack manifest not found: {declared_path}")
|
|
33
|
+
continue
|
|
34
|
+
try:
|
|
35
|
+
with manifest_path.open("rb") as fh:
|
|
36
|
+
payload = tomllib.load(fh)
|
|
37
|
+
except Exception as exc: # noqa: BLE001
|
|
38
|
+
overlay.warnings.append(f"Pack manifest parse error in {manifest_path}: {exc}")
|
|
39
|
+
continue
|
|
40
|
+
if not isinstance(payload, dict):
|
|
41
|
+
overlay.warnings.append(f"Pack manifest must be a TOML table: {manifest_path}")
|
|
42
|
+
continue
|
|
43
|
+
|
|
44
|
+
schema = str(payload.get("schema") or "").strip()
|
|
45
|
+
if schema != PACK_SCHEMA_VERSION:
|
|
46
|
+
overlay.warnings.append(
|
|
47
|
+
f"Unsupported pack schema in {manifest_path}: {schema or 'missing'} "
|
|
48
|
+
f"(expected {PACK_SCHEMA_VERSION})"
|
|
49
|
+
)
|
|
50
|
+
continue
|
|
51
|
+
|
|
52
|
+
pack_id = str(payload.get("pack_id") or manifest_path.stem).strip() or manifest_path.stem
|
|
53
|
+
categories = _as_str_list(payload.get("challenge_categories"))
|
|
54
|
+
invariants = _as_str_list(payload.get("invariant_paths"))
|
|
55
|
+
blocked_tools = _as_str_list(payload.get("blocked_tools"))
|
|
56
|
+
allowed_tools = _as_str_list(payload.get("allowed_tools"))
|
|
57
|
+
if not (categories or invariants or blocked_tools or allowed_tools):
|
|
58
|
+
overlay.warnings.append(f"Pack '{pack_id}' declares no challenges/invariants/blocklist entries.")
|
|
59
|
+
continue
|
|
60
|
+
|
|
61
|
+
overlay.challenge_categories.extend(categories)
|
|
62
|
+
overlay.invariant_paths.extend(
|
|
63
|
+
_resolve_invariant_paths(
|
|
64
|
+
repo_root=root,
|
|
65
|
+
manifest_dir=manifest_path.parent,
|
|
66
|
+
invariant_paths=invariants,
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
overlay.blocked_tools.extend(blocked_tools)
|
|
70
|
+
overlay.allowed_tools.extend(allowed_tools)
|
|
71
|
+
|
|
72
|
+
overlay.challenge_categories = _unique_nonempty(overlay.challenge_categories)
|
|
73
|
+
overlay.invariant_paths = _unique_nonempty(overlay.invariant_paths)
|
|
74
|
+
overlay.blocked_tools = _unique_nonempty(overlay.blocked_tools)
|
|
75
|
+
overlay.allowed_tools = _unique_nonempty(overlay.allowed_tools)
|
|
76
|
+
overlay.warnings = _unique_nonempty(overlay.warnings)
|
|
77
|
+
return overlay
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _resolve_invariant_paths(
|
|
81
|
+
*,
|
|
82
|
+
repo_root: Path,
|
|
83
|
+
manifest_dir: Path,
|
|
84
|
+
invariant_paths: list[str],
|
|
85
|
+
) -> list[str]:
|
|
86
|
+
resolved: list[str] = []
|
|
87
|
+
for value in invariant_paths:
|
|
88
|
+
path = Path(value)
|
|
89
|
+
if path.is_absolute():
|
|
90
|
+
candidate = path.resolve()
|
|
91
|
+
else:
|
|
92
|
+
# Prefer repo-root-relative paths so pack manifests can be dropped into a project
|
|
93
|
+
# without requiring path rewrites for test suites that already use repo-relative layout.
|
|
94
|
+
root_candidate = (repo_root / path).resolve()
|
|
95
|
+
manifest_candidate = (manifest_dir / path).resolve()
|
|
96
|
+
candidate = root_candidate if (root_candidate.exists() or not manifest_candidate.exists()) else manifest_candidate
|
|
97
|
+
try:
|
|
98
|
+
candidate = candidate.relative_to(repo_root)
|
|
99
|
+
except ValueError:
|
|
100
|
+
pass
|
|
101
|
+
resolved.append(candidate.as_posix() if isinstance(candidate, Path) else str(candidate))
|
|
102
|
+
return resolved
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _as_str_list(value: Any) -> list[str]:
|
|
106
|
+
return [str(v).strip() for v in value] if isinstance(value, list) else []
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _unique_nonempty(values: list[str]) -> list[str]:
|
|
110
|
+
seen: set[str] = set()
|
|
111
|
+
result: list[str] = []
|
|
112
|
+
for item in values:
|
|
113
|
+
token = str(item).strip()
|
|
114
|
+
if not token or token in seen:
|
|
115
|
+
continue
|
|
116
|
+
seen.add(token)
|
|
117
|
+
result.append(token)
|
|
118
|
+
return result
|
cortex/repomap.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from importlib import import_module
|
|
3
|
+
from types import ModuleType
|
|
4
|
+
|
|
5
|
+
_IMPL = import_module("cortex_repomap.engine")
|
|
6
|
+
sys.modules[__name__].__class__ = type("_ShimModule", (ModuleType,), {"__getattr__": lambda self, name: getattr(_IMPL, name), "__setattr__": lambda self, name, value: (setattr(_IMPL, name, value), ModuleType.__setattr__(self, name, value))[1]})
|