nexo-brain 6.5.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.
@@ -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