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.
Files changed (52) hide show
  1. cortex/__init__.py +7 -0
  2. cortex/adapters.py +339 -0
  3. cortex/blocklist.py +51 -0
  4. cortex/challenges.py +210 -0
  5. cortex/cli.py +7 -0
  6. cortex/core.py +601 -0
  7. cortex/core_helpers.py +190 -0
  8. cortex/data/identity_preamble.md +5 -0
  9. cortex/data/layer1_part_a.md +65 -0
  10. cortex/data/layer1_part_b.md +17 -0
  11. cortex/executive.py +295 -0
  12. cortex/foundation.py +185 -0
  13. cortex/genome.py +348 -0
  14. cortex/graveyard.py +226 -0
  15. cortex/hooks/__init__.py +27 -0
  16. cortex/hooks/_shared.py +167 -0
  17. cortex/hooks/post_tool_use.py +13 -0
  18. cortex/hooks/pre_tool_use.py +13 -0
  19. cortex/hooks/session_start.py +13 -0
  20. cortex/hooks/stop.py +13 -0
  21. cortex/invariants.py +258 -0
  22. cortex/packs.py +118 -0
  23. cortex/repomap.py +6 -0
  24. cortex/requirements.py +497 -0
  25. cortex/retry.py +312 -0
  26. cortex/stop_contract.py +217 -0
  27. cortex/stop_payload.py +122 -0
  28. cortex/stop_policy.py +100 -0
  29. cortex/stop_runtime.py +400 -0
  30. cortex/stop_signals.py +75 -0
  31. cortex/store.py +793 -0
  32. cortex/templates/__init__.py +10 -0
  33. cortex/utils.py +58 -0
  34. cortex_loop-0.1.0a1.dist-info/METADATA +121 -0
  35. cortex_loop-0.1.0a1.dist-info/RECORD +52 -0
  36. cortex_loop-0.1.0a1.dist-info/WHEEL +5 -0
  37. cortex_loop-0.1.0a1.dist-info/entry_points.txt +3 -0
  38. cortex_loop-0.1.0a1.dist-info/licenses/LICENSE +21 -0
  39. cortex_loop-0.1.0a1.dist-info/top_level.txt +3 -0
  40. cortex_ops_cli/__init__.py +3 -0
  41. cortex_ops_cli/_adapter_validation.py +119 -0
  42. cortex_ops_cli/_check_report.py +454 -0
  43. cortex_ops_cli/_check_report_output.py +270 -0
  44. cortex_ops_cli/_openai_bridge_probe.py +241 -0
  45. cortex_ops_cli/_openai_bridge_protocol.py +469 -0
  46. cortex_ops_cli/_runtime_profile_templates.py +341 -0
  47. cortex_ops_cli/_runtime_profiles.py +445 -0
  48. cortex_ops_cli/gemini_hooks.py +301 -0
  49. cortex_ops_cli/main.py +911 -0
  50. cortex_ops_cli/openai_app_server_bridge.py +375 -0
  51. cortex_repomap/__init__.py +1 -0
  52. cortex_repomap/engine.py +1201 -0
@@ -0,0 +1,454 @@
1
+ from __future__ import annotations
2
+
3
+ import importlib.util
4
+ import shutil
5
+ import sqlite3
6
+ import subprocess
7
+ import tomllib
8
+ from collections.abc import Callable
9
+ from datetime import datetime, timezone
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ from cortex import __version__
14
+ from cortex.adapters import load_adapter
15
+ from cortex.genome import GENOME_SCHEMA_VERSION, load_genome
16
+ from cortex.store import DB_SCHEMA_VERSION
17
+ from cortex_ops_cli._runtime_profiles import (
18
+ CLAUDE_ADAPTER_ALIASES,
19
+ CLAUDE_DIRNAME,
20
+ GEMINI_ADAPTER_ALIASES,
21
+ OPENAI_ADAPTER_ALIASES,
22
+ resolve_claude_settings_path,
23
+ resolve_gemini_settings_path,
24
+ resolve_openai_bridge_profile_path,
25
+ validate_claude_settings,
26
+ validate_gemini_settings,
27
+ validate_openai_bridge_profile,
28
+ )
29
+
30
+ REQUIRED_DB_TABLES = {"sessions", "graveyard", "invariants", "challenge_results", "events"}
31
+ DEFAULT_REPOMAP_SESSION_START_TIMEOUT_MS = 2500
32
+
33
+
34
+ def collect_check_report(
35
+ root: Path,
36
+ *,
37
+ repomap_dependency_status: Callable[[], list[str]] | None = None,
38
+ repomap_parser_dependency_status: Callable[[], list[str]] | None = None,
39
+ which: Callable[[str], str | None] = shutil.which,
40
+ find_spec: Callable[[str], object | None] = importlib.util.find_spec,
41
+ run_exec: Callable[..., Any] = subprocess.run,
42
+ ) -> dict[str, Any]:
43
+ ok: list[str] = []
44
+ warn: list[str] = []
45
+ err: list[str] = []
46
+ config_path = root / "cortex.toml"
47
+ db_path = root / ".cortex" / "cortex.db"
48
+ check_data: dict[str, Any] = {
49
+ "root": str(root),
50
+ "generated_at": _now_iso(),
51
+ "cortex_version": __version__,
52
+ "config_schema_version": GENOME_SCHEMA_VERSION,
53
+ "config": {"path": str(config_path), "exists": config_path.exists(), "parse_error": None},
54
+ "db": {
55
+ "path": str(db_path),
56
+ "exists": db_path.exists(),
57
+ "tables": [],
58
+ "missing_tables": [],
59
+ "schema_version": 0,
60
+ "expected_schema_version": DB_SCHEMA_VERSION,
61
+ },
62
+ "hooks": {"settings_path": None, "legacy_settings_path": None, "valid": False},
63
+ "runtime": {
64
+ "adapter_path": None,
65
+ "adapter_valid": False,
66
+ "profile": None,
67
+ },
68
+ "invariants": {"paths": [], "missing_paths": []},
69
+ "repomap": {
70
+ "enabled": None,
71
+ "prefer_ast_graph": None,
72
+ "parity_profile": None,
73
+ "deps_missing": [],
74
+ "parser_deps_missing": [],
75
+ },
76
+ "packs": {"temporal_protocol": {"phase_gate_patterns": []}},
77
+ "ok": ok,
78
+ "warnings": warn,
79
+ "errors": err,
80
+ }
81
+
82
+ repomap_dependency_status = repomap_dependency_status or _repomap_dependency_status
83
+ repomap_parser_dependency_status = (
84
+ repomap_parser_dependency_status or _repomap_parser_dependency_status
85
+ )
86
+
87
+ genome = None
88
+ if not config_path.exists():
89
+ err.append(f"Missing config file: {config_path}")
90
+ else:
91
+ genome = load_genome(config_path)
92
+ check_data["config"]["parse_error"] = genome.parse_error
93
+ if genome.parse_error:
94
+ err.append(f"Config parse error in {config_path}: {genome.parse_error}")
95
+ else:
96
+ ok.append(f"Config parsed: {config_path}")
97
+ adapter_path = str(genome.runtime.adapter).strip()
98
+ check_data["runtime"]["adapter_path"] = adapter_path or None
99
+ check_data["packs"]["temporal_protocol"] = pack_temporal_protocol_summary(
100
+ root=root,
101
+ pack_paths=list(genome.packs.paths),
102
+ )
103
+ if not adapter_path:
104
+ err.append(
105
+ "Missing runtime adapter config: set [runtime].adapter = "
106
+ '"module.path:ClassName" (for example cortex.adapters.claude:ClaudeAdapter '
107
+ "or cortex.adapters.gemini:GeminiAdapter)."
108
+ )
109
+ else:
110
+ try:
111
+ load_adapter(adapter_path)
112
+ except Exception as exc: # noqa: BLE001
113
+ err.append(f"Runtime adapter load failed ({adapter_path}): {exc}")
114
+ else:
115
+ ok.append(f"Runtime adapter loaded: {adapter_path}")
116
+ check_data["runtime"]["adapter_valid"] = True
117
+
118
+ if not db_path.exists():
119
+ err.append(f"Missing database file: {db_path}")
120
+ else:
121
+ db_tables, db_schema_version, db_error = _inspect_db(db_path)
122
+ check_data["db"]["tables"] = sorted(db_tables)
123
+ check_data["db"]["schema_version"] = db_schema_version
124
+ if db_error:
125
+ err.append(f"Database check failed: {db_error}")
126
+ else:
127
+ missing_tables = sorted(REQUIRED_DB_TABLES - db_tables)
128
+ check_data["db"]["missing_tables"] = missing_tables
129
+ if missing_tables:
130
+ err.append("Database missing required tables: " + ", ".join(missing_tables))
131
+ else:
132
+ ok.append(f"Database ready: {db_path} (required tables present)")
133
+ if db_schema_version != DB_SCHEMA_VERSION:
134
+ warn.append(
135
+ "Database schema version mismatch: "
136
+ f"found {db_schema_version}, expected {DB_SCHEMA_VERSION} "
137
+ "(run `cortex init-db --root <project>` to refresh metadata)"
138
+ )
139
+
140
+ if genome and not genome.parse_error:
141
+ check_data["invariants"]["paths"] = list(genome.invariants.suite_paths)
142
+ check_data["repomap"]["enabled"] = genome.repomap.enabled
143
+ check_data["repomap"]["prefer_ast_graph"] = genome.repomap.prefer_ast_graph
144
+ check_data["repomap"]["parity_profile"] = genome.repomap.parity_profile
145
+ _append_check_policy_warnings(genome=genome, warn=warn)
146
+
147
+ if genome.invariants.suite_paths:
148
+ for suite_path in genome.invariants.suite_paths:
149
+ full_path = root / suite_path
150
+ if not full_path.exists():
151
+ check_data["invariants"]["missing_paths"].append(suite_path)
152
+ warn.append(f"Invariant path missing (warning only): {suite_path}")
153
+ if not check_data["invariants"]["missing_paths"]:
154
+ ok.append(f"Invariant paths present: {len(genome.invariants.suite_paths)} configured")
155
+ else:
156
+ warn.append("No invariant suite paths configured in cortex.toml")
157
+ _check_invariant_execution_mode(
158
+ genome=genome,
159
+ root=root,
160
+ ok=ok,
161
+ warn=warn,
162
+ err=err,
163
+ which=which,
164
+ find_spec=find_spec,
165
+ )
166
+
167
+ if genome.repomap.enabled:
168
+ missing = repomap_dependency_status() if genome.repomap.prefer_ast_graph else []
169
+ parser_missing = (
170
+ repomap_parser_dependency_status() if genome.repomap.prefer_ast_graph else []
171
+ )
172
+ check_data["repomap"]["deps_missing"] = list(missing)
173
+ check_data["repomap"]["parser_deps_missing"] = list(parser_missing)
174
+ if genome.repomap.prefer_ast_graph and missing:
175
+ warn.append(
176
+ "Repo-map enabled; optional ranking dependencies missing "
177
+ "(AST graph + lightweight fallback remain available): "
178
+ + ", ".join(missing)
179
+ + " (install with: pip install -e '.[repomap]')"
180
+ )
181
+ elif genome.repomap.prefer_ast_graph:
182
+ ok.append("Repo-map AST parser/ranking dependencies available")
183
+ else:
184
+ ok.append("Repo-map configured for heuristic-only mode (prefer_ast_graph=false)")
185
+ if genome.repomap.prefer_ast_graph and parser_missing:
186
+ warn.append(
187
+ "Repo-map tree-sitter parser dependencies missing: "
188
+ + ", ".join(parser_missing)
189
+ + " (install with: pip install -e '.[repomap]')"
190
+ )
191
+ elif genome.repomap.prefer_ast_graph:
192
+ ok.append("Repo-map tree-sitter parser dependencies available")
193
+ if genome.repomap.parity_profile and parser_missing:
194
+ warn.append(
195
+ "Repo-map parity profile is enabled but parser dependencies are missing; "
196
+ "parity runs will fail until dependencies are installed"
197
+ )
198
+ repomap_artifact = root / genome.repomap.artifact_path
199
+ check_data["repomap"]["artifact_path"] = str(repomap_artifact)
200
+ check_data["repomap"]["artifact_exists"] = repomap_artifact.exists()
201
+ if repomap_artifact.exists():
202
+ ok.append(f"Repo-map artifact present: {repomap_artifact}")
203
+ else:
204
+ warn.append(
205
+ "Repo-map artifact missing (warning only): "
206
+ f"{genome.repomap.artifact_path} (run `cortex repomap --root {root}`)"
207
+ )
208
+ else:
209
+ warn.append(
210
+ "Repo-map is disabled in cortex.toml (set [repomap].enabled = true to test it)"
211
+ )
212
+
213
+ adapter_path = str(check_data["runtime"]["adapter_path"] or "")
214
+ if adapter_path in CLAUDE_ADAPTER_ALIASES and bool(check_data["runtime"]["adapter_valid"]):
215
+ check_data["runtime"]["profile"] = "claude"
216
+ settings_path, legacy_settings_path = resolve_claude_settings_path(root)
217
+ check_data["hooks"]["settings_path"] = str(settings_path) if settings_path else None
218
+ check_data["hooks"]["legacy_settings_path"] = (
219
+ str(legacy_settings_path) if legacy_settings_path and legacy_settings_path.exists() else None
220
+ )
221
+ if settings_path is None:
222
+ warn.append(
223
+ f"Claude runtime profile settings not found at {root / CLAUDE_DIRNAME / 'settings.json'}; "
224
+ f"run `cortex runtime install --profile claude --root {root}` for hook wiring"
225
+ )
226
+ else:
227
+ settings_errors, settings_warnings = validate_claude_settings(settings_path)
228
+ err.extend(settings_errors)
229
+ warn.extend(settings_warnings)
230
+ if not settings_errors:
231
+ check_data["hooks"]["valid"] = True
232
+ ok.append(f"Runtime profile wiring found in {settings_path}")
233
+ if legacy_settings_path and legacy_settings_path.exists():
234
+ warn.append(
235
+ "Legacy runtime settings also present at "
236
+ f"{legacy_settings_path}; prefer {root / CLAUDE_DIRNAME / 'settings.json'}"
237
+ )
238
+ elif adapter_path in GEMINI_ADAPTER_ALIASES and bool(check_data["runtime"]["adapter_valid"]):
239
+ check_data["runtime"]["profile"] = "gemini"
240
+ settings_path = resolve_gemini_settings_path(root)
241
+ check_data["hooks"]["settings_path"] = str(settings_path) if settings_path else None
242
+ check_data["hooks"]["legacy_settings_path"] = None
243
+ if settings_path is None:
244
+ err.append(
245
+ "No Gemini settings file found. Run cortex runtime install --profile gemini to create one."
246
+ )
247
+ else:
248
+ settings_errors, settings_warnings = validate_gemini_settings(settings_path)
249
+ err.extend(settings_errors)
250
+ warn.extend(settings_warnings)
251
+ if not settings_errors:
252
+ check_data["hooks"]["valid"] = True
253
+ ok.append(f"Runtime profile wiring found in {settings_path}")
254
+ elif adapter_path in OPENAI_ADAPTER_ALIASES and bool(check_data["runtime"]["adapter_valid"]):
255
+ check_data["runtime"]["profile"] = "openai"
256
+ settings_path = resolve_openai_bridge_profile_path(root)
257
+ check_data["hooks"]["settings_path"] = str(settings_path) if settings_path else None
258
+ check_data["hooks"]["legacy_settings_path"] = None
259
+ if settings_path is None:
260
+ err.append(
261
+ "No OpenAI bridge profile found. Run cortex runtime install --profile openai to create one."
262
+ )
263
+ else:
264
+ settings_errors, settings_warnings = validate_openai_bridge_profile(
265
+ settings_path,
266
+ which=which,
267
+ run_exec=run_exec,
268
+ )
269
+ err.extend(settings_errors)
270
+ warn.extend(settings_warnings)
271
+ if not settings_errors:
272
+ check_data["hooks"]["valid"] = True
273
+ ok.append(f"Runtime profile wiring found in {settings_path}")
274
+ elif adapter_path:
275
+ warn.append(
276
+ f"No runtime profile validator implemented for adapter '{adapter_path}' "
277
+ "(kernel checks still active)."
278
+ )
279
+
280
+ check_data["summary"] = {"ok": len(ok), "warnings": len(warn), "errors": len(err)}
281
+ check_data["status"] = "ok" if not err else "error"
282
+ return check_data
283
+ def pack_temporal_protocol_summary(*, root: Path, pack_paths: list[str]) -> dict[str, list[str]]:
284
+ patterns: list[str] = []
285
+ for raw_path in pack_paths:
286
+ path_token = str(raw_path).strip()
287
+ if not path_token:
288
+ continue
289
+ manifest = (
290
+ Path(path_token) if Path(path_token).is_absolute() else (root / path_token)
291
+ ).expanduser().resolve()
292
+ if not manifest.exists():
293
+ continue
294
+ try:
295
+ with manifest.open("rb") as fh:
296
+ payload = tomllib.load(fh)
297
+ except Exception: # noqa: BLE001
298
+ continue
299
+ if not isinstance(payload, dict):
300
+ continue
301
+ temporal = payload.get("temporal_protocol")
302
+ if not isinstance(temporal, dict):
303
+ continue
304
+ pattern = str(temporal.get("phase_gate_pattern") or "").strip()
305
+ if pattern:
306
+ patterns.append(pattern)
307
+ seen: set[str] = set()
308
+ deduped: list[str] = []
309
+ for pattern in patterns:
310
+ if pattern in seen:
311
+ continue
312
+ seen.add(pattern)
313
+ deduped.append(pattern)
314
+ return {"phase_gate_patterns": deduped}
315
+
316
+
317
+ def _append_check_policy_warnings(*, genome: Any, warn: list[str]) -> None:
318
+ warning_rules = (
319
+ (
320
+ bool(genome.repomap.run_on_session_start),
321
+ "repomap.run_on_session_start=true is deprecated/no-op under magic-kernel policy; "
322
+ "use `cortex repomap` for explicit artifact generation",
323
+ ),
324
+ (
325
+ genome.repomap.session_start_timeout_ms != DEFAULT_REPOMAP_SESSION_START_TIMEOUT_MS,
326
+ "repomap.session_start_timeout_ms is ignored because SessionStart repo-map execution "
327
+ "is deprecated/no-op; use `cortex repomap --timeout-ms ...` when needed",
328
+ ),
329
+ (
330
+ genome.hooks.mode != "strict"
331
+ and genome.hooks.fail_on_missing_challenge_coverage
332
+ and genome.challenges.require_coverage,
333
+ "hooks.fail_on_missing_challenge_coverage=true has no blocking effect while "
334
+ "hooks.mode='advisory'; set [hooks].mode='strict' to enforce missing challenge "
335
+ "coverage as a real gate",
336
+ ),
337
+ (
338
+ genome.hooks.require_structured_stop_payload
339
+ and genome.hooks.allow_message_stop_fallback,
340
+ "hooks.require_structured_stop_payload=true while "
341
+ "hooks.allow_message_stop_fallback=true; set allow_message_stop_fallback=false "
342
+ "for fully deterministic structured stop gating",
343
+ ),
344
+ )
345
+ for enabled, message in warning_rules:
346
+ if enabled:
347
+ warn.append(message)
348
+
349
+
350
+ def _inspect_db(db_path: Path) -> tuple[set[str], int, str | None]:
351
+ try:
352
+ conn = sqlite3.connect(db_path)
353
+ try:
354
+ rows = conn.execute("SELECT name FROM sqlite_master WHERE type = 'table'").fetchall()
355
+ user_version_row = conn.execute("PRAGMA user_version").fetchone()
356
+ finally:
357
+ conn.close()
358
+ except sqlite3.Error as exc:
359
+ return set(), 0, str(exc)
360
+ user_version = int(user_version_row[0]) if user_version_row else 0
361
+ return {str(row[0]) for row in rows}, user_version, None
362
+
363
+
364
+ def _check_invariant_execution_mode(
365
+ *,
366
+ genome: Any,
367
+ root: Path,
368
+ ok: list[str],
369
+ warn: list[str],
370
+ err: list[str],
371
+ which: Callable[[str], str | None],
372
+ find_spec: Callable[[str], object | None],
373
+ ) -> None:
374
+ trust_profile = str(
375
+ getattr(getattr(genome, "project", object()), "trust_profile", "trusted")
376
+ ).strip().lower()
377
+ if trust_profile not in {"trusted", "untrusted"}:
378
+ err.append(
379
+ "project.trust_profile must be 'trusted' or 'untrusted'; "
380
+ f"got {trust_profile or '<empty>'!r}"
381
+ )
382
+ trust_profile = "untrusted"
383
+ if genome.invariants.execution_mode == "host":
384
+ if trust_profile == "untrusted":
385
+ err.append(
386
+ "invariants.execution_mode='host' is blocked when "
387
+ "project.trust_profile='untrusted'; set execution_mode='container' "
388
+ "or explicitly set trust_profile='trusted'"
389
+ )
390
+ return
391
+ pytest_bin = str(genome.invariants.pytest_bin).strip() or "pytest"
392
+ pytest_path = Path(pytest_bin).expanduser()
393
+ has_configured_bin = False
394
+ if pytest_path.is_absolute():
395
+ has_configured_bin = pytest_path.exists()
396
+ elif any(sep in pytest_bin for sep in ("/", "\\")):
397
+ has_configured_bin = (root / pytest_path).exists() or pytest_path.exists()
398
+ else:
399
+ has_configured_bin = which(pytest_bin) is not None
400
+
401
+ if has_configured_bin:
402
+ ok.append(f"Invariant host pytest runner available: {pytest_bin}")
403
+ elif find_spec("pytest") is not None:
404
+ warn.append(
405
+ f"invariants.pytest_bin='{pytest_bin}' is unavailable; host mode will fallback "
406
+ "to 'python -m pytest' when invariants run"
407
+ )
408
+ else:
409
+ err.append(
410
+ f"invariants.execution_mode='host' but pytest is unavailable "
411
+ f"(configured pytest_bin='{pytest_bin}' not found and python -m pytest not installed)"
412
+ )
413
+ warn.append(
414
+ "invariants.execution_mode='host' runs tests on the host machine; use only for "
415
+ "trusted repositories (switch to execution_mode='container' for stronger isolation)"
416
+ )
417
+ return
418
+ engine = genome.invariants.container_engine.strip()
419
+ if not engine:
420
+ message = (
421
+ "invariants.execution_mode='container' but container_engine is empty; "
422
+ "set [invariants].container_engine (for example 'docker') or switch "
423
+ "to execution_mode='host'"
424
+ )
425
+ if trust_profile == "untrusted":
426
+ err.append(message)
427
+ else:
428
+ warn.append(message)
429
+ return
430
+ if which(engine): # pragma: no branch
431
+ ok.append(f"Invariant container engine available: {engine}")
432
+ return
433
+ message = (
434
+ f"invariants.execution_mode='container' but container engine '{engine}' is not on PATH; "
435
+ f"install '{engine}' or switch to execution_mode='host'"
436
+ )
437
+ if trust_profile == "untrusted":
438
+ err.append(message)
439
+ else:
440
+ warn.append(message)
441
+
442
+
443
+ def _repomap_dependency_status() -> list[str]:
444
+ from cortex.repomap import repomap_missing_dependencies
445
+
446
+ return repomap_missing_dependencies()
447
+
448
+
449
+ def _repomap_parser_dependency_status() -> list[str]:
450
+ from cortex.repomap import repomap_missing_parser_dependencies
451
+
452
+ return repomap_missing_parser_dependencies()
453
+ def _now_iso() -> str:
454
+ return datetime.now(timezone.utc).isoformat()