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_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
|