nexo-brain 5.2.1 → 5.3.1

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.
@@ -16,7 +16,9 @@ import stat
16
16
  import subprocess
17
17
  from pathlib import Path
18
18
 
19
- NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
19
+ from runtime_home import export_resolved_nexo_home
20
+
21
+ NEXO_HOME = export_resolved_nexo_home()
20
22
  NEXO_CODE = Path(os.environ.get("NEXO_CODE", str(Path(__file__).resolve().parent)))
21
23
 
22
24
  # Internal artifacts to always ignore
@@ -73,6 +75,7 @@ METADATA_KEYS = {
73
75
  "run_on_wake",
74
76
  "idempotent",
75
77
  "max_catchup_age",
78
+ "doctor_allow_db",
76
79
  }
77
80
  SUPPORTED_RUNTIMES = {"python", "shell", "node", "php", "unknown"}
78
81
  PERSONAL_SCHEDULE_MANAGED_ENV = "NEXO_MANAGED_PERSONAL_CRON"
@@ -213,7 +216,11 @@ def _is_ignored(path: Path) -> bool:
213
216
  return True
214
217
  if path.name.startswith("."):
215
218
  return True
216
- for parent in path.relative_to(get_scripts_dir()).parents:
219
+ try:
220
+ relative_path = path.resolve().relative_to(get_scripts_dir().resolve())
221
+ except Exception:
222
+ return False
223
+ for parent in relative_path.parents:
217
224
  if parent.name in _IGNORED_DIRS:
218
225
  return True
219
226
  return False
@@ -1425,14 +1432,19 @@ def doctor_script(path_or_name: str) -> dict:
1425
1432
  elif cmd:
1426
1433
  items.append({"level": "pass", "msg": f"Required command found: {cmd}"})
1427
1434
 
1435
+ allow_db_access = str(meta.get("doctor_allow_db", "")).strip().lower() in {"1", "true", "yes", "on"}
1436
+ if allow_db_access:
1437
+ items.append({"level": "pass", "msg": "Doctor DB access explicitly allowed"})
1438
+
1428
1439
  # Forbidden patterns (only for personal scripts)
1429
1440
  if not is_core:
1430
1441
  try:
1431
1442
  content = p.read_text(errors="ignore")
1432
- for pat in _FORBIDDEN_PATTERNS:
1433
- match = pat.search(content)
1434
- if match:
1435
- items.append({"level": "fail", "msg": f"Forbidden DB pattern found: {match.group()}"})
1443
+ if not allow_db_access:
1444
+ for pat in _FORBIDDEN_PATTERNS:
1445
+ match = pat.search(content)
1446
+ if match:
1447
+ items.append({"level": "fail", "msg": f"Forbidden DB pattern found: {match.group()}"})
1436
1448
  except Exception:
1437
1449
  pass
1438
1450
 
@@ -63,7 +63,7 @@ def _open_correction_fatigue_followup(fatigued: list) -> str:
63
63
  lines.append(f"... and {len(fatigued) - 10} more")
64
64
  description = "\n".join(lines)
65
65
  verification = (
66
- "sqlite3 ~/claude/data/cognitive.db \"SELECT id, content, strength, tags "
66
+ "sqlite3 ~/.nexo/data/cognitive.db \"SELECT id, content, strength, tags "
67
67
  "FROM ltm_memories WHERE tags LIKE '%under_review%' ORDER BY strength ASC LIMIT 50\""
68
68
  )
69
69
  now_epoch = datetime.now().timestamp()
@@ -12,7 +12,7 @@ recommendation pattern could persist indefinitely between user reports.
12
12
  What this script does (idempotent and best-effort):
13
13
 
14
14
  1. Loads cortex_evaluation_summary for the last 7 days and last 1 day.
15
- 2. Persists the snapshot to ~/claude/operations/cortex-quality-latest.json
15
+ 2. Persists the snapshot to ~/.nexo/operations/cortex-quality-latest.json
16
16
  so dashboards / morning briefings can read fresh metrics without
17
17
  re-running the SQL.
18
18
  3. Detects degradation signals on the 7-day window. The criteria are
@@ -25,7 +25,7 @@ What this script does (idempotent and best-effort):
25
25
  metrics when degradation is detected. Idempotent: if a non-PENDING /
26
26
  resolved followup of the same id already exists, it is updated in
27
27
  place rather than duplicated.
28
- 5. Logs every run to ~/claude/logs/cortex-cycle.log.
28
+ 5. Logs every run to ~/.nexo/logs/cortex-cycle.log.
29
29
 
30
30
  Catchup-friendly: a stale plist firing twice in quick succession is fine.
31
31
  The quality file is rewritten in place, the followup is upserted, no
@@ -3,7 +3,7 @@
3
3
 
4
4
  Closes Fase 3 item 7 of NEXO-AUDIT-2026-04-11. Bash hooks can call:
5
5
 
6
- python3 ~/Documents/_PhpstormProjects/nexo/src/scripts/nexo-hook-record.py \
6
+ NEXO_HOME=~/.nexo python3 ~/.nexo/scripts/nexo-hook-record.py \
7
7
  --hook session-start --duration-ms 142 --exit $? --session $SID
8
8
 
9
9
  This script is intentionally minimal so it adds <50ms of latency to the
@@ -7,7 +7,7 @@ Usage:
7
7
  python3 nexo-migrate.py --from 1.6.0 # override detected current version
8
8
 
9
9
  Reads current version from $NEXO_HOME/version.json.
10
- Reads target version from the repo's package.json.
10
+ Reads target version from the installed runtime package.json.
11
11
  Backs up NEXO_HOME/db/ before any migration.
12
12
  Runs DB schema migrations via the existing _schema.py system.
13
13
  """
@@ -16,13 +16,43 @@ import argparse
16
16
  import json
17
17
  import os
18
18
  import shutil
19
- import sqlite3
20
19
  import sys
21
20
  from datetime import datetime
22
21
  from pathlib import Path
23
22
 
24
- NEXO_HOME = Path(os.environ.get("NEXO_HOME", Path.home() / ".nexo"))
25
- REPO_ROOT = Path(__file__).resolve().parent.parent.parent # nexo/src/scripts -> nexo/
23
+ NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo"))).expanduser()
24
+ SCRIPT_ROOT = Path(__file__).resolve().parent.parent
25
+
26
+
27
+ def _resolve_runtime_root() -> Path:
28
+ candidates: list[Path] = []
29
+
30
+ raw_code = os.environ.get("NEXO_CODE", "").strip()
31
+ if raw_code:
32
+ env_code = Path(raw_code).expanduser()
33
+ candidates.append(env_code)
34
+ if env_code.name == "src":
35
+ candidates.append(env_code.parent)
36
+
37
+ candidates.extend([SCRIPT_ROOT, NEXO_HOME])
38
+
39
+ seen: set[str] = set()
40
+ for candidate in candidates:
41
+ candidate = candidate if candidate.is_dir() else candidate.parent
42
+ key = str(candidate)
43
+ if key in seen:
44
+ continue
45
+ seen.add(key)
46
+ if (candidate / "package.json").is_file():
47
+ return candidate
48
+ if (candidate / "src" / "server.py").is_file() and (candidate / "package.json").is_file():
49
+ return candidate
50
+
51
+ return SCRIPT_ROOT
52
+
53
+
54
+ RUNTIME_ROOT = _resolve_runtime_root()
55
+ SOURCE_ROOT = RUNTIME_ROOT / "src" if (RUNTIME_ROOT / "src" / "db").is_dir() else RUNTIME_ROOT
26
56
 
27
57
 
28
58
  # ── Version helpers ──────────────────────────────────────────────
@@ -56,8 +86,8 @@ def get_current_version() -> str:
56
86
 
57
87
 
58
88
  def get_target_version() -> str:
59
- """Read target version from repo package.json."""
60
- pkg = REPO_ROOT / "package.json"
89
+ """Read target version from installed runtime package.json."""
90
+ pkg = RUNTIME_ROOT / "package.json"
61
91
  if not pkg.exists():
62
92
  print(f"ERROR: package.json not found at {pkg}", file=sys.stderr)
63
93
  sys.exit(1)
@@ -107,10 +137,8 @@ def ensure_nexo_home_dirs():
107
137
 
108
138
  def run_db_schema_migrations():
109
139
  """Run the formal DB schema migration system from _schema.py."""
110
- # Add src/ to path so we can import the db module
111
- src_dir = REPO_ROOT / "src"
112
- if str(src_dir) not in sys.path:
113
- sys.path.insert(0, str(src_dir))
140
+ if str(SOURCE_ROOT) not in sys.path:
141
+ sys.path.insert(0, str(SOURCE_ROOT))
114
142
 
115
143
  # Set NEXO_HOME env for the db module
116
144
  os.environ["NEXO_HOME"] = str(NEXO_HOME)
@@ -374,7 +374,7 @@ def _open_watcher_followup(result: dict) -> dict:
374
374
  )
375
375
  description = "\n".join(description_lines)
376
376
  verification = (
377
- f"sqlite3 ~/claude/data/nexo.db \"SELECT last_health, last_result "
377
+ f"sqlite3 ~/.nexo/data/nexo.db \"SELECT last_health, last_result "
378
378
  f"FROM state_watchers WHERE watcher_id = '{watcher_id}'\""
379
379
  )
380
380
 
@@ -1,4 +1,4 @@
1
- <!-- nexo-claude-md-version: 2.1.3 -->
1
+ <!-- nexo-claude-md-version: 2.1.4 -->
2
2
  ******CORE******
3
3
  <!-- nexo:core:start -->
4
4
  # {{NAME}} — Cognitive Co-Operator
@@ -35,6 +35,12 @@ NEXO updates may rewrite `CORE`, but they must preserve `USER` verbatim.
35
35
  - For single-artifact asks, the default cap before the first visible answer is one lookup plus one detail read. Do not keep chaining tools before answering unless the user explicitly asked for more depth.
36
36
  - After `nexo_email_read`, `nexo_session_diary_read`, `nexo_reminders`, `nexo_followups`, or equivalent single-artifact retrieval, answer immediately. Do not launch another search, another read, or background analysis before the first user-visible answer unless the user explicitly asked for it.
37
37
 
38
+ ## Communication Guardrail
39
+ - In the first answer to Francisco on any thread, lead with the direct recommendation, decision, or status.
40
+ - Keep each decision point to 2-3 short sentences maximum. Hold extra detail unless he explicitly asks for it.
41
+ - Do not use internal NEXO jargon in the first answer (`protocol debt`, `cortex evaluation`, `runtime check`, `guard_check`, `heartbeat`, etc.). Translate it into plain operational language.
42
+ - Prefer conclusion plus next action over option dumps, raw diagnostics, or internal process narration. Apply this equally to chat replies, emails, briefings, and headless reports intended for Francisco.
43
+
38
44
  <!-- nexo:start:profile -->
39
45
  ## User Profile
40
46
  - **Calibration:** `{{NEXO_HOME}}/brain/calibration.json` (personality settings + language + user name)
@@ -1,4 +1,4 @@
1
- <!-- nexo-codex-agents-version: 1.2.3 -->
1
+ <!-- nexo-codex-agents-version: 1.2.4 -->
2
2
  ******CORE******
3
3
  <!-- nexo:core:start -->
4
4
  # {{NAME}} — NEXO Shared Brain for Codex
@@ -32,6 +32,12 @@ NEXO updates may rewrite `CORE`, but they must preserve `USER` verbatim.
32
32
  - For single-artifact asks, the default cap before the first visible answer is one lookup plus one detail read. Do not keep chaining tools before answering unless the user explicitly asked for more depth.
33
33
  - After `nexo_email_read`, `nexo_session_diary_read`, `nexo_reminders`, `nexo_followups`, or equivalent single-artifact retrieval, answer immediately. Do not launch another search, another read, or background analysis before the first visible answer unless the user explicitly asked for it.
34
34
 
35
+ ## Communication Guardrail
36
+ - In the first answer to Francisco on any thread, lead with the direct recommendation, decision, or status.
37
+ - Keep each decision point to 2-3 short sentences maximum. Hold extra detail unless he explicitly asks for it.
38
+ - Do not use internal NEXO jargon in the first answer (`protocol debt`, `cortex evaluation`, `runtime check`, `guard_check`, `heartbeat`, etc.). Translate it into plain operational language.
39
+ - Prefer conclusion plus next action over option dumps, raw diagnostics, or internal process narration. Apply this equally to chat replies, emails, briefings, and headless reports intended for Francisco.
40
+
35
41
  ## Codex Runtime Notes
36
42
  - Codex does not provide Claude Code hooks, so protocol discipline must be explicit.
37
43
  - If a stable session token is useful, pass `session_token='codex-<task>-<date>'` and `session_client='codex'` to `nexo_startup`; otherwise leave them blank.
@@ -30,11 +30,15 @@ def _detect_nexo_home() -> Path:
30
30
  ):
31
31
  return inferred_home
32
32
 
33
+ default_home = Path.home() / ".nexo"
34
+ if default_home.is_dir():
35
+ return default_home
36
+
33
37
  claude_home = Path.home() / "claude"
34
38
  if claude_home.is_dir():
35
39
  return claude_home
36
40
 
37
- return Path.home() / ".nexo"
41
+ return default_home
38
42
 
39
43
 
40
44
  NEXO_HOME = _detect_nexo_home()