nexo-brain 2.3.0 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (287) hide show
  1. package/README.md +1 -1
  2. package/package.json +6 -3
  3. package/src/auto_update.py +1 -0
  4. package/src/crons/sync.py +1 -2
  5. package/src/db/_core.py +1 -0
  6. package/src/db/_entities.py +1 -0
  7. package/src/db/_episodic.py +1 -0
  8. package/src/db/_learnings.py +1 -0
  9. package/src/db/_reminders.py +1 -0
  10. package/src/db/_sessions.py +1 -0
  11. package/src/db/_skills.py +1 -0
  12. package/src/plugin_loader.py +1 -0
  13. package/src/plugins/update.py +1 -0
  14. package/src/scripts/deep-sleep/apply_findings.py +1 -0
  15. package/src/scripts/deep-sleep/collect.py +1 -0
  16. package/src/scripts/deep-sleep/extract.py +1 -0
  17. package/src/scripts/deep-sleep/synthesize.py +1 -0
  18. package/src/scripts/nexo-learning-housekeep.py +1 -0
  19. package/src/scripts/nexo-watchdog.sh +19 -11
  20. package/src/server.py +1 -0
  21. package/src/tools_coordination.py +1 -0
  22. package/src/tools_sessions.py +1 -0
  23. package/scripts/migrate-to-unified 2.sh +0 -813
  24. package/scripts/migrate-to-unified.sh +0 -813
  25. package/scripts/migrate-v1.5-to-v1.6 2.py +0 -778
  26. package/scripts/migrate-v1.5-to-v1.6.py +0 -778
  27. package/scripts/migrate-v1.7-to-v1.8 2.py +0 -214
  28. package/scripts/migrate-v1.7-to-v1.8.py +0 -214
  29. package/scripts/nexo-preflight.sh +0 -236
  30. package/scripts/pre-commit-check 2.sh +0 -55
  31. package/scripts/pre-commit-check.sh +0 -55
  32. package/src/__pycache__/auto_close_sessions.cpython-314.pyc +0 -0
  33. package/src/__pycache__/auto_update.cpython-310.pyc +0 -0
  34. package/src/__pycache__/hnsw_index.cpython-310.pyc +0 -0
  35. package/src/__pycache__/hnsw_index.cpython-314.pyc +0 -0
  36. package/src/__pycache__/kg_populate.cpython-310.pyc +0 -0
  37. package/src/__pycache__/knowledge_graph.cpython-310.pyc +0 -0
  38. package/src/__pycache__/plugin_loader.cpython-310.pyc +0 -0
  39. package/src/__pycache__/plugin_loader.cpython-314.pyc +0 -0
  40. package/src/__pycache__/tools_coordination.cpython-310.pyc +0 -0
  41. package/src/__pycache__/tools_credentials.cpython-310.pyc +0 -0
  42. package/src/__pycache__/tools_learnings.cpython-310.pyc +0 -0
  43. package/src/__pycache__/tools_menu.cpython-310.pyc +0 -0
  44. package/src/__pycache__/tools_reminders.cpython-310.pyc +0 -0
  45. package/src/__pycache__/tools_reminders_crud.cpython-310.pyc +0 -0
  46. package/src/__pycache__/tools_sessions.cpython-310.pyc +0 -0
  47. package/src/__pycache__/tools_task_history.cpython-310.pyc +0 -0
  48. package/src/auto_close_sessions 2.py +0 -159
  49. package/src/auto_update 2.py +0 -634
  50. package/src/claim_graph 2.py +0 -323
  51. package/src/cognitive/__init__ 2.py +0 -62
  52. package/src/cognitive/__pycache__/__init__.cpython-310.pyc +0 -0
  53. package/src/cognitive/__pycache__/__init__.cpython-312.pyc +0 -0
  54. package/src/cognitive/__pycache__/__init__.cpython-314.pyc +0 -0
  55. package/src/cognitive/__pycache__/_core.cpython-310.pyc +0 -0
  56. package/src/cognitive/__pycache__/_core.cpython-312.pyc +0 -0
  57. package/src/cognitive/__pycache__/_core.cpython-314.pyc +0 -0
  58. package/src/cognitive/__pycache__/_decay.cpython-310.pyc +0 -0
  59. package/src/cognitive/__pycache__/_decay.cpython-312.pyc +0 -0
  60. package/src/cognitive/__pycache__/_decay.cpython-314.pyc +0 -0
  61. package/src/cognitive/__pycache__/_ingest.cpython-310.pyc +0 -0
  62. package/src/cognitive/__pycache__/_ingest.cpython-312.pyc +0 -0
  63. package/src/cognitive/__pycache__/_ingest.cpython-314.pyc +0 -0
  64. package/src/cognitive/__pycache__/_memory.cpython-310.pyc +0 -0
  65. package/src/cognitive/__pycache__/_memory.cpython-312.pyc +0 -0
  66. package/src/cognitive/__pycache__/_memory.cpython-314.pyc +0 -0
  67. package/src/cognitive/__pycache__/_search.cpython-310.pyc +0 -0
  68. package/src/cognitive/__pycache__/_search.cpython-312.pyc +0 -0
  69. package/src/cognitive/__pycache__/_search.cpython-314.pyc +0 -0
  70. package/src/cognitive/__pycache__/_trust.cpython-310.pyc +0 -0
  71. package/src/cognitive/__pycache__/_trust.cpython-312.pyc +0 -0
  72. package/src/cognitive/__pycache__/_trust.cpython-314.pyc +0 -0
  73. package/src/cognitive/_core 2.py +0 -567
  74. package/src/cognitive/_decay 2.py +0 -382
  75. package/src/cognitive/_ingest 2.py +0 -892
  76. package/src/cognitive/_memory 2.py +0 -912
  77. package/src/cognitive/_search 2.py +0 -949
  78. package/src/cognitive/_trust 2.py +0 -464
  79. package/src/crons/__pycache__/sync.cpython-314.pyc +0 -0
  80. package/src/crons/manifest 2.json +0 -106
  81. package/src/crons/sync 2.py +0 -217
  82. package/src/dashboard/__init__ 2.py +0 -0
  83. package/src/dashboard/__pycache__/__init__.cpython-310.pyc +0 -0
  84. package/src/dashboard/__pycache__/app.cpython-310.pyc +0 -0
  85. package/src/dashboard/app 2.py +0 -789
  86. package/src/db/__init__ 2.py +0 -89
  87. package/src/db/__pycache__/__init__.cpython-310.pyc +0 -0
  88. package/src/db/__pycache__/__init__.cpython-312.pyc +0 -0
  89. package/src/db/__pycache__/__init__.cpython-314.pyc +0 -0
  90. package/src/db/__pycache__/_core.cpython-310.pyc +0 -0
  91. package/src/db/__pycache__/_core.cpython-312.pyc +0 -0
  92. package/src/db/__pycache__/_core.cpython-314.pyc +0 -0
  93. package/src/db/__pycache__/_credentials.cpython-310.pyc +0 -0
  94. package/src/db/__pycache__/_credentials.cpython-312.pyc +0 -0
  95. package/src/db/__pycache__/_credentials.cpython-314.pyc +0 -0
  96. package/src/db/__pycache__/_cron_runs.cpython-310.pyc +0 -0
  97. package/src/db/__pycache__/_cron_runs.cpython-314.pyc +0 -0
  98. package/src/db/__pycache__/_entities.cpython-310.pyc +0 -0
  99. package/src/db/__pycache__/_entities.cpython-312.pyc +0 -0
  100. package/src/db/__pycache__/_entities.cpython-314.pyc +0 -0
  101. package/src/db/__pycache__/_episodic.cpython-310.pyc +0 -0
  102. package/src/db/__pycache__/_episodic.cpython-312.pyc +0 -0
  103. package/src/db/__pycache__/_episodic.cpython-314.pyc +0 -0
  104. package/src/db/__pycache__/_evolution.cpython-310.pyc +0 -0
  105. package/src/db/__pycache__/_evolution.cpython-312.pyc +0 -0
  106. package/src/db/__pycache__/_evolution.cpython-314.pyc +0 -0
  107. package/src/db/__pycache__/_fts.cpython-310.pyc +0 -0
  108. package/src/db/__pycache__/_fts.cpython-312.pyc +0 -0
  109. package/src/db/__pycache__/_fts.cpython-314.pyc +0 -0
  110. package/src/db/__pycache__/_learnings.cpython-310.pyc +0 -0
  111. package/src/db/__pycache__/_learnings.cpython-312.pyc +0 -0
  112. package/src/db/__pycache__/_learnings.cpython-314.pyc +0 -0
  113. package/src/db/__pycache__/_reminders.cpython-310.pyc +0 -0
  114. package/src/db/__pycache__/_reminders.cpython-312.pyc +0 -0
  115. package/src/db/__pycache__/_reminders.cpython-314.pyc +0 -0
  116. package/src/db/__pycache__/_schema.cpython-310.pyc +0 -0
  117. package/src/db/__pycache__/_schema.cpython-312.pyc +0 -0
  118. package/src/db/__pycache__/_schema.cpython-314.pyc +0 -0
  119. package/src/db/__pycache__/_sessions.cpython-310.pyc +0 -0
  120. package/src/db/__pycache__/_sessions.cpython-312.pyc +0 -0
  121. package/src/db/__pycache__/_sessions.cpython-314.pyc +0 -0
  122. package/src/db/__pycache__/_skills.cpython-310.pyc +0 -0
  123. package/src/db/__pycache__/_skills.cpython-312.pyc +0 -0
  124. package/src/db/__pycache__/_skills.cpython-314.pyc +0 -0
  125. package/src/db/__pycache__/_tasks.cpython-310.pyc +0 -0
  126. package/src/db/__pycache__/_tasks.cpython-312.pyc +0 -0
  127. package/src/db/__pycache__/_tasks.cpython-314.pyc +0 -0
  128. package/src/db/_core 2.py +0 -417
  129. package/src/db/_credentials 2.py +0 -124
  130. package/src/db/_entities 2.py +0 -178
  131. package/src/db/_episodic 2.py +0 -738
  132. package/src/db/_evolution 2.py +0 -54
  133. package/src/db/_fts 2.py +0 -406
  134. package/src/db/_learnings 2.py +0 -168
  135. package/src/db/_reminders 2.py +0 -338
  136. package/src/db/_schema 2.py +0 -364
  137. package/src/db/_sessions 2.py +0 -300
  138. package/src/db/_tasks 2.py +0 -91
  139. package/src/evolution_cycle 2.py +0 -266
  140. package/src/hnsw_index 2.py +0 -254
  141. package/src/hooks/auto_capture 2.py +0 -208
  142. package/src/hooks/caffeinate-guard 2.sh +0 -8
  143. package/src/hooks/capture-session 2.sh +0 -21
  144. package/src/hooks/capture-tool-logs 2.sh +0 -127
  145. package/src/hooks/daily-briefing-check 2.sh +0 -33
  146. package/src/hooks/inbox-hook 2.sh +0 -76
  147. package/src/hooks/post-compact 2.sh +0 -148
  148. package/src/hooks/pre-compact 2.sh +0 -151
  149. package/src/hooks/session-start 2.sh +0 -268
  150. package/src/hooks/session-stop 2.sh +0 -140
  151. package/src/kg_populate 2.py +0 -290
  152. package/src/knowledge_graph 2.py +0 -257
  153. package/src/maintenance 2.py +0 -59
  154. package/src/migrate_embeddings 2.py +0 -122
  155. package/src/plugin_loader 2.py +0 -202
  156. package/src/plugins/__init__ 2.py +0 -0
  157. package/src/plugins/__pycache__/__init__ 2.cpython-310.pyc +0 -0
  158. package/src/plugins/__pycache__/__init__.cpython-310.pyc +0 -0
  159. package/src/plugins/__pycache__/__init__.cpython-314.pyc +0 -0
  160. package/src/plugins/__pycache__/adaptive_mode 2.cpython-310.pyc +0 -0
  161. package/src/plugins/__pycache__/adaptive_mode.cpython-310.pyc +0 -0
  162. package/src/plugins/__pycache__/adaptive_mode.cpython-314.pyc +0 -0
  163. package/src/plugins/__pycache__/agents 2.cpython-310.pyc +0 -0
  164. package/src/plugins/__pycache__/agents.cpython-310.pyc +0 -0
  165. package/src/plugins/__pycache__/artifact_registry 2.cpython-310.pyc +0 -0
  166. package/src/plugins/__pycache__/artifact_registry.cpython-310.pyc +0 -0
  167. package/src/plugins/__pycache__/backup 2.cpython-310.pyc +0 -0
  168. package/src/plugins/__pycache__/backup.cpython-310.pyc +0 -0
  169. package/src/plugins/__pycache__/cognitive_memory 2.cpython-310.pyc +0 -0
  170. package/src/plugins/__pycache__/cognitive_memory.cpython-310.pyc +0 -0
  171. package/src/plugins/__pycache__/core_rules 2.cpython-310.pyc +0 -0
  172. package/src/plugins/__pycache__/core_rules.cpython-310.pyc +0 -0
  173. package/src/plugins/__pycache__/cortex 2.cpython-310.pyc +0 -0
  174. package/src/plugins/__pycache__/cortex.cpython-310.pyc +0 -0
  175. package/src/plugins/__pycache__/entities 2.cpython-310.pyc +0 -0
  176. package/src/plugins/__pycache__/entities.cpython-310.pyc +0 -0
  177. package/src/plugins/__pycache__/episodic_memory 2.cpython-310.pyc +0 -0
  178. package/src/plugins/__pycache__/episodic_memory.cpython-310.pyc +0 -0
  179. package/src/plugins/__pycache__/evolution 2.cpython-310.pyc +0 -0
  180. package/src/plugins/__pycache__/evolution.cpython-310.pyc +0 -0
  181. package/src/plugins/__pycache__/guard 2.cpython-310.pyc +0 -0
  182. package/src/plugins/__pycache__/guard.cpython-310.pyc +0 -0
  183. package/src/plugins/__pycache__/knowledge_graph_tools 2.cpython-310.pyc +0 -0
  184. package/src/plugins/__pycache__/knowledge_graph_tools.cpython-310.pyc +0 -0
  185. package/src/plugins/__pycache__/preferences 2.cpython-310.pyc +0 -0
  186. package/src/plugins/__pycache__/preferences.cpython-310.pyc +0 -0
  187. package/src/plugins/__pycache__/schedule.cpython-310.pyc +0 -0
  188. package/src/plugins/__pycache__/schedule.cpython-314.pyc +0 -0
  189. package/src/plugins/__pycache__/skills.cpython-310.pyc +0 -0
  190. package/src/plugins/__pycache__/skills.cpython-314.pyc +0 -0
  191. package/src/plugins/__pycache__/update 2.cpython-310.pyc +0 -0
  192. package/src/plugins/__pycache__/update.cpython-310.pyc +0 -0
  193. package/src/plugins/adaptive_mode 2.py +0 -805
  194. package/src/plugins/agents 2.py +0 -52
  195. package/src/plugins/artifact_registry 2.py +0 -450
  196. package/src/plugins/backup 2.py +0 -104
  197. package/src/plugins/cognitive_memory 2.py +0 -564
  198. package/src/plugins/core_rules 2.py +0 -252
  199. package/src/plugins/cortex 2.py +0 -299
  200. package/src/plugins/entities 2.py +0 -67
  201. package/src/plugins/episodic_memory 2.py +0 -533
  202. package/src/plugins/evolution 2.py +0 -115
  203. package/src/plugins/guard 2.py +0 -746
  204. package/src/plugins/knowledge_graph_tools 2.py +0 -105
  205. package/src/plugins/preferences 2.py +0 -47
  206. package/src/plugins/update 2.py +0 -256
  207. package/src/requirements 2.txt +0 -12
  208. package/src/rules/__init__ 2.py +0 -0
  209. package/src/rules/core-rules 2.json +0 -331
  210. package/src/rules/migrate 2.py +0 -207
  211. package/src/scripts/__pycache__/nexo-auto-update.cpython-314.pyc +0 -0
  212. package/src/scripts/__pycache__/nexo-catchup.cpython-314.pyc +0 -0
  213. package/src/scripts/__pycache__/nexo-cognitive-decay.cpython-314.pyc +0 -0
  214. package/src/scripts/__pycache__/nexo-daily-self-audit.cpython-314.pyc +0 -0
  215. package/src/scripts/__pycache__/nexo-evolution-run.cpython-314.pyc +0 -0
  216. package/src/scripts/__pycache__/nexo-followup-hygiene.cpython-314.pyc +0 -0
  217. package/src/scripts/__pycache__/nexo-immune.cpython-314.pyc +0 -0
  218. package/src/scripts/__pycache__/nexo-install.cpython-314.pyc +0 -0
  219. package/src/scripts/__pycache__/nexo-learning-housekeep.cpython-314.pyc +0 -0
  220. package/src/scripts/__pycache__/nexo-learning-validator.cpython-314.pyc +0 -0
  221. package/src/scripts/__pycache__/nexo-migrate.cpython-314.pyc +0 -0
  222. package/src/scripts/__pycache__/nexo-postmortem-consolidator.cpython-314.pyc +0 -0
  223. package/src/scripts/__pycache__/nexo-pre-commit.cpython-314.pyc +0 -0
  224. package/src/scripts/__pycache__/nexo-proactive-dashboard.cpython-314.pyc +0 -0
  225. package/src/scripts/__pycache__/nexo-reflection.cpython-314.pyc +0 -0
  226. package/src/scripts/__pycache__/nexo-runtime-preflight.cpython-314.pyc +0 -0
  227. package/src/scripts/__pycache__/nexo-send-email.cpython-314.pyc +0 -0
  228. package/src/scripts/__pycache__/nexo-send-reply.cpython-314.pyc +0 -0
  229. package/src/scripts/__pycache__/nexo-sleep.cpython-314.pyc +0 -0
  230. package/src/scripts/__pycache__/nexo-synthesis.cpython-314.pyc +0 -0
  231. package/src/scripts/__pycache__/nexo-watchdog-smoke.cpython-314.pyc +0 -0
  232. package/src/scripts/check-context 2.py +0 -264
  233. package/src/scripts/nexo-auto-update 2.py +0 -6
  234. package/src/scripts/nexo-backup 2.sh +0 -25
  235. package/src/scripts/nexo-brain-activation 2.sh +0 -140
  236. package/src/scripts/nexo-catchup 2.py +0 -242
  237. package/src/scripts/nexo-cognitive-decay 2.py +0 -182
  238. package/src/scripts/nexo-daily-self-audit 2.py +0 -552
  239. package/src/scripts/nexo-deep-sleep 2.sh +0 -97
  240. package/src/scripts/nexo-evolution-run 2.py +0 -597
  241. package/src/scripts/nexo-followup-hygiene 2.py +0 -112
  242. package/src/scripts/nexo-github-monitor 2.py +0 -256
  243. package/src/scripts/nexo-immune 2.py +0 -927
  244. package/src/scripts/nexo-inbox-hook 2.sh +0 -74
  245. package/src/scripts/nexo-install 2.py +0 -6
  246. package/src/scripts/nexo-learning-housekeep 2.py +0 -245
  247. package/src/scripts/nexo-learning-validator 2.py +0 -207
  248. package/src/scripts/nexo-migrate 2.py +0 -232
  249. package/src/scripts/nexo-postmortem-consolidator 2.py +0 -421
  250. package/src/scripts/nexo-pre-commit 2.py +0 -120
  251. package/src/scripts/nexo-prevent-sleep 2.sh +0 -29
  252. package/src/scripts/nexo-proactive-dashboard 2.py +0 -345
  253. package/src/scripts/nexo-reflection 2.py +0 -253
  254. package/src/scripts/nexo-runtime-preflight 2.py +0 -274
  255. package/src/scripts/nexo-send-email 2.py +0 -25
  256. package/src/scripts/nexo-send-email.py +0 -25
  257. package/src/scripts/nexo-send-reply 2.py +0 -178
  258. package/src/scripts/nexo-send-reply.py +0 -178
  259. package/src/scripts/nexo-sleep 2.py +0 -592
  260. package/src/scripts/nexo-snapshot-restore 2.sh +0 -35
  261. package/src/scripts/nexo-synthesis 2.py +0 -253
  262. package/src/scripts/nexo-tcc-approve 2.sh +0 -79
  263. package/src/scripts/nexo-update 2.sh +0 -161
  264. package/src/scripts/nexo-watchdog 2.sh +0 -878
  265. package/src/scripts/nexo-watchdog-smoke 2.py +0 -119
  266. package/src/server 2.py +0 -733
  267. package/src/storage_router 2.py +0 -32
  268. package/src/tools_coordination 2.py +0 -102
  269. package/src/tools_credentials 2.py +0 -68
  270. package/src/tools_learnings 2.py +0 -220
  271. package/src/tools_menu 2.py +0 -227
  272. package/src/tools_reminders 2.py +0 -86
  273. package/src/tools_reminders_crud 2.py +0 -159
  274. package/src/tools_sessions 2.py +0 -476
  275. package/src/tools_task_history 2.py +0 -57
  276. package/templates/CLAUDE.md 2.template +0 -63
  277. package/templates/openclaw 2.json +0 -13
  278. package/tests/__init__ 2.py +0 -0
  279. package/tests/__init__.py +0 -0
  280. package/tests/conftest 2.py +0 -71
  281. package/tests/conftest.py +0 -71
  282. package/tests/test_cognitive 2.py +0 -205
  283. package/tests/test_cognitive.py +0 -205
  284. package/tests/test_knowledge_graph 2.py +0 -140
  285. package/tests/test_knowledge_graph.py +0 -140
  286. package/tests/test_migrations 2.py +0 -137
  287. 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()