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
cortex_ops_cli/main.py ADDED
@@ -0,0 +1,911 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import importlib.util
5
+ import json
6
+ import shutil
7
+ import sqlite3
8
+ import subprocess
9
+ import sys
10
+ from datetime import datetime, timezone
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+ from cortex import __version__
15
+ from cortex.core import CortexKernel
16
+ from cortex.executive import consolidate_event, effective_weight
17
+ from cortex.genome import load_genome
18
+ from cortex.store import SQLiteStore
19
+ from cortex_ops_cli._check_report import (
20
+ collect_check_report,
21
+ pack_temporal_protocol_summary,
22
+ )
23
+ from cortex_ops_cli._check_report_output import (
24
+ build_kernel_metrics_payload,
25
+ print_check_report,
26
+ print_fleet_report,
27
+ write_status_artifact,
28
+ )
29
+ from cortex_ops_cli._runtime_profiles import (
30
+ CLAUDE_PINNED_HOOK_SCHEMA,
31
+ OPENAI_BRIDGE_COMMAND_PATTERN,
32
+ OPENAI_BRIDGE_SCHEMA_VERSION,
33
+ runtime_profile_install_spec,
34
+ set_runtime_adapter,
35
+ )
36
+
37
+ __all__ = [
38
+ "CLAUDE_PINNED_HOOK_SCHEMA",
39
+ "OPENAI_BRIDGE_COMMAND_PATTERN",
40
+ "OPENAI_BRIDGE_SCHEMA_VERSION",
41
+ "main",
42
+ ]
43
+
44
+
45
+ def main(argv: list[str] | None = None) -> int:
46
+ parser = _build_parser()
47
+ args = parser.parse_args(argv)
48
+ handlers = {
49
+ "init": _run_init,
50
+ "check": _run_check,
51
+ "fleet": _run_fleet,
52
+ "graveyard": _run_graveyard,
53
+ "memory": _run_memory,
54
+ "repomap": _run_repomap,
55
+ "runtime": _run_runtime,
56
+ "init-db": _run_init_db,
57
+ "session-events": _run_session_events,
58
+ "show-genome": _run_show_genome,
59
+ "hook": _run_hook,
60
+ }
61
+ handler = handlers.get(args.command)
62
+ if handler is None:
63
+ parser.print_help()
64
+ return 0
65
+ return handler(args)
66
+
67
+
68
+ def _build_parser() -> argparse.ArgumentParser:
69
+ parser = argparse.ArgumentParser(prog="cortex", description="Cortex hook kernel CLI")
70
+ parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
71
+ subparsers = parser.add_subparsers(dest="command")
72
+ init = subparsers.add_parser("init", help="Bootstrap a new project with cortex.toml and storage")
73
+ init.add_argument("--root", default=".", help="Target project root (default: current directory)")
74
+ init.add_argument(
75
+ "--force",
76
+ action="store_true",
77
+ help="Overwrite an existing cortex.toml in the target root",
78
+ )
79
+
80
+ check = subparsers.add_parser("check", help="Validate Cortex setup in the current project")
81
+ check.add_argument("--root", default=".", help="Project root to validate (default: current directory)")
82
+ check.add_argument("--json", action="store_true", help="Emit machine-readable JSON report")
83
+ check.add_argument(
84
+ "--kernel-metrics",
85
+ action="store_true",
86
+ help="Include non-gating stop-path kernel metrics in check output",
87
+ )
88
+ check.add_argument(
89
+ "--kernel-metrics-baseline",
90
+ help="Optional path to a prior kernel metrics baseline JSON for non-gating diff output",
91
+ )
92
+ check.add_argument(
93
+ "--write-kernel-metrics-baseline",
94
+ help="Optional path to write the current kernel metrics baseline JSON (must stay under --root)",
95
+ )
96
+ check.add_argument(
97
+ "--write-status",
98
+ action="store_true",
99
+ help="Write latest check report to .cortex/status.json",
100
+ )
101
+
102
+ fleet = subparsers.add_parser("fleet", help="Fleet-level Cortex operations")
103
+ fleet_subparsers = fleet.add_subparsers(dest="fleet_command")
104
+ fleet_status = fleet_subparsers.add_parser(
105
+ "status", help="Check multiple project roots and summarize Cortex readiness"
106
+ )
107
+ fleet_status.add_argument(
108
+ "--roots",
109
+ nargs="+",
110
+ required=True,
111
+ help="One or more project roots to inspect",
112
+ )
113
+ fleet_status.add_argument("--json", action="store_true", help="Emit machine-readable JSON output")
114
+
115
+ graveyard = subparsers.add_parser("graveyard", help="List graveyard entries from .cortex/cortex.db")
116
+ graveyard.add_argument("--root", default=".", help="Project root (default: current directory)")
117
+ graveyard.add_argument("--limit", type=int, default=20, help="Max entries to show (default: 20)")
118
+
119
+ memory = subparsers.add_parser("memory", help="Manage executive memory entries")
120
+ memory_subparsers = memory.add_subparsers(dest="memory_command")
121
+ memory_export = memory_subparsers.add_parser("export", help="Export executive memory to JSON")
122
+ memory_export.add_argument("--root", default=".", help="Project root (default: current directory)")
123
+ memory_export.add_argument("--output", required=True, help="Output JSON file path")
124
+ memory_export.add_argument("--min-strength", type=int, default=1, help="Minimum strength filter")
125
+
126
+ memory_import = memory_subparsers.add_parser("import", help="Import executive memory from JSON")
127
+ memory_import.add_argument("input", help="Path to export JSON")
128
+ memory_import.add_argument("--root", default=".", help="Project root (default: current directory)")
129
+
130
+ memory_list = memory_subparsers.add_parser("list", help="List executive memory entries")
131
+ memory_list.add_argument("--root", default=".", help="Project root (default: current directory)")
132
+ memory_list.add_argument(
133
+ "--include-decayed",
134
+ action="store_true",
135
+ help="Include entries below decay threshold",
136
+ )
137
+
138
+ memory_delete = memory_subparsers.add_parser("delete", help="Delete executive memory entry by id")
139
+ memory_delete.add_argument("entry_id", help="Entry id")
140
+ memory_delete.add_argument("--root", default=".", help="Project root (default: current directory)")
141
+
142
+ repomap = subparsers.add_parser("repomap", help="Generate or inspect a repo-map artifact")
143
+ repomap.add_argument("--root", default=".", help="Project root (default: current directory)")
144
+ repomap.add_argument("--config-path", help="Override cortex.toml path")
145
+ repomap_output = repomap.add_mutually_exclusive_group()
146
+ repomap_output.add_argument(
147
+ "--json", action="store_true", help="Print pure repo-map artifact JSON (repomap_artifact_v1)"
148
+ )
149
+ repomap_output.add_argument(
150
+ "--debug-json", action="store_true", help="Print CLI/debug JSON envelope (artifact + metadata)"
151
+ )
152
+ repomap.add_argument("--output", help="Override artifact output path")
153
+ repomap.add_argument("--stdout-text", action="store_true", help="Print text summary to stdout (when implemented)")
154
+ repomap.add_argument("--scope", action="append", default=[], help="Limit scan scope path (repeatable)")
155
+ repomap.add_argument("--focus-file", action="append", default=[], help="Prioritize a file path (repeatable)")
156
+ repomap.add_argument("--max-files", type=int, help="Override max ranked files for this run")
157
+ repomap.add_argument("--max-text-bytes", type=int, help="Override text render byte cap for this run")
158
+ repomap.add_argument(
159
+ "--parity-profile",
160
+ action="store_true",
161
+ help="Enforce parity profile: require tree-sitter parser deps and tree-sitter-backed parsing.",
162
+ )
163
+ repomap.add_argument(
164
+ "--timeout-ms",
165
+ type=int,
166
+ help="Optional timeout for this CLI run (milliseconds). Omit for no CLI timeout.",
167
+ )
168
+
169
+ init_db = subparsers.add_parser("init-db", help="Initialize .cortex/cortex.db")
170
+ init_db.add_argument("--root", default=".", help="Repository root (default: current directory)")
171
+
172
+ session_events = subparsers.add_parser("session-events", help="Read session event stream as JSON")
173
+ session_events.add_argument("--root", default=".", help="Repository root (default: current directory)")
174
+ session_events.add_argument("--session-id", required=True, help="Session id to read")
175
+ session_events.add_argument("--hooks", nargs="+", help="Optional hook filter list")
176
+ session_events.add_argument("--json", action="store_true", help="Emit JSON output (default)")
177
+
178
+ show_genome = subparsers.add_parser("show-genome", help="Load and print cortex.toml")
179
+ show_genome.add_argument("--root", default=".", help="Repository root (default: current directory)")
180
+
181
+ runtime = subparsers.add_parser("runtime", help="Install or inspect runtime integration profiles")
182
+ runtime_subparsers = runtime.add_subparsers(dest="runtime_command")
183
+ runtime_install = runtime_subparsers.add_parser("install", help="Install runtime wiring profile")
184
+ runtime_install.add_argument("--root", default=".", help="Project root (default: current directory)")
185
+ runtime_install.add_argument(
186
+ "--profile",
187
+ choices=["claude", "gemini", "openai"],
188
+ required=True,
189
+ help="Runtime profile name",
190
+ )
191
+ runtime_install.add_argument("--force", action="store_true", help="Overwrite existing runtime wiring files")
192
+
193
+ hook = subparsers.add_parser("hook", help="Invoke a Cortex hook entrypoint")
194
+ hook.add_argument("event", choices=["session-start", "pre-tool-use", "post-tool-use", "stop"])
195
+ hook.add_argument("--root", default=".", help="Repository root (default: current directory)")
196
+ hook.add_argument("--config-path", help="Override cortex.toml path")
197
+ hook.add_argument("--db-path", help="Override SQLite database path")
198
+ hook.add_argument("--adapter", help="Deprecated. Configure [runtime].adapter in cortex.toml.")
199
+ hook.add_argument(
200
+ "--payload-file",
201
+ help="Read hook payload JSON from a file; otherwise reads JSON from stdin (or {} if empty).",
202
+ )
203
+ return parser
204
+
205
+
206
+ def _run_init(args: argparse.Namespace) -> int:
207
+ root = Path(args.root).resolve()
208
+ managed_files = {
209
+ root / "cortex.toml": _starter_config_toml(),
210
+ root / "tests" / "invariants" / "example_invariant_test.py": _starter_invariant_test(),
211
+ }
212
+ created: list[str] = []
213
+ overwritten: list[str] = []
214
+ skipped: list[str] = []
215
+ root.mkdir(parents=True, exist_ok=True)
216
+
217
+ invariants_dir = root / "tests" / "invariants"
218
+ if not invariants_dir.exists():
219
+ created.append(str(invariants_dir))
220
+ invariants_dir.mkdir(parents=True, exist_ok=True)
221
+
222
+ cortex_dir = root / ".cortex"
223
+ if not cortex_dir.exists():
224
+ created.append(str(cortex_dir))
225
+ cortex_dir.mkdir(parents=True, exist_ok=True)
226
+
227
+ for path, content in managed_files.items():
228
+ if path.exists():
229
+ if args.force:
230
+ overwritten.append(str(path))
231
+ path.parent.mkdir(parents=True, exist_ok=True)
232
+ path.write_text(content, encoding="utf-8")
233
+ else:
234
+ skipped.append(str(path))
235
+ continue
236
+ created.append(str(path))
237
+ path.parent.mkdir(parents=True, exist_ok=True)
238
+ path.write_text(content, encoding="utf-8")
239
+
240
+ db_path = cortex_dir / "cortex.db"
241
+ if not db_path.exists():
242
+ created.append(str(db_path))
243
+ store = SQLiteStore(db_path)
244
+ store.initialize()
245
+
246
+ print(
247
+ json.dumps(
248
+ {
249
+ "ok": True,
250
+ "root": str(root),
251
+ "created": created,
252
+ "overwritten": overwritten,
253
+ "skipped": skipped,
254
+ "runtime_profile": None,
255
+ },
256
+ indent=2,
257
+ sort_keys=True,
258
+ )
259
+ )
260
+ return 0
261
+
262
+
263
+ def _run_check(args: argparse.Namespace) -> int:
264
+ root = Path(args.root).resolve()
265
+ report = _collect_project_report(root)
266
+ kernel_metrics = build_kernel_metrics_payload(
267
+ include=any((args.kernel_metrics, args.kernel_metrics_baseline, args.write_kernel_metrics_baseline)),
268
+ root=root,
269
+ baseline_path=args.kernel_metrics_baseline,
270
+ write_baseline_path=args.write_kernel_metrics_baseline,
271
+ warnings=report["warnings"],
272
+ )
273
+ if kernel_metrics is not None: report["kernel_metrics"] = kernel_metrics # noqa: E701
274
+ if args.write_status:
275
+ report["status_artifact"] = write_status_artifact(root, report)
276
+ if args.json:
277
+ print(json.dumps(report, indent=2, sort_keys=True))
278
+ else:
279
+ print_check_report(report)
280
+ return 1 if report["summary"]["errors"] else 0
281
+
282
+
283
+ def _run_fleet(args: argparse.Namespace) -> int:
284
+ if args.fleet_command != "status":
285
+ print("Usage: cortex fleet status --roots <path...> [--json]", file=sys.stderr)
286
+ return 1
287
+ return _run_fleet_status(args)
288
+
289
+
290
+ def _run_fleet_status(args: argparse.Namespace) -> int:
291
+ roots = [Path(p).resolve() for p in args.roots]
292
+ reports = [_collect_project_report(root) for root in roots]
293
+ payload = {
294
+ "ok": all(r["summary"]["errors"] == 0 for r in reports),
295
+ "generated_at": datetime.now(timezone.utc).isoformat(),
296
+ "cortex_version": __version__,
297
+ "projects": reports,
298
+ "summary": {
299
+ "projects": len(reports),
300
+ "ok_projects": sum(1 for r in reports if r["summary"]["errors"] == 0),
301
+ "warning_projects": sum(1 for r in reports if r["summary"]["warnings"] > 0),
302
+ "error_projects": sum(1 for r in reports if r["summary"]["errors"] > 0),
303
+ },
304
+ }
305
+ if args.json:
306
+ print(json.dumps(payload, indent=2, sort_keys=True))
307
+ else:
308
+ print_fleet_report(payload)
309
+ return 0 if payload["ok"] else 1
310
+
311
+
312
+ def _run_runtime(args: argparse.Namespace) -> int:
313
+ if args.runtime_command != "install":
314
+ print("Usage: cortex runtime install --profile <name> --root <path>", file=sys.stderr)
315
+ return 1
316
+ return _run_runtime_install(args)
317
+
318
+
319
+ def _run_runtime_install(args: argparse.Namespace) -> int:
320
+ root = Path(args.root).resolve()
321
+ profile = str(args.profile).strip().lower()
322
+ if profile not in {"claude", "gemini", "openai"}:
323
+ print(f"Unsupported runtime profile: {profile}", file=sys.stderr)
324
+ return 1
325
+
326
+ runtime_dir, managed_files, adapter_path = runtime_profile_install_spec(
327
+ root=root,
328
+ profile=profile,
329
+ python_executable=sys.executable,
330
+ )
331
+
332
+ created: list[str] = []
333
+ overwritten: list[str] = []
334
+ skipped: list[str] = []
335
+ root.mkdir(parents=True, exist_ok=True)
336
+ if not runtime_dir.exists():
337
+ created.append(str(runtime_dir))
338
+ runtime_dir.mkdir(parents=True, exist_ok=True)
339
+ for path, content in managed_files.items():
340
+ if path.exists():
341
+ if args.force:
342
+ overwritten.append(str(path))
343
+ path.write_text(content, encoding="utf-8")
344
+ else:
345
+ skipped.append(str(path))
346
+ continue
347
+ created.append(str(path))
348
+ path.write_text(content, encoding="utf-8")
349
+
350
+ config_path = root / "cortex.toml"
351
+ config_status = set_runtime_adapter(config_path, adapter_path)
352
+ print(
353
+ json.dumps(
354
+ {
355
+ "ok": True,
356
+ "root": str(root),
357
+ "profile": profile,
358
+ "adapter": adapter_path,
359
+ "config_status": config_status,
360
+ "created": created,
361
+ "overwritten": overwritten,
362
+ "skipped": skipped,
363
+ },
364
+ indent=2,
365
+ sort_keys=True,
366
+ )
367
+ )
368
+ return 0
369
+
370
+
371
+ def _collect_project_report(root: Path) -> dict[str, Any]:
372
+ return collect_check_report(
373
+ root,
374
+ repomap_dependency_status=_repomap_dependency_status,
375
+ repomap_parser_dependency_status=_repomap_parser_dependency_status,
376
+ which=shutil.which,
377
+ find_spec=importlib.util.find_spec,
378
+ run_exec=subprocess.run,
379
+ )
380
+
381
+
382
+ def _run_graveyard(args: argparse.Namespace) -> int:
383
+ root = Path(args.root).resolve()
384
+ db_path = root / ".cortex" / "cortex.db"
385
+ if not db_path.exists():
386
+ print(f"Cortex graveyard: missing database at {db_path}", file=sys.stderr)
387
+ return 1
388
+
389
+ store = SQLiteStore(db_path)
390
+ entries = store.list_graveyard(limit=max(1, args.limit))
391
+ print(f"Cortex Graveyard ({root})")
392
+ if not entries:
393
+ print("No graveyard entries found.")
394
+ return 0
395
+
396
+ for entry in entries:
397
+ print(f"[{entry['id']}] {entry['created_at']}")
398
+ print(f"Summary: {entry['summary']}")
399
+ print(f"Reason: {entry['reason']}")
400
+ files = ", ".join(entry["files"]) if entry["files"] else "(none)"
401
+ print(f"Files: {files}")
402
+ print()
403
+ return 0
404
+
405
+
406
+ def _run_repomap(args: argparse.Namespace) -> int:
407
+ root = Path(args.root).resolve()
408
+ config_path = Path(args.config_path).resolve() if args.config_path else root / "cortex.toml"
409
+ genome = load_genome(config_path)
410
+ if genome.parse_error:
411
+ print(
412
+ json.dumps(
413
+ {
414
+ "ok": False,
415
+ "status": "config_error",
416
+ "message": f"Failed to parse Cortex config: {genome.parse_error}",
417
+ "config_path": str(config_path),
418
+ }
419
+ ),
420
+ file=sys.stderr,
421
+ )
422
+ return 1
423
+
424
+ from cortex.repomap import run_repomap
425
+
426
+ result = run_repomap(
427
+ root=root,
428
+ repomap_config=genome.repomap,
429
+ scope=[str(v) for v in args.scope] or None,
430
+ focus_files=[str(v) for v in args.focus_file] or None,
431
+ output_path=args.output,
432
+ max_files=args.max_files,
433
+ max_text_bytes=args.max_text_bytes,
434
+ timeout_ms=args.timeout_ms,
435
+ parity_profile=(True if args.parity_profile else None),
436
+ )
437
+ payload = result.to_dict()
438
+ payload["status"] = "ok" if result.ok else "error"
439
+ payload["repomap"] = {
440
+ "enabled": genome.repomap.enabled,
441
+ "run_on_session_start": genome.repomap.run_on_session_start,
442
+ "prefer_ast_graph": genome.repomap.prefer_ast_graph,
443
+ "parity_profile": genome.repomap.parity_profile,
444
+ "non_blocking": genome.repomap.non_blocking,
445
+ "artifact_path": genome.repomap.artifact_path,
446
+ "session_start_timeout_ms": genome.repomap.session_start_timeout_ms,
447
+ }
448
+ payload["requested"] = {
449
+ "root": str(root),
450
+ "config_path": str(config_path),
451
+ "output": args.output or genome.repomap.artifact_path,
452
+ "stdout_text": bool(args.stdout_text),
453
+ "json": bool(args.json),
454
+ "scope": [str(v) for v in args.scope],
455
+ "focus_files": [str(v) for v in args.focus_file],
456
+ "max_files": args.max_files if args.max_files is not None else genome.repomap.max_ranked_files,
457
+ "max_text_bytes": (
458
+ args.max_text_bytes if args.max_text_bytes is not None else genome.repomap.max_text_bytes
459
+ ),
460
+ "timeout_ms": args.timeout_ms,
461
+ "parity_profile": bool(args.parity_profile or genome.repomap.parity_profile),
462
+ }
463
+
464
+ if args.json:
465
+ print(json.dumps(result.artifact.to_dict(), indent=2, sort_keys=True))
466
+ elif args.debug_json:
467
+ print(json.dumps(payload, indent=2, sort_keys=True))
468
+ else:
469
+ print(f"Cortex Repo-Map: {root}")
470
+ print(f"Config: {config_path}")
471
+ if args.parity_profile or genome.repomap.parity_profile:
472
+ print("Profile: parity")
473
+ if result.ok:
474
+ method = str(result.artifact.provenance.get("method", "heuristic_fallback"))
475
+ if method == "ast_pagerank":
476
+ print("Repo-map artifact generated (ast_pagerank mode).")
477
+ else:
478
+ print("Repo-map artifact generated (heuristic fallback mode).")
479
+ if result.artifact_path:
480
+ print(f"Artifact: {result.artifact_path}")
481
+ stats = result.artifact.stats
482
+ print(
483
+ "Stats: "
484
+ f"files_parsed={stats.get('files_parsed', 0)} "
485
+ f"symbols_found={stats.get('symbols_found', 0)} "
486
+ f"graph_edges={stats.get('graph_edges', 0)} "
487
+ f"byte_count={stats.get('byte_count', 0)}"
488
+ )
489
+ if result.artifact.ranking:
490
+ top_paths = ", ".join(entry.path for entry in result.artifact.ranking[:5])
491
+ print(f"Top files: {top_paths}")
492
+ else:
493
+ print("Top files: (none)")
494
+ if args.stdout_text and result.artifact.text:
495
+ print()
496
+ print(result.artifact.text, end="" if result.artifact.text.endswith("\n") else "\n")
497
+ else:
498
+ error = result.artifact.error or {}
499
+ print(f"Repo-map generation failed: {error.get('code', 'unknown_error')}")
500
+ print(error.get("message", "Unknown repo-map error"))
501
+ return 0 if result.ok else 1
502
+
503
+
504
+ def _run_init_db(args: argparse.Namespace) -> int:
505
+ root = Path(args.root).resolve()
506
+ db_path = root / ".cortex" / "cortex.db"
507
+ store = SQLiteStore(db_path)
508
+ store.initialize()
509
+ print(json.dumps({"ok": True, "db_path": str(db_path)}))
510
+ return 0
511
+
512
+
513
+ def _run_memory(args: argparse.Namespace) -> int:
514
+ command = str(getattr(args, "memory_command", "") or "").strip().lower()
515
+ handlers = {
516
+ "export": _run_memory_export,
517
+ "import": _run_memory_import,
518
+ "list": _run_memory_list,
519
+ "delete": _run_memory_delete,
520
+ }
521
+ handler = handlers.get(command)
522
+ if handler is None:
523
+ print("Usage: cortex memory <export|import|list|delete> ...", file=sys.stderr)
524
+ return 2
525
+ return handler(args)
526
+
527
+
528
+ def _run_memory_export(args: argparse.Namespace) -> int:
529
+ root = Path(args.root).resolve()
530
+ output_path = Path(args.output).expanduser()
531
+ if not output_path.is_absolute():
532
+ output_path = (root / output_path).resolve()
533
+ genome = load_genome(root / "cortex.toml")
534
+ store = SQLiteStore(root / ".cortex" / "cortex.db")
535
+ store.initialize()
536
+
537
+ entries = store.get_executive_memory()
538
+ min_strength = max(1, int(args.min_strength))
539
+ current = store.get_session_count()
540
+ exported: list[dict[str, Any]] = []
541
+ for entry in entries:
542
+ strength = int(entry.get("strength", 1))
543
+ if strength < min_strength:
544
+ continue
545
+ sessions_since = max(0, current - int(entry.get("last_accessed_at_session", 0)))
546
+ exported.append(
547
+ {
548
+ "id": str(entry.get("id") or ""),
549
+ "type": str(entry.get("type") or ""),
550
+ "trigger": str(entry.get("trigger_pattern") or ""),
551
+ "error": str(entry.get("error_pattern") or ""),
552
+ "resolution": str(entry.get("resolution") or ""),
553
+ "strength": strength,
554
+ "effective_weight": effective_weight(
555
+ entry,
556
+ current_session_count=current,
557
+ halflife_sessions=genome.executive.halflife_sessions,
558
+ ),
559
+ "created_at_session": int(entry.get("created_at_session", 0)),
560
+ "last_accessed_at_session": int(entry.get("last_accessed_at_session", 0)),
561
+ "sessions_since_relevant": sessions_since,
562
+ }
563
+ )
564
+ exported.sort(key=lambda item: float(item["effective_weight"]), reverse=True)
565
+
566
+ payload = {
567
+ "exported_at": datetime.now(timezone.utc).isoformat(),
568
+ "project_root": str(root),
569
+ "total_sessions": current,
570
+ "halflife_sessions": genome.executive.halflife_sessions,
571
+ "entries": exported,
572
+ }
573
+ output_path.parent.mkdir(parents=True, exist_ok=True)
574
+ output_path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
575
+ print(json.dumps({"ok": True, "path": str(output_path), "entries": len(exported)}))
576
+ return 0
577
+
578
+
579
+ def _run_memory_import(args: argparse.Namespace) -> int:
580
+ root = Path(args.root).resolve()
581
+ input_path = Path(args.input).expanduser()
582
+ if not input_path.is_absolute():
583
+ input_path = (root / input_path).resolve()
584
+ if not input_path.exists():
585
+ print(f"Memory import file not found: {input_path}", file=sys.stderr)
586
+ return 1
587
+ try:
588
+ payload = json.loads(input_path.read_text(encoding="utf-8"))
589
+ except Exception as exc: # noqa: BLE001
590
+ print(f"Invalid memory import JSON: {exc}", file=sys.stderr)
591
+ return 1
592
+ if not isinstance(payload, dict) or not isinstance(payload.get("entries"), list):
593
+ print("Invalid memory import payload: expected object with entries list.", file=sys.stderr)
594
+ return 1
595
+
596
+ store = SQLiteStore(root / ".cortex" / "cortex.db")
597
+ store.initialize()
598
+ import_session = store.get_session_count()
599
+ imported = 0
600
+ source_session_id = f"import-{datetime.now(timezone.utc).strftime('%Y%m%d%H%M%S')}"
601
+ for raw in payload["entries"]:
602
+ if not isinstance(raw, dict):
603
+ continue
604
+ event_type = str(raw.get("type") or "technical").strip() or "technical"
605
+ trigger = str(raw.get("trigger") or raw.get("trigger_pattern") or "").strip()
606
+ error = str(raw.get("error") or raw.get("error_pattern") or "").strip()
607
+ resolution = str(raw.get("resolution") or "").strip()
608
+ strength = max(1, int(raw.get("strength") or 1))
609
+ if not trigger or not error or not resolution:
610
+ continue
611
+ for _ in range(strength):
612
+ created = consolidate_event(
613
+ store,
614
+ event_type=event_type,
615
+ trigger_pattern=trigger,
616
+ error_pattern=error,
617
+ resolution=resolution,
618
+ session_id=source_session_id,
619
+ )
620
+ store.update_executive_entry(str(created["id"]), last_accessed_at_session=import_session)
621
+ imported += 1
622
+ print(json.dumps({"ok": True, "imported_entries": imported, "source": str(input_path)}))
623
+ return 0
624
+
625
+
626
+ def _run_memory_list(args: argparse.Namespace) -> int:
627
+ root = Path(args.root).resolve()
628
+ genome = load_genome(root / "cortex.toml")
629
+ store = SQLiteStore(root / ".cortex" / "cortex.db")
630
+ store.initialize()
631
+ current = store.get_session_count()
632
+ include_decayed = bool(args.include_decayed)
633
+ rows: list[dict[str, Any]] = []
634
+ for entry in store.get_executive_memory():
635
+ weight = effective_weight(
636
+ entry,
637
+ current_session_count=current,
638
+ halflife_sessions=genome.executive.halflife_sessions,
639
+ )
640
+ sessions_since = max(0, current - int(entry.get("last_accessed_at_session", 0)))
641
+ if weight < genome.executive.decay_threshold:
642
+ injection_state = "decayed"
643
+ elif weight >= genome.executive.inject_threshold:
644
+ injection_state = "active"
645
+ elif sessions_since <= genome.executive.min_hold_sessions:
646
+ injection_state = "active_recent_hold"
647
+ else:
648
+ injection_state = "fading_not_injected"
649
+ if not include_decayed and weight < genome.executive.decay_threshold:
650
+ continue
651
+ rows.append(
652
+ {
653
+ "id": str(entry.get("id") or ""),
654
+ "type": str(entry.get("type") or ""),
655
+ "strength": int(entry.get("strength", 1)),
656
+ "effective_weight": weight,
657
+ "sessions_since_relevant": sessions_since,
658
+ "injection_state": injection_state,
659
+ "trigger": str(entry.get("trigger_pattern") or ""),
660
+ "error": str(entry.get("error_pattern") or ""),
661
+ "resolution": str(entry.get("resolution") or ""),
662
+ }
663
+ )
664
+ rows.sort(key=lambda item: float(item["effective_weight"]), reverse=True)
665
+ print(json.dumps({"ok": True, "total_sessions": current, "entries": rows}, indent=2))
666
+ return 0
667
+
668
+
669
+ def _run_memory_delete(args: argparse.Namespace) -> int:
670
+ root = Path(args.root).resolve()
671
+ entry_id = str(args.entry_id or "").strip()
672
+ if not entry_id:
673
+ print("entry_id is required", file=sys.stderr)
674
+ return 2
675
+ store = SQLiteStore(root / ".cortex" / "cortex.db")
676
+ store.initialize()
677
+ store.delete_executive_entries([entry_id])
678
+ print(json.dumps({"ok": True, "deleted": entry_id}))
679
+ return 0
680
+
681
+
682
+ def _run_session_events(args: argparse.Namespace) -> int:
683
+ root = Path(args.root).resolve()
684
+ db_path = root / ".cortex" / "cortex.db"
685
+ if not db_path.exists():
686
+ print(json.dumps({"ok": False, "error": f"Missing database file: {db_path}"}), file=sys.stderr)
687
+ return 1
688
+ try:
689
+ events = _query_session_events(
690
+ db_path=db_path,
691
+ session_id=str(args.session_id),
692
+ hooks=[str(item) for item in (args.hooks or [])],
693
+ )
694
+ except sqlite3.Error as exc:
695
+ print(json.dumps({"ok": False, "error": f"Failed to read session events: {exc}"}), file=sys.stderr)
696
+ return 1
697
+ payload = {"ok": True, "session_id": str(args.session_id), "events": events}
698
+ print(json.dumps(payload, indent=2 if args.json else None, sort_keys=True))
699
+ return 0
700
+
701
+
702
+ def _run_show_genome(args: argparse.Namespace) -> int:
703
+ root = Path(args.root).resolve()
704
+ genome = load_genome(root / "cortex.toml")
705
+ payload = genome.to_dict()
706
+ payload["pack_temporal_protocol"] = pack_temporal_protocol_summary(
707
+ root=root,
708
+ pack_paths=list(genome.packs.paths),
709
+ )
710
+ print(json.dumps(payload, indent=2, sort_keys=True))
711
+ return 0
712
+
713
+
714
+ def _run_hook(args: argparse.Namespace) -> int:
715
+ if args.adapter:
716
+ print(
717
+ "--adapter is no longer supported. Configure [runtime].adapter in cortex.toml.",
718
+ file=sys.stderr,
719
+ )
720
+ return 1
721
+ payload = _read_payload(args.payload_file)
722
+ kernel = CortexKernel(
723
+ root=args.root,
724
+ config_path=args.config_path,
725
+ db_path=args.db_path,
726
+ )
727
+ result = kernel.dispatch(args.event, payload)
728
+ print(json.dumps(result, indent=2, sort_keys=True))
729
+ return 0
730
+
731
+
732
+ def _starter_config_toml() -> str:
733
+ return """# Cortex project configuration (starter)
734
+
735
+ [project]
736
+ name = "your-project"
737
+ type = "generic"
738
+ root = "."
739
+ trust_profile = "untrusted"
740
+
741
+ [invariants]
742
+ suite_paths = ["tests/invariants"]
743
+ pytest_bin = "pytest"
744
+ run_on_stop = true
745
+ execution_mode = "container"
746
+ container_engine = "docker"
747
+ container_image = "python:3.11-slim"
748
+ container_workdir = "/workspace"
749
+
750
+ [invariants.graduation]
751
+ enabled = true
752
+ target_dir = "tests/invariants/graduated"
753
+
754
+ [challenges]
755
+ active_categories = [
756
+ "null_inputs",
757
+ "boundary_values",
758
+ "error_handling",
759
+ "graveyard_regression",
760
+ ]
761
+ custom_paths = []
762
+ require_coverage = true
763
+
764
+ [graveyard]
765
+ enabled = true
766
+ max_matches = 5
767
+ similarity_threshold = 0.35
768
+ min_keyword_overlap = 1
769
+ context_max_matches = 2
770
+ context_summary_chars = 140
771
+ context_reason_chars = 200
772
+ context_max_tokens = 300
773
+
774
+ [foundation]
775
+ enabled = true
776
+ watch_paths = ["src"]
777
+ ignored_dirs = ["node_modules", "dist", "build", ".git", "__pycache__"]
778
+ churn_window_commits = 200
779
+ git_timeout_ms = 1500
780
+
781
+ [foundation.stability_thresholds]
782
+ warn_churn_count = 8
783
+ high_churn_count = 15
784
+
785
+ [repomap]
786
+ enabled = false
787
+ run_on_session_start = false
788
+ prefer_ast_graph = true
789
+ parity_profile = false
790
+ extended_language_profile = false
791
+ watch_paths = ["src"]
792
+ ignored_dirs = ["node_modules", "dist", "build", ".git", ".cortex", "__pycache__"]
793
+ max_ranked_files = 20
794
+ max_text_bytes = 8192
795
+ artifact_path = ".cortex/artifacts/repomap/latest.json"
796
+ non_blocking = true
797
+ session_start_timeout_ms = 2500
798
+
799
+ [runtime]
800
+ adapter = ""
801
+
802
+ [executive]
803
+ enabled = true
804
+ inject_identity_preamble = true
805
+ part_a_mode = "once_per_project"
806
+ halflife_sessions = 30
807
+ decay_threshold = 0.3
808
+ inject_threshold = 0.45
809
+ max_entries = 20
810
+ max_tokens = 1200
811
+ min_hold_sessions = 3
812
+
813
+ [hooks]
814
+ mode = "advisory"
815
+ fail_on_missing_challenge_coverage = false
816
+ recommend_revert_on_invariant_failure = true
817
+ require_requirement_audit = false
818
+ fail_on_requirement_audit_gap = false
819
+ require_evidence_for_passed_requirement = true
820
+ require_structured_stop_payload = true
821
+ allow_message_stop_fallback = false
822
+
823
+ [metrics]
824
+ enabled = true
825
+ track = [
826
+ "human_oversight_minutes",
827
+ "interrupt_count",
828
+ "escaped_defects",
829
+ "completion_minutes",
830
+ "foundation_quality",
831
+ ]
832
+ """
833
+
834
+
835
+ def _starter_invariant_test() -> str:
836
+ return """from __future__ import annotations
837
+
838
+ from pathlib import Path
839
+
840
+
841
+ # Invariant tests are external constraints the current agent did not author.
842
+ # They encode project rules that should keep passing across sessions and tasks.
843
+ def test_cortex_config_exists() -> None:
844
+ project_root = Path(__file__).resolve().parents[2]
845
+ assert (project_root / "cortex.toml").exists()
846
+
847
+
848
+ # Example of a real invariant (commented out):
849
+ # def test_no_banned_imports() -> None:
850
+ # project_root = Path(__file__).resolve().parents[2]
851
+ # banned = "legacy_unsafe_module"
852
+ # for path in project_root.rglob("*.py"):
853
+ # if ".venv" in path.parts or ".cortex" in path.parts:
854
+ # continue
855
+ # assert banned not in path.read_text(encoding="utf-8")
856
+ """
857
+ def _query_session_events(*, db_path: Path, session_id: str, hooks: list[str]) -> list[dict[str, Any]]:
858
+ session_token = str(session_id).strip()
859
+ if not session_token:
860
+ return []
861
+ hook_tokens = [str(item).strip() for item in hooks if str(item).strip()]
862
+ where = "WHERE session_id = ?"
863
+ params: list[Any] = [session_token]
864
+ if hook_tokens:
865
+ where += f" AND hook IN ({','.join(['?'] * len(hook_tokens))})"
866
+ params.extend(hook_tokens)
867
+ with sqlite3.connect(db_path) as conn:
868
+ conn.row_factory = sqlite3.Row
869
+ rows = conn.execute(
870
+ f"""
871
+ SELECT id, hook, tool_name, status, payload_json, created_at
872
+ FROM events
873
+ {where}
874
+ ORDER BY created_at ASC, id ASC
875
+ """,
876
+ tuple(params),
877
+ ).fetchall()
878
+ return [
879
+ {
880
+ "id": int(row["id"]),
881
+ "hook": str(row["hook"]),
882
+ "created_at": str(row["created_at"]),
883
+ "tool_name": row["tool_name"],
884
+ "status": row["status"],
885
+ "payload": json.loads(row["payload_json"] or "{}"),
886
+ }
887
+ for row in rows
888
+ ]
889
+
890
+
891
+ def _repomap_dependency_status() -> list[str]:
892
+ from cortex.repomap import repomap_missing_dependencies
893
+ return repomap_missing_dependencies()
894
+
895
+
896
+ def _repomap_parser_dependency_status() -> list[str]:
897
+ from cortex.repomap import repomap_missing_parser_dependencies
898
+ return repomap_missing_parser_dependencies()
899
+
900
+
901
+ def _read_payload(payload_file: str | None) -> dict[str, Any]:
902
+ raw = Path(payload_file).read_text(encoding="utf-8") if payload_file else sys.stdin.read()
903
+ raw = raw.strip()
904
+ if not raw: return {} # noqa: E701
905
+ decoded = json.loads(raw)
906
+ if not isinstance(decoded, dict):
907
+ raise ValueError("Hook payload must decode to a JSON object")
908
+ return decoded
909
+
910
+
911
+ if __name__ == "__main__": raise SystemExit(main()) # noqa: E701