nexo-brain 1.6.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (301) hide show
  1. package/README.md +102 -79
  2. package/bin/nexo-brain.js +681 -303
  3. package/bin/postinstall.js +46 -0
  4. package/package.json +14 -2
  5. package/scripts/migrate-to-unified.sh +813 -0
  6. package/scripts/migrate-v1.7-to-v1.8.py +214 -0
  7. package/scripts/pre-commit-check.sh +1 -1
  8. package/src/__pycache__/auto_close_sessions.cpython-310.pyc +0 -0
  9. package/src/__pycache__/auto_close_sessions.cpython-314.pyc +0 -0
  10. package/src/__pycache__/auto_update.cpython-310.pyc +0 -0
  11. package/src/__pycache__/auto_update.cpython-314.pyc +0 -0
  12. package/src/__pycache__/claim_graph.cpython-310.pyc +0 -0
  13. package/src/__pycache__/claim_graph.cpython-314.pyc +0 -0
  14. package/src/__pycache__/evolution_cycle.cpython-310.pyc +0 -0
  15. package/src/__pycache__/evolution_cycle.cpython-314.pyc +0 -0
  16. package/src/__pycache__/hnsw_index.cpython-310.pyc +0 -0
  17. package/src/__pycache__/hnsw_index.cpython-314.pyc +0 -0
  18. package/src/__pycache__/kg_populate.cpython-310.pyc +0 -0
  19. package/src/__pycache__/kg_populate.cpython-314.pyc +0 -0
  20. package/src/__pycache__/knowledge_graph.cpython-310.pyc +0 -0
  21. package/src/__pycache__/knowledge_graph.cpython-314.pyc +0 -0
  22. package/src/__pycache__/maintenance.cpython-310.pyc +0 -0
  23. package/src/__pycache__/maintenance.cpython-314.pyc +0 -0
  24. package/src/__pycache__/migrate_embeddings.cpython-310.pyc +0 -0
  25. package/src/__pycache__/migrate_embeddings.cpython-314.pyc +0 -0
  26. package/src/__pycache__/plugin_loader.cpython-310.pyc +0 -0
  27. package/src/__pycache__/plugin_loader.cpython-314.pyc +0 -0
  28. package/src/__pycache__/server.cpython-310.pyc +0 -0
  29. package/src/__pycache__/server.cpython-314.pyc +0 -0
  30. package/src/__pycache__/storage_router.cpython-310.pyc +0 -0
  31. package/src/__pycache__/storage_router.cpython-314.pyc +0 -0
  32. package/src/__pycache__/tools_coordination.cpython-310.pyc +0 -0
  33. package/src/__pycache__/tools_coordination.cpython-314.pyc +0 -0
  34. package/src/__pycache__/tools_credentials.cpython-310.pyc +0 -0
  35. package/src/__pycache__/tools_credentials.cpython-314.pyc +0 -0
  36. package/src/__pycache__/tools_learnings.cpython-310.pyc +0 -0
  37. package/src/__pycache__/tools_learnings.cpython-314.pyc +0 -0
  38. package/src/__pycache__/tools_menu.cpython-310.pyc +0 -0
  39. package/src/__pycache__/tools_menu.cpython-314.pyc +0 -0
  40. package/src/__pycache__/tools_reminders.cpython-310.pyc +0 -0
  41. package/src/__pycache__/tools_reminders.cpython-314.pyc +0 -0
  42. package/src/__pycache__/tools_reminders_crud.cpython-310.pyc +0 -0
  43. package/src/__pycache__/tools_reminders_crud.cpython-314.pyc +0 -0
  44. package/src/__pycache__/tools_sessions.cpython-310.pyc +0 -0
  45. package/src/__pycache__/tools_sessions.cpython-314.pyc +0 -0
  46. package/src/__pycache__/tools_task_history.cpython-310.pyc +0 -0
  47. package/src/__pycache__/tools_task_history.cpython-314.pyc +0 -0
  48. package/src/auto_close_sessions.py +4 -3
  49. package/src/auto_update.py +634 -0
  50. package/src/cognitive/__pycache__/__init__.cpython-310.pyc +0 -0
  51. package/src/cognitive/__pycache__/__init__.cpython-314.pyc +0 -0
  52. package/src/cognitive/__pycache__/_core.cpython-310.pyc +0 -0
  53. package/src/cognitive/__pycache__/_core.cpython-314.pyc +0 -0
  54. package/src/cognitive/__pycache__/_decay.cpython-310.pyc +0 -0
  55. package/src/cognitive/__pycache__/_decay.cpython-314.pyc +0 -0
  56. package/src/cognitive/__pycache__/_ingest.cpython-310.pyc +0 -0
  57. package/src/cognitive/__pycache__/_ingest.cpython-314.pyc +0 -0
  58. package/src/cognitive/__pycache__/_memory.cpython-310.pyc +0 -0
  59. package/src/cognitive/__pycache__/_memory.cpython-314.pyc +0 -0
  60. package/src/cognitive/__pycache__/_search.cpython-310.pyc +0 -0
  61. package/src/cognitive/__pycache__/_search.cpython-314.pyc +0 -0
  62. package/src/cognitive/__pycache__/_trust.cpython-310.pyc +0 -0
  63. package/src/cognitive/__pycache__/_trust.cpython-314.pyc +0 -0
  64. package/src/cognitive/_core.py +7 -3
  65. package/src/cognitive/_decay.py +1 -1
  66. package/src/cognitive/_memory.py +7 -3
  67. package/src/cognitive/_search.py +12 -10
  68. package/src/cognitive/_trust.py +3 -3
  69. package/src/dashboard/__pycache__/__init__.cpython-310.pyc +0 -0
  70. package/src/dashboard/__pycache__/__init__.cpython-314.pyc +0 -0
  71. package/src/dashboard/__pycache__/app.cpython-310.pyc +0 -0
  72. package/src/dashboard/__pycache__/app.cpython-314.pyc +0 -0
  73. package/src/dashboard/app.py +9 -3
  74. package/src/dashboard/templates/dashboard.html +4 -4
  75. package/src/dashboard/templates/operations.html +6 -6
  76. package/src/db/__init__.py +2 -1
  77. package/src/db/__pycache__/__init__.cpython-310.pyc +0 -0
  78. package/src/db/__pycache__/__init__.cpython-312.pyc +0 -0
  79. package/src/db/__pycache__/__init__.cpython-314.pyc +0 -0
  80. package/src/db/__pycache__/_core.cpython-310.pyc +0 -0
  81. package/src/db/__pycache__/_core.cpython-312.pyc +0 -0
  82. package/src/db/__pycache__/_core.cpython-314.pyc +0 -0
  83. package/src/db/__pycache__/_credentials.cpython-310.pyc +0 -0
  84. package/src/db/__pycache__/_credentials.cpython-312.pyc +0 -0
  85. package/src/db/__pycache__/_credentials.cpython-314.pyc +0 -0
  86. package/src/db/__pycache__/_entities.cpython-310.pyc +0 -0
  87. package/src/db/__pycache__/_entities.cpython-312.pyc +0 -0
  88. package/src/db/__pycache__/_entities.cpython-314.pyc +0 -0
  89. package/src/db/__pycache__/_episodic.cpython-310.pyc +0 -0
  90. package/src/db/__pycache__/_episodic.cpython-312.pyc +0 -0
  91. package/src/db/__pycache__/_episodic.cpython-314.pyc +0 -0
  92. package/src/db/__pycache__/_evolution.cpython-310.pyc +0 -0
  93. package/src/db/__pycache__/_evolution.cpython-312.pyc +0 -0
  94. package/src/db/__pycache__/_evolution.cpython-314.pyc +0 -0
  95. package/src/db/__pycache__/_fts.cpython-310.pyc +0 -0
  96. package/src/db/__pycache__/_fts.cpython-312.pyc +0 -0
  97. package/src/db/__pycache__/_fts.cpython-314.pyc +0 -0
  98. package/src/db/__pycache__/_learnings.cpython-310.pyc +0 -0
  99. package/src/db/__pycache__/_learnings.cpython-312.pyc +0 -0
  100. package/src/db/__pycache__/_learnings.cpython-314.pyc +0 -0
  101. package/src/db/__pycache__/_reminders.cpython-310.pyc +0 -0
  102. package/src/db/__pycache__/_reminders.cpython-312.pyc +0 -0
  103. package/src/db/__pycache__/_reminders.cpython-314.pyc +0 -0
  104. package/src/db/__pycache__/_schema.cpython-310.pyc +0 -0
  105. package/src/db/__pycache__/_schema.cpython-312.pyc +0 -0
  106. package/src/db/__pycache__/_schema.cpython-314.pyc +0 -0
  107. package/src/db/__pycache__/_sessions.cpython-310.pyc +0 -0
  108. package/src/db/__pycache__/_sessions.cpython-312.pyc +0 -0
  109. package/src/db/__pycache__/_sessions.cpython-314.pyc +0 -0
  110. package/src/db/__pycache__/_tasks.cpython-310.pyc +0 -0
  111. package/src/db/__pycache__/_tasks.cpython-312.pyc +0 -0
  112. package/src/db/__pycache__/_tasks.cpython-314.pyc +0 -0
  113. package/src/db/_core.py +7 -3
  114. package/src/db/_episodic.py +69 -1
  115. package/src/db/_fts.py +12 -12
  116. package/src/db/_reminders.py +89 -15
  117. package/src/db/_schema.py +41 -0
  118. package/src/evolution_cycle.py +33 -11
  119. package/src/hooks/__pycache__/auto_capture.cpython-310.pyc +0 -0
  120. package/src/hooks/__pycache__/auto_capture.cpython-314.pyc +0 -0
  121. package/src/hooks/auto_capture.py +1 -1
  122. package/src/hooks/capture-tool-logs.sh +76 -0
  123. package/src/hooks/inbox-hook.sh +2 -1
  124. package/src/hooks/post-compact.sh +2 -1
  125. package/src/hooks/pre-compact.sh +104 -2
  126. package/src/hooks/session-start.sh +6 -2
  127. package/src/hooks/session-stop.sh +2 -1
  128. package/src/kg_populate.py +4 -1
  129. package/src/migrate_embeddings.py +4 -1
  130. package/src/plugin_loader.py +100 -34
  131. package/src/plugins/__pycache__/__init__.cpython-310.pyc +0 -0
  132. package/src/plugins/__pycache__/__init__.cpython-314.pyc +0 -0
  133. package/src/plugins/__pycache__/adaptive_mode.cpython-310.pyc +0 -0
  134. package/src/plugins/__pycache__/adaptive_mode.cpython-314.pyc +0 -0
  135. package/src/plugins/__pycache__/agents.cpython-310.pyc +0 -0
  136. package/src/plugins/__pycache__/agents.cpython-314.pyc +0 -0
  137. package/src/plugins/__pycache__/artifact_registry.cpython-310.pyc +0 -0
  138. package/src/plugins/__pycache__/artifact_registry.cpython-314.pyc +0 -0
  139. package/src/plugins/__pycache__/backup.cpython-310.pyc +0 -0
  140. package/src/plugins/__pycache__/backup.cpython-314.pyc +0 -0
  141. package/src/plugins/__pycache__/cognitive_memory.cpython-310.pyc +0 -0
  142. package/src/plugins/__pycache__/cognitive_memory.cpython-314.pyc +0 -0
  143. package/src/plugins/__pycache__/core_rules.cpython-310.pyc +0 -0
  144. package/src/plugins/__pycache__/core_rules.cpython-314.pyc +0 -0
  145. package/src/plugins/__pycache__/cortex.cpython-310.pyc +0 -0
  146. package/src/plugins/__pycache__/cortex.cpython-314.pyc +0 -0
  147. package/src/plugins/__pycache__/entities.cpython-310.pyc +0 -0
  148. package/src/plugins/__pycache__/entities.cpython-314.pyc +0 -0
  149. package/src/plugins/__pycache__/episodic_memory.cpython-310.pyc +0 -0
  150. package/src/plugins/__pycache__/episodic_memory.cpython-314.pyc +0 -0
  151. package/src/plugins/__pycache__/evolution.cpython-310.pyc +0 -0
  152. package/src/plugins/__pycache__/evolution.cpython-314.pyc +0 -0
  153. package/src/plugins/__pycache__/guard.cpython-310.pyc +0 -0
  154. package/src/plugins/__pycache__/guard.cpython-314.pyc +0 -0
  155. package/src/plugins/__pycache__/knowledge_graph_tools.cpython-310.pyc +0 -0
  156. package/src/plugins/__pycache__/knowledge_graph_tools.cpython-314.pyc +0 -0
  157. package/src/plugins/__pycache__/preferences.cpython-310.pyc +0 -0
  158. package/src/plugins/__pycache__/preferences.cpython-314.pyc +0 -0
  159. package/src/plugins/__pycache__/update.cpython-310.pyc +0 -0
  160. package/src/plugins/agents.py +2 -2
  161. package/src/plugins/backup.py +5 -4
  162. package/src/plugins/cognitive_memory.py +1 -1
  163. package/src/plugins/core_rules.py +5 -1
  164. package/src/plugins/episodic_memory.py +43 -16
  165. package/src/plugins/evolution.py +7 -2
  166. package/src/plugins/guard.py +45 -17
  167. package/src/plugins/update.py +238 -0
  168. package/src/requirements.txt +12 -0
  169. package/src/rules/__pycache__/__init__.cpython-310.pyc +0 -0
  170. package/src/rules/__pycache__/__init__.cpython-314.pyc +0 -0
  171. package/src/rules/__pycache__/migrate.cpython-310.pyc +0 -0
  172. package/src/rules/__pycache__/migrate.cpython-314.pyc +0 -0
  173. package/src/scripts/__pycache__/check-context.cpython-310.pyc +0 -0
  174. package/src/scripts/__pycache__/check-context.cpython-314.pyc +0 -0
  175. package/src/scripts/__pycache__/nexo-auto-update.cpython-310.pyc +0 -0
  176. package/src/scripts/__pycache__/nexo-auto-update.cpython-314.pyc +0 -0
  177. package/src/scripts/__pycache__/nexo-catchup.cpython-310.pyc +0 -0
  178. package/src/scripts/__pycache__/nexo-catchup.cpython-314.pyc +0 -0
  179. package/src/scripts/__pycache__/nexo-cognitive-decay.cpython-310.pyc +0 -0
  180. package/src/scripts/__pycache__/nexo-cognitive-decay.cpython-314.pyc +0 -0
  181. package/src/scripts/__pycache__/nexo-daily-self-audit.cpython-310.pyc +0 -0
  182. package/src/scripts/__pycache__/nexo-daily-self-audit.cpython-314.pyc +0 -0
  183. package/src/scripts/__pycache__/nexo-evolution-run.cpython-310.pyc +0 -0
  184. package/src/scripts/__pycache__/nexo-evolution-run.cpython-314.pyc +0 -0
  185. package/src/scripts/__pycache__/nexo-followup-hygiene.cpython-310.pyc +0 -0
  186. package/src/scripts/__pycache__/nexo-followup-hygiene.cpython-314.pyc +0 -0
  187. package/src/scripts/__pycache__/nexo-github-monitor.cpython-310.pyc +0 -0
  188. package/src/scripts/__pycache__/nexo-github-monitor.cpython-314.pyc +0 -0
  189. package/src/scripts/__pycache__/nexo-immune.cpython-310.pyc +0 -0
  190. package/src/scripts/__pycache__/nexo-immune.cpython-314.pyc +0 -0
  191. package/src/scripts/__pycache__/nexo-install.cpython-310.pyc +0 -0
  192. package/src/scripts/__pycache__/nexo-install.cpython-314.pyc +0 -0
  193. package/src/scripts/__pycache__/nexo-learning-housekeep.cpython-310.pyc +0 -0
  194. package/src/scripts/__pycache__/nexo-learning-housekeep.cpython-314.pyc +0 -0
  195. package/src/scripts/__pycache__/nexo-learning-validator.cpython-310.pyc +0 -0
  196. package/src/scripts/__pycache__/nexo-learning-validator.cpython-314.pyc +0 -0
  197. package/src/scripts/__pycache__/nexo-migrate.cpython-310.pyc +0 -0
  198. package/src/scripts/__pycache__/nexo-migrate.cpython-314.pyc +0 -0
  199. package/src/scripts/__pycache__/nexo-postmortem-consolidator.cpython-310.pyc +0 -0
  200. package/src/scripts/__pycache__/nexo-postmortem-consolidator.cpython-314.pyc +0 -0
  201. package/src/scripts/__pycache__/nexo-pre-commit.cpython-310.pyc +0 -0
  202. package/src/scripts/__pycache__/nexo-pre-commit.cpython-314.pyc +0 -0
  203. package/src/scripts/__pycache__/nexo-proactive-dashboard.cpython-310.pyc +0 -0
  204. package/src/scripts/__pycache__/nexo-proactive-dashboard.cpython-314.pyc +0 -0
  205. package/src/scripts/__pycache__/nexo-reflection.cpython-310.pyc +0 -0
  206. package/src/scripts/__pycache__/nexo-reflection.cpython-314.pyc +0 -0
  207. package/src/scripts/__pycache__/nexo-runtime-preflight.cpython-310.pyc +0 -0
  208. package/src/scripts/__pycache__/nexo-runtime-preflight.cpython-314.pyc +0 -0
  209. package/src/scripts/__pycache__/nexo-send-email.cpython-310.pyc +0 -0
  210. package/src/scripts/__pycache__/nexo-send-email.cpython-314.pyc +0 -0
  211. package/src/scripts/__pycache__/nexo-send-reply.cpython-310.pyc +0 -0
  212. package/src/scripts/__pycache__/nexo-send-reply.cpython-314.pyc +0 -0
  213. package/src/scripts/__pycache__/nexo-sleep.cpython-310.pyc +0 -0
  214. package/src/scripts/__pycache__/nexo-sleep.cpython-314.pyc +0 -0
  215. package/src/scripts/__pycache__/nexo-synthesis.cpython-310.pyc +0 -0
  216. package/src/scripts/__pycache__/nexo-synthesis.cpython-314.pyc +0 -0
  217. package/src/scripts/__pycache__/nexo-watchdog-smoke.cpython-310.pyc +0 -0
  218. package/src/scripts/__pycache__/nexo-watchdog-smoke.cpython-314.pyc +0 -0
  219. package/src/scripts/check-context.py +13 -3
  220. package/src/scripts/deep-sleep/__pycache__/analyze_session.cpython-310.pyc +0 -0
  221. package/src/scripts/deep-sleep/__pycache__/analyze_session.cpython-314.pyc +0 -0
  222. package/src/scripts/deep-sleep/__pycache__/apply_findings.cpython-310.pyc +0 -0
  223. package/src/scripts/deep-sleep/__pycache__/apply_findings.cpython-314.pyc +0 -0
  224. package/src/scripts/deep-sleep/__pycache__/collect_transcripts.cpython-310.pyc +0 -0
  225. package/src/scripts/deep-sleep/__pycache__/collect_transcripts.cpython-314.pyc +0 -0
  226. package/src/scripts/deep-sleep/analyze_session.py +3 -1
  227. package/src/scripts/deep-sleep/apply_findings.py +7 -4
  228. package/src/scripts/deep-sleep/collect_transcripts.py +3 -1
  229. package/src/scripts/nexo-auto-update.py +4 -211
  230. package/src/scripts/nexo-backup.sh +25 -0
  231. package/src/scripts/nexo-brain-activation.sh +26 -26
  232. package/src/scripts/nexo-catchup.py +39 -25
  233. package/src/scripts/nexo-cognitive-decay.py +12 -5
  234. package/src/scripts/nexo-daily-self-audit.py +36 -14
  235. package/src/scripts/nexo-deep-sleep.sh +4 -3
  236. package/src/scripts/nexo-evolution-run.py +40 -12
  237. package/src/scripts/nexo-followup-hygiene.py +6 -3
  238. package/src/scripts/nexo-github-monitor.py +11 -4
  239. package/src/scripts/nexo-immune.py +20 -3
  240. package/src/scripts/nexo-inbox-hook.sh +2 -1
  241. package/src/scripts/nexo-install.py +6 -0
  242. package/src/scripts/nexo-learning-housekeep.py +245 -0
  243. package/src/scripts/nexo-learning-validator.py +12 -2
  244. package/src/scripts/nexo-migrate.py +232 -0
  245. package/src/scripts/nexo-postmortem-consolidator.py +38 -19
  246. package/src/scripts/nexo-pre-commit.py +3 -1
  247. package/src/scripts/nexo-prevent-sleep.sh +29 -0
  248. package/src/scripts/nexo-proactive-dashboard.py +8 -6
  249. package/src/scripts/nexo-runtime-preflight.py +59 -55
  250. package/src/scripts/nexo-send-email.py +2 -2
  251. package/src/scripts/nexo-send-reply.py +3 -1
  252. package/src/scripts/nexo-sleep.py +21 -5
  253. package/src/scripts/nexo-snapshot-restore.sh +2 -1
  254. package/src/scripts/nexo-synthesis.py +19 -4
  255. package/src/scripts/nexo-tcc-approve.sh +79 -0
  256. package/src/scripts/nexo-update.sh +161 -0
  257. package/src/scripts/nexo-watchdog-smoke.py +18 -13
  258. package/src/scripts/nexo-watchdog.sh +41 -31
  259. package/src/server.py +107 -44
  260. package/src/storage_router.py +6 -2
  261. package/src/tools_coordination.py +14 -14
  262. package/src/tools_credentials.py +11 -11
  263. package/src/tools_learnings.py +36 -27
  264. package/src/tools_menu.py +7 -6
  265. package/src/tools_reminders.py +11 -5
  266. package/src/tools_reminders_crud.py +11 -9
  267. package/src/tools_sessions.py +62 -187
  268. package/src/tools_task_history.py +7 -7
  269. package/templates/CLAUDE.md.template +49 -469
  270. package/templates/launchagents/README.md +7 -7
  271. package/templates/launchagents/com.nexo.auto-close-sessions.plist +5 -1
  272. package/templates/launchagents/com.nexo.catchup.plist +4 -0
  273. package/templates/launchagents/com.nexo.cognitive-decay.plist +7 -0
  274. package/templates/launchagents/com.nexo.dashboard.plist +5 -1
  275. package/templates/launchagents/com.nexo.deep-sleep.plist +4 -0
  276. package/templates/launchagents/com.nexo.evolution.plist +4 -0
  277. package/templates/launchagents/com.nexo.followup-hygiene.plist +4 -0
  278. package/templates/launchagents/com.nexo.github-monitor.plist +3 -1
  279. package/templates/launchagents/com.nexo.immune.plist +4 -0
  280. package/templates/launchagents/com.nexo.postmortem.plist +4 -0
  281. package/templates/launchagents/com.nexo.self-audit.plist +4 -0
  282. package/templates/launchagents/com.nexo.synthesis.plist +4 -0
  283. package/templates/launchagents/com.nexo.watchdog.plist +4 -0
  284. package/templates/openclaw.json +1 -1
  285. package/tests/__pycache__/__init__.cpython-310.pyc +0 -0
  286. package/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  287. package/tests/__pycache__/conftest.cpython-310-pytest-9.0.2.pyc +0 -0
  288. package/tests/__pycache__/conftest.cpython-310.pyc +0 -0
  289. package/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  290. package/tests/__pycache__/test_cognitive.cpython-310-pytest-9.0.2.pyc +0 -0
  291. package/tests/__pycache__/test_cognitive.cpython-310.pyc +0 -0
  292. package/tests/__pycache__/test_cognitive.cpython-314-pytest-9.0.2.pyc +0 -0
  293. package/tests/__pycache__/test_knowledge_graph.cpython-310-pytest-9.0.2.pyc +0 -0
  294. package/tests/__pycache__/test_knowledge_graph.cpython-310.pyc +0 -0
  295. package/tests/__pycache__/test_knowledge_graph.cpython-314-pytest-9.0.2.pyc +0 -0
  296. package/tests/__pycache__/test_migrations.cpython-310-pytest-9.0.2.pyc +0 -0
  297. package/tests/__pycache__/test_migrations.cpython-310.pyc +0 -0
  298. package/tests/__pycache__/test_migrations.cpython-314-pytest-9.0.2.pyc +0 -0
  299. package/tests/conftest.py +2 -2
  300. package/tests/test_cognitive.py +7 -6
  301. package/tests/test_migrations.py +29 -3
@@ -254,7 +254,7 @@ def dream_cycle(max_insights: int = 50) -> dict:
254
254
  })
255
255
 
256
256
  if len(recent_memories) < 2:
257
- return {"insights_created": 0, "insights": [], "memories_scanned": len(recent_memories)}
257
+ return {"insights_created": 0, "insights": [], "memories_scanned": len(recent_memories), "candidates_found": 0}
258
258
 
259
259
  # 2. Get already-dreamed pairs to skip
260
260
  dreamed = set()
@@ -398,18 +398,20 @@ def get_stats() -> dict:
398
398
  """Return statistics about the cognitive memory system."""
399
399
  db = _get_db()
400
400
 
401
- stm_active = db.execute("SELECT COUNT(*) FROM stm_memories WHERE promoted_to_ltm = 0").fetchone()[0]
401
+ stm_active = db.execute("SELECT COUNT(*) FROM stm_memories WHERE lifecycle_state = 'active' AND promoted_to_ltm = 0").fetchone()[0]
402
+ stm_promoted = db.execute("SELECT COUNT(*) FROM stm_memories WHERE promoted_to_ltm = 1").fetchone()[0]
403
+ stm_total = db.execute("SELECT COUNT(*) FROM stm_memories WHERE lifecycle_state = 'active'").fetchone()[0]
402
404
  ltm_active = db.execute("SELECT COUNT(*) FROM ltm_memories WHERE is_dormant = 0").fetchone()[0]
403
405
  ltm_dormant = db.execute("SELECT COUNT(*) FROM ltm_memories WHERE is_dormant = 1").fetchone()[0]
404
406
 
405
- avg_stm = db.execute("SELECT AVG(strength) FROM stm_memories WHERE promoted_to_ltm = 0").fetchone()[0] or 0.0
407
+ avg_stm = db.execute("SELECT AVG(strength) FROM stm_memories WHERE lifecycle_state = 'active' AND promoted_to_ltm = 0").fetchone()[0] or 0.0
406
408
  avg_ltm = db.execute("SELECT AVG(strength) FROM ltm_memories WHERE is_dormant = 0").fetchone()[0] or 0.0
407
409
 
408
410
  total_retrievals = db.execute("SELECT COUNT(*) FROM retrieval_log").fetchone()[0]
409
411
  avg_retrieval_score = db.execute("SELECT AVG(top_score) FROM retrieval_log").fetchone()[0] or 0.0
410
412
 
411
413
  top_domains_stm = db.execute(
412
- "SELECT domain, COUNT(*) as cnt FROM stm_memories WHERE promoted_to_ltm = 0 AND domain != '' GROUP BY domain ORDER BY cnt DESC LIMIT 5"
414
+ "SELECT domain, COUNT(*) as cnt FROM stm_memories WHERE lifecycle_state = 'active' AND promoted_to_ltm = 0 AND domain != '' GROUP BY domain ORDER BY cnt DESC LIMIT 5"
413
415
  ).fetchall()
414
416
  top_domains_ltm = db.execute(
415
417
  "SELECT domain, COUNT(*) as cnt FROM ltm_memories WHERE is_dormant = 0 AND domain != '' GROUP BY domain ORDER BY cnt DESC LIMIT 5"
@@ -420,6 +422,8 @@ def get_stats() -> dict:
420
422
 
421
423
  return {
422
424
  "stm_active": stm_active,
425
+ "stm_promoted": stm_promoted,
426
+ "stm_total": stm_total,
423
427
  "ltm_active": ltm_active,
424
428
  "ltm_dormant": ltm_dormant,
425
429
  "avg_stm_strength": round(avg_stm, 3),
@@ -1,5 +1,6 @@
1
1
  """NEXO Cognitive — Search, retrieval, ranking."""
2
2
  import math
3
+ import sqlite3
3
4
  import numpy as np
4
5
  from datetime import datetime
5
6
  from cognitive._core import _get_db, embed, cosine_similarity, _blob_to_array, _array_to_blob, _get_model, _get_reranker, rerank_results, EMBEDDING_DIM
@@ -112,11 +113,12 @@ def _rrf_fuse(vector_results: list[dict], bm25_results: list[dict],
112
113
 
113
114
  # If we have the original cosine score, blend it in to preserve semantic confidence
114
115
  if vec_result and "score" in vec_result:
115
- # Weighted blend: RRF for ranking + cosine for confidence
116
- result["score"] = 0.6 * vec_result["score"] + 0.4 * (rrf_score * k * 3)
116
+ # Weighted blend: cosine for confidence + RRF for ranking boost
117
+ rrf_normalized = min(1.0, rrf_score * k) # normalize to 0-1 range
118
+ result["score"] = 0.7 * vec_result["score"] + 0.3 * rrf_normalized
117
119
  else:
118
- # BM25-only result: use RRF score scaled to ~0.5-0.7 range
119
- result["score"] = min(0.85, rrf_score * k * 3)
120
+ # BM25-only result: use RRF score scaled to ~0.3-0.7 range
121
+ result["score"] = min(0.75, rrf_score * k)
120
122
 
121
123
  result["bm25_boosted"] = key in bm25_lookup
122
124
  result["bm25_only"] = key not in vec_lookup
@@ -199,8 +201,8 @@ def _apply_temporal_boost(results: list[dict], query_text: str) -> list[dict]:
199
201
  # Bounded exponential decay boost
200
202
  boost = alpha * math.exp(-ln2 * age_days / half_life_days)
201
203
 
202
- # Apply boost (capped at 1.0)
203
- r["score"] = min(1.0, r["score"] + boost)
204
+ # Apply boost (capped at 0.95 — reserve 1.0 for exact matches only)
205
+ r["score"] = min(0.95, r["score"] + boost)
204
206
  if boost > 0.001:
205
207
  r["temporal_boost"] = round(boost, 4)
206
208
 
@@ -282,7 +284,7 @@ def _kg_boost_results(results: list[dict], max_boost: float = 0.08) -> list[dict
282
284
  for idx in ref_map.get(node_ref, []):
283
285
  r = results[idx]
284
286
  if r.get("score", 0) >= 0.45: # Same relevance gate as temporal
285
- r["score"] = min(1.0, r["score"] + boost)
287
+ r["score"] = min(0.95, r["score"] + boost)
286
288
  r["kg_boost"] = round(boost, 4)
287
289
  r["kg_connections"] = connections
288
290
 
@@ -613,7 +615,7 @@ def _rehearse_results(results: list[dict], skip_ids: set = None):
613
615
  continue
614
616
  table = "stm_memories" if r["store"] == "stm" else "ltm_memories"
615
617
  db.execute(
616
- f"UPDATE {table} SET strength = 1.0, access_count = access_count + 1, last_accessed = ? WHERE id = ?",
618
+ f"UPDATE {table} SET strength = MIN(1.0, strength + 0.08), access_count = access_count + 1, last_accessed = ? WHERE id = ?",
617
619
  (now, r["id"])
618
620
  )
619
621
  db.commit()
@@ -834,7 +836,7 @@ def search(
834
836
  if is_temporal_query:
835
837
  for r in results:
836
838
  if r.get("temporal_date"):
837
- r["score"] = min(1.0, r["score"] + 0.05)
839
+ r["score"] = min(0.95, r["score"] + 0.05)
838
840
 
839
841
  # Recency temporal boost: recent memories get additive bonus (query-adaptive)
840
842
  results = _apply_temporal_boost(results, query_text)
@@ -866,7 +868,7 @@ def search(
866
868
  existing_hashes.add(co_hash)
867
869
  if co_hash in neighbor_boosts:
868
870
  boost = neighbor_boosts[co_hash]
869
- r["score"] = min(1.0, r["score"] + boost)
871
+ r["score"] = min(0.95, r["score"] + boost)
870
872
  r["co_activation_boost"] = boost
871
873
 
872
874
  # Add neighbor memories not already in results
@@ -1,7 +1,7 @@
1
1
  """NEXO Cognitive — Trust scoring, sentiment, dissonance."""
2
2
  import re
3
3
  import numpy as np
4
- from datetime import datetime
4
+ from datetime import datetime, timedelta
5
5
  from cognitive._core import _get_db, embed, cosine_similarity, _blob_to_array
6
6
  from cognitive._core import POSITIVE_SIGNALS, NEGATIVE_SIGNALS, URGENCY_SIGNALS
7
7
 
@@ -314,13 +314,13 @@ def detect_sentiment(text: str) -> dict:
314
314
  sentiment = "negative"
315
315
  intensity = min(1.0, 0.3 + neg_score * 0.15)
316
316
  if intensity > 0.7:
317
- guidance = "MODE: Ultra-conciso. Cero explicaciones. Resolver y mostrar resultado."
317
+ guidance = "MODE: Ultra-concise. Zero explanations. Solve and show result."
318
318
  else:
319
319
  guidance = "MODE: Concise. Less context, more direct action."
320
320
  elif pos_score > neg_score and pos_score >= 1:
321
321
  sentiment = "positive"
322
322
  intensity = min(1.0, 0.3 + pos_score * 0.15)
323
- guidance = "MODE: Normal. Buen momento para proponer ideas de backlog o mejoras."
323
+ guidance = "MODE: Normal. Good time to suggest backlog ideas or improvements."
324
324
  elif urgency_hits:
325
325
  sentiment = "urgent"
326
326
  intensity = 0.8
@@ -10,6 +10,7 @@ Usage:
10
10
  import argparse
11
11
  import json
12
12
  import os
13
+ import platform
13
14
  import subprocess
14
15
  import sys
15
16
  import time
@@ -22,7 +23,7 @@ from fastapi.responses import HTMLResponse, JSONResponse
22
23
  from fastapi.staticfiles import StaticFiles
23
24
  from pydantic import BaseModel
24
25
 
25
- # Add parent dir to path so we can import nexo-mcp modules
26
+ # Add parent dir to path so we can import NEXO modules
26
27
  _PARENT = str(Path(__file__).resolve().parent.parent)
27
28
  if _PARENT not in sys.path:
28
29
  sys.path.insert(0, _PARENT)
@@ -63,7 +64,7 @@ async def create_tables():
63
64
 
64
65
 
65
66
  # ---------------------------------------------------------------------------
66
- # Lazy imports — modules live in the parent nexo-mcp directory
67
+ # Lazy imports — modules live in the parent source directory
67
68
  # ---------------------------------------------------------------------------
68
69
 
69
70
  def _cognitive():
@@ -602,6 +603,11 @@ async def api_ops_execute(fid: str):
602
603
  return JSONResponse({"error": f"Followup {fid} not found"}, status_code=404)
603
604
  item = dict(row)
604
605
  description = item["description"].replace('"', '\\"').replace("'", "\\'")
606
+ if platform.system() != "Darwin":
607
+ return JSONResponse(
608
+ {"error": "This operation requires macOS (uses osascript to open Terminal)"},
609
+ status_code=501,
610
+ )
605
611
  script = f'tell application "Terminal" to do script "claude \\"NEXO: execute followup #{fid} — {description}\\""'
606
612
  subprocess.Popen(["osascript", "-e", script])
607
613
  return {"success": True, "followup_id": fid}
@@ -728,7 +734,7 @@ async def api_calendar(
728
734
  @app.get("/api/watchdog")
729
735
  async def api_watchdog():
730
736
  """Read watchdog status from file."""
731
- nexo_home = os.environ.get("NEXO_HOME", str(Path.home() / "claude"))
737
+ nexo_home = os.environ.get("NEXO_HOME", str(Path.home() / ".nexo"))
732
738
  watchdog_path = Path(nexo_home) / "operations" / "watchdog-status.json"
733
739
  if not watchdog_path.exists():
734
740
  return JSONResponse(
@@ -447,10 +447,10 @@
447
447
  // --- Overdue Items ---
448
448
  if (remindersData || followupsData) {
449
449
  const reminders = (remindersData?.reminders || []).filter(r =>
450
- r.status === 'PENDIENTE' && r.date && r.date < today
450
+ r.status === 'PENDING' && r.date && r.date < today
451
451
  );
452
452
  const followups = (followupsData?.followups || []).filter(f =>
453
- f.status === 'PENDIENTE' && f.date && f.date < today
453
+ f.status === 'PENDING' && f.date && f.date < today
454
454
  );
455
455
  const total = reminders.length + followups.length;
456
456
  const el = document.getElementById('overdue-count');
@@ -530,12 +530,12 @@
530
530
  if (remindersData || followupsData) {
531
531
  const items = [];
532
532
  (remindersData?.reminders || []).forEach(r => {
533
- if (r.status === 'PENDIENTE' && r.date && r.date <= today) {
533
+ if (r.status === 'PENDING' && r.date && r.date <= today) {
534
534
  items.push({ ...r, _type: 'R', _overdue: r.date < today });
535
535
  }
536
536
  });
537
537
  (followupsData?.followups || []).forEach(f => {
538
- if (f.status === 'PENDIENTE' && f.date && f.date <= today) {
538
+ if (f.status === 'PENDING' && f.date && f.date <= today) {
539
539
  items.push({ ...f, _type: 'F', _overdue: f.date < today });
540
540
  }
541
541
  });
@@ -154,9 +154,9 @@
154
154
  onchange="loadOpsData()"
155
155
  class="bg-slate-800 border border-slate-700 rounded-lg px-2 py-1 text-xs text-slate-200 focus:outline-none focus:ring-1 focus:ring-violet-500"
156
156
  >
157
- <option value="PENDIENTE">Pending</option>
157
+ <option value="PENDING">Pending</option>
158
158
  <option value="all">All</option>
159
- <option value="COMPLETADO">Completed</option>
159
+ <option value="COMPLETED">Completed</option>
160
160
  </select>
161
161
  </div>
162
162
  </header>
@@ -380,7 +380,7 @@
380
380
  const today = getToday();
381
381
  const weekEnd = getWeekEnd();
382
382
  return {
383
- overdue: items.filter(i => i.date && i.date < today && i.status !== 'COMPLETADO'),
383
+ overdue: items.filter(i => i.date && i.date < today && i.status !== 'COMPLETED'),
384
384
  today: items.filter(i => i.date === today),
385
385
  week: items.filter(i => i.date && i.date > today && i.date <= weekEnd),
386
386
  upcoming: items.filter(i => i.date && i.date > weekEnd),
@@ -393,9 +393,9 @@
393
393
  // -----------------------------------------------------------------------
394
394
  function renderItem(item, type) {
395
395
  const today = getToday();
396
- const isOverdue = item.date && item.date < today && item.status !== 'COMPLETADO';
396
+ const isOverdue = item.date && item.date < today && item.status !== 'COMPLETED';
397
397
  const isToday = item.date === today;
398
- const isCompleted = item.status === 'COMPLETADO';
398
+ const isCompleted = item.status === 'COMPLETED';
399
399
 
400
400
  const dotColor = isCompleted ? 'bg-emerald-400' : isOverdue ? 'bg-red-400' : isToday ? 'bg-amber-400' : 'bg-slate-500';
401
401
  const dateLabel = relativeDate(item.date);
@@ -542,7 +542,7 @@
542
542
  const res = await fetch(url, {
543
543
  method: 'PUT',
544
544
  headers: { 'Content-Type': 'application/json' },
545
- body: JSON.stringify({ status: 'COMPLETADO' })
545
+ body: JSON.stringify({ status: 'COMPLETED' })
546
546
  });
547
547
  const data = await res.json();
548
548
  if (data.success) {
@@ -39,6 +39,7 @@ from db._reminders import (
39
39
  get_reminders, get_reminder,
40
40
  create_followup, update_followup, complete_followup, delete_followup,
41
41
  get_followups, get_followup,
42
+ find_similar_followups,
42
43
  )
43
44
 
44
45
  # Learnings
@@ -69,7 +70,7 @@ from db._entities import (
69
70
 
70
71
  # Episodic memory
71
72
  from db._episodic import (
72
- cleanup_old_changes, log_change, search_changes, update_change_commit,
73
+ cleanup_old_changes, log_change, search_changes, update_change_commit, auto_resolve_followups,
73
74
  cleanup_old_decisions, log_decision, update_decision_outcome,
74
75
  get_memory_review_queue, find_decisions_by_context_ref, search_decisions,
75
76
  cleanup_old_diaries, write_session_diary,
package/src/db/_core.py CHANGED
@@ -9,11 +9,15 @@ import datetime
9
9
  import pathlib
10
10
  import threading
11
11
 
12
+ NEXO_HOME = os.environ.get("NEXO_HOME", os.path.expanduser("~/.nexo"))
13
+ _data_dir = os.path.join(NEXO_HOME, "data")
14
+ os.makedirs(_data_dir, exist_ok=True)
15
+
12
16
  DB_PATH = os.environ.get(
13
17
  "NEXO_TEST_DB",
14
18
  os.environ.get(
15
19
  "NEXO_DB",
16
- os.path.join(os.path.dirname(os.path.abspath(__file__)), "nexo.db"),
20
+ os.path.join(_data_dir, "nexo.db"),
17
21
  ),
18
22
  )
19
23
 
@@ -168,7 +172,7 @@ def init_db():
168
172
  id TEXT PRIMARY KEY,
169
173
  date TEXT,
170
174
  description TEXT NOT NULL,
171
- status TEXT NOT NULL DEFAULT 'PENDIENTE',
175
+ status TEXT NOT NULL DEFAULT 'PENDING',
172
176
  category TEXT DEFAULT 'general',
173
177
  created_at REAL NOT NULL,
174
178
  updated_at REAL NOT NULL
@@ -179,7 +183,7 @@ def init_db():
179
183
  date TEXT,
180
184
  description TEXT NOT NULL,
181
185
  verification TEXT DEFAULT '',
182
- status TEXT NOT NULL DEFAULT 'PENDIENTE',
186
+ status TEXT NOT NULL DEFAULT 'PENDING',
183
187
  recurrence TEXT DEFAULT NULL,
184
188
  created_at REAL NOT NULL,
185
189
  updated_at REAL NOT NULL
@@ -69,8 +69,73 @@ def search_changes(query: str = '', files: str = '', days: int = 30) -> list[dic
69
69
  return [dict(r) for r in rows]
70
70
 
71
71
 
72
+ def auto_resolve_followups(change: dict) -> list[str]:
73
+ """Cross-reference a change_log entry with open followups. Auto-completes matches.
74
+
75
+ Matching logic:
76
+ 1. File overlap: if change touched files mentioned in followup description
77
+ 2. Keyword overlap: Jaccard similarity between change text and followup text
78
+ 3. ID reference: if followup ID appears in the change's triggered_by/why fields
79
+
80
+ Returns list of followup IDs that were auto-resolved.
81
+ """
82
+ conn = get_db()
83
+ open_followups = conn.execute(
84
+ "SELECT * FROM followups WHERE status NOT LIKE 'COMPLETED%' "
85
+ "AND status != 'DELETED'"
86
+ ).fetchall()
87
+
88
+ if not open_followups:
89
+ return []
90
+
91
+ change_text = " ".join(str(change.get(f, "")) for f in
92
+ ["files", "what_changed", "why", "triggered_by", "affects"])
93
+ change_files = set(change.get("files", "").replace(",", " ").split())
94
+ change_tokens = {w.lower() for w in change_text.split() if len(w) > 3}
95
+
96
+ resolved = []
97
+ for f in open_followups:
98
+ fid = f["id"]
99
+ fdesc = f"{fid} {f['description']} {f['verification'] or ''}"
100
+ ftokens = {w.lower() for w in fdesc.split() if len(w) > 3}
101
+
102
+ # Check 1: followup ID explicitly in change trigger/why
103
+ if fid.lower() in change_text.lower():
104
+ resolved.append(fid)
105
+ continue
106
+
107
+ # Check 2: file overlap (any changed file mentioned in followup)
108
+ if change_files:
109
+ for cf in change_files:
110
+ basename = cf.rsplit("/", 1)[-1] if "/" in cf else cf
111
+ if basename and len(basename) > 4 and basename.lower() in fdesc.lower():
112
+ resolved.append(fid)
113
+ break
114
+ if fid in resolved:
115
+ continue
116
+
117
+ # Check 3: keyword similarity (asymmetric overlap >= 0.35)
118
+ if ftokens and change_tokens:
119
+ intersection = ftokens & change_tokens
120
+ smaller = min(len(ftokens), len(change_tokens))
121
+ score = len(intersection) / smaller if smaller else 0
122
+ if score >= 0.35:
123
+ resolved.append(fid)
124
+
125
+ # Auto-complete matched followups
126
+ from db._reminders import complete_followup
127
+ commit_ref = change.get("commit_ref", "")
128
+ for fid in resolved:
129
+ complete_followup(fid, result=f"Auto-resolved by change #{change.get('id', '?')} (commit {commit_ref[:8] if commit_ref else 'N/A'})")
130
+
131
+ return resolved
132
+
133
+
72
134
  def update_change_commit(id: int, commit_ref: str) -> dict:
73
- """Link a change log entry to its git commit after commit."""
135
+ """Link a change log entry to its git commit after commit.
136
+
137
+ After linking, auto-resolves any open followups that match the change.
138
+ """
74
139
  conn = get_db()
75
140
  row = conn.execute("SELECT * FROM change_log WHERE id = ?", (id,)).fetchone()
76
141
  if not row:
@@ -81,6 +146,9 @@ def update_change_commit(id: int, commit_ref: str) -> dict:
81
146
  r = dict(row)
82
147
  body = f"{r.get('what_changed','')} {r.get('why','')} {r.get('triggered_by','')} {r.get('affects','')} {r.get('risks','')}"
83
148
  fts_upsert("change", str(id), r.get("files",""), body, "change_log", commit=False)
149
+
150
+ # Auto-resolve followups that match this change
151
+ r["_auto_resolved"] = auto_resolve_followups(r)
84
152
  return r
85
153
 
86
154
 
package/src/db/_fts.py CHANGED
@@ -2,27 +2,27 @@
2
2
  import os, pathlib, sqlite3, threading, datetime
3
3
  from db._core import get_db, now_epoch, DB_PATH
4
4
 
5
+ NEXO_HOME = os.environ.get("NEXO_HOME", os.path.expanduser("~/.nexo"))
6
+
5
7
  # ── FTS5 Unified Search ──────────────────────────────────────────
6
8
 
7
9
  # Directories to index for unified search
8
10
  _FTS_MD_DIRS = [
9
- os.path.expanduser("~/claude/docs"),
10
- os.path.expanduser("~/claude/projects"),
11
- os.path.expanduser("~/claude/memory"),
12
- os.path.expanduser("~/claude/operations"),
13
- os.path.expanduser("~/claude/learnings"),
14
- os.path.expanduser("~/claude/brain"),
15
- os.path.expanduser("~/claude/agents"),
16
- os.path.expanduser("~/claude/skills"),
11
+ os.path.join(NEXO_HOME, "docs"),
12
+ os.path.join(NEXO_HOME, "projects"),
13
+ os.path.join(NEXO_HOME, "memory"),
14
+ os.path.join(NEXO_HOME, "operations"),
15
+ os.path.join(NEXO_HOME, "learnings"),
16
+ os.path.join(NEXO_HOME, "brain"),
17
+ os.path.join(NEXO_HOME, "agents"),
18
+ os.path.join(NEXO_HOME, "skills"),
17
19
  ]
18
20
  # Code repos: index source files (skip vendor, node_modules, etc.)
19
- _FTS_CODE_DIRS = [
20
- (os.path.expanduser("~/Documents/_PhpstormProjects"), ["*.php", "*.js", "*.json", "*.py", "*.ts", "*.tsx"]),
21
- ]
21
+ _FTS_CODE_DIRS = [] # Users can add project dirs via nexo_index_add_dir
22
22
  _FTS_CODE_SKIP = {
23
23
  "vendor", "node_modules", ".git", "cache", "tmp", "logs", "uploads",
24
24
  "assets/img", "assets/fonts", ".next", "dist", "build", ".prisma",
25
- "PROYECTOS ANTIGUOS", "public/build", ".turbo", "__pycache__",
25
+ "public/build", ".turbo", "__pycache__",
26
26
  "coverage", ".nyc_output", "storage/framework", "bootstrap/cache",
27
27
  }
28
28
  _FTS_MAX_FILE_SIZE = 50_000 # skip .md files >50KB