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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +9 -0
- package/bin/nexo-brain.js +30 -4
- package/bin/nexo.js +43 -7
- package/package.json +2 -1
- package/scripts/sync_release_artifacts.py +128 -0
- package/src/auto_update.py +66 -8
- package/src/bootstrap_docs.py +53 -2
- package/src/cli.py +225 -1
- package/src/client_sync.py +2 -1
- package/src/db/_personal_scripts.py +30 -8
- package/src/doctor/providers/runtime.py +37 -1
- package/src/hooks/session-start.sh +8 -4
- package/src/plugins/update.py +90 -3
- package/src/runtime_home.py +46 -0
- package/src/script_registry.py +18 -6
- package/src/scripts/nexo-cognitive-decay.py +1 -1
- package/src/scripts/nexo-cortex-cycle.py +2 -2
- package/src/scripts/nexo-hook-record.py +1 -1
- package/src/scripts/nexo-migrate.py +38 -10
- package/src/state_watchers_runtime.py +1 -1
- package/templates/CLAUDE.md.template +7 -1
- package/templates/CODEX.AGENTS.md.template +7 -1
- package/templates/nexo_helper.py +5 -1
package/src/script_registry.py
CHANGED
|
@@ -16,7 +16,9 @@ import stat
|
|
|
16
16
|
import subprocess
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
|
|
19
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
60
|
-
pkg =
|
|
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
|
-
|
|
111
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
package/templates/nexo_helper.py
CHANGED
|
@@ -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
|
|
41
|
+
return default_home
|
|
38
42
|
|
|
39
43
|
|
|
40
44
|
NEXO_HOME = _detect_nexo_home()
|