nexo-brain 6.4.0 → 7.0.0

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.
package/src/cli.py CHANGED
@@ -37,6 +37,7 @@ import contextlib
37
37
  import io
38
38
  import json
39
39
  import os
40
+ import paths
40
41
  import shutil
41
42
  import subprocess
42
43
  import sys
@@ -303,7 +304,7 @@ def _scripts_list(args):
303
304
  print(json.dumps(scripts, indent=2))
304
305
  else:
305
306
  if not scripts:
306
- print("No personal scripts found in", NEXO_HOME / "scripts")
307
+ print("No personal scripts found in", paths.core_scripts_dir())
307
308
  return 0
308
309
  # Table output
309
310
  name_w = max(len(s["name"]) for s in scripts)
@@ -347,7 +348,7 @@ def _scripts_classify(args):
347
348
 
348
349
  entries = report.get("entries", [])
349
350
  if not entries:
350
- print("No scripts directory found:", report.get("scripts_dir", NEXO_HOME / "scripts"))
351
+ print("No scripts directory found:", report.get("scripts_dir", paths.core_scripts_dir()))
351
352
  return 0
352
353
 
353
354
  path_w = max(len(Path(entry["path"]).name) for entry in entries)
@@ -452,6 +453,49 @@ def _scripts_unschedule(args):
452
453
  return 0 if result.get("ok") else 1
453
454
 
454
455
 
456
+ def _scripts_set_enabled(args, enabled):
457
+ from script_registry import set_personal_script_enabled
458
+
459
+ result = set_personal_script_enabled(args.name, enabled)
460
+ if args.json:
461
+ print(json.dumps(result, indent=2, ensure_ascii=False))
462
+ return 0 if result.get("ok") else 1
463
+ if not result.get("ok"):
464
+ print(result.get("error", "Failed to toggle script"), file=sys.stderr)
465
+ return 1
466
+ verb = "enabled" if enabled else "disabled"
467
+ if result.get("changed"):
468
+ print(f"Script {result['name']} {verb}.")
469
+ else:
470
+ print(f"Script {result['name']} already {verb}.")
471
+ return 0
472
+
473
+
474
+ def _scripts_status(args):
475
+ from script_registry import get_personal_script_status
476
+
477
+ result = get_personal_script_status(args.name)
478
+ if args.json:
479
+ print(json.dumps(result, indent=2, ensure_ascii=False))
480
+ return 0 if result.get("ok") else 1
481
+ if not result.get("ok"):
482
+ print(result.get("error", "Failed to read status"), file=sys.stderr)
483
+ return 1
484
+ state = "enabled" if result.get("enabled") else "DISABLED"
485
+ print(f"{result.get('name')} [{result.get('classification')}] -> {state}")
486
+ last = result.get("last_run") or {}
487
+ if last:
488
+ exit_code = last.get("exit_code")
489
+ started = last.get("started_at") or "?"
490
+ print(f" last run: {started} (exit={exit_code})")
491
+ summary = (last.get("summary") or "").strip()
492
+ if summary:
493
+ print(f" summary: {summary[:120]}")
494
+ else:
495
+ print(" last run: (none)")
496
+ return 0
497
+
498
+
455
499
  def _scripts_remove(args):
456
500
  from script_registry import remove_personal_script
457
501
 
@@ -495,8 +539,8 @@ def _scripts_run(args):
495
539
 
496
540
  # Only inject DB paths for core scripts
497
541
  if is_core:
498
- env["NEXO_DB"] = str(NEXO_HOME / "data" / "nexo.db")
499
- env["NEXO_COGNITIVE_DB"] = str(NEXO_HOME / "data" / "cognitive.db")
542
+ env["NEXO_DB"] = str(paths.db_path())
543
+ env["NEXO_COGNITIVE_DB"] = str(paths.data_dir() / "cognitive.db")
500
544
 
501
545
  # Timeout
502
546
  timeout = None
@@ -988,7 +1032,7 @@ def _update(args):
988
1032
  # the sensory-register buffer. Keeps a .pre-v5.4.1.bak backup the first
989
1033
  # time it runs on a given host.
990
1034
  try:
991
- buf = NEXO_HOME / "brain" / "session_buffer.jsonl"
1035
+ buf = paths.brain_dir() / "session_buffer.jsonl"
992
1036
  marker = buf.with_suffix(".jsonl.pre-v5.4.1.bak")
993
1037
  if buf.is_file() and not marker.is_file():
994
1038
  raw = buf.read_text(errors="ignore").splitlines()
@@ -1120,7 +1164,7 @@ def _write_calibration_default_resonance(tier: str) -> None:
1120
1164
  …). This helper keeps the CLI path writing to both calibration.json
1121
1165
  AND schedule.json so the two surfaces never disagree.
1122
1166
  """
1123
- cal_path = NEXO_HOME / "brain" / "calibration.json"
1167
+ cal_path = paths.brain_dir() / "calibration.json"
1124
1168
  try:
1125
1169
  cal_path.parent.mkdir(parents=True, exist_ok=True)
1126
1170
  if cal_path.exists():
@@ -2116,6 +2160,19 @@ def main():
2116
2160
  unschedule_p.add_argument("name", help="Script name or path")
2117
2161
  unschedule_p.add_argument("--json", action="store_true", help="JSON output")
2118
2162
 
2163
+ # scripts enable / disable / status (Plan F0.2.2)
2164
+ enable_p = scripts_sub.add_parser("enable", help="Enable a personal script (cron wrapper will run it again)")
2165
+ enable_p.add_argument("name", help="Script name or path")
2166
+ enable_p.add_argument("--json", action="store_true", help="JSON output")
2167
+
2168
+ disable_p = scripts_sub.add_parser("disable", help="Disable a personal script (cron wrapper will skip it)")
2169
+ disable_p.add_argument("name", help="Script name or path")
2170
+ disable_p.add_argument("--json", action="store_true", help="JSON output")
2171
+
2172
+ status_p = scripts_sub.add_parser("status", help="Show enabled flag + last cron_runs row for a script")
2173
+ status_p.add_argument("name", help="Script name or path")
2174
+ status_p.add_argument("--json", action="store_true", help="JSON output")
2175
+
2119
2176
  # scripts remove
2120
2177
  remove_p = scripts_sub.add_parser("remove", help="Remove a personal script and any attached schedules")
2121
2178
  remove_p.add_argument("name", help="Script name or path")
@@ -2394,6 +2451,12 @@ def main():
2394
2451
  return _scripts_doctor(args)
2395
2452
  elif args.scripts_command == "call":
2396
2453
  return _scripts_call(args)
2454
+ elif args.scripts_command == "enable":
2455
+ return _scripts_set_enabled(args, True)
2456
+ elif args.scripts_command == "disable":
2457
+ return _scripts_set_enabled(args, False)
2458
+ elif args.scripts_command == "status":
2459
+ return _scripts_status(args)
2397
2460
  else:
2398
2461
  scripts_parser.print_help()
2399
2462
  return 0
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
 
4
4
  import json
5
5
  import os
6
+ import paths
6
7
  import plistlib
7
8
  import sqlite3
8
9
  import contextlib
@@ -16,8 +17,8 @@ NEXO_CODE = Path(os.environ.get("NEXO_CODE", str(Path(__file__).resolve().parent
16
17
  LAUNCH_AGENTS_DIR = Path.home() / "Library" / "LaunchAgents"
17
18
  OPTIONALS_FILE = NEXO_HOME / "config" / "optionals.json"
18
19
  SCHEDULE_FILE = NEXO_HOME / "config" / "schedule.json"
19
- DB_PATH = NEXO_HOME / "data" / "nexo.db"
20
- STATE_FILE = NEXO_HOME / "operations" / ".catchup-state.json"
20
+ DB_PATH = paths.db_path()
21
+ STATE_FILE = paths.operations_dir() / ".catchup-state.json"
21
22
 
22
23
 
23
24
  def _local_timezone():
@@ -84,7 +85,7 @@ def resolve_declared_schedule(cron: dict) -> dict:
84
85
 
85
86
  def load_enabled_crons() -> list[dict]:
86
87
  manifest_candidates = [
87
- NEXO_HOME / "crons" / "manifest.json",
88
+ paths.crons_dir() / "manifest.json",
88
89
  NEXO_CODE / "crons" / "manifest.json",
89
90
  ]
90
91
  optionals = _load_json(OPTIONALS_FILE, {})
@@ -122,7 +122,9 @@ def upsert_personal_script(
122
122
  metadata_json = excluded.metadata_json,
123
123
  created_by = COALESCE(NULLIF(personal_scripts.created_by, ''), excluded.created_by),
124
124
  source = excluded.source,
125
- enabled = excluded.enabled,
125
+ -- Plan F0.2.2: preserve operator-set `enabled` flag across sync runs.
126
+ -- Sync defaults to enabled=True for INSERTs; on UPDATE we keep
127
+ -- whatever the operator (or `nexo scripts disable`) set.
126
128
  has_inline_metadata = excluded.has_inline_metadata,
127
129
  last_synced_at = excluded.last_synced_at,
128
130
  updated_at = excluded.updated_at
package/src/db/_skills.py CHANGED
@@ -11,6 +11,7 @@ Executable skills are indexed in SQLite but sourced from filesystem definitions.
11
11
  import datetime
12
12
  import json
13
13
  import os
14
+ import paths
14
15
  import re
15
16
  import shutil
16
17
  from pathlib import Path
@@ -25,7 +26,7 @@ NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
25
26
  NEXO_CODE = Path(os.environ.get("NEXO_CODE", str(Path(__file__).resolve().parents[1])))
26
27
 
27
28
  NEXO_ROOT = NEXO_CODE.parent
28
- PERSONAL_SKILLS_DIR = NEXO_HOME / "skills"
29
+ PERSONAL_SKILLS_DIR = paths.personal_skills_dir()
29
30
 
30
31
 
31
32
  def _resolve_core_skills_dir() -> Path:
@@ -27,11 +27,13 @@ def _nexo_home() -> Path:
27
27
 
28
28
 
29
29
  def _calibration_path() -> Path:
30
- return _nexo_home() / "brain" / "calibration.json"
30
+ import paths
31
+ return paths.brain_dir() / "calibration.json"
31
32
 
32
33
 
33
34
  def _profile_path() -> Path:
34
- return _nexo_home() / "brain" / "profile.json"
35
+ import paths
36
+ return paths.brain_dir() / "profile.json"
35
37
 
36
38
 
37
39
  def _read_json(path: Path) -> dict:
@@ -13,7 +13,8 @@ NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
13
13
 
14
14
  def check_db_exists() -> DoctorCheck:
15
15
  """Check that the main database file exists and is readable."""
16
- db_path = NEXO_HOME / "data" / "nexo.db"
16
+ import paths
17
+ db_path = paths.db_path()
17
18
  if db_path.is_file():
18
19
  size_kb = db_path.stat().st_size / 1024
19
20
  return DoctorCheck(
@@ -37,9 +38,21 @@ def check_db_exists() -> DoctorCheck:
37
38
 
38
39
 
39
40
  def check_required_dirs() -> DoctorCheck:
40
- """Check that required NEXO_HOME directories exist."""
41
- required = ["data", "scripts", "plugins", "crons", "hooks", "coordination", "operations", "logs"]
42
- missing = [d for d in required if not (NEXO_HOME / d).is_dir()]
41
+ """Check that required NEXO_HOME directories exist (post-F0.6 layout
42
+ or pre-F0.6 fallback)."""
43
+ import paths
44
+ # Each required dir maps to one of the new locations OR the legacy fallback.
45
+ required_helpers = [
46
+ ("data", paths.data_dir()),
47
+ ("scripts", paths.core_scripts_dir()),
48
+ ("plugins", paths.core_plugins_dir()),
49
+ ("crons", paths.crons_dir()),
50
+ ("hooks", paths.core_hooks_dir()),
51
+ ("coordination", paths.coordination_dir()),
52
+ ("operations", paths.operations_dir()),
53
+ ("logs", paths.logs_dir()),
54
+ ]
55
+ missing = [name for (name, path) in required_helpers if not path.is_dir()]
43
56
 
44
57
  if not missing:
45
58
  return DoctorCheck(
@@ -47,7 +60,7 @@ def check_required_dirs() -> DoctorCheck:
47
60
  tier="boot",
48
61
  status="healthy",
49
62
  severity="info",
50
- summary=f"All {len(required)} required directories present",
63
+ summary=f"All {len(required_helpers)} required directories present",
51
64
  )
52
65
 
53
66
  return DoctorCheck(
@@ -57,7 +70,7 @@ def check_required_dirs() -> DoctorCheck:
57
70
  severity="warn" if len(missing) < 3 else "error",
58
71
  summary=f"{len(missing)} required director{'y' if len(missing) == 1 else 'ies'} missing",
59
72
  evidence=[f"Missing: {d}" for d in missing],
60
- repair_plan=[f"mkdir -p {NEXO_HOME / d}" for d in missing],
73
+ repair_plan=[f"mkdir -p {dict(required_helpers)[d]}" for d in missing],
61
74
  )
62
75
 
63
76
 
@@ -107,7 +120,8 @@ def check_disk_space() -> DoctorCheck:
107
120
 
108
121
  def check_wrapper_scripts() -> DoctorCheck:
109
122
  """Check that cron wrapper script exists."""
110
- wrapper = NEXO_HOME / "scripts" / "nexo-cron-wrapper.sh"
123
+ import paths
124
+ wrapper = paths.core_scripts_dir() / "nexo-cron-wrapper.sh"
111
125
  if wrapper.is_file():
112
126
  return DoctorCheck(
113
127
  id="boot.wrapper_scripts",
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
  import datetime as dt
5
5
  import json
6
6
  import os
7
+ import paths
7
8
  import time
8
9
  from pathlib import Path
9
10
 
@@ -67,7 +68,7 @@ def _self_audit_enabled() -> bool | None:
67
68
 
68
69
  def check_self_audit_summary() -> DoctorCheck:
69
70
  """Check latest self-audit summary exists and is recent."""
70
- summary_file = NEXO_HOME / "logs" / "self-audit-summary.json"
71
+ summary_file = paths.logs_dir() / "self-audit-summary.json"
71
72
  age = _file_age_seconds(summary_file)
72
73
 
73
74
  if age is None:
@@ -154,7 +155,7 @@ def check_schema_version() -> DoctorCheck:
154
155
  """Check DB schema version is present and reasonable."""
155
156
  try:
156
157
  import sqlite3
157
- db_path = NEXO_HOME / "data" / "nexo.db"
158
+ db_path = paths.db_path()
158
159
  if not db_path.is_file():
159
160
  return DoctorCheck(
160
161
  id="deep.schema_version",
@@ -187,7 +188,7 @@ def check_schema_version() -> DoctorCheck:
187
188
 
188
189
  def check_preflight_summary() -> DoctorCheck:
189
190
  """Check runtime preflight summary."""
190
- summary_file = NEXO_HOME / "logs" / "runtime-preflight-summary.json"
191
+ summary_file = paths.logs_dir() / "runtime-preflight-summary.json"
191
192
  age = _file_age_seconds(summary_file)
192
193
 
193
194
  if age is None:
@@ -242,7 +243,7 @@ def check_preflight_summary() -> DoctorCheck:
242
243
 
243
244
  def check_watchdog_smoke() -> DoctorCheck:
244
245
  """Check watchdog smoke summary."""
245
- summary_file = NEXO_HOME / "logs" / "watchdog-smoke-summary.json"
246
+ summary_file = paths.logs_dir() / "watchdog-smoke-summary.json"
246
247
  age = _file_age_seconds(summary_file)
247
248
 
248
249
  if age is None:
@@ -300,7 +301,7 @@ def check_learning_count() -> DoctorCheck:
300
301
  """Check learning count as a health proxy."""
301
302
  try:
302
303
  import sqlite3
303
- db_path = NEXO_HOME / "data" / "nexo.db"
304
+ db_path = paths.db_path()
304
305
  if not db_path.is_file():
305
306
  return DoctorCheck(
306
307
  id="deep.learning_count",
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
  import datetime as dt
5
5
  import json
6
6
  import os
7
+ import paths
7
8
  import platform
8
9
  import plistlib
9
10
  import re
@@ -423,7 +424,7 @@ def _extract_declared_file_targets(args: dict, cwd: str) -> set[str]:
423
424
 
424
425
 
425
426
  def _load_active_conditioned_learnings() -> list[dict]:
426
- db_path = NEXO_HOME / "data" / "nexo.db"
427
+ db_path = paths.db_path()
427
428
  if not db_path.is_file():
428
429
  return []
429
430
  try:
@@ -622,7 +623,7 @@ def _recent_codex_conditioned_file_discipline_status(*, days: int = 7, max_files
622
623
 
623
624
 
624
625
  def _open_protocol_debt_summary(*debt_types: str) -> dict:
625
- db_path = NEXO_HOME / "data" / "nexo.db"
626
+ db_path = paths.db_path()
626
627
  summary = {"available": False, "open_total": 0, "counts": {}}
627
628
  if not db_path.is_file() or not debt_types:
628
629
  return summary
@@ -661,7 +662,7 @@ def _client_assumption_regressions() -> list[str]:
661
662
  if not src_root.is_dir():
662
663
  return []
663
664
  src_root = src_root.resolve()
664
- backup_root = (NEXO_HOME / "backups").resolve()
665
+ backup_root = (paths.backups_dir()).resolve()
665
666
  contrib_root = (NEXO_HOME / "contrib").resolve()
666
667
  allowed_relative_paths = {
667
668
  Path("scripts") / "deep-sleep" / "collect.py",
@@ -715,7 +716,7 @@ def _load_json(path: Path) -> dict:
715
716
 
716
717
 
717
718
  def _latest_cron_run_age_seconds(cron_id: str) -> float | None:
718
- db_path = NEXO_HOME / "data" / "nexo.db"
719
+ db_path = paths.db_path()
719
720
  if not db_path.is_file():
720
721
  return None
721
722
  try:
@@ -748,7 +749,7 @@ def _latest_cron_run_age_seconds(cron_id: str) -> float | None:
748
749
  def _latest_periodic_summary(kind: str) -> dict | None:
749
750
  pattern = f"*-{kind}-summary.json"
750
751
  candidates: list[tuple[str, Path]] = []
751
- for path in (NEXO_HOME / "operations" / "deep-sleep").glob(pattern):
752
+ for path in (paths.operations_dir() / "deep-sleep").glob(pattern):
752
753
  try:
753
754
  payload = json.loads(path.read_text())
754
755
  except Exception:
@@ -830,7 +831,7 @@ def _enabled_optionals() -> dict[str, bool]:
830
831
 
831
832
  def _enabled_manifest_crons() -> list[dict]:
832
833
  manifest_candidates = [
833
- NEXO_HOME / "crons" / "manifest.json",
834
+ paths.crons_dir() / "manifest.json",
834
835
  NEXO_CODE / "crons" / "manifest.json",
835
836
  ]
836
837
  optionals = _enabled_optionals()
@@ -1049,7 +1050,7 @@ def _plist_runtime_paths(plist_data: dict) -> list[str]:
1049
1050
 
1050
1051
 
1051
1052
  def _recent_permission_denial(cron_id: str, max_age_seconds: int = 7 * 86400) -> bool:
1052
- stderr_path = NEXO_HOME / "logs" / f"{cron_id}-stderr.log"
1053
+ stderr_path = paths.logs_dir() / f"{cron_id}-stderr.log"
1053
1054
  age = _file_age_seconds(stderr_path)
1054
1055
  if age is None or age > max_age_seconds:
1055
1056
  return False
@@ -1135,7 +1136,7 @@ def _sync_launchagents_from_manifest() -> tuple[bool, list[str]]:
1135
1136
 
1136
1137
  def check_immune_status() -> DoctorCheck:
1137
1138
  """Check immune system status freshness."""
1138
- status_file = NEXO_HOME / "coordination" / "immune-status.json"
1139
+ status_file = paths.coordination_dir() / "immune-status.json"
1139
1140
  age = _file_age_seconds(status_file)
1140
1141
 
1141
1142
  if age is None:
@@ -1230,7 +1231,7 @@ def check_immune_status() -> DoctorCheck:
1230
1231
 
1231
1232
  def check_watchdog_status() -> DoctorCheck:
1232
1233
  """Check watchdog status freshness."""
1233
- status_file = NEXO_HOME / "operations" / "watchdog-status.json"
1234
+ status_file = paths.operations_dir() / "watchdog-status.json"
1234
1235
  age = _file_age_seconds(status_file)
1235
1236
 
1236
1237
  if age is None:
@@ -1307,7 +1308,7 @@ def check_stale_sessions() -> DoctorCheck:
1307
1308
  """Check for stale sessions from DB."""
1308
1309
  try:
1309
1310
  import sqlite3
1310
- db_path = NEXO_HOME / "data" / "nexo.db"
1311
+ db_path = paths.db_path()
1311
1312
  if not db_path.is_file():
1312
1313
  return DoctorCheck(
1313
1314
  id="runtime.stale_sessions",
@@ -1358,7 +1359,7 @@ def check_cron_freshness() -> DoctorCheck:
1358
1359
  """Check cron_runs table for recent executions."""
1359
1360
  try:
1360
1361
  import sqlite3
1361
- db_path = NEXO_HOME / "data" / "nexo.db"
1362
+ db_path = paths.db_path()
1362
1363
  if not db_path.is_file():
1363
1364
  return DoctorCheck(
1364
1365
  id="runtime.cron_freshness",
@@ -2404,7 +2405,7 @@ def check_protocol_compliance() -> DoctorCheck:
2404
2405
  try:
2405
2406
  import sqlite3
2406
2407
 
2407
- db_path = NEXO_HOME / "data" / "nexo.db"
2408
+ db_path = paths.db_path()
2408
2409
  if db_path.is_file():
2409
2410
  conn = sqlite3.connect(str(db_path), timeout=2)
2410
2411
  try:
@@ -2775,7 +2776,7 @@ def check_release_artifact_sync() -> DoctorCheck:
2775
2776
 
2776
2777
 
2777
2778
  def check_release_trace_hygiene() -> DoctorCheck:
2778
- db_path = NEXO_HOME / "data" / "nexo.db"
2779
+ db_path = paths.db_path()
2779
2780
  if not db_path.is_file():
2780
2781
  return DoctorCheck(
2781
2782
  id="runtime.release_trace_hygiene",
@@ -2906,8 +2907,8 @@ def check_release_trace_hygiene() -> DoctorCheck:
2906
2907
 
2907
2908
 
2908
2909
  def check_state_watchers() -> DoctorCheck:
2909
- db_path = NEXO_HOME / "data" / "nexo.db"
2910
- summary_path = NEXO_HOME / "operations" / "state-watchers-status.json"
2910
+ db_path = paths.db_path()
2911
+ summary_path = paths.operations_dir() / "state-watchers-status.json"
2911
2912
  active_watchers = 0
2912
2913
  if db_path.is_file():
2913
2914
  try:
@@ -3007,7 +3008,7 @@ def check_state_watchers() -> DoctorCheck:
3007
3008
 
3008
3009
 
3009
3010
  def check_automation_telemetry(days: int = 7) -> DoctorCheck:
3010
- db_path = NEXO_HOME / "data" / "nexo.db"
3011
+ db_path = paths.db_path()
3011
3012
  if not db_path.is_file():
3012
3013
  return DoctorCheck(
3013
3014
  id="runtime.automation_telemetry",
@@ -7,6 +7,7 @@ v1.1 (future): sandbox execution of auto-approved changes.
7
7
 
8
8
  import json
9
9
  import os
10
+ import paths
10
11
  import shutil
11
12
  import subprocess
12
13
  import sqlite3
@@ -16,17 +17,17 @@ from pathlib import Path
16
17
 
17
18
  NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
18
19
  NEXO_CODE = Path(os.environ.get("NEXO_CODE", str(NEXO_HOME)))
19
- NEXO_DB = NEXO_HOME / "data" / "nexo.db"
20
+ NEXO_DB = paths.db_path()
20
21
  SANDBOX_DIR = NEXO_HOME / "sandbox" / "workspace"
21
- SNAPSHOTS_DIR = NEXO_HOME / "snapshots"
22
- RESTORE_LOG = NEXO_HOME / "logs" / "snapshot-restores.log"
22
+ SNAPSHOTS_DIR = paths.snapshots_dir()
23
+ RESTORE_LOG = paths.logs_dir() / "snapshot-restores.log"
23
24
 
24
25
  # Evolution config: brain/ (canonical) > cortex/ (legacy) > NEXO_CODE (dev)
25
26
  def _resolve_evolution_file(name: str) -> Path:
26
- for candidate in [NEXO_HOME / "brain" / name, NEXO_HOME / "cortex" / name, NEXO_CODE / name]:
27
+ for candidate in [paths.brain_dir() / name, NEXO_HOME / "cortex" / name, NEXO_CODE / name]:
27
28
  if candidate.exists():
28
29
  return candidate
29
- return NEXO_HOME / "brain" / name # default canonical path
30
+ return paths.brain_dir() / name # default canonical path
30
31
 
31
32
  OBJECTIVE_FILE = _resolve_evolution_file("evolution-objective.json")
32
33
  PROMPT_FILE = _resolve_evolution_file("evolution-prompt.md")
@@ -233,7 +234,7 @@ def dry_run_restore_test() -> bool:
233
234
  _nexo_code = Path(os.environ.get("NEXO_CODE", ""))
234
235
  restore_script = None
235
236
  for candidate in [_nexo_code / "scripts" / "nexo-snapshot-restore.sh",
236
- NEXO_HOME / "scripts" / "nexo-snapshot-restore.sh"]:
237
+ paths.core_scripts_dir() / "nexo-snapshot-restore.sh"]:
237
238
  if candidate.exists():
238
239
  restore_script = candidate
239
240
  break
@@ -47,7 +47,8 @@ def _check_runtime() -> dict:
47
47
 
48
48
 
49
49
  def _check_database() -> dict:
50
- db_path = _nexo_home() / "data" / "nexo.db"
50
+ import paths
51
+ db_path = paths.db_path()
51
52
  out: dict[str, Any] = {"path": str(db_path), "exists": db_path.is_file()}
52
53
  if not out["exists"]:
53
54
  out["status"] = "error"
@@ -108,7 +109,8 @@ def _check_mcp() -> dict:
108
109
 
109
110
 
110
111
  def _check_errors(hours: int = 24) -> dict:
111
- ops_dir = _nexo_home() / "operations"
112
+ import paths
113
+ ops_dir = paths.operations_dir()
112
114
  out: dict[str, Any] = {"dir": str(ops_dir)}
113
115
  if not ops_dir.is_dir():
114
116
  out["recent_errors"] = 0