nexo-brain 5.3.0 → 5.3.2

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
@@ -42,6 +44,14 @@ _LEGACY_WAKE_RECOVERY_METADATA = [
42
44
  "# nexo: run_on_boot=true",
43
45
  ]
44
46
 
47
+ _LEGACY_CORE_RUNTIME_FILES = {
48
+ "capture-tool-logs.sh",
49
+ "daily-briefing-check.sh",
50
+ "heartbeat-enforcement.py",
51
+ "heartbeat-posttool.sh",
52
+ "heartbeat-user-msg.sh",
53
+ }
54
+
45
55
  # Forbidden patterns — direct DB access from personal scripts
46
56
  _FORBIDDEN_PATTERNS = [
47
57
  re.compile(r"\bsqlite3\b"),
@@ -73,6 +83,7 @@ METADATA_KEYS = {
73
83
  "run_on_wake",
74
84
  "idempotent",
75
85
  "max_catchup_age",
86
+ "doctor_allow_db",
76
87
  }
77
88
  SUPPORTED_RUNTIMES = {"python", "shell", "node", "php", "unknown"}
78
89
  PERSONAL_SCHEDULE_MANAGED_ENV = "NEXO_MANAGED_PERSONAL_CRON"
@@ -116,7 +127,7 @@ def _apply_legacy_personal_script_backfills() -> None:
116
127
 
117
128
 
118
129
  def load_core_script_names() -> set[str]:
119
- """Load script names from crons/manifest.json (these are core, not personal)."""
130
+ """Load runtime-managed script names (core, not personal)."""
120
131
  names: set[str] = set()
121
132
  for manifest_path in [NEXO_CODE / "crons" / "manifest.json", NEXO_HOME / "crons" / "manifest.json"]:
122
133
  if manifest_path.exists():
@@ -129,6 +140,26 @@ def load_core_script_names() -> set[str]:
129
140
  break
130
141
  except Exception:
131
142
  continue
143
+ runtime_manifest = NEXO_HOME / "config" / "runtime-core-artifacts.json"
144
+ if runtime_manifest.exists():
145
+ try:
146
+ data = json.loads(runtime_manifest.read_text())
147
+ for key in ("script_names", "hook_names"):
148
+ for name in data.get(key, []):
149
+ clean = Path(str(name)).name
150
+ if clean:
151
+ names.add(clean)
152
+ except Exception:
153
+ pass
154
+ hooks_dir = NEXO_HOME / "hooks"
155
+ if hooks_dir.is_dir():
156
+ try:
157
+ for item in hooks_dir.iterdir():
158
+ if item.is_file():
159
+ names.add(item.name)
160
+ except Exception:
161
+ pass
162
+ names.update(_LEGACY_CORE_RUNTIME_FILES)
132
163
  return names
133
164
 
134
165
 
@@ -213,7 +244,11 @@ def _is_ignored(path: Path) -> bool:
213
244
  return True
214
245
  if path.name.startswith("."):
215
246
  return True
216
- for parent in path.relative_to(get_scripts_dir()).parents:
247
+ try:
248
+ relative_path = path.resolve().relative_to(get_scripts_dir().resolve())
249
+ except Exception:
250
+ return False
251
+ for parent in relative_path.parents:
217
252
  if parent.name in _IGNORED_DIRS:
218
253
  return True
219
254
  return False
@@ -1425,14 +1460,19 @@ def doctor_script(path_or_name: str) -> dict:
1425
1460
  elif cmd:
1426
1461
  items.append({"level": "pass", "msg": f"Required command found: {cmd}"})
1427
1462
 
1463
+ allow_db_access = str(meta.get("doctor_allow_db", "")).strip().lower() in {"1", "true", "yes", "on"}
1464
+ if allow_db_access:
1465
+ items.append({"level": "pass", "msg": "Doctor DB access explicitly allowed"})
1466
+
1428
1467
  # Forbidden patterns (only for personal scripts)
1429
1468
  if not is_core:
1430
1469
  try:
1431
1470
  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()}"})
1471
+ if not allow_db_access:
1472
+ for pat in _FORBIDDEN_PATTERNS:
1473
+ match = pat.search(content)
1474
+ if match:
1475
+ items.append({"level": "fail", "msg": f"Forbidden DB pattern found: {match.group()}"})
1436
1476
  except Exception:
1437
1477
  pass
1438
1478
 
@@ -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.
@@ -112,7 +112,6 @@ These agents power NEXO's learning and memory systems. Strongly recommended.
112
112
  | File | Schedule | What it does |
113
113
  |------|----------|-------------|
114
114
  | `com.nexo.dashboard.plist` | Persistent (KeepAlive) | Runs the NEXO web dashboard on `http://localhost:6174`. Provides a browser-based view of sessions, reminders, followups, and system health. Only needed if you want the dashboard UI. |
115
- | `com.nexo.github-monitor.plist` | Daily 08:00 | Checks the NEXO public GitHub repository for open issues, pull requests, and pending releases. Writes results to `~/.nexo/github-status.json` for NEXO to read at startup. Only relevant if you maintain the public NEXO repository. |
116
115
 
117
116
  ---
118
117
 
@@ -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()
@@ -1,45 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <!-- com.nexo.github-monitor
3
- Runs nexo-github-monitor.py every day at 08:00 to check the NEXO
4
- public GitHub repository for new issues, pull requests, and pending
5
- releases. Writes results to ~/.nexo/github-status.json. At the next
6
- session startup, NEXO reads this file and responds to open issues,
7
- reviews PRs, and proposes a release if enough commits have
8
- accumulated since the last tag.
9
- -->
10
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
11
- <plist version="1.0">
12
- <dict>
13
- <key>Label</key>
14
- <string>com.nexo.github-monitor</string>
15
- <key>ProgramArguments</key>
16
- <array>
17
- <string>/usr/bin/python3</string>
18
- <string>{{NEXO_HOME}}/scripts/nexo-github-monitor.py</string>
19
- </array>
20
- <key>StartCalendarInterval</key>
21
- <dict>
22
- <key>Hour</key>
23
- <integer>8</integer>
24
- <key>Minute</key>
25
- <integer>0</integer>
26
- </dict>
27
- <key>StandardOutPath</key>
28
- <string>{{NEXO_HOME}}/logs/github-monitor-stdout.log</string>
29
- <key>StandardErrorPath</key>
30
- <string>{{NEXO_HOME}}/logs/github-monitor-stderr.log</string>
31
- <key>EnvironmentVariables</key>
32
- <dict>
33
- <key>HOME</key>
34
- <string>{{HOME}}</string>
35
- <key>NEXO_HOME</key>
36
- <string>{{NEXO_HOME}}</string>
37
- <key>NEXO_CODE</key>
38
- <string>{{NEXO_CODE}}</string>
39
- <key>PATH</key>
40
- <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
41
- </dict>
42
- <key>RunAtLoad</key>
43
- <false/>
44
- </dict>
45
- </plist>