nexo-brain 2.3.0 → 2.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.
Files changed (299) hide show
  1. package/README.md +1 -1
  2. package/bin/nexo-brain.js +92 -9
  3. package/bin/postinstall.js +22 -15
  4. package/package.json +7 -4
  5. package/src/auto_update.py +194 -5
  6. package/src/crons/sync.py +6 -2
  7. package/src/db/_core.py +1 -0
  8. package/src/db/_entities.py +1 -0
  9. package/src/db/_episodic.py +1 -0
  10. package/src/db/_learnings.py +1 -0
  11. package/src/db/_reminders.py +1 -0
  12. package/src/db/_schema.py +11 -1
  13. package/src/db/_sessions.py +1 -0
  14. package/src/db/_skills.py +1 -0
  15. package/src/hooks/capture-tool-logs.sh +23 -6
  16. package/src/hooks/session-start.sh +4 -3
  17. package/src/plugin_loader.py +1 -0
  18. package/src/plugins/update.py +377 -26
  19. package/src/scripts/deep-sleep/apply_findings.py +1 -0
  20. package/src/scripts/deep-sleep/collect.py +1 -0
  21. package/src/scripts/deep-sleep/extract.py +1 -0
  22. package/src/scripts/deep-sleep/synthesize.py +1 -0
  23. package/src/scripts/nexo-catchup.py +29 -4
  24. package/src/scripts/nexo-daily-self-audit.py +21 -1
  25. package/src/scripts/nexo-evolution-run.py +21 -1
  26. package/src/scripts/nexo-learning-housekeep.py +1 -0
  27. package/src/scripts/nexo-postmortem-consolidator.py +34 -9
  28. package/src/scripts/nexo-sleep.py +32 -10
  29. package/src/scripts/nexo-synthesis.py +29 -9
  30. package/src/scripts/nexo-update.sh +109 -7
  31. package/src/scripts/nexo-watchdog.sh +122 -58
  32. package/src/server.py +66 -1
  33. package/src/tools_coordination.py +1 -0
  34. package/src/tools_sessions.py +1 -0
  35. package/scripts/migrate-to-unified 2.sh +0 -813
  36. package/scripts/migrate-to-unified.sh +0 -813
  37. package/scripts/migrate-v1.5-to-v1.6 2.py +0 -778
  38. package/scripts/migrate-v1.5-to-v1.6.py +0 -778
  39. package/scripts/migrate-v1.7-to-v1.8 2.py +0 -214
  40. package/scripts/migrate-v1.7-to-v1.8.py +0 -214
  41. package/scripts/nexo-preflight.sh +0 -236
  42. package/scripts/pre-commit-check 2.sh +0 -55
  43. package/scripts/pre-commit-check.sh +0 -55
  44. package/src/__pycache__/auto_close_sessions.cpython-314.pyc +0 -0
  45. package/src/__pycache__/auto_update.cpython-310.pyc +0 -0
  46. package/src/__pycache__/hnsw_index.cpython-310.pyc +0 -0
  47. package/src/__pycache__/hnsw_index.cpython-314.pyc +0 -0
  48. package/src/__pycache__/kg_populate.cpython-310.pyc +0 -0
  49. package/src/__pycache__/knowledge_graph.cpython-310.pyc +0 -0
  50. package/src/__pycache__/plugin_loader.cpython-310.pyc +0 -0
  51. package/src/__pycache__/plugin_loader.cpython-314.pyc +0 -0
  52. package/src/__pycache__/tools_coordination.cpython-310.pyc +0 -0
  53. package/src/__pycache__/tools_credentials.cpython-310.pyc +0 -0
  54. package/src/__pycache__/tools_learnings.cpython-310.pyc +0 -0
  55. package/src/__pycache__/tools_menu.cpython-310.pyc +0 -0
  56. package/src/__pycache__/tools_reminders.cpython-310.pyc +0 -0
  57. package/src/__pycache__/tools_reminders_crud.cpython-310.pyc +0 -0
  58. package/src/__pycache__/tools_sessions.cpython-310.pyc +0 -0
  59. package/src/__pycache__/tools_task_history.cpython-310.pyc +0 -0
  60. package/src/auto_close_sessions 2.py +0 -159
  61. package/src/auto_update 2.py +0 -634
  62. package/src/claim_graph 2.py +0 -323
  63. package/src/cognitive/__init__ 2.py +0 -62
  64. package/src/cognitive/__pycache__/__init__.cpython-310.pyc +0 -0
  65. package/src/cognitive/__pycache__/__init__.cpython-312.pyc +0 -0
  66. package/src/cognitive/__pycache__/__init__.cpython-314.pyc +0 -0
  67. package/src/cognitive/__pycache__/_core.cpython-310.pyc +0 -0
  68. package/src/cognitive/__pycache__/_core.cpython-312.pyc +0 -0
  69. package/src/cognitive/__pycache__/_core.cpython-314.pyc +0 -0
  70. package/src/cognitive/__pycache__/_decay.cpython-310.pyc +0 -0
  71. package/src/cognitive/__pycache__/_decay.cpython-312.pyc +0 -0
  72. package/src/cognitive/__pycache__/_decay.cpython-314.pyc +0 -0
  73. package/src/cognitive/__pycache__/_ingest.cpython-310.pyc +0 -0
  74. package/src/cognitive/__pycache__/_ingest.cpython-312.pyc +0 -0
  75. package/src/cognitive/__pycache__/_ingest.cpython-314.pyc +0 -0
  76. package/src/cognitive/__pycache__/_memory.cpython-310.pyc +0 -0
  77. package/src/cognitive/__pycache__/_memory.cpython-312.pyc +0 -0
  78. package/src/cognitive/__pycache__/_memory.cpython-314.pyc +0 -0
  79. package/src/cognitive/__pycache__/_search.cpython-310.pyc +0 -0
  80. package/src/cognitive/__pycache__/_search.cpython-312.pyc +0 -0
  81. package/src/cognitive/__pycache__/_search.cpython-314.pyc +0 -0
  82. package/src/cognitive/__pycache__/_trust.cpython-310.pyc +0 -0
  83. package/src/cognitive/__pycache__/_trust.cpython-312.pyc +0 -0
  84. package/src/cognitive/__pycache__/_trust.cpython-314.pyc +0 -0
  85. package/src/cognitive/_core 2.py +0 -567
  86. package/src/cognitive/_decay 2.py +0 -382
  87. package/src/cognitive/_ingest 2.py +0 -892
  88. package/src/cognitive/_memory 2.py +0 -912
  89. package/src/cognitive/_search 2.py +0 -949
  90. package/src/cognitive/_trust 2.py +0 -464
  91. package/src/crons/__pycache__/sync.cpython-314.pyc +0 -0
  92. package/src/crons/manifest 2.json +0 -106
  93. package/src/crons/sync 2.py +0 -217
  94. package/src/dashboard/__init__ 2.py +0 -0
  95. package/src/dashboard/__pycache__/__init__.cpython-310.pyc +0 -0
  96. package/src/dashboard/__pycache__/app.cpython-310.pyc +0 -0
  97. package/src/dashboard/app 2.py +0 -789
  98. package/src/db/__init__ 2.py +0 -89
  99. package/src/db/__pycache__/__init__.cpython-310.pyc +0 -0
  100. package/src/db/__pycache__/__init__.cpython-312.pyc +0 -0
  101. package/src/db/__pycache__/__init__.cpython-314.pyc +0 -0
  102. package/src/db/__pycache__/_core.cpython-310.pyc +0 -0
  103. package/src/db/__pycache__/_core.cpython-312.pyc +0 -0
  104. package/src/db/__pycache__/_core.cpython-314.pyc +0 -0
  105. package/src/db/__pycache__/_credentials.cpython-310.pyc +0 -0
  106. package/src/db/__pycache__/_credentials.cpython-312.pyc +0 -0
  107. package/src/db/__pycache__/_credentials.cpython-314.pyc +0 -0
  108. package/src/db/__pycache__/_cron_runs.cpython-310.pyc +0 -0
  109. package/src/db/__pycache__/_cron_runs.cpython-314.pyc +0 -0
  110. package/src/db/__pycache__/_entities.cpython-310.pyc +0 -0
  111. package/src/db/__pycache__/_entities.cpython-312.pyc +0 -0
  112. package/src/db/__pycache__/_entities.cpython-314.pyc +0 -0
  113. package/src/db/__pycache__/_episodic.cpython-310.pyc +0 -0
  114. package/src/db/__pycache__/_episodic.cpython-312.pyc +0 -0
  115. package/src/db/__pycache__/_episodic.cpython-314.pyc +0 -0
  116. package/src/db/__pycache__/_evolution.cpython-310.pyc +0 -0
  117. package/src/db/__pycache__/_evolution.cpython-312.pyc +0 -0
  118. package/src/db/__pycache__/_evolution.cpython-314.pyc +0 -0
  119. package/src/db/__pycache__/_fts.cpython-310.pyc +0 -0
  120. package/src/db/__pycache__/_fts.cpython-312.pyc +0 -0
  121. package/src/db/__pycache__/_fts.cpython-314.pyc +0 -0
  122. package/src/db/__pycache__/_learnings.cpython-310.pyc +0 -0
  123. package/src/db/__pycache__/_learnings.cpython-312.pyc +0 -0
  124. package/src/db/__pycache__/_learnings.cpython-314.pyc +0 -0
  125. package/src/db/__pycache__/_reminders.cpython-310.pyc +0 -0
  126. package/src/db/__pycache__/_reminders.cpython-312.pyc +0 -0
  127. package/src/db/__pycache__/_reminders.cpython-314.pyc +0 -0
  128. package/src/db/__pycache__/_schema.cpython-310.pyc +0 -0
  129. package/src/db/__pycache__/_schema.cpython-312.pyc +0 -0
  130. package/src/db/__pycache__/_schema.cpython-314.pyc +0 -0
  131. package/src/db/__pycache__/_sessions.cpython-310.pyc +0 -0
  132. package/src/db/__pycache__/_sessions.cpython-312.pyc +0 -0
  133. package/src/db/__pycache__/_sessions.cpython-314.pyc +0 -0
  134. package/src/db/__pycache__/_skills.cpython-310.pyc +0 -0
  135. package/src/db/__pycache__/_skills.cpython-312.pyc +0 -0
  136. package/src/db/__pycache__/_skills.cpython-314.pyc +0 -0
  137. package/src/db/__pycache__/_tasks.cpython-310.pyc +0 -0
  138. package/src/db/__pycache__/_tasks.cpython-312.pyc +0 -0
  139. package/src/db/__pycache__/_tasks.cpython-314.pyc +0 -0
  140. package/src/db/_core 2.py +0 -417
  141. package/src/db/_credentials 2.py +0 -124
  142. package/src/db/_entities 2.py +0 -178
  143. package/src/db/_episodic 2.py +0 -738
  144. package/src/db/_evolution 2.py +0 -54
  145. package/src/db/_fts 2.py +0 -406
  146. package/src/db/_learnings 2.py +0 -168
  147. package/src/db/_reminders 2.py +0 -338
  148. package/src/db/_schema 2.py +0 -364
  149. package/src/db/_sessions 2.py +0 -300
  150. package/src/db/_tasks 2.py +0 -91
  151. package/src/evolution_cycle 2.py +0 -266
  152. package/src/hnsw_index 2.py +0 -254
  153. package/src/hooks/auto_capture 2.py +0 -208
  154. package/src/hooks/caffeinate-guard 2.sh +0 -8
  155. package/src/hooks/capture-session 2.sh +0 -21
  156. package/src/hooks/capture-tool-logs 2.sh +0 -127
  157. package/src/hooks/daily-briefing-check 2.sh +0 -33
  158. package/src/hooks/inbox-hook 2.sh +0 -76
  159. package/src/hooks/post-compact 2.sh +0 -148
  160. package/src/hooks/pre-compact 2.sh +0 -151
  161. package/src/hooks/session-start 2.sh +0 -268
  162. package/src/hooks/session-stop 2.sh +0 -140
  163. package/src/kg_populate 2.py +0 -290
  164. package/src/knowledge_graph 2.py +0 -257
  165. package/src/maintenance 2.py +0 -59
  166. package/src/migrate_embeddings 2.py +0 -122
  167. package/src/plugin_loader 2.py +0 -202
  168. package/src/plugins/__init__ 2.py +0 -0
  169. package/src/plugins/__pycache__/__init__ 2.cpython-310.pyc +0 -0
  170. package/src/plugins/__pycache__/__init__.cpython-310.pyc +0 -0
  171. package/src/plugins/__pycache__/__init__.cpython-314.pyc +0 -0
  172. package/src/plugins/__pycache__/adaptive_mode 2.cpython-310.pyc +0 -0
  173. package/src/plugins/__pycache__/adaptive_mode.cpython-310.pyc +0 -0
  174. package/src/plugins/__pycache__/adaptive_mode.cpython-314.pyc +0 -0
  175. package/src/plugins/__pycache__/agents 2.cpython-310.pyc +0 -0
  176. package/src/plugins/__pycache__/agents.cpython-310.pyc +0 -0
  177. package/src/plugins/__pycache__/artifact_registry 2.cpython-310.pyc +0 -0
  178. package/src/plugins/__pycache__/artifact_registry.cpython-310.pyc +0 -0
  179. package/src/plugins/__pycache__/backup 2.cpython-310.pyc +0 -0
  180. package/src/plugins/__pycache__/backup.cpython-310.pyc +0 -0
  181. package/src/plugins/__pycache__/cognitive_memory 2.cpython-310.pyc +0 -0
  182. package/src/plugins/__pycache__/cognitive_memory.cpython-310.pyc +0 -0
  183. package/src/plugins/__pycache__/core_rules 2.cpython-310.pyc +0 -0
  184. package/src/plugins/__pycache__/core_rules.cpython-310.pyc +0 -0
  185. package/src/plugins/__pycache__/cortex 2.cpython-310.pyc +0 -0
  186. package/src/plugins/__pycache__/cortex.cpython-310.pyc +0 -0
  187. package/src/plugins/__pycache__/entities 2.cpython-310.pyc +0 -0
  188. package/src/plugins/__pycache__/entities.cpython-310.pyc +0 -0
  189. package/src/plugins/__pycache__/episodic_memory 2.cpython-310.pyc +0 -0
  190. package/src/plugins/__pycache__/episodic_memory.cpython-310.pyc +0 -0
  191. package/src/plugins/__pycache__/evolution 2.cpython-310.pyc +0 -0
  192. package/src/plugins/__pycache__/evolution.cpython-310.pyc +0 -0
  193. package/src/plugins/__pycache__/guard 2.cpython-310.pyc +0 -0
  194. package/src/plugins/__pycache__/guard.cpython-310.pyc +0 -0
  195. package/src/plugins/__pycache__/knowledge_graph_tools 2.cpython-310.pyc +0 -0
  196. package/src/plugins/__pycache__/knowledge_graph_tools.cpython-310.pyc +0 -0
  197. package/src/plugins/__pycache__/preferences 2.cpython-310.pyc +0 -0
  198. package/src/plugins/__pycache__/preferences.cpython-310.pyc +0 -0
  199. package/src/plugins/__pycache__/schedule.cpython-310.pyc +0 -0
  200. package/src/plugins/__pycache__/schedule.cpython-314.pyc +0 -0
  201. package/src/plugins/__pycache__/skills.cpython-310.pyc +0 -0
  202. package/src/plugins/__pycache__/skills.cpython-314.pyc +0 -0
  203. package/src/plugins/__pycache__/update 2.cpython-310.pyc +0 -0
  204. package/src/plugins/__pycache__/update.cpython-310.pyc +0 -0
  205. package/src/plugins/adaptive_mode 2.py +0 -805
  206. package/src/plugins/agents 2.py +0 -52
  207. package/src/plugins/artifact_registry 2.py +0 -450
  208. package/src/plugins/backup 2.py +0 -104
  209. package/src/plugins/cognitive_memory 2.py +0 -564
  210. package/src/plugins/core_rules 2.py +0 -252
  211. package/src/plugins/cortex 2.py +0 -299
  212. package/src/plugins/entities 2.py +0 -67
  213. package/src/plugins/episodic_memory 2.py +0 -533
  214. package/src/plugins/evolution 2.py +0 -115
  215. package/src/plugins/guard 2.py +0 -746
  216. package/src/plugins/knowledge_graph_tools 2.py +0 -105
  217. package/src/plugins/preferences 2.py +0 -47
  218. package/src/plugins/update 2.py +0 -256
  219. package/src/requirements 2.txt +0 -12
  220. package/src/rules/__init__ 2.py +0 -0
  221. package/src/rules/core-rules 2.json +0 -331
  222. package/src/rules/migrate 2.py +0 -207
  223. package/src/scripts/__pycache__/nexo-auto-update.cpython-314.pyc +0 -0
  224. package/src/scripts/__pycache__/nexo-catchup.cpython-314.pyc +0 -0
  225. package/src/scripts/__pycache__/nexo-cognitive-decay.cpython-314.pyc +0 -0
  226. package/src/scripts/__pycache__/nexo-daily-self-audit.cpython-314.pyc +0 -0
  227. package/src/scripts/__pycache__/nexo-evolution-run.cpython-314.pyc +0 -0
  228. package/src/scripts/__pycache__/nexo-followup-hygiene.cpython-314.pyc +0 -0
  229. package/src/scripts/__pycache__/nexo-immune.cpython-314.pyc +0 -0
  230. package/src/scripts/__pycache__/nexo-install.cpython-314.pyc +0 -0
  231. package/src/scripts/__pycache__/nexo-learning-housekeep.cpython-314.pyc +0 -0
  232. package/src/scripts/__pycache__/nexo-learning-validator.cpython-314.pyc +0 -0
  233. package/src/scripts/__pycache__/nexo-migrate.cpython-314.pyc +0 -0
  234. package/src/scripts/__pycache__/nexo-postmortem-consolidator.cpython-314.pyc +0 -0
  235. package/src/scripts/__pycache__/nexo-pre-commit.cpython-314.pyc +0 -0
  236. package/src/scripts/__pycache__/nexo-proactive-dashboard.cpython-314.pyc +0 -0
  237. package/src/scripts/__pycache__/nexo-reflection.cpython-314.pyc +0 -0
  238. package/src/scripts/__pycache__/nexo-runtime-preflight.cpython-314.pyc +0 -0
  239. package/src/scripts/__pycache__/nexo-send-email.cpython-314.pyc +0 -0
  240. package/src/scripts/__pycache__/nexo-send-reply.cpython-314.pyc +0 -0
  241. package/src/scripts/__pycache__/nexo-sleep.cpython-314.pyc +0 -0
  242. package/src/scripts/__pycache__/nexo-synthesis.cpython-314.pyc +0 -0
  243. package/src/scripts/__pycache__/nexo-watchdog-smoke.cpython-314.pyc +0 -0
  244. package/src/scripts/check-context 2.py +0 -264
  245. package/src/scripts/nexo-auto-update 2.py +0 -6
  246. package/src/scripts/nexo-backup 2.sh +0 -25
  247. package/src/scripts/nexo-brain-activation 2.sh +0 -140
  248. package/src/scripts/nexo-catchup 2.py +0 -242
  249. package/src/scripts/nexo-cognitive-decay 2.py +0 -182
  250. package/src/scripts/nexo-daily-self-audit 2.py +0 -552
  251. package/src/scripts/nexo-deep-sleep 2.sh +0 -97
  252. package/src/scripts/nexo-evolution-run 2.py +0 -597
  253. package/src/scripts/nexo-followup-hygiene 2.py +0 -112
  254. package/src/scripts/nexo-github-monitor 2.py +0 -256
  255. package/src/scripts/nexo-immune 2.py +0 -927
  256. package/src/scripts/nexo-inbox-hook 2.sh +0 -74
  257. package/src/scripts/nexo-install 2.py +0 -6
  258. package/src/scripts/nexo-learning-housekeep 2.py +0 -245
  259. package/src/scripts/nexo-learning-validator 2.py +0 -207
  260. package/src/scripts/nexo-migrate 2.py +0 -232
  261. package/src/scripts/nexo-postmortem-consolidator 2.py +0 -421
  262. package/src/scripts/nexo-pre-commit 2.py +0 -120
  263. package/src/scripts/nexo-prevent-sleep 2.sh +0 -29
  264. package/src/scripts/nexo-proactive-dashboard 2.py +0 -345
  265. package/src/scripts/nexo-reflection 2.py +0 -253
  266. package/src/scripts/nexo-runtime-preflight 2.py +0 -274
  267. package/src/scripts/nexo-send-email 2.py +0 -25
  268. package/src/scripts/nexo-send-email.py +0 -25
  269. package/src/scripts/nexo-send-reply 2.py +0 -178
  270. package/src/scripts/nexo-send-reply.py +0 -178
  271. package/src/scripts/nexo-sleep 2.py +0 -592
  272. package/src/scripts/nexo-snapshot-restore 2.sh +0 -35
  273. package/src/scripts/nexo-synthesis 2.py +0 -253
  274. package/src/scripts/nexo-tcc-approve 2.sh +0 -79
  275. package/src/scripts/nexo-update 2.sh +0 -161
  276. package/src/scripts/nexo-watchdog 2.sh +0 -878
  277. package/src/scripts/nexo-watchdog-smoke 2.py +0 -119
  278. package/src/server 2.py +0 -733
  279. package/src/storage_router 2.py +0 -32
  280. package/src/tools_coordination 2.py +0 -102
  281. package/src/tools_credentials 2.py +0 -68
  282. package/src/tools_learnings 2.py +0 -220
  283. package/src/tools_menu 2.py +0 -227
  284. package/src/tools_reminders 2.py +0 -86
  285. package/src/tools_reminders_crud 2.py +0 -159
  286. package/src/tools_sessions 2.py +0 -476
  287. package/src/tools_task_history 2.py +0 -57
  288. package/templates/CLAUDE.md 2.template +0 -63
  289. package/templates/openclaw 2.json +0 -13
  290. package/tests/__init__ 2.py +0 -0
  291. package/tests/__init__.py +0 -0
  292. package/tests/conftest 2.py +0 -71
  293. package/tests/conftest.py +0 -71
  294. package/tests/test_cognitive 2.py +0 -205
  295. package/tests/test_cognitive.py +0 -205
  296. package/tests/test_knowledge_graph 2.py +0 -140
  297. package/tests/test_knowledge_graph.py +0 -140
  298. package/tests/test_migrations 2.py +0 -137
  299. package/tests/test_migrations.py +0 -137
@@ -43,7 +43,27 @@ RUNTIME_PREFLIGHT_SUMMARY = LOG_DIR / "runtime-preflight-summary.json"
43
43
  WATCHDOG_SMOKE_SUMMARY = LOG_DIR / "watchdog-smoke-summary.json"
44
44
  RESTORE_LOG = LOG_DIR / "snapshot-restores.log"
45
45
  CORTEX_LOG_DIR = NEXO_HOME / "brain" / "logs"
46
- CLAUDE_CLI = Path.home() / ".local" / "bin" / "claude"
46
+ def _resolve_claude_cli() -> Path:
47
+ """Find claude CLI: saved path > PATH > common locations."""
48
+ import shutil as _shutil
49
+ saved = NEXO_HOME / "config" / "claude-cli-path"
50
+ if saved.exists():
51
+ p = Path(saved.read_text().strip())
52
+ if p.exists():
53
+ return p
54
+ found = _shutil.which("claude")
55
+ if found:
56
+ return Path(found)
57
+ for candidate in [
58
+ Path.home() / ".local" / "bin" / "claude",
59
+ Path.home() / ".npm-global" / "bin" / "claude",
60
+ Path("/usr/local/bin/claude"),
61
+ ]:
62
+ if candidate.exists():
63
+ return candidate
64
+ return Path.home() / ".local" / "bin" / "claude"
65
+
66
+ CLAUDE_CLI = _resolve_claude_cli()
47
67
 
48
68
  findings = []
49
69
 
@@ -64,7 +64,27 @@ IMMUTABLE_FILES = {
64
64
  }
65
65
 
66
66
  # ── Claude CLI path ──────────────────────────────────────────────────────
67
- CLAUDE_CLI = Path.home() / ".local" / "bin" / "claude"
67
+ def _resolve_claude_cli() -> Path:
68
+ """Find claude CLI: saved path > PATH > common locations."""
69
+ import shutil as _shutil
70
+ saved = NEXO_HOME / "config" / "claude-cli-path"
71
+ if saved.exists():
72
+ p = Path(saved.read_text().strip())
73
+ if p.exists():
74
+ return p
75
+ found = _shutil.which("claude")
76
+ if found:
77
+ return Path(found)
78
+ for candidate in [
79
+ Path.home() / ".local" / "bin" / "claude",
80
+ Path.home() / ".npm-global" / "bin" / "claude",
81
+ Path("/usr/local/bin/claude"),
82
+ ]:
83
+ if candidate.exists():
84
+ return candidate
85
+ return Path.home() / ".local" / "bin" / "claude"
86
+
87
+ CLAUDE_CLI = _resolve_claude_cli()
68
88
 
69
89
  # ── Logging ──────────────────────────────────────────────────────────────
70
90
  LOG_DIR.mkdir(parents=True, exist_ok=True)
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env python3
2
+ from __future__ import annotations
2
3
  """NEXO Learning Housekeeping — Nightly dedup, weight adjustment, and review.
3
4
 
4
5
  Runs daily. Adjusts learning weights based on usage (guard_hits),
@@ -42,7 +42,27 @@ MEMORY_DIR = NEXO_HOME / "memory"
42
42
  MEMORY_INDEX = MEMORY_DIR / "MEMORY.md"
43
43
  HISTORY_FILE = NEXO_HOME / "coordination" / "postmortem-history.json"
44
44
  CONSOLIDATION_LOG = NEXO_HOME / "logs" / "postmortem-consolidation.log"
45
- CLAUDE_CLI = HOME / ".local" / "bin" / "claude"
45
+ def _resolve_claude_cli() -> Path:
46
+ """Find claude CLI: saved path > PATH > common locations."""
47
+ import shutil as _shutil
48
+ saved = NEXO_HOME / "config" / "claude-cli-path"
49
+ if saved.exists():
50
+ p = Path(saved.read_text().strip())
51
+ if p.exists():
52
+ return p
53
+ found = _shutil.which("claude")
54
+ if found:
55
+ return Path(found)
56
+ for candidate in [
57
+ HOME / ".local" / "bin" / "claude",
58
+ HOME / ".npm-global" / "bin" / "claude",
59
+ Path("/usr/local/bin/claude"),
60
+ ]:
61
+ if candidate.exists():
62
+ return candidate
63
+ return HOME / ".local" / "bin" / "claude"
64
+
65
+ CLAUDE_CLI = _resolve_claude_cli()
46
66
  SESSION_BUFFER = NEXO_HOME / "brain" / "session_buffer.jsonl"
47
67
 
48
68
  TODAY = date.today()
@@ -379,6 +399,7 @@ def main():
379
399
  return
380
400
 
381
401
  log("=== NEXO Post-Mortem Consolidator v2 starting ===")
402
+ had_errors = False
382
403
 
383
404
  # Stage 1: Collect data
384
405
  data = collect_data()
@@ -392,27 +413,31 @@ def main():
392
413
  if not success:
393
414
  log("Stage 2 failed (CLI unavailable or error). "
394
415
  "Skipping intelligent consolidation. Stage 3 (sensory + force) will still run.")
416
+ had_errors = True
395
417
 
396
418
  # Stage 3: Sensory Register (mechanical, kept from v1)
397
419
  try:
398
420
  process_sensory_register()
399
421
  except Exception as e:
400
422
  log(f"Sensory register failed: {e}")
423
+ had_errors = True
401
424
 
402
425
  # Stage 3b: Force analysis (mechanical, kept from v1)
403
426
  try:
404
427
  analyze_force_events()
405
428
  except Exception as e:
406
429
  log(f"Force analysis failed: {e}")
430
+ had_errors = True
407
431
 
408
- # Register successful run
409
- try:
410
- state_file = NEXO_HOME / "operations" / ".catchup-state.json"
411
- state = json.loads(state_file.read_text()) if state_file.exists() else {}
412
- state["postmortem"] = datetime.now().isoformat()
413
- state_file.write_text(json.dumps(state, indent=2))
414
- except Exception:
415
- pass
432
+ # Register successful run only if no stages failed
433
+ if not had_errors:
434
+ try:
435
+ state_file = NEXO_HOME / "operations" / ".catchup-state.json"
436
+ state = json.loads(state_file.read_text()) if state_file.exists() else {}
437
+ state["postmortem"] = datetime.now().isoformat()
438
+ state_file.write_text(json.dumps(state, indent=2))
439
+ except Exception:
440
+ pass
416
441
 
417
442
  mark_done()
418
443
  log("=== Consolidation v2 complete ===")
@@ -49,7 +49,26 @@ SLEEP_LOG = COORD_DIR / "sleep-log.json"
49
49
  MEMORY_MD = NEXO_HOME / "memory" / "MEMORY.md"
50
50
  NEXO_DB = NEXO_HOME / "data" / "nexo.db"
51
51
  CLAUDE_MEM_DB = Path.home() / ".claude-mem" / "claude-mem.db"
52
- CLAUDE_CLI = Path.home() / ".local" / "bin" / "claude"
52
+ def _resolve_claude_cli() -> Path:
53
+ """Find claude CLI: saved path > PATH > common locations."""
54
+ saved = NEXO_HOME / "config" / "claude-cli-path"
55
+ if saved.exists():
56
+ p = Path(saved.read_text().strip())
57
+ if p.exists():
58
+ return p
59
+ found = shutil.which("claude")
60
+ if found:
61
+ return Path(found)
62
+ for candidate in [
63
+ Path.home() / ".local" / "bin" / "claude",
64
+ Path.home() / ".npm-global" / "bin" / "claude",
65
+ Path("/usr/local/bin/claude"),
66
+ ]:
67
+ if candidate.exists():
68
+ return candidate
69
+ return Path.home() / ".local" / "bin" / "claude"
70
+
71
+ CLAUDE_CLI = _resolve_claude_cli()
53
72
 
54
73
  LAST_RUN_FILE = COORD_DIR / "sleep-last-run"
55
74
  LOCK_FILE = COORD_DIR / "sleep.lock"
@@ -534,6 +553,7 @@ def main():
534
553
 
535
554
  run_log = {"date": str(TODAY), "started": TIMESTAMP,
536
555
  "stage_a": None, "stage_b": None, "completed": None}
556
+ sleep_had_errors = False
537
557
 
538
558
  # Stage A: Housekeeping (mechanical)
539
559
  if start_phase == "stage_a":
@@ -555,7 +575,8 @@ def main():
555
575
 
556
576
  if "error" in dream_result:
557
577
  log(f"Stage B: Dreaming failed ({dream_result['error']}). "
558
- "Stage A cleanup completed successfully. Marking done to avoid retry loop.")
578
+ "Stage A cleanup completed successfully. Not marking catchup to allow retry.")
579
+ sleep_had_errors = True
559
580
  else:
560
581
  # Stage B2: Execute actions from CLI output
561
582
  actions_file = COORD_DIR / "sleep-actions.json"
@@ -575,14 +596,15 @@ def main():
575
596
  append_sleep_log(run_log)
576
597
  log(f"NEXO Sleep v2 complete at {run_log['completed']}")
577
598
 
578
- # Register for catch-up
579
- try:
580
- state_file = NEXO_HOME / "operations" / ".catchup-state.json"
581
- st = json.loads(state_file.read_text()) if state_file.exists() else {}
582
- st["sleep"] = datetime.now().isoformat()
583
- state_file.write_text(json.dumps(st, indent=2))
584
- except Exception:
585
- pass
599
+ # Register for catch-up only if all stages succeeded
600
+ if not sleep_had_errors:
601
+ try:
602
+ state_file = NEXO_HOME / "operations" / ".catchup-state.json"
603
+ st = json.loads(state_file.read_text()) if state_file.exists() else {}
604
+ st["sleep"] = datetime.now().isoformat()
605
+ state_file.write_text(json.dumps(st, indent=2))
606
+ except Exception:
607
+ pass
586
608
 
587
609
  finally:
588
610
  try:
@@ -26,7 +26,27 @@ NEXO_DB = NEXO_HOME / "data" / "nexo.db"
26
26
  OUTPUT_FILE = COORD_DIR / "daily-synthesis.md"
27
27
  LAST_RUN_FILE = COORD_DIR / "synthesis-last-run"
28
28
  LOCK_FILE = COORD_DIR / "synthesis.lock"
29
- CLAUDE_CLI = HOME / ".local" / "bin" / "claude"
29
+ def _resolve_claude_cli() -> Path:
30
+ """Find claude CLI: saved path > PATH > common locations."""
31
+ import shutil as _shutil
32
+ saved = NEXO_HOME / "config" / "claude-cli-path"
33
+ if saved.exists():
34
+ p = Path(saved.read_text().strip())
35
+ if p.exists():
36
+ return p
37
+ found = _shutil.which("claude")
38
+ if found:
39
+ return Path(found)
40
+ for candidate in [
41
+ HOME / ".local" / "bin" / "claude",
42
+ HOME / ".npm-global" / "bin" / "claude",
43
+ Path("/usr/local/bin/claude"),
44
+ ]:
45
+ if candidate.exists():
46
+ return candidate
47
+ return HOME / ".local" / "bin" / "claude"
48
+
49
+ CLAUDE_CLI = _resolve_claude_cli()
30
50
 
31
51
  TODAY = date.today()
32
52
  TODAY_STR = TODAY.isoformat()
@@ -109,17 +129,17 @@ def collect_data() -> dict:
109
129
  (TODAY_STR,)
110
130
  )
111
131
 
112
- # Overdue reminders
132
+ # Overdue reminders (schema: description, date, status uppercase)
113
133
  data["overdue_reminders"] = safe_query(
114
- "SELECT id, title, due_date FROM reminders "
115
- "WHERE status='PENDING' AND due_date <= ? ORDER BY due_date",
134
+ "SELECT id, description, date FROM reminders "
135
+ "WHERE status='PENDING' AND date <= ? ORDER BY date",
116
136
  (TODAY_STR,)
117
137
  )
118
138
 
119
- # Pending followups
139
+ # Pending followups (schema: description, date, status uppercase)
120
140
  data["pending_followups"] = safe_query(
121
- "SELECT id, title, description, due_date FROM followups "
122
- "WHERE status='pending' ORDER BY due_date"
141
+ "SELECT id, description, date FROM followups "
142
+ "WHERE status='PENDING' ORDER BY date"
123
143
  )
124
144
 
125
145
  # Guard stats
@@ -240,13 +260,13 @@ def fallback_synthesis(data: dict):
240
260
  if data.get("overdue_reminders"):
241
261
  lines.append("## Overdue Reminders")
242
262
  for r in data["overdue_reminders"][:10]:
243
- lines.append(f"- #{r.get('id', '?')} {r.get('title', '')} (due {r.get('due_date', '?')})")
263
+ lines.append(f"- #{r.get('id', '?')} {r.get('description', '')} (due {r.get('date', '?')})")
244
264
  lines.append("")
245
265
 
246
266
  if data.get("pending_followups"):
247
267
  lines.append("## Pending Followups")
248
268
  for f in data["pending_followups"][:10]:
249
- lines.append(f"- #{f.get('id', '?')} {f.get('title', '')} (due {f.get('due_date', '?')})")
269
+ lines.append(f"- #{f.get('id', '?')} {f.get('description', '')} (due {f.get('date', '?')})")
250
270
  lines.append("")
251
271
 
252
272
  OUTPUT_FILE.parent.mkdir(parents=True, exist_ok=True)
@@ -34,13 +34,20 @@ read_version() {
34
34
  python3 -c "import json; print(json.load(open('$PACKAGE_JSON')).get('version','unknown'))" 2>/dev/null || echo "unknown"
35
35
  }
36
36
 
37
- # --- Step 1: Check for uncommitted changes in src/ ---
38
- log "Checking for uncommitted changes in src/..."
37
+ # --- Check if this is a git repo ---
38
+ if [ ! -d "$REPO_DIR/.git" ] && [ ! -f "$REPO_DIR/.git" ]; then
39
+ err "ABORTED: Not a git repository at $REPO_DIR"
40
+ err "For packaged installs, use: npm update -g nexo-brain"
41
+ exit 1
42
+ fi
43
+
44
+ # --- Step 1: Check for uncommitted changes in entire worktree ---
45
+ log "Checking for uncommitted changes..."
39
46
  cd "$REPO_DIR"
40
47
 
41
- if [ -n "$(git status --porcelain -- src/ 2>/dev/null)" ]; then
42
- err "ABORTED: Uncommitted changes in src/"
43
- git status --short -- src/
48
+ if [ -n "$(git status --porcelain 2>/dev/null)" ]; then
49
+ err "ABORTED: Uncommitted changes in worktree"
50
+ git status --short
44
51
  exit 1
45
52
  fi
46
53
  log "Working tree clean."
@@ -48,6 +55,11 @@ log "Working tree clean."
48
55
  # Record current state
49
56
  OLD_VERSION="$(read_version)"
50
57
  OLD_COMMIT="$(git rev-parse HEAD)"
58
+ REQ_FILE="$SRC_DIR/requirements.txt"
59
+ OLD_REQ_HASH=""
60
+ if [ -f "$REQ_FILE" ]; then
61
+ OLD_REQ_HASH="$(shasum -a 256 "$REQ_FILE" | cut -d' ' -f1)"
62
+ fi
51
63
  log "Current: v${OLD_VERSION} (${OLD_COMMIT:0:8})"
52
64
 
53
65
  # --- Step 2: Backup databases ---
@@ -94,6 +106,54 @@ fi
94
106
  NEW_VERSION="$(read_version)"
95
107
  log "New version: v${NEW_VERSION}"
96
108
 
109
+ # --- Step 4b: Reinstall Python dependencies if requirements.txt changed ---
110
+ NEW_REQ_HASH=""
111
+ if [ -f "$REQ_FILE" ]; then
112
+ NEW_REQ_HASH="$(shasum -a 256 "$REQ_FILE" | cut -d' ' -f1)"
113
+ fi
114
+
115
+ DEPS_CHANGED=false
116
+ if [ "$OLD_REQ_HASH" != "$NEW_REQ_HASH" ]; then
117
+ DEPS_CHANGED=true
118
+ fi
119
+
120
+ reinstall_pip_deps() {
121
+ local VENV_PIP="$NEXO_HOME/.venv/bin/pip"
122
+ if [ -f "$REQ_FILE" ]; then
123
+ if [ -x "$VENV_PIP" ]; then
124
+ "$VENV_PIP" install --quiet -r "$REQ_FILE" || return 1
125
+ else
126
+ python3 -m pip install --quiet -r "$REQ_FILE" --break-system-packages 2>/dev/null || return 1
127
+ fi
128
+ fi
129
+ return 0
130
+ }
131
+
132
+ if [ "$DEPS_CHANGED" = true ] || [ "$OLD_VERSION" != "$NEW_VERSION" ]; then
133
+ log "Reinstalling Python dependencies..."
134
+ if ! reinstall_pip_deps; then
135
+ err "pip install failed! Rolling back..."
136
+ git reset --hard "$OLD_COMMIT"
137
+ reinstall_pip_deps || warn "pip rollback also had issues"
138
+ if [ -d "$BACKUP_DIR" ]; then
139
+ for db in "$BACKUP_DIR"/*.db; do
140
+ [ -f "$db" ] || continue
141
+ BASENAME="$(basename "$db")"
142
+ for candidate in "$NEXO_HOME/data/$BASENAME" "$NEXO_HOME/$BASENAME" "$SRC_DIR/$BASENAME"; do
143
+ if [ -f "$candidate" ]; then
144
+ cp "$db" "$candidate"
145
+ warn " Restored: $BASENAME"
146
+ break
147
+ fi
148
+ done
149
+ done
150
+ fi
151
+ err "Rolled back to ${OLD_COMMIT:0:8}. Databases restored."
152
+ exit 1
153
+ fi
154
+ log "Python dependencies updated."
155
+ fi
156
+
97
157
  # --- Step 5: Run migrations if version changed ---
98
158
  if [ "$OLD_VERSION" != "$NEW_VERSION" ]; then
99
159
  log "Version changed: ${OLD_VERSION} -> ${NEW_VERSION}"
@@ -101,6 +161,8 @@ if [ "$OLD_VERSION" != "$NEW_VERSION" ]; then
101
161
  if ! (cd "$SRC_DIR" && python3 -c "import db; db.init_db()" 2>&1); then
102
162
  err "Migration failed! Rolling back..."
103
163
  git reset --hard "$OLD_COMMIT"
164
+ # Reinstall pip deps from restored old requirements.txt
165
+ reinstall_pip_deps || warn "pip rollback also had issues"
104
166
  # Restore DB backups
105
167
  if [ -d "$BACKUP_DIR" ]; then
106
168
  for db in "$BACKUP_DIR"/*.db; do
@@ -115,7 +177,7 @@ if [ "$OLD_VERSION" != "$NEW_VERSION" ]; then
115
177
  done
116
178
  done
117
179
  fi
118
- err "Rolled back to ${OLD_COMMIT:0:8}. Databases restored."
180
+ err "Rolled back to ${OLD_COMMIT:0:8}. Databases and deps restored."
119
181
  exit 1
120
182
  fi
121
183
  log "Migrations applied."
@@ -128,6 +190,8 @@ log "Verifying server.py import..."
128
190
  if ! (cd "$SRC_DIR" && python3 -c "import server" 2>&1); then
129
191
  err "Import verification failed! Rolling back..."
130
192
  git reset --hard "$OLD_COMMIT"
193
+ # Reinstall pip deps from restored old requirements.txt
194
+ reinstall_pip_deps || warn "pip rollback also had issues"
131
195
  if [ -d "$BACKUP_DIR" ]; then
132
196
  for db in "$BACKUP_DIR"/*.db; do
133
197
  [ -f "$db" ] || continue
@@ -141,10 +205,48 @@ if ! (cd "$SRC_DIR" && python3 -c "import server" 2>&1); then
141
205
  done
142
206
  done
143
207
  fi
144
- err "Rolled back to ${OLD_COMMIT:0:8}. Databases restored."
208
+ err "Rolled back to ${OLD_COMMIT:0:8}. Databases and deps restored."
145
209
  exit 1
146
210
  fi
147
211
 
212
+ # --- Step 7: Sync hooks to NEXO_HOME ---
213
+ HOOKS_SRC="$SRC_DIR/hooks"
214
+ HOOKS_DEST="$NEXO_HOME/hooks"
215
+ if [ -d "$HOOKS_SRC" ]; then
216
+ mkdir -p "$HOOKS_DEST"
217
+ SYNCED=0
218
+ for hook in "$HOOKS_SRC"/*.sh; do
219
+ [ -f "$hook" ] || continue
220
+ cp "$hook" "$HOOKS_DEST/$(basename "$hook")"
221
+ chmod 755 "$HOOKS_DEST/$(basename "$hook")"
222
+ SYNCED=$((SYNCED + 1))
223
+ done
224
+ if [ "$SYNCED" -gt 0 ]; then
225
+ log "Synced $SYNCED hook(s) to $HOOKS_DEST"
226
+ fi
227
+ fi
228
+
229
+ # --- Step 8: Sync cron definitions with manifest ---
230
+ CRON_SYNC="$SRC_DIR/crons/sync.py"
231
+ CRON_SYNC_OK=false
232
+ if [ -f "$CRON_SYNC" ]; then
233
+ log "Syncing cron definitions..."
234
+ if NEXO_HOME="$NEXO_HOME" NEXO_CODE="$SRC_DIR" python3 "$CRON_SYNC" 2>&1; then
235
+ log "Cron definitions synced."
236
+ CRON_SYNC_OK=true
237
+ else
238
+ warn "Cron sync failed (non-fatal). Installed manifest NOT refreshed to avoid divergence."
239
+ fi
240
+ fi
241
+
242
+ # --- Step 8b: Refresh installed manifest for catchup/watchdog (only if sync succeeded) ---
243
+ if $CRON_SYNC_OK && [ -d "$SRC_DIR/crons" ]; then
244
+ mkdir -p "$NEXO_HOME/crons"
245
+ cp -f "$SRC_DIR/crons/"*.json "$NEXO_HOME/crons/" 2>/dev/null
246
+ cp -f "$SRC_DIR/crons/"*.py "$NEXO_HOME/crons/" 2>/dev/null
247
+ log "Refreshed installed crons manifest."
248
+ fi
249
+
148
250
  # --- Done ---
149
251
  echo ""
150
252
  log "========================================="