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
@@ -1,232 +0,0 @@
1
- #!/usr/bin/env python3
2
- """NEXO Migration Tool — automatic, idempotent upgrades between versions.
3
-
4
- Usage:
5
- python3 nexo-migrate.py # auto-detect current → target
6
- python3 nexo-migrate.py --dry-run # show what would happen
7
- python3 nexo-migrate.py --from 1.6.0 # override detected current version
8
-
9
- Reads current version from $NEXO_HOME/version.json.
10
- Reads target version from the repo's package.json.
11
- Backs up NEXO_HOME/db/ before any migration.
12
- Runs DB schema migrations via the existing _schema.py system.
13
- """
14
-
15
- import argparse
16
- import json
17
- import os
18
- import shutil
19
- import sqlite3
20
- import sys
21
- from datetime import datetime
22
- from pathlib import Path
23
-
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/
26
-
27
-
28
- # ── Version helpers ──────────────────────────────────────────────
29
-
30
- def parse_version(v: str) -> tuple:
31
- """Parse '1.7.0-beta.1' → (1, 7, 0, 'beta.1'). Pre-release is optional."""
32
- parts = v.strip().lstrip("v").split("-", 1)
33
- nums = tuple(int(x) for x in parts[0].split("."))
34
- pre = parts[1] if len(parts) > 1 else ""
35
- return (*nums, pre)
36
-
37
-
38
- def version_key(v: str) -> tuple:
39
- """Sortable key: releases sort after pre-releases of same version."""
40
- nums = parse_version(v)
41
- # Empty pre-release string sorts AFTER any pre-release tag
42
- pre = nums[3] if len(nums) > 3 else ""
43
- return (nums[0], nums[1], nums[2], 0 if pre else 1, pre)
44
-
45
-
46
- def get_current_version() -> str:
47
- """Read installed version from NEXO_HOME/version.json."""
48
- vfile = NEXO_HOME / "version.json"
49
- if not vfile.exists():
50
- return "0.0.0"
51
- try:
52
- data = json.loads(vfile.read_text())
53
- return data.get("version", "0.0.0")
54
- except Exception:
55
- return "0.0.0"
56
-
57
-
58
- def get_target_version() -> str:
59
- """Read target version from repo package.json."""
60
- pkg = REPO_ROOT / "package.json"
61
- if not pkg.exists():
62
- print(f"ERROR: package.json not found at {pkg}", file=sys.stderr)
63
- sys.exit(1)
64
- data = json.loads(pkg.read_text())
65
- return data["version"]
66
-
67
-
68
- # ── Backup ───────────────────────────────────────────────────────
69
-
70
- def backup_databases() -> str:
71
- """Backup all .db files before migration. Returns backup dir path."""
72
- ts = datetime.now().strftime("%Y%m%d-%H%M%S")
73
- backup_dir = NEXO_HOME / "backups" / f"pre-migrate-{ts}"
74
- backup_dir.mkdir(parents=True, exist_ok=True)
75
-
76
- data_dir = NEXO_HOME / "data"
77
- if data_dir.exists():
78
- for db_file in data_dir.glob("*.db*"):
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)
86
-
87
- # Also backup version.json
88
- vfile = NEXO_HOME / "version.json"
89
- if vfile.exists():
90
- shutil.copy2(vfile, backup_dir / "version.json")
91
-
92
- return str(backup_dir)
93
-
94
-
95
- # ── Migration steps ──────────────────────────────────────────────
96
-
97
- def ensure_nexo_home_dirs():
98
- """Create all required NEXO_HOME subdirectories."""
99
- dirs = [
100
- "db", "brain", "logs", "operations", "coordination",
101
- "scripts", "hooks", "plugins", "backups", "memory",
102
- "docs", "projects", "learnings", "agents", "skills",
103
- ]
104
- for d in dirs:
105
- (NEXO_HOME / d).mkdir(parents=True, exist_ok=True)
106
-
107
-
108
- def run_db_schema_migrations():
109
- """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))
114
-
115
- # Set NEXO_HOME env for the db module
116
- os.environ["NEXO_HOME"] = str(NEXO_HOME)
117
- os.environ["NEXO_SKIP_FS_INDEX"] = "1" # Don't rebuild FTS during migration
118
-
119
- try:
120
- from db import init_db
121
- init_db()
122
- print(" DB schema migrations applied.")
123
- except Exception as e:
124
- print(f" WARNING: DB schema migration error: {e}", file=sys.stderr)
125
-
126
-
127
- def write_version_json(version: str):
128
- """Write version.json with the installed version."""
129
- vfile = NEXO_HOME / "version.json"
130
- data = {
131
- "version": version,
132
- "installed_at": datetime.now().isoformat(timespec="seconds"),
133
- "nexo_home": str(NEXO_HOME),
134
- }
135
- vfile.write_text(json.dumps(data, indent=2) + "\n")
136
-
137
-
138
- # ── Migration registry ───────────────────────────────────────────
139
- # Each entry: version → list of (description, callable)
140
- # Migrations run for all versions > current AND <= target.
141
-
142
- def _migrate_1_7_0():
143
- """1.7.0: Ensure NEXO_HOME paths, create directories, update version."""
144
- ensure_nexo_home_dirs()
145
- run_db_schema_migrations()
146
- print(" Created/verified all NEXO_HOME directories.")
147
-
148
-
149
- MIGRATION_REGISTRY: dict[str, list[tuple[str, callable]]] = {
150
- "1.7.0": [
151
- ("Ensure NEXO_HOME dirs + DB schema", _migrate_1_7_0),
152
- ],
153
- }
154
-
155
-
156
- # ── Main ─────────────────────────────────────────────────────────
157
-
158
- def get_applicable_migrations(current: str, target: str) -> list[tuple[str, str, callable]]:
159
- """Return list of (version, description, fn) for migrations between current and target."""
160
- current_key = version_key(current)
161
- target_key = version_key(target)
162
-
163
- applicable = []
164
- for ver, steps in sorted(MIGRATION_REGISTRY.items(), key=lambda x: version_key(x[0])):
165
- ver_key = version_key(ver)
166
- # Run if version > current and <= target (base version comparison)
167
- base_ver = ver.split("-")[0] # strip pre-release for comparison
168
- base_ver_key = version_key(base_ver)
169
- if base_ver_key > (current_key[0], current_key[1], current_key[2], current_key[3], current_key[4] if len(current_key) > 4 else ""):
170
- if base_ver_key <= (target_key[0], target_key[1], target_key[2], 1, ""):
171
- for desc, fn in steps:
172
- applicable.append((ver, desc, fn))
173
-
174
- return applicable
175
-
176
-
177
- def main():
178
- parser = argparse.ArgumentParser(description="NEXO Migration Tool")
179
- parser.add_argument("--dry-run", action="store_true", help="Show what would happen without executing")
180
- parser.add_argument("--from", dest="from_ver", help="Override detected current version")
181
- args = parser.parse_args()
182
-
183
- current = args.from_ver or get_current_version()
184
- target = get_target_version()
185
-
186
- print(f"NEXO Migration: {current} → {target}")
187
- print(f"NEXO_HOME: {NEXO_HOME}")
188
- print()
189
-
190
- if version_key(current) >= version_key(target):
191
- print("Already up to date. Nothing to migrate.")
192
- return
193
-
194
- migrations = get_applicable_migrations(current, target)
195
- if not migrations:
196
- print("No migration steps needed (only version bump).")
197
- else:
198
- print(f"Migrations to run ({len(migrations)}):")
199
- for ver, desc, _ in migrations:
200
- print(f" [{ver}] {desc}")
201
- print()
202
-
203
- if args.dry_run:
204
- print("DRY RUN — no changes made.")
205
- return
206
-
207
- # Backup before anything
208
- backup_path = backup_databases()
209
- print(f"Backup created: {backup_path}")
210
- print()
211
-
212
- # Ensure base directories exist
213
- ensure_nexo_home_dirs()
214
-
215
- # Run migrations
216
- for ver, desc, fn in migrations:
217
- print(f"Running [{ver}] {desc}...")
218
- try:
219
- fn()
220
- print(f" Done.")
221
- except Exception as e:
222
- print(f" ERROR: {e}", file=sys.stderr)
223
- print(f" Backup at: {backup_path}", file=sys.stderr)
224
- sys.exit(1)
225
-
226
- # Write final version
227
- write_version_json(target)
228
- print(f"\nMigration complete: {current} → {target}")
229
-
230
-
231
- if __name__ == "__main__":
232
- main()
@@ -1,421 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- NEXO Post-Mortem Consolidator v2 — The brain consolidates memories.
4
-
5
- Before: 595 lines of word-overlap at 50% to detect "patterns".
6
- Now: Collects data, passes them to CLI which UNDERSTANDS what it reads.
7
-
8
- Runs daily at 23:30 via LaunchAgent. Reads session diaries from today,
9
- passes them to Claude CLI (opus) which decides what deserves permanent memory.
10
-
11
- Stage 1 — Data collection (Pure Python):
12
- Query session diaries, existing feedbacks, history.
13
-
14
- Stage 2 — Intelligence (Claude CLI opus):
15
- Read diaries, understand patterns, decide what to promote.
16
-
17
- Stage 3 — Sensory Register + Force analysis (Pure Python):
18
- Process cognitive events. Kept from v1 — genuinely mechanical.
19
- """
20
-
21
- import json
22
- import os
23
- import sqlite3
24
- import subprocess
25
- import sys
26
- from datetime import datetime, date, timedelta
27
- from pathlib import Path
28
-
29
- HOME = Path.home()
30
- NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(HOME / ".nexo")))
31
-
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"
40
- # Memory directory — adjust to match your project's memory location
41
- MEMORY_DIR = NEXO_HOME / "memory"
42
- MEMORY_INDEX = MEMORY_DIR / "MEMORY.md"
43
- HISTORY_FILE = NEXO_HOME / "coordination" / "postmortem-history.json"
44
- CONSOLIDATION_LOG = NEXO_HOME / "logs" / "postmortem-consolidation.log"
45
- CLAUDE_CLI = HOME / ".local" / "bin" / "claude"
46
- SESSION_BUFFER = NEXO_HOME / "brain" / "session_buffer.jsonl"
47
-
48
- TODAY = date.today()
49
- TODAY_STR = TODAY.isoformat()
50
-
51
-
52
- def log(msg: str):
53
- ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
54
- line = f"[{ts}] {msg}"
55
- print(line, flush=True)
56
- CONSOLIDATION_LOG.parent.mkdir(parents=True, exist_ok=True)
57
- with open(CONSOLIDATION_LOG, "a") as f:
58
- f.write(line + "\n")
59
-
60
-
61
- # ─── Stage 1: Data Collection (Pure Python) ─────────────────────────────────
62
-
63
- def collect_data() -> dict:
64
- """Collects all data the CLI will need to decide."""
65
- data = {
66
- "date": TODAY_STR,
67
- "diaries": [],
68
- "existing_feedbacks": [],
69
- "history_summary": {},
70
- }
71
-
72
- if not NEXO_DB.exists():
73
- return data
74
-
75
- conn = sqlite3.connect(str(NEXO_DB))
76
- conn.row_factory = sqlite3.Row
77
-
78
- # Today's diaries with self-critique
79
- rows = conn.execute(
80
- "SELECT id, session_id, summary, self_critique, user_signals, "
81
- "mental_state, domain, created_at "
82
- "FROM session_diary WHERE date(created_at) = ? ORDER BY created_at",
83
- (TODAY_STR,)
84
- ).fetchall()
85
- data["diaries"] = [dict(r) for r in rows]
86
-
87
- conn.close()
88
-
89
- # Existing postmortem feedbacks (nombres, para no duplicar)
90
- data["existing_feedbacks"] = [
91
- f.stem for f in MEMORY_DIR.glob("feedback_postmortem_*.md")
92
- ]
93
-
94
- # History summary
95
- if HISTORY_FILE.exists():
96
- try:
97
- history = json.loads(HISTORY_FILE.read_text())
98
- data["history_summary"] = {
99
- "total_permanent_rules": len(history.get("permanent_rules", [])),
100
- "days_tracked": len(history.get("days", {})),
101
- "recent_rules": history.get("permanent_rules", [])[-10:],
102
- }
103
- except Exception:
104
- pass
105
-
106
- return data
107
-
108
-
109
- # ─── Stage 2: Intelligence (Claude CLI opus) ────────────────────────────────
110
-
111
- def consolidate_with_cli(data: dict) -> bool:
112
- """The brain consolidates — CLI decides what to promote."""
113
-
114
- diaries_with_critique = [
115
- d for d in data["diaries"]
116
- if d.get("self_critique") and not str(d["self_critique"]).strip().lower().startswith("no self-critique")
117
- ]
118
-
119
- if not diaries_with_critique:
120
- log("All sessions clean or trivial. Nothing to consolidate.")
121
- return True
122
-
123
- # Prepare data for CLI (truncate to avoid exceeding context)
124
- diaries_json = json.dumps(diaries_with_critique, ensure_ascii=False, indent=1)
125
- if len(diaries_json) > 12000:
126
- diaries_json = diaries_json[:12000] + "\n... (truncated)"
127
-
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.
132
-
133
- DATE: {data['date']}
134
- SESSIONS TODAY: {len(data['diaries'])} total, {len(diaries_with_critique)} with self-critique
135
-
136
- DIARIES WITH SELF-CRITIQUE:
137
- {diaries_json}
138
-
139
- EXISTING POSTMORTEM FEEDBACKS ({len(data['existing_feedbacks'])}):
140
- {json.dumps(data['existing_feedbacks'][:30], ensure_ascii=False)}
141
-
142
- RECENT PERMANENT RULES:
143
- {json.dumps(data['history_summary'].get('recent_rules', []), ensure_ascii=False)}
144
-
145
- INSTRUCTIONS:
146
-
147
- 1. Read each self_critique and understand its MEANING (don't count words).
148
-
149
- 2. PROMOTE to permanent feedback ONLY IF:
150
- - A pattern appears in 2+ different sessions of the day (by meaning, not literal text)
151
- - Or the user explicitly corrected (user_signals contains correction)
152
- - And the self-critique contains a CONCRETE ACTION that prevents a future error
153
- - And a similar feedback does NOT already exist in the existing ones
154
-
155
- 3. DO NOT promote if:
156
- - It's a negative response ("Nothing happened", "clean session")
157
- - It's generic without concrete action
158
- - A feedback covering the same topic already exists
159
-
160
- 4. For each rule to promote, create the file with Write en {MEMORY_DIR}/:
161
- Nombre: feedback_postmortem_[slug_descriptivo].md
162
- Formato:
163
- ---
164
- name: [descriptive title]
165
- description: Behavioral rule extracted from self-critique — recurring pattern
166
- type: feedback
167
- ---
168
-
169
- [Clear description of the pattern and rule]
170
-
171
- **Why:** [Why this matters — with evidence from sessions]
172
- **How to apply:** [When and how to apply this rule]
173
-
174
- 5. Write the daily summary en $NEXO_HOME/coordination/postmortem-daily.md:
175
- # Post-Mortem Daily — {data['date']}
176
- Sessions: X | Self-critiques: Y | Promoted: Z
177
-
178
- ## Today's self-critiques (summary)
179
- [Lista breve]
180
-
181
- ## Promoted to permanent memory
182
- [What you promoted and why]
183
-
184
- ## Discarded (and why)
185
- [What you did NOT promote and the reason]
186
-
187
- Execute without asking."""
188
-
189
- log(f"Stage 2: Invoking Claude CLI (opus) with {len(diaries_with_critique)} critiques...")
190
- env = os.environ.copy()
191
- env["NEXO_HEADLESS"] = "1" # Skip stop hook post-mortem
192
- env.pop("CLAUDECODE", None)
193
- env.pop("CLAUDE_CODE", None)
194
-
195
- try:
196
- result = subprocess.run(
197
- [str(CLAUDE_CLI), "-p", prompt, "--model", "opus",
198
- "--output-format", "text",
199
- "--allowedTools", "Read,Write,Edit,Glob,Grep,Bash,mcp__nexo__*"],
200
- capture_output=True, text=True, timeout=21600, env=env
201
- )
202
-
203
- if result.returncode != 0:
204
- log(f"Stage 2: CLI error (code {result.returncode}): {(result.stderr or '')[:300]}")
205
- return False
206
-
207
- log(f"Stage 2: Completed. Output: {len(result.stdout or '')} chars")
208
- # Log last 500 chars of output for debugging
209
- if result.stdout:
210
- log(f"Stage 2 output tail: {result.stdout[-500:]}")
211
- return True
212
-
213
- except subprocess.TimeoutExpired:
214
- log("Stage 2: CLI timed out (300s)")
215
- return False
216
- except Exception as e:
217
- log(f"Stage 2: Exception: {e}")
218
- return False
219
-
220
-
221
- # ─── Stage 3: Sensory Register + Force Analysis (Pure Python) ───────────────
222
- # Kept from v1 — these are genuinely mechanical (embedding vectors, DB updates)
223
-
224
- def process_sensory_register():
225
- """Sensory Register — Atkinson-Shiffrin Layer 1. Embeds events into STM."""
226
- log("--- Sensory Register processing ---")
227
-
228
- if not SESSION_BUFFER.exists():
229
- log(" No session_buffer.jsonl found, skipping")
230
- return
231
-
232
- today_events = []
233
- try:
234
- with open(SESSION_BUFFER) as f:
235
- for line in f:
236
- line = line.strip()
237
- if not line:
238
- continue
239
- try:
240
- event = json.loads(line)
241
- if event.get("ts", "").startswith(TODAY_STR):
242
- today_events.append(event)
243
- except json.JSONDecodeError:
244
- continue
245
- except Exception as e:
246
- log(f" Error reading session_buffer: {e}")
247
- return
248
-
249
- if not today_events:
250
- log(" No events from today")
251
- return
252
-
253
- log(f" Found {len(today_events)} events")
254
-
255
- try:
256
- import cognitive
257
- except ImportError as e:
258
- log(f" Cannot import cognitive: {e}")
259
- return
260
-
261
- ingested = 0
262
- for event in today_events:
263
- source = event.get("source", "")
264
- if source == "hook-fallback":
265
- task_str = " ".join(event.get("tasks", []))
266
- if len(task_str) < 50 or "," in task_str:
267
- continue
268
-
269
- parts = []
270
- for key, label in [("tasks", "Tasks"), ("decisions", "Decisions"),
271
- ("errors_resolved", "Errors"), ("user_patterns", "the user")]:
272
- val = event.get(key, [])
273
- if val:
274
- parts.append(f"{label}: {'; '.join(str(v) for v in val[:3])}")
275
-
276
- critique = event.get("self_critique", "")
277
- if critique and "hook-fallback" not in critique:
278
- parts.append(f"Self-critique: {critique[:200]}")
279
-
280
- content = " | ".join(parts)
281
- if not content or len(content) < 20:
282
- continue
283
-
284
- try:
285
- vec = cognitive.embed(content)
286
- domain = ""
287
- lower = content.lower()
288
- # Add your project keywords for domain detection
289
- for keyword, dom in [("nexo", "nexo")]:
290
- if keyword in lower:
291
- domain = dom
292
- break
293
-
294
- cognitive.ingest_sensory(
295
- content=content, source_id=f"buffer#{event.get('ts', '')}",
296
- domain=domain, created_at=event.get("ts", "")
297
- )
298
- ingested += 1
299
- except Exception as e:
300
- log(f" Error embedding: {e}")
301
-
302
- log(f" Ingested {ingested} sensory events into STM")
303
-
304
-
305
- def analyze_force_events():
306
- """Analyze --force dissonance resolutions from today."""
307
- log("--- Force event analysis ---")
308
-
309
- try:
310
- import cognitive
311
- except ImportError:
312
- log(" Cannot import cognitive, skipping")
313
- return
314
-
315
- db = cognitive._get_db()
316
- today_forces = db.execute(
317
- """SELECT memory_id, context, created_at FROM memory_corrections
318
- WHERE correction_type = 'exception' AND context LIKE '%[FORCE]%'
319
- AND date(created_at) = ? ORDER BY created_at""",
320
- (TODAY_STR,)
321
- ).fetchall()
322
-
323
- if not today_forces:
324
- log(" No --force events today")
325
- return
326
-
327
- log(f" {len(today_forces)} --force events")
328
-
329
- from collections import Counter
330
- memory_counts = Counter(r["memory_id"] for r in today_forces)
331
- for mem_id, count in memory_counts.most_common():
332
- mem = db.execute(
333
- "SELECT content, strength FROM ltm_memories WHERE id = ?", (mem_id,)
334
- ).fetchone()
335
- if not mem:
336
- continue
337
-
338
- total = db.execute(
339
- "SELECT COUNT(*) FROM memory_corrections WHERE memory_id = ? AND context LIKE '%[FORCE]%'",
340
- (mem_id,)
341
- ).fetchone()[0]
342
-
343
- if total >= 3:
344
- log(f" PARADIGM SHIFT: LTM #{mem_id} overridden {total}x → decay to 0.3")
345
- db.execute(
346
- "UPDATE ltm_memories SET strength = 0.3, "
347
- "tags = CASE WHEN tags LIKE '%paradigm_candidate%' THEN tags "
348
- "ELSE tags || ',paradigm_candidate' END WHERE id = ?",
349
- (mem_id,)
350
- )
351
- elif count >= 2:
352
- log(f" WATCH: LTM #{mem_id} overridden {count}x today")
353
-
354
- db.commit()
355
-
356
-
357
- # ─── Main ────────────────────────────────────────────────────────────────────
358
-
359
- def already_ran_today() -> bool:
360
- """Prevent running twice on the same day."""
361
- marker = NEXO_HOME / "coordination" / "postmortem-last-run"
362
- if marker.exists():
363
- try:
364
- return marker.read_text().strip() == TODAY_STR
365
- except Exception:
366
- return False
367
- return False
368
-
369
-
370
- def mark_done():
371
- marker = NEXO_HOME / "coordination" / "postmortem-last-run"
372
- marker.parent.mkdir(parents=True, exist_ok=True)
373
- marker.write_text(TODAY_STR)
374
-
375
-
376
- def main():
377
- if already_ran_today():
378
- log("Already ran today. Skipping.")
379
- return
380
-
381
- log("=== NEXO Post-Mortem Consolidator v2 starting ===")
382
-
383
- # Stage 1: Collect data
384
- data = collect_data()
385
- log(f"Stage 1: {len(data['diaries'])} diaries, {len(data['existing_feedbacks'])} existing feedbacks")
386
-
387
- if not data["diaries"]:
388
- log("No session diaries today. Nothing to consolidate.")
389
- else:
390
- # Stage 2: CLI intelligence
391
- success = consolidate_with_cli(data)
392
- if not success:
393
- log("Stage 2 failed — falling back to skip (no v1 fallback)")
394
-
395
- # Stage 3: Sensory Register (mechanical, kept from v1)
396
- try:
397
- process_sensory_register()
398
- except Exception as e:
399
- log(f"Sensory register failed: {e}")
400
-
401
- # Stage 3b: Force analysis (mechanical, kept from v1)
402
- try:
403
- analyze_force_events()
404
- except Exception as e:
405
- log(f"Force analysis failed: {e}")
406
-
407
- # Register successful run
408
- try:
409
- state_file = NEXO_HOME / "operations" / ".catchup-state.json"
410
- state = json.loads(state_file.read_text()) if state_file.exists() else {}
411
- state["postmortem"] = datetime.now().isoformat()
412
- state_file.write_text(json.dumps(state, indent=2))
413
- except Exception:
414
- pass
415
-
416
- mark_done()
417
- log("=== Consolidation v2 complete ===")
418
-
419
-
420
- if __name__ == "__main__":
421
- main()