nexo-brain 1.7.0 → 2.1.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.
Files changed (261) hide show
  1. package/README.md +160 -60
  2. package/bin/nexo-brain.js +680 -381
  3. package/package.json +18 -3
  4. package/scripts/migrate-to-unified.sh +813 -0
  5. package/scripts/migrate-v1.7-to-v1.8.py +214 -0
  6. package/scripts/pre-commit-check.sh +1 -1
  7. package/src/__pycache__/auto_update.cpython-310.pyc +0 -0
  8. package/src/__pycache__/hnsw_index.cpython-310.pyc +0 -0
  9. package/src/__pycache__/hnsw_index.cpython-314.pyc +0 -0
  10. package/src/__pycache__/kg_populate.cpython-310.pyc +0 -0
  11. package/src/__pycache__/knowledge_graph.cpython-310.pyc +0 -0
  12. package/src/__pycache__/plugin_loader.cpython-310.pyc +0 -0
  13. package/src/__pycache__/tools_coordination.cpython-310.pyc +0 -0
  14. package/src/__pycache__/tools_credentials.cpython-310.pyc +0 -0
  15. package/src/__pycache__/tools_learnings.cpython-310.pyc +0 -0
  16. package/src/__pycache__/tools_menu.cpython-310.pyc +0 -0
  17. package/src/__pycache__/tools_reminders.cpython-310.pyc +0 -0
  18. package/src/__pycache__/tools_reminders_crud.cpython-310.pyc +0 -0
  19. package/src/__pycache__/tools_sessions.cpython-310.pyc +0 -0
  20. package/src/__pycache__/tools_task_history.cpython-310.pyc +0 -0
  21. package/src/auto_close_sessions.py +1 -1
  22. package/src/auto_update.py +634 -0
  23. package/src/cognitive/__pycache__/__init__.cpython-310.pyc +0 -0
  24. package/src/cognitive/__pycache__/__init__.cpython-312.pyc +0 -0
  25. package/src/cognitive/__pycache__/__init__.cpython-314.pyc +0 -0
  26. package/src/cognitive/__pycache__/_core.cpython-310.pyc +0 -0
  27. package/src/cognitive/__pycache__/_core.cpython-312.pyc +0 -0
  28. package/src/cognitive/__pycache__/_core.cpython-314.pyc +0 -0
  29. package/src/cognitive/__pycache__/_decay.cpython-310.pyc +0 -0
  30. package/src/cognitive/__pycache__/_decay.cpython-312.pyc +0 -0
  31. package/src/cognitive/__pycache__/_decay.cpython-314.pyc +0 -0
  32. package/src/cognitive/__pycache__/_ingest.cpython-310.pyc +0 -0
  33. package/src/cognitive/__pycache__/_ingest.cpython-312.pyc +0 -0
  34. package/src/cognitive/__pycache__/_ingest.cpython-314.pyc +0 -0
  35. package/src/cognitive/__pycache__/_memory.cpython-310.pyc +0 -0
  36. package/src/cognitive/__pycache__/_memory.cpython-312.pyc +0 -0
  37. package/src/cognitive/__pycache__/_memory.cpython-314.pyc +0 -0
  38. package/src/cognitive/__pycache__/_search.cpython-310.pyc +0 -0
  39. package/src/cognitive/__pycache__/_search.cpython-312.pyc +0 -0
  40. package/src/cognitive/__pycache__/_search.cpython-314.pyc +0 -0
  41. package/src/cognitive/__pycache__/_trust.cpython-310.pyc +0 -0
  42. package/src/cognitive/__pycache__/_trust.cpython-312.pyc +0 -0
  43. package/src/cognitive/__pycache__/_trust.cpython-314.pyc +0 -0
  44. package/src/cognitive/_core.py +7 -3
  45. package/src/cognitive/_decay.py +1 -1
  46. package/src/cognitive/_search.py +1 -0
  47. package/src/cognitive/_trust.py +3 -3
  48. package/src/crons/manifest.json +106 -0
  49. package/src/crons/sync.py +217 -0
  50. package/src/dashboard/__pycache__/__init__.cpython-310.pyc +0 -0
  51. package/src/dashboard/__pycache__/app.cpython-310.pyc +0 -0
  52. package/src/dashboard/app.py +24 -4
  53. package/src/dashboard/templates/dashboard.html +3 -2
  54. package/src/db/__pycache__/__init__.cpython-310.pyc +0 -0
  55. package/src/db/__pycache__/__init__.cpython-312.pyc +0 -0
  56. package/src/db/__pycache__/__init__.cpython-314.pyc +0 -0
  57. package/src/db/__pycache__/_core.cpython-310.pyc +0 -0
  58. package/src/db/__pycache__/_core.cpython-312.pyc +0 -0
  59. package/src/db/__pycache__/_core.cpython-314.pyc +0 -0
  60. package/src/db/__pycache__/_credentials.cpython-310.pyc +0 -0
  61. package/src/db/__pycache__/_credentials.cpython-312.pyc +0 -0
  62. package/src/db/__pycache__/_credentials.cpython-314.pyc +0 -0
  63. package/src/db/__pycache__/_entities.cpython-310.pyc +0 -0
  64. package/src/db/__pycache__/_entities.cpython-312.pyc +0 -0
  65. package/src/db/__pycache__/_entities.cpython-314.pyc +0 -0
  66. package/src/db/__pycache__/_episodic.cpython-310.pyc +0 -0
  67. package/src/db/__pycache__/_episodic.cpython-312.pyc +0 -0
  68. package/src/db/__pycache__/_episodic.cpython-314.pyc +0 -0
  69. package/src/db/__pycache__/_evolution.cpython-310.pyc +0 -0
  70. package/src/db/__pycache__/_evolution.cpython-312.pyc +0 -0
  71. package/src/db/__pycache__/_evolution.cpython-314.pyc +0 -0
  72. package/src/db/__pycache__/_fts.cpython-310.pyc +0 -0
  73. package/src/db/__pycache__/_fts.cpython-312.pyc +0 -0
  74. package/src/db/__pycache__/_fts.cpython-314.pyc +0 -0
  75. package/src/db/__pycache__/_learnings.cpython-310.pyc +0 -0
  76. package/src/db/__pycache__/_learnings.cpython-312.pyc +0 -0
  77. package/src/db/__pycache__/_learnings.cpython-314.pyc +0 -0
  78. package/src/db/__pycache__/_reminders.cpython-310.pyc +0 -0
  79. package/src/db/__pycache__/_reminders.cpython-312.pyc +0 -0
  80. package/src/db/__pycache__/_reminders.cpython-314.pyc +0 -0
  81. package/src/db/__pycache__/_schema.cpython-310.pyc +0 -0
  82. package/src/db/__pycache__/_schema.cpython-312.pyc +0 -0
  83. package/src/db/__pycache__/_schema.cpython-314.pyc +0 -0
  84. package/src/db/__pycache__/_sessions.cpython-310.pyc +0 -0
  85. package/src/db/__pycache__/_sessions.cpython-312.pyc +0 -0
  86. package/src/db/__pycache__/_sessions.cpython-314.pyc +0 -0
  87. package/src/db/__pycache__/_tasks.cpython-310.pyc +0 -0
  88. package/src/db/__pycache__/_tasks.cpython-312.pyc +0 -0
  89. package/src/db/__pycache__/_tasks.cpython-314.pyc +0 -0
  90. package/src/db/_core.py +5 -1
  91. package/src/db/_episodic.py +2 -4
  92. package/src/db/_reminders.py +45 -6
  93. package/src/db/_schema.py +31 -0
  94. package/src/evolution_cycle.py +33 -11
  95. package/src/hooks/auto_capture.py +1 -1
  96. package/src/hooks/capture-tool-logs.sh +76 -0
  97. package/src/hooks/inbox-hook.sh +2 -1
  98. package/src/hooks/post-compact.sh +2 -1
  99. package/src/hooks/pre-compact.sh +104 -2
  100. package/src/hooks/session-start.sh +6 -2
  101. package/src/hooks/session-stop.sh +4 -2
  102. package/src/kg_populate.py +4 -1
  103. package/src/migrate_embeddings.py +4 -1
  104. package/src/plugin_loader.py +100 -34
  105. package/src/plugins/__pycache__/__init__.cpython-310.pyc +0 -0
  106. package/src/plugins/__pycache__/adaptive_mode.cpython-310.pyc +0 -0
  107. package/src/plugins/__pycache__/adaptive_mode.cpython-314.pyc +0 -0
  108. package/src/plugins/__pycache__/agents.cpython-310.pyc +0 -0
  109. package/src/plugins/__pycache__/artifact_registry.cpython-310.pyc +0 -0
  110. package/src/plugins/__pycache__/backup.cpython-310.pyc +0 -0
  111. package/src/plugins/__pycache__/cognitive_memory.cpython-310.pyc +0 -0
  112. package/src/plugins/__pycache__/core_rules.cpython-310.pyc +0 -0
  113. package/src/plugins/__pycache__/cortex.cpython-310.pyc +0 -0
  114. package/src/plugins/__pycache__/entities.cpython-310.pyc +0 -0
  115. package/src/plugins/__pycache__/episodic_memory.cpython-310.pyc +0 -0
  116. package/src/plugins/__pycache__/evolution.cpython-310.pyc +0 -0
  117. package/src/plugins/__pycache__/guard.cpython-310.pyc +0 -0
  118. package/src/plugins/__pycache__/knowledge_graph_tools.cpython-310.pyc +0 -0
  119. package/src/plugins/__pycache__/preferences.cpython-310.pyc +0 -0
  120. package/src/plugins/__pycache__/update.cpython-310.pyc +0 -0
  121. package/src/plugins/agents.py +2 -2
  122. package/src/plugins/backup.py +5 -4
  123. package/src/plugins/core_rules.py +37 -16
  124. package/src/plugins/episodic_memory.py +14 -5
  125. package/src/plugins/evolution.py +6 -2
  126. package/src/plugins/guard.py +20 -11
  127. package/src/plugins/update.py +256 -0
  128. package/src/requirements.txt +12 -0
  129. package/src/scripts/check-context.py +8 -3
  130. package/src/scripts/deep-sleep/__pycache__/extract.cpython-314.pyc +0 -0
  131. package/src/scripts/deep-sleep/apply_findings.py +514 -169
  132. package/src/scripts/deep-sleep/collect.py +480 -0
  133. package/src/scripts/deep-sleep/extract-prompt.md +233 -0
  134. package/src/scripts/deep-sleep/extract.py +249 -0
  135. package/src/scripts/deep-sleep/synthesize-prompt.md +168 -0
  136. package/src/scripts/deep-sleep/synthesize.py +191 -0
  137. package/src/scripts/nexo-auto-update.py +4 -211
  138. package/src/scripts/nexo-backup.sh +5 -13
  139. package/src/scripts/nexo-brain-activation.sh +26 -26
  140. package/src/scripts/nexo-catchup.py +36 -25
  141. package/src/scripts/nexo-cognitive-decay.py +7 -3
  142. package/src/scripts/nexo-daily-self-audit.py +44 -15
  143. package/src/scripts/nexo-deep-sleep.sh +31 -16
  144. package/src/scripts/nexo-evolution-run.py +21 -11
  145. package/src/scripts/nexo-followup-hygiene.py +6 -4
  146. package/src/scripts/nexo-github-monitor.py +12 -8
  147. package/src/scripts/nexo-immune.py +6 -4
  148. package/src/scripts/nexo-inbox-hook.sh +2 -1
  149. package/src/scripts/nexo-install.py +4 -225
  150. package/src/scripts/nexo-learning-housekeep.py +7 -3
  151. package/src/scripts/nexo-learning-validator.py +1 -22
  152. package/src/scripts/nexo-migrate.py +9 -3
  153. package/src/scripts/nexo-postmortem-consolidator.py +17 -10
  154. package/src/scripts/nexo-pre-commit.py +3 -1
  155. package/src/scripts/nexo-prevent-sleep.sh +29 -0
  156. package/src/scripts/nexo-proactive-dashboard.py +5 -4
  157. package/src/scripts/nexo-runtime-preflight.py +59 -55
  158. package/src/scripts/nexo-send-email.py +1 -1
  159. package/src/scripts/nexo-send-reply.py +3 -1
  160. package/src/scripts/nexo-sleep.py +13 -9
  161. package/src/scripts/nexo-snapshot-restore.sh +2 -1
  162. package/src/scripts/nexo-synthesis.py +11 -7
  163. package/src/scripts/nexo-tcc-approve.sh +79 -0
  164. package/src/scripts/nexo-update.sh +161 -0
  165. package/src/scripts/nexo-watchdog-smoke.py +18 -13
  166. package/src/scripts/nexo-watchdog.sh +22 -13
  167. package/src/server.py +77 -28
  168. package/src/storage_router.py +6 -2
  169. package/src/tools_learnings.py +6 -6
  170. package/src/tools_menu.py +1 -1
  171. package/src/tools_reminders_crud.py +10 -8
  172. package/src/tools_sessions.py +76 -4
  173. package/templates/CLAUDE.md.template +14 -80
  174. package/templates/launchagents/README.md +7 -7
  175. package/templates/launchagents/com.nexo.auto-close-sessions.plist +5 -1
  176. package/templates/launchagents/com.nexo.catchup.plist +4 -0
  177. package/templates/launchagents/com.nexo.cognitive-decay.plist +7 -0
  178. package/templates/launchagents/com.nexo.dashboard.plist +5 -1
  179. package/templates/launchagents/com.nexo.deep-sleep.plist +4 -0
  180. package/templates/launchagents/com.nexo.evolution.plist +4 -0
  181. package/templates/launchagents/com.nexo.followup-hygiene.plist +4 -0
  182. package/templates/launchagents/com.nexo.github-monitor.plist +3 -1
  183. package/templates/launchagents/com.nexo.immune.plist +4 -0
  184. package/templates/launchagents/com.nexo.postmortem.plist +4 -0
  185. package/templates/launchagents/com.nexo.self-audit.plist +4 -0
  186. package/templates/launchagents/com.nexo.synthesis.plist +4 -0
  187. package/templates/launchagents/com.nexo.watchdog.plist +4 -0
  188. package/templates/openclaw.json +1 -1
  189. package/tests/conftest.py +2 -2
  190. package/tests/test_cognitive.py +7 -6
  191. package/tests/test_migrations.py +26 -0
  192. package/src/__pycache__/auto_close_sessions.cpython-314.pyc +0 -0
  193. package/src/__pycache__/claim_graph.cpython-314.pyc +0 -0
  194. package/src/__pycache__/evolution_cycle.cpython-314.pyc +0 -0
  195. package/src/__pycache__/kg_populate.cpython-314.pyc +0 -0
  196. package/src/__pycache__/knowledge_graph.cpython-314.pyc +0 -0
  197. package/src/__pycache__/maintenance.cpython-314.pyc +0 -0
  198. package/src/__pycache__/migrate_embeddings.cpython-314.pyc +0 -0
  199. package/src/__pycache__/plugin_loader.cpython-314.pyc +0 -0
  200. package/src/__pycache__/server.cpython-314.pyc +0 -0
  201. package/src/__pycache__/storage_router.cpython-314.pyc +0 -0
  202. package/src/__pycache__/tools_coordination.cpython-314.pyc +0 -0
  203. package/src/__pycache__/tools_credentials.cpython-314.pyc +0 -0
  204. package/src/__pycache__/tools_learnings.cpython-314.pyc +0 -0
  205. package/src/__pycache__/tools_menu.cpython-314.pyc +0 -0
  206. package/src/__pycache__/tools_reminders.cpython-314.pyc +0 -0
  207. package/src/__pycache__/tools_reminders_crud.cpython-314.pyc +0 -0
  208. package/src/__pycache__/tools_sessions.cpython-314.pyc +0 -0
  209. package/src/__pycache__/tools_task_history.cpython-314.pyc +0 -0
  210. package/src/dashboard/__pycache__/__init__.cpython-314.pyc +0 -0
  211. package/src/dashboard/__pycache__/app.cpython-314.pyc +0 -0
  212. package/src/hooks/__pycache__/auto_capture.cpython-314.pyc +0 -0
  213. package/src/plugins/__pycache__/__init__.cpython-314.pyc +0 -0
  214. package/src/plugins/__pycache__/agents.cpython-314.pyc +0 -0
  215. package/src/plugins/__pycache__/artifact_registry.cpython-314.pyc +0 -0
  216. package/src/plugins/__pycache__/backup.cpython-314.pyc +0 -0
  217. package/src/plugins/__pycache__/cognitive_memory.cpython-314.pyc +0 -0
  218. package/src/plugins/__pycache__/core_rules.cpython-314.pyc +0 -0
  219. package/src/plugins/__pycache__/cortex.cpython-314.pyc +0 -0
  220. package/src/plugins/__pycache__/entities.cpython-314.pyc +0 -0
  221. package/src/plugins/__pycache__/episodic_memory.cpython-314.pyc +0 -0
  222. package/src/plugins/__pycache__/evolution.cpython-314.pyc +0 -0
  223. package/src/plugins/__pycache__/guard.cpython-314.pyc +0 -0
  224. package/src/plugins/__pycache__/knowledge_graph_tools.cpython-314.pyc +0 -0
  225. package/src/plugins/__pycache__/preferences.cpython-314.pyc +0 -0
  226. package/src/rules/__pycache__/__init__.cpython-314.pyc +0 -0
  227. package/src/rules/__pycache__/migrate.cpython-314.pyc +0 -0
  228. package/src/scripts/__pycache__/check-context.cpython-314.pyc +0 -0
  229. package/src/scripts/__pycache__/nexo-auto-update.cpython-314.pyc +0 -0
  230. package/src/scripts/__pycache__/nexo-catchup.cpython-314.pyc +0 -0
  231. package/src/scripts/__pycache__/nexo-cognitive-decay.cpython-314.pyc +0 -0
  232. package/src/scripts/__pycache__/nexo-daily-self-audit.cpython-314.pyc +0 -0
  233. package/src/scripts/__pycache__/nexo-evolution-run.cpython-314.pyc +0 -0
  234. package/src/scripts/__pycache__/nexo-followup-hygiene.cpython-314.pyc +0 -0
  235. package/src/scripts/__pycache__/nexo-github-monitor.cpython-314.pyc +0 -0
  236. package/src/scripts/__pycache__/nexo-immune.cpython-314.pyc +0 -0
  237. package/src/scripts/__pycache__/nexo-install.cpython-314.pyc +0 -0
  238. package/src/scripts/__pycache__/nexo-learning-housekeep.cpython-314.pyc +0 -0
  239. package/src/scripts/__pycache__/nexo-learning-validator.cpython-314.pyc +0 -0
  240. package/src/scripts/__pycache__/nexo-migrate.cpython-314.pyc +0 -0
  241. package/src/scripts/__pycache__/nexo-postmortem-consolidator.cpython-314.pyc +0 -0
  242. package/src/scripts/__pycache__/nexo-pre-commit.cpython-314.pyc +0 -0
  243. package/src/scripts/__pycache__/nexo-proactive-dashboard.cpython-314.pyc +0 -0
  244. package/src/scripts/__pycache__/nexo-reflection.cpython-314.pyc +0 -0
  245. package/src/scripts/__pycache__/nexo-runtime-preflight.cpython-314.pyc +0 -0
  246. package/src/scripts/__pycache__/nexo-send-email.cpython-314.pyc +0 -0
  247. package/src/scripts/__pycache__/nexo-send-reply.cpython-314.pyc +0 -0
  248. package/src/scripts/__pycache__/nexo-sleep.cpython-314.pyc +0 -0
  249. package/src/scripts/__pycache__/nexo-synthesis.cpython-314.pyc +0 -0
  250. package/src/scripts/__pycache__/nexo-watchdog-smoke.cpython-314.pyc +0 -0
  251. package/src/scripts/deep-sleep/__pycache__/analyze_session.cpython-314.pyc +0 -0
  252. package/src/scripts/deep-sleep/__pycache__/apply_findings.cpython-314.pyc +0 -0
  253. package/src/scripts/deep-sleep/__pycache__/collect_transcripts.cpython-314.pyc +0 -0
  254. package/src/scripts/deep-sleep/analyze_session.py +0 -217
  255. package/src/scripts/deep-sleep/collect_transcripts.py +0 -145
  256. package/src/scripts/deep-sleep/prompt.md +0 -109
  257. package/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  258. package/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  259. package/tests/__pycache__/test_cognitive.cpython-314-pytest-9.0.2.pyc +0 -0
  260. package/tests/__pycache__/test_knowledge_graph.cpython-314-pytest-9.0.2.pyc +0 -0
  261. package/tests/__pycache__/test_migrations.cpython-314-pytest-9.0.2.pyc +0 -0
@@ -1,227 +1,6 @@
1
1
  #!/usr/bin/env python3
2
- """NEXO Install First-time setup for NEXO Brain.
3
-
4
- Creates ~/.nexo/ directory structure, initializes databases,
5
- copies scripts/hooks/plugins from the repo, and sets NEXO_HOME
6
- in the user's shell profile.
7
-
8
- Usage:
9
- python3 nexo-install.py # interactive install
10
- python3 nexo-install.py --yes # skip confirmations
11
- python3 nexo-install.py --nexo-home /path # custom location
12
- """
13
-
14
- import argparse
15
- import json
16
- import os
17
- import shutil
18
- import subprocess
2
+ """DEPRECATED: Use 'npx nexo-brain' instead. This installer is no longer maintained."""
19
3
  import sys
20
- from datetime import datetime
21
- from pathlib import Path
22
-
23
- REPO_ROOT = Path(__file__).resolve().parent.parent.parent # nexo/src/scripts -> nexo/
24
-
25
- # Required directory structure
26
- NEXO_DIRS = [
27
- "db",
28
- "brain",
29
- "logs",
30
- "operations",
31
- "coordination",
32
- "scripts",
33
- "hooks",
34
- "plugins",
35
- "backups",
36
- "memory",
37
- "docs",
38
- "projects",
39
- "learnings",
40
- "agents",
41
- "skills",
42
- ]
43
-
44
-
45
- def get_version() -> str:
46
- """Read version from package.json."""
47
- pkg = REPO_ROOT / "package.json"
48
- if not pkg.exists():
49
- return "0.0.0"
50
- return json.loads(pkg.read_text()).get("version", "0.0.0")
51
-
52
-
53
- def detect_shell() -> tuple[str, Path]:
54
- """Detect user's shell and return (shell_name, profile_path)."""
55
- shell = os.environ.get("SHELL", "/bin/bash")
56
- home = Path.home()
57
-
58
- if "zsh" in shell:
59
- return "zsh", home / ".zshrc"
60
- elif "fish" in shell:
61
- return "fish", home / ".config" / "fish" / "config.fish"
62
- else:
63
- # bash — prefer .bashrc, fall back to .bash_profile
64
- bashrc = home / ".bashrc"
65
- if bashrc.exists():
66
- return "bash", bashrc
67
- return "bash", home / ".bash_profile"
68
-
69
-
70
- def create_directory_structure(nexo_home: Path):
71
- """Create all required directories."""
72
- for d in NEXO_DIRS:
73
- (nexo_home / d).mkdir(parents=True, exist_ok=True)
74
- print(f" Created {len(NEXO_DIRS)} directories in {nexo_home}")
75
-
76
-
77
- def initialize_databases(nexo_home: Path):
78
- """Initialize empty databases with schema."""
79
- os.environ["NEXO_HOME"] = str(nexo_home)
80
- os.environ["NEXO_SKIP_FS_INDEX"] = "1"
81
-
82
- src_dir = REPO_ROOT / "src"
83
- if str(src_dir) not in sys.path:
84
- sys.path.insert(0, str(src_dir))
85
-
86
- try:
87
- from db import init_db
88
- init_db()
89
- print(" Initialized nexo.db with schema.")
90
- except Exception as e:
91
- print(f" WARNING: DB init error: {e}", file=sys.stderr)
92
- print(" You can run schema migrations later with nexo-migrate.py")
93
-
94
-
95
- def copy_repo_files(nexo_home: Path):
96
- """Copy hooks, scripts, and plugins from the repo."""
97
- src = REPO_ROOT / "src"
98
-
99
- # Copy scripts
100
- scripts_src = src / "scripts"
101
- scripts_dst = nexo_home / "scripts"
102
- if scripts_src.exists():
103
- count = 0
104
- for f in scripts_src.iterdir():
105
- if f.is_file() and not f.name.startswith("__"):
106
- shutil.copy2(f, scripts_dst / f.name)
107
- count += 1
108
- print(f" Copied {count} scripts.")
109
-
110
- # Copy plugins
111
- plugins_src = src / "plugins"
112
- plugins_dst = nexo_home / "plugins"
113
- if plugins_src.exists():
114
- count = 0
115
- for f in plugins_src.iterdir():
116
- if f.is_file() and f.suffix == ".py" and not f.name.startswith("__"):
117
- shutil.copy2(f, plugins_dst / f.name)
118
- count += 1
119
- print(f" Copied {count} plugins.")
120
-
121
- # Copy hooks (templates directory)
122
- hooks_src = REPO_ROOT / "templates" / "hooks"
123
- hooks_dst = nexo_home / "hooks"
124
- if hooks_src.exists():
125
- count = 0
126
- for f in hooks_src.iterdir():
127
- if f.is_file():
128
- shutil.copy2(f, hooks_dst / f.name)
129
- count += 1
130
- if count:
131
- print(f" Copied {count} hooks.")
132
-
133
-
134
- def set_nexo_home_env(nexo_home: Path, shell_name: str, profile_path: Path):
135
- """Add NEXO_HOME export to shell profile if not already present."""
136
- export_line = f'export NEXO_HOME="{nexo_home}"'
137
-
138
- if profile_path.exists():
139
- content = profile_path.read_text()
140
- if "NEXO_HOME" in content:
141
- print(f" NEXO_HOME already set in {profile_path}")
142
- return
143
- else:
144
- content = ""
145
-
146
- with open(profile_path, "a") as f:
147
- f.write(f"\n# NEXO Brain — cognitive co-operator\n")
148
- f.write(f"{export_line}\n")
149
-
150
- print(f" Added NEXO_HOME to {profile_path}")
151
-
152
-
153
- def write_version_json(nexo_home: Path, version: str):
154
- """Write version.json."""
155
- data = {
156
- "version": version,
157
- "installed_at": datetime.now().isoformat(timespec="seconds"),
158
- "nexo_home": str(nexo_home),
159
- }
160
- (nexo_home / "version.json").write_text(json.dumps(data, indent=2) + "\n")
161
- print(f" Written version.json (v{version})")
162
-
163
-
164
- def main():
165
- parser = argparse.ArgumentParser(description="NEXO Brain — First-time setup")
166
- parser.add_argument("--yes", "-y", action="store_true", help="Skip confirmations")
167
- parser.add_argument("--nexo-home", type=str, default=None, help="Custom NEXO_HOME path")
168
- args = parser.parse_args()
169
-
170
- nexo_home = Path(args.nexo_home) if args.nexo_home else Path.home() / ".nexo"
171
- version = get_version()
172
- shell_name, profile_path = detect_shell()
173
-
174
- print(f"NEXO Brain Installer v{version}")
175
- print(f"=" * 40)
176
- print(f" NEXO_HOME: {nexo_home}")
177
- print(f" Shell: {shell_name} ({profile_path})")
178
- print(f" Version: {version}")
179
- print()
180
-
181
- if nexo_home.exists() and (nexo_home / "version.json").exists():
182
- existing = json.loads((nexo_home / "version.json").read_text())
183
- print(f" Existing installation found: v{existing.get('version', '?')}")
184
- print(f" Use nexo-migrate.py to upgrade instead.")
185
- if not args.yes:
186
- resp = input(" Continue anyway? (y/N) ")
187
- if resp.lower() != "y":
188
- print("Aborted.")
189
- return
190
-
191
- if not args.yes:
192
- resp = input("Install NEXO Brain? (Y/n) ")
193
- if resp.lower() == "n":
194
- print("Aborted.")
195
- return
196
-
197
- print()
198
- print("Installing...")
199
-
200
- # Step 1: Create directories
201
- create_directory_structure(nexo_home)
202
-
203
- # Step 2: Initialize databases
204
- initialize_databases(nexo_home)
205
-
206
- # Step 3: Copy files from repo
207
- copy_repo_files(nexo_home)
208
-
209
- # Step 4: Set NEXO_HOME in shell profile
210
- set_nexo_home_env(nexo_home, shell_name, profile_path)
211
-
212
- # Step 5: Write version.json
213
- write_version_json(nexo_home, version)
214
-
215
- print()
216
- print("=" * 40)
217
- print("NEXO Brain installed successfully!")
218
- print()
219
- print("Next steps:")
220
- print(f" 1. Restart your shell or run: source {profile_path}")
221
- print(f" 2. Add the NEXO MCP server to your Claude Code config:")
222
- print(f" claude mcp add nexo -- python3 {REPO_ROOT / 'src' / 'server.py'}")
223
- print(f" 3. Start a Claude Code session — NEXO will initialize automatically.")
224
-
225
-
226
- if __name__ == "__main__":
227
- main()
4
+ print("This installer is deprecated. Please use: npx nexo-brain")
5
+ print("See: https://github.com/wazionapps/nexo#installation")
6
+ sys.exit(1)
@@ -1,4 +1,3 @@
1
- import os
2
1
  #!/usr/bin/env python3
3
2
  """NEXO Learning Housekeeping — Nightly dedup, weight adjustment, and review.
4
3
 
@@ -7,6 +6,7 @@ detects duplicates via semantic similarity, and archives stale learnings.
7
6
  """
8
7
 
9
8
  import json
9
+ import os
10
10
  import sqlite3
11
11
  import sys
12
12
  import time
@@ -14,10 +14,14 @@ from datetime import datetime, timedelta
14
14
  from pathlib import Path
15
15
 
16
16
  NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
17
+ # Auto-detect: if running from repo (src/scripts/), use src/ as NEXO_CODE
18
+ _script_dir = Path(__file__).resolve().parent
19
+ _repo_src = _script_dir.parent # src/scripts/ -> src/
20
+ NEXO_CODE = Path(os.environ.get("NEXO_CODE", str(_repo_src) if (_repo_src / "server.py").exists() else str(NEXO_HOME)))
17
21
 
18
- sys.path.insert(0, str(NEXO_HOME / "nexo-mcp"))
22
+ sys.path.insert(0, str(NEXO_CODE))
19
23
 
20
- DB_PATH = NEXO_HOME / "nexo-mcp" / "db" / "nexo.db"
24
+ DB_PATH = NEXO_HOME / "data" / "nexo.db"
21
25
  STATE_FILE = NEXO_HOME / "operations" / ".catchup-state.json"
22
26
 
23
27
  # Weight adjustment rates
@@ -30,7 +30,7 @@ from pathlib import Path
30
30
 
31
31
  NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
32
32
 
33
- NEXO_DB = Path.home() / ".nexo" / "nexo.db"
33
+ NEXO_DB = NEXO_HOME / "data" / "nexo.db"
34
34
  CLAUDE_CLI = Path.home() / ".local" / "bin" / "claude"
35
35
 
36
36
 
@@ -111,27 +111,6 @@ Rules:
111
111
 
112
112
  # Try CLI first, fall back to mechanical similarity
113
113
  if CLAUDE_CLI.exists():
114
- env = os.environ.copy()
115
- env.pop("CLAUDECODE", None)
116
- env.pop("CLAUDE_CODE", None)
117
-
118
- try:
119
- result = subprocess.run(
120
- [str(CLAUDE_CLI), "-p", prompt, "--model", "opus",
121
- "--allowedTools", "Read,Write,Edit,Glob,Grep"],
122
- capture_output=True, text=True, timeout=60, env=env
123
- )
124
- if result.returncode == 0:
125
- text = result.stdout.strip()
126
- # Strip markdown fences if present
127
- if "```json" in text:
128
- text = text.split("```json")[1].split("```")[0]
129
- elif "```" in text:
130
- text = text.split("```")[1].split("```")[0]
131
- return json.loads(text.strip())
132
- except (subprocess.TimeoutExpired, json.JSONDecodeError, Exception):
133
- pass # Fall through to mechanical fallback
134
-
135
114
  # Fallback: mechanical SequenceMatcher (original logic)
136
115
  return _mechanical_validate(finding, learnings)
137
116
 
@@ -73,10 +73,16 @@ def backup_databases() -> str:
73
73
  backup_dir = NEXO_HOME / "backups" / f"pre-migrate-{ts}"
74
74
  backup_dir.mkdir(parents=True, exist_ok=True)
75
75
 
76
- db_dir = NEXO_HOME / "db"
77
- if db_dir.exists():
78
- for db_file in db_dir.glob("*.db*"):
76
+ data_dir = NEXO_HOME / "data"
77
+ if data_dir.exists():
78
+ for db_file in data_dir.glob("*.db*"):
79
79
  shutil.copy2(db_file, backup_dir / db_file.name)
80
+ # Also check legacy db/ location
81
+ legacy_db_dir = NEXO_HOME / "db"
82
+ if legacy_db_dir.exists():
83
+ for db_file in legacy_db_dir.glob("*.db*"):
84
+ if not (backup_dir / db_file.name).exists():
85
+ shutil.copy2(db_file, backup_dir / db_file.name)
80
86
 
81
87
  # Also backup version.json
82
88
  vfile = NEXO_HOME / "version.json"
@@ -29,10 +29,14 @@ from pathlib import Path
29
29
  HOME = Path.home()
30
30
  NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(HOME / ".nexo")))
31
31
 
32
- # Add nexo-mcp to path for cognitive engine (Stage 3)
33
- sys.path.insert(0, str(NEXO_HOME))
34
-
35
- NEXO_DB = NEXO_HOME / "db" / "nexo.db"
32
+ # Add NEXO_HOME to path for cognitive engine (Stage 3)
33
+ # Auto-detect: if running from repo (src/scripts/), use src/ as NEXO_CODE
34
+ _script_dir = Path(__file__).resolve().parent
35
+ _repo_src = _script_dir.parent # src/scripts/ -> src/
36
+ NEXO_CODE = Path(os.environ.get("NEXO_CODE", str(_repo_src) if (_repo_src / "server.py").exists() else str(NEXO_HOME)))
37
+ sys.path.insert(0, str(NEXO_CODE))
38
+
39
+ NEXO_DB = NEXO_HOME / "data" / "nexo.db"
36
40
  # Memory directory — adjust to match your project's memory location
37
41
  MEMORY_DIR = NEXO_HOME / "memory"
38
42
  MEMORY_INDEX = MEMORY_DIR / "MEMORY.md"
@@ -109,7 +113,7 @@ def consolidate_with_cli(data: dict) -> bool:
109
113
 
110
114
  diaries_with_critique = [
111
115
  d for d in data["diaries"]
112
- if d.get("self_critique") and not (d["self_critique"] or "").strip().lower().startswith("no self-critique")
116
+ if d.get("self_critique") and not str(d["self_critique"]).strip().lower().startswith("no self-critique")
113
117
  ]
114
118
 
115
119
  if not diaries_with_critique:
@@ -121,8 +125,10 @@ def consolidate_with_cli(data: dict) -> bool:
121
125
  if len(diaries_json) > 12000:
122
126
  diaries_json = diaries_json[:12000] + "\n... (truncated)"
123
127
 
124
- prompt = f"""You are NEXO's nightly consolidator. Your job is to review the self-critiques
125
- from today and decide which deserve to become permanent rules (feedback_postmortem_*.md).
128
+ prompt = f"""FIRST: Call nexo_startup(task='nightly postmortem consolidation') to register this session.
129
+
130
+ You are NEXO's nightly consolidator. Your job is to review the self-critiques
131
+ from today and decide which deserve to become permanent rules. Use nexo_learning_add for permanent rules and nexo_followup_create for action items.
126
132
 
127
133
  DATE: {data['date']}
128
134
  SESSIONS TODAY: {len(data['diaries'])} total, {len(diaries_with_critique)} with self-critique
@@ -181,16 +187,17 @@ INSTRUCTIONS:
181
187
  Execute without asking."""
182
188
 
183
189
  log(f"Stage 2: Invoking Claude CLI (opus) with {len(diaries_with_critique)} critiques...")
184
-
185
190
  env = os.environ.copy()
191
+ env["NEXO_HEADLESS"] = "1" # Skip stop hook post-mortem
186
192
  env.pop("CLAUDECODE", None)
187
193
  env.pop("CLAUDE_CODE", None)
188
194
 
189
195
  try:
190
196
  result = subprocess.run(
191
197
  [str(CLAUDE_CLI), "-p", prompt, "--model", "opus",
192
- "--allowedTools", "Read,Write,Edit,Glob,Grep"],
193
- capture_output=True, text=True, timeout=300, env=env
198
+ "--output-format", "text",
199
+ "--allowedTools", "Read,Write,Edit,Glob,Grep,Bash,mcp__nexo__*"],
200
+ capture_output=True, text=True, timeout=21600, env=env
194
201
  )
195
202
 
196
203
  if result.returncode != 0:
@@ -4,12 +4,14 @@ NEXO Pre-Commit Validator — Dynamic version
4
4
  Queries nexo.db learnings to surface relevant warnings/blockers before commit.
5
5
  Runs standalone (no MCP dependency, just sqlite3).
6
6
  """
7
+ import os
7
8
  import sqlite3
8
9
  import subprocess
9
10
  import sys
10
11
  from pathlib import Path
11
12
 
12
- NEXO_DB = Path.home() / ".nexo" / "nexo-mcp" / "db" / "nexo.db"
13
+ NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
14
+ NEXO_DB = NEXO_HOME / "data" / "nexo.db"
13
15
 
14
16
 
15
17
  def get_staged_files():
@@ -0,0 +1,29 @@
1
+ #!/bin/bash
2
+ # NEXO Prevent Sleep — keeps the machine awake so nocturnal processes run.
3
+ #
4
+ # macOS: uses caffeinate -s -i (prevent system + idle sleep)
5
+ # Linux: uses systemd-inhibit or caffeine if available, otherwise no-op
6
+ #
7
+ # Run as LaunchAgent (KeepAlive) or systemd service.
8
+
9
+ case "$(uname -s)" in
10
+ Darwin)
11
+ exec caffeinate -s -i -w $$
12
+ ;;
13
+ Linux)
14
+ if command -v systemd-inhibit &>/dev/null; then
15
+ exec systemd-inhibit --what=idle:sleep --who=nexo --why="NEXO nocturnal processes" sleep infinity
16
+ elif command -v caffeine &>/dev/null; then
17
+ exec caffeine
18
+ else
19
+ echo "[NEXO] No sleep prevention tool found. Install systemd-inhibit or caffeine."
20
+ echo "[NEXO] Nocturnal processes may not run if the system sleeps."
21
+ # Keep running so launchd/systemd doesn't restart loop
22
+ exec sleep infinity
23
+ fi
24
+ ;;
25
+ *)
26
+ echo "[NEXO] Unsupported platform: $(uname -s)"
27
+ exit 1
28
+ ;;
29
+ esac
@@ -21,7 +21,7 @@ from pathlib import Path
21
21
 
22
22
  NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
23
23
 
24
- NEXO_DB = NEXO_HOME / "nexo-mcp" / "db" / "nexo.db"
24
+ NEXO_DB = NEXO_HOME / "data" / "nexo.db"
25
25
 
26
26
 
27
27
  def get_db():
@@ -38,6 +38,7 @@ def check_overdue_followups() -> list[dict]:
38
38
  SELECT id, description, date, created_at, reasoning
39
39
  FROM followups
40
40
  WHERE status NOT LIKE 'COMPLETED%'
41
+ AND status NOT IN ('DELETED','archived','blocked','waiting')
41
42
  AND date IS NOT NULL AND date != ''
42
43
  ORDER BY date ASC
43
44
  """).fetchall()
@@ -151,7 +152,7 @@ def check_session_gaps() -> list[dict]:
151
152
  def check_evolution_status() -> list[dict]:
152
153
  """Check if evolution system is healthy."""
153
154
  alerts = []
154
- obj_file = NEXO_HOME / "cortex" / "evolution-objective.json"
155
+ obj_file = NEXO_HOME / "brain" / "evolution-objective.json"
155
156
  if obj_file.exists():
156
157
  obj = json.loads(obj_file.read_text())
157
158
  if not obj.get("evolution_enabled", True):
@@ -219,7 +220,7 @@ def check_recurring_errors() -> list[dict]:
219
220
  alerts.append({
220
221
  "type": "recurring_errors",
221
222
  "severity": "medium",
222
- "title": f"Categoria '{r['category']}' tiene {r['cnt']} learnings this week — possible systemic issue",
223
+ "title": f"Category '{r['category']}' has {r['cnt']} learnings this week — possible systemic issue",
223
224
  "category": r["category"],
224
225
  "count": r["cnt"],
225
226
  })
@@ -231,7 +232,7 @@ def check_cron_health() -> list[dict]:
231
232
  alerts = []
232
233
 
233
234
  # Check backup cron
234
- backup_dir = NEXO_HOME / "nexo-mcp" / "backups"
235
+ backup_dir = NEXO_HOME / "backups"
235
236
  if backup_dir.exists():
236
237
  backups = sorted(backup_dir.glob("nexo-*.db"), key=lambda p: p.stat().st_mtime, reverse=True)
237
238
  if backups:
@@ -4,13 +4,14 @@ NEXO Runtime Preflight
4
4
 
5
5
  Runs safe end-to-end smoke tests for Cortex and Evolution using a temporary
6
6
  workspace and a copied SQLite database. No external API calls are performed.
7
- Results are written to ~/.nexo/logs/runtime-preflight-summary.json.
7
+ Results are written to NEXO_HOME/logs/runtime-preflight-summary.json.
8
8
  """
9
9
 
10
10
  from __future__ import annotations
11
11
 
12
12
  import importlib.util
13
13
  import json
14
+ import os
14
15
  import shutil
15
16
  import sqlite3
16
17
  import sys
@@ -20,13 +21,25 @@ from datetime import datetime
20
21
  from pathlib import Path
21
22
 
22
23
  HOME = Path.home()
23
- CLAUDE_DIR = HOME / ".nexo"
24
- LOG_DIR = CLAUDE_DIR / "logs"
24
+ NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(HOME / ".nexo")))
25
+ # Auto-detect: if running from repo (src/scripts/), use src/ as NEXO_CODE
26
+ _script_dir = Path(__file__).resolve().parent
27
+ _repo_src = _script_dir.parent # src/scripts/ -> src/
28
+ NEXO_CODE = Path(os.environ.get("NEXO_CODE", str(_repo_src) if (_repo_src / "server.py").exists() else str(NEXO_HOME)))
29
+
30
+ LOG_DIR = NEXO_HOME / "logs"
25
31
  LOG_DIR.mkdir(parents=True, exist_ok=True)
26
32
  SUMMARY_FILE = LOG_DIR / "runtime-preflight-summary.json"
27
- DB_FILE = CLAUDE_DIR / "nexo-mcp" / "db" / "nexo.db"
28
- CORTEX_OBJECTIVE = CLAUDE_DIR / "cortex" / "evolution-objective.json"
29
- CORTEX_PROMPT = CLAUDE_DIR / "cortex" / "evolution-prompt.md"
33
+ DB_FILE = NEXO_HOME / "data" / "nexo.db"
34
+ # Evolution config: NEXO_HOME/brain/ (canonical), NEXO_HOME/cortex/ (legacy fallback), NEXO_CODE (dev fallback)
35
+ def _find_evolution_file(name: str) -> Path:
36
+ for candidate in [NEXO_HOME / "brain" / name, NEXO_HOME / "cortex" / name, NEXO_CODE / name]:
37
+ if candidate.exists():
38
+ return candidate
39
+ return NEXO_HOME / "brain" / name # default canonical path
40
+
41
+ CORTEX_OBJECTIVE = _find_evolution_file("evolution-objective.json")
42
+ CORTEX_PROMPT = _find_evolution_file("evolution-prompt.md")
30
43
 
31
44
 
32
45
  def _load_module(name: str, path: Path):
@@ -143,7 +156,7 @@ def main() -> int:
143
156
  temp_db = temp_root / "nexo.db"
144
157
  shutil.copy2(DB_FILE, temp_db)
145
158
 
146
- temp_cortex_dir = temp_root / "cortex"
159
+ temp_cortex_dir = temp_root / "brain"
147
160
  temp_logs_dir = temp_root / "logs"
148
161
  temp_coord_dir = temp_root / "coordination"
149
162
  temp_daily_dir = temp_root / "daily_summaries"
@@ -159,18 +172,23 @@ def main() -> int:
159
172
 
160
173
  temp_objective = temp_cortex_dir / "evolution-objective.json"
161
174
  temp_prompt = temp_cortex_dir / "evolution-prompt.md"
162
- shutil.copy2(CORTEX_OBJECTIVE, temp_objective)
175
+ if CORTEX_OBJECTIVE.exists():
176
+ shutil.copy2(CORTEX_OBJECTIVE, temp_objective)
177
+ else:
178
+ # Create a minimal objective so evolution smoke tests can proceed
179
+ temp_objective.write_text(json.dumps({"evolution_enabled": True, "dimensions": {}}, indent=2))
163
180
  if CORTEX_PROMPT.exists():
164
181
  shutil.copy2(CORTEX_PROMPT, temp_prompt)
165
- shutil.copy2(CLAUDE_DIR / "scripts" / "nexo-snapshot-restore.sh", temp_scripts_dir / "nexo-snapshot-restore.sh")
182
+ snapshot_restore = NEXO_CODE / "scripts" / "nexo-snapshot-restore.sh"
183
+ if snapshot_restore.exists():
184
+ shutil.copy2(snapshot_restore, temp_scripts_dir / "nexo-snapshot-restore.sh")
166
185
 
167
186
  temp_api_key = temp_root / "anthropic-api-key.txt"
168
187
  temp_api_key.write_text("smoke-test-key")
169
188
 
170
- evolution_cycle = _load_module("evolution_cycle", CLAUDE_DIR / "cortex" / "evolution_cycle.py")
189
+ evolution_cycle = _load_module("evolution_cycle", NEXO_CODE / "evolution_cycle.py")
171
190
  evolution_cycle.NEXO_DB = temp_db
172
- evolution_cycle.CLAUDE_DIR = temp_root
173
- evolution_cycle.CORTEX_DIR = temp_cortex_dir
191
+ evolution_cycle.NEXO_HOME = temp_root
174
192
  evolution_cycle.SANDBOX_DIR = temp_sandbox_dir
175
193
  evolution_cycle.SNAPSHOTS_DIR = temp_snapshots_dir
176
194
  evolution_cycle.OBJECTIVE_FILE = temp_objective
@@ -189,7 +207,7 @@ def main() -> int:
189
207
  "restore_ok": restore_ok,
190
208
  }
191
209
 
192
- cortex = _load_module("cortex_wrapper", CLAUDE_DIR / "cortex" / "cortex-wrapper.py")
210
+ cortex = _load_module("cortex_plugin", NEXO_CODE / "plugins" / "cortex.py")
193
211
  cortex.NEXO_DB = temp_db
194
212
  cortex.DRY_RUN = True
195
213
  cortex.BRIEFING_FILE = temp_cortex_dir / "briefing.json"
@@ -209,50 +227,36 @@ def main() -> int:
209
227
  cortex._check_health_endpoints = lambda: []
210
228
  cortex._call_anthropic = lambda prompt, model=None, max_tokens=4096: _fake_cortex_response(model or "")
211
229
 
212
- state = cortex.load_state()
213
- state = cortex.run_perception_cycle(state)
214
- if not cortex.BRIEFING_FILE.exists() or not cortex.HEALTH_FILE.exists():
215
- raise RuntimeError("cortex perception did not write briefing/health")
216
-
217
- before_logs = sqlite3.connect(str(temp_db)).execute("SELECT COUNT(*) FROM evolution_log").fetchone()[0]
218
- state = cortex.run_evolution_cycle(state)
219
- after_logs = sqlite3.connect(str(temp_db)).execute("SELECT COUNT(*) FROM evolution_log").fetchone()[0]
220
- if after_logs <= before_logs:
221
- raise RuntimeError("cortex evolution smoke did not log proposals")
222
- summary["checks"]["cortex_wrapper"] = {
223
- "state_status": state.get("status"),
224
- "briefing_written": cortex.BRIEFING_FILE.exists(),
225
- "health_written": cortex.HEALTH_FILE.exists(),
226
- "evolution_logs_added": after_logs - before_logs,
230
+ # Smoke test: verify cortex plugin loads and has expected tools
231
+ cortex_path = NEXO_CODE / "plugins" / "cortex.py"
232
+ if cortex_path.exists():
233
+ import importlib.util
234
+ spec = importlib.util.spec_from_file_location("cortex_plugin", str(cortex_path))
235
+ cortex_mod = importlib.util.module_from_spec(spec)
236
+ spec.loader.exec_module(cortex_mod)
237
+ assert hasattr(cortex_mod, 'TOOLS'), "cortex plugin missing TOOLS"
238
+ tool_names = [t[1] for t in cortex_mod.TOOLS]
239
+ assert "nexo_cortex_check" in tool_names, "cortex plugin missing nexo_cortex_check tool"
240
+ else:
241
+ tool_names = ["cortex.py not found"]
242
+ summary["checks"]["cortex_plugin"] = {
243
+ "status": "pass",
244
+ "tools_found": tool_names,
227
245
  }
228
246
 
229
- runner = _load_module("nexo_evolution_run", CLAUDE_DIR / "scripts" / "nexo-evolution-run.py")
230
- runner.CLAUDE_DIR = temp_root
231
- runner.NEXO_DB = temp_db
232
- runner.CORTEX_DIR = temp_cortex_dir
233
- runner.OBJECTIVE_FILE = temp_objective
234
- runner.LOG_DIR = temp_logs_dir
235
- runner.LOG_FILE = temp_logs_dir / "evolution.log"
236
- runner.SNAPSHOTS_DIR = temp_snapshots_dir
237
- runner.SANDBOX_DIR = temp_sandbox_dir
238
- runner.API_KEY_FILE = temp_api_key
239
- runner.BUDGET_FILE = temp_logs_dir / "evolution-budget.json"
240
- runner.call_anthropic = lambda prompt: _fake_runner_response()
241
- runner.check_budget = lambda: True
242
-
243
- before_runner_logs = sqlite3.connect(str(temp_db)).execute("SELECT COUNT(*) FROM evolution_log").fetchone()[0]
244
- runner.run()
245
- conn = sqlite3.connect(str(temp_db))
246
- after_runner_logs = conn.execute("SELECT COUNT(*) FROM evolution_log").fetchone()[0]
247
- latest_cycle = conn.execute("SELECT MAX(cycle_number) FROM evolution_log").fetchone()[0]
248
- conn.close()
249
- if after_runner_logs <= before_runner_logs:
250
- raise RuntimeError("standalone evolution runner did not log proposals")
251
- summary["checks"]["standalone_runner"] = {
252
- "evolution_logs_added": after_runner_logs - before_runner_logs,
253
- "latest_cycle": latest_cycle,
254
- "budget_file_written": runner.BUDGET_FILE.exists(),
255
- }
247
+ # Evolution runner: component verification (not full execution that needs CLI)
248
+ runner_path = NEXO_CODE / "scripts" / "nexo-evolution-run.py"
249
+ if runner_path.exists():
250
+ runner = _load_module("nexo_evolution_run", runner_path)
251
+ checks = {
252
+ "module_loads": True,
253
+ "has_run": hasattr(runner, 'run'),
254
+ "has_load_objective": hasattr(runner, 'load_objective'),
255
+ "has_get_week_data": hasattr(runner, 'get_week_data'),
256
+ }
257
+ summary["checks"]["standalone_runner"] = checks
258
+ else:
259
+ summary["checks"]["standalone_runner"] = {"status": "skip", "reason": "runner not found"}
256
260
 
257
261
  summary["ok"] = True
258
262
  _write_summary(summary)
@@ -4,7 +4,7 @@ import smtplib, sys
4
4
  from email.mime.text import MIMEText
5
5
  from email.utils import formataddr
6
6
 
7
- def send(subject, body, to="userp@gmail.com", cc="user.example.com@gmail.com"):
7
+ def send(subject, body, to="user@example.com", cc="user@example.com"):
8
8
  msg = MIMEText(body, 'plain', 'utf-8')
9
9
  msg['From'] = formataddr(('NEXO', 'nexo@example.com'))
10
10
  msg['To'] = to