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,552 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- NEXO Daily Self-Audit v2
4
-
5
- Stage A — Mechanical checks (Python pure, unchanged):
6
- 18 checks: overdue reminders, disk space, DB size, stale sessions, guard stats,
7
- cognitive health, snapshot drift, etc. All pure queries, no intelligence needed.
8
-
9
- Stage B — Interpretation (Claude CLI opus):
10
- Takes the raw findings from Stage A and UNDERSTANDS them:
11
- - Groups related findings
12
- - Identifies root causes
13
- - Prioritizes what actually matters
14
- - Suggests specific actions
15
- - Writes actionable summary
16
-
17
- Runs via launchd at 7:00 AM daily.
18
- """
19
- import json
20
- import hashlib
21
- import os
22
- import sqlite3
23
- import subprocess
24
- import sys
25
- from datetime import datetime, timedelta
26
- from pathlib import Path
27
-
28
- NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
29
- # Auto-detect: if running from repo (src/scripts/), use src/ as NEXO_CODE
30
- _script_dir = Path(__file__).resolve().parent
31
- _repo_src = _script_dir.parent # src/scripts/ -> src/
32
- NEXO_CODE = Path(os.environ.get("NEXO_CODE", str(_repo_src) if (_repo_src / "server.py").exists() else str(NEXO_HOME)))
33
-
34
- LOG_DIR = NEXO_HOME / "logs"
35
- LOG_DIR.mkdir(parents=True, exist_ok=True)
36
- LOG_FILE = LOG_DIR / "self-audit.log"
37
- NEXO_DB = NEXO_HOME / "data" / "nexo.db"
38
- # Configure your main project repo to check for uncommitted changes (optional)
39
- PROJECT_REPO_DIR = None # e.g., Path.home() / "projects" / "my-repo"
40
- HASH_REGISTRY = NEXO_HOME / "scripts" / ".watchdog-hashes"
41
- SNAPSHOT_GOLDEN = NEXO_HOME / "snapshots" / "golden" / "files" / "claude"
42
- RUNTIME_PREFLIGHT_SUMMARY = LOG_DIR / "runtime-preflight-summary.json"
43
- WATCHDOG_SMOKE_SUMMARY = LOG_DIR / "watchdog-smoke-summary.json"
44
- RESTORE_LOG = LOG_DIR / "snapshot-restores.log"
45
- CORTEX_LOG_DIR = NEXO_HOME / "brain" / "logs"
46
- CLAUDE_CLI = Path.home() / ".local" / "bin" / "claude"
47
-
48
- findings = []
49
-
50
-
51
- def log(msg):
52
- ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
53
- line = f"[{ts}] {msg}"
54
- print(line, flush=True)
55
- with open(LOG_FILE, "a") as f:
56
- f.write(line + "\n")
57
-
58
-
59
- def finding(severity, area, msg):
60
- findings.append({"severity": severity, "area": area, "msg": msg})
61
- log(f" [{severity}] {area}: {msg}")
62
-
63
-
64
- # ═══════════════════════════════════════════════════════════════════════════════
65
- # Stage A: Mechanical checks (UNCHANGED from v1 — all 18 checks)
66
- # ═══════════════════════════════════════════════════════════════════════════════
67
-
68
- def check_overdue_reminders():
69
- if not NEXO_DB.exists():
70
- return
71
- conn = sqlite3.connect(str(NEXO_DB))
72
- today = datetime.now().strftime("%Y-%m-%d")
73
- rows = conn.execute(
74
- "SELECT description, date FROM reminders WHERE status='PENDING' AND date < ? AND date != '' ORDER BY date",
75
- (today,)
76
- ).fetchall()
77
- conn.close()
78
- if rows:
79
- finding("WARN", "reminders", f"{len(rows)} overdue: {', '.join(r[0][:40] for r in rows[:5])}")
80
-
81
-
82
- def check_overdue_followups():
83
- if not NEXO_DB.exists():
84
- return
85
- conn = sqlite3.connect(str(NEXO_DB))
86
- today = datetime.now().strftime("%Y-%m-%d")
87
- rows = conn.execute(
88
- "SELECT description, date FROM followups WHERE status='PENDING' AND date < ? AND date != '' ORDER BY date",
89
- (today,)
90
- ).fetchall()
91
- conn.close()
92
- if rows:
93
- finding("WARN", "followups", f"{len(rows)} overdue: {', '.join(r[0][:40] for r in rows[:5])}")
94
-
95
-
96
- def check_uncommitted_changes():
97
- if not PROJECT_REPO_DIR or not PROJECT_REPO_DIR.exists():
98
- return
99
- result = subprocess.run(
100
- ["git", "status", "--porcelain"],
101
- cwd=str(PROJECT_REPO_DIR), capture_output=True, text=True
102
- )
103
- lines = [l for l in result.stdout.strip().split("\n") if l.strip()]
104
- if len(lines) > 10:
105
- finding("WARN", "git", f"{len(lines)} uncommitted changes in project repo")
106
-
107
-
108
- def check_cron_errors():
109
- if not NEXO_DB.exists():
110
- return
111
- conn = sqlite3.connect(str(NEXO_DB))
112
- yesterday = (datetime.now() - timedelta(days=1)).isoformat()
113
- rows = conn.execute(
114
- "SELECT category, title FROM learnings WHERE category='cron_error' AND created_at > ? ORDER BY created_at DESC",
115
- (yesterday,)
116
- ).fetchall()
117
- conn.close()
118
- if rows:
119
- finding("ERROR", "crons", f"{len(rows)} cron errors in last 24h")
120
-
121
-
122
- def check_evolution_health():
123
- # Check brain/ (canonical) first, fall back to cortex/ (legacy)
124
- obj_file = NEXO_HOME / "brain" / "evolution-objective.json"
125
- if not obj_file.exists():
126
- obj_file = NEXO_HOME / "cortex" / "evolution-objective.json"
127
- if not obj_file.exists():
128
- return
129
- obj = json.loads(obj_file.read_text())
130
- failures = obj.get("consecutive_failures", 0)
131
- if failures >= 2:
132
- finding("WARN", "evolution", f"{failures} consecutive failures — circuit breaker at 3")
133
- if not obj.get("evolution_enabled", True):
134
- finding("ERROR", "evolution", f"Evolution DISABLED: {obj.get('disabled_reason', 'unknown')}")
135
-
136
-
137
- def check_disk_space():
138
- result = subprocess.run(["df", "-h", "/"], capture_output=True, text=True)
139
- for line in result.stdout.strip().split("\n")[1:]:
140
- parts = line.split()
141
- if len(parts) >= 5:
142
- usage_pct = int(parts[4].replace("%", ""))
143
- if usage_pct > 90:
144
- finding("ERROR", "disk", f"Root disk at {usage_pct}% capacity")
145
- elif usage_pct > 80:
146
- finding("WARN", "disk", f"Root disk at {usage_pct}% capacity")
147
-
148
-
149
- def check_db_size():
150
- if NEXO_DB.exists():
151
- size_mb = NEXO_DB.stat().st_size / (1024 * 1024)
152
- if size_mb > 100:
153
- finding("WARN", "database", f"nexo.db is {size_mb:.1f} MB — consider cleanup")
154
-
155
-
156
- def check_stale_sessions():
157
- if not NEXO_DB.exists():
158
- return
159
- conn = sqlite3.connect(str(NEXO_DB))
160
- cutoff = (datetime.now() - timedelta(hours=2)).timestamp()
161
- day_ago = (datetime.now() - timedelta(days=1)).timestamp()
162
- rows = conn.execute(
163
- "SELECT sid, task FROM sessions WHERE last_update_epoch < ? AND last_update_epoch > ?",
164
- (cutoff, day_ago)
165
- ).fetchall()
166
- conn.close()
167
- if rows:
168
- finding("INFO", "sessions", f"{len(rows)} stale sessions (no heartbeat >2h)")
169
-
170
-
171
- def check_repetition_rate():
172
- if not NEXO_DB.exists():
173
- return
174
- conn = sqlite3.connect(str(NEXO_DB))
175
- cutoff_epoch = (datetime.now() - timedelta(days=3)).timestamp()
176
- cutoff_3d = (datetime.now() - timedelta(days=3)).strftime("%Y-%m-%d %H:%M:%S")
177
- new_learnings = conn.execute(
178
- "SELECT COUNT(*) FROM learnings WHERE created_at > ?", (cutoff_epoch,)
179
- ).fetchone()[0]
180
- repetitions = conn.execute(
181
- "SELECT COUNT(*) FROM error_repetitions WHERE created_at > ?", (cutoff_3d,)
182
- ).fetchone()[0]
183
- conn.close()
184
- if new_learnings > 0:
185
- rate = repetitions / new_learnings
186
- if rate > 0.30:
187
- finding("ERROR", "guard", f"Repetition rate {rate:.0%} ({repetitions}/{new_learnings})")
188
- elif rate > 0.20:
189
- finding("WARN", "guard", f"Repetition rate {rate:.0%} ({repetitions}/{new_learnings})")
190
-
191
-
192
- def check_unused_learnings():
193
- if not NEXO_DB.exists():
194
- return
195
- conn = sqlite3.connect(str(NEXO_DB))
196
- cutoff_epoch = (datetime.now() - timedelta(days=7)).timestamp()
197
- old_learnings = conn.execute(
198
- "SELECT COUNT(*) FROM learnings WHERE created_at < ?", (cutoff_epoch,)
199
- ).fetchone()[0]
200
- total_checks = conn.execute("SELECT COUNT(*) FROM guard_checks").fetchone()[0]
201
- conn.close()
202
- if total_checks == 0 and old_learnings > 10:
203
- finding("WARN", "guard", f"Guard never used — {old_learnings} learnings idle")
204
-
205
-
206
- def check_memory_reviews():
207
- if not NEXO_DB.exists():
208
- return
209
- conn = sqlite3.connect(str(NEXO_DB))
210
- now_epoch = datetime.now().timestamp()
211
- now_iso = datetime.now().isoformat(timespec="seconds")
212
- try:
213
- due_learnings = conn.execute(
214
- "SELECT COUNT(*) FROM learnings WHERE review_due_at IS NOT NULL AND status != 'superseded' AND review_due_at <= ?",
215
- (now_epoch,)
216
- ).fetchone()[0]
217
- due_decisions = conn.execute(
218
- "SELECT COUNT(*) FROM decisions WHERE review_due_at IS NOT NULL AND status != 'reviewed' AND review_due_at <= ?",
219
- (now_iso,)
220
- ).fetchone()[0]
221
- except sqlite3.OperationalError:
222
- conn.close()
223
- return
224
- conn.close()
225
- total = due_learnings + due_decisions
226
- if total >= 10:
227
- finding("WARN", "memory", f"{total} reviews due ({due_decisions} decisions, {due_learnings} learnings)")
228
- elif total > 0:
229
- finding("INFO", "memory", f"{total} reviews due")
230
-
231
-
232
- def _sha256(path):
233
- return hashlib.sha256(path.read_bytes()).hexdigest()
234
-
235
-
236
- def check_watchdog_registry():
237
- if not HASH_REGISTRY.exists():
238
- return
239
- text = HASH_REGISTRY.read_text(errors="ignore")
240
- forbidden = ["CLAUDE.md", "server.py", "plugin_loader.py"]
241
- bad = [name for name in forbidden if name in text]
242
- if bad:
243
- finding("ERROR", "watchdog", f"mutable files still protected: {', '.join(bad)}")
244
-
245
-
246
- def check_snapshot_sync():
247
- pairs = [
248
- (NEXO_CODE / "db" / "__init__.py", SNAPSHOT_GOLDEN / "db" / "__init__.py"),
249
- (NEXO_CODE / "evolution_cycle.py", SNAPSHOT_GOLDEN / "evolution_cycle.py"),
250
- ]
251
- drift = [live.name for live, snap in pairs
252
- if not live.exists() or not snap.exists() or _sha256(live) != _sha256(snap)]
253
- if drift:
254
- finding("WARN", "snapshots", f"golden snapshot drift: {', '.join(drift)}")
255
-
256
-
257
- def check_restore_activity():
258
- if not RESTORE_LOG.exists():
259
- return
260
- cutoff_day = datetime.now() - timedelta(days=1)
261
- current_hour_prefix = datetime.now().strftime("%Y-%m-%d %H")
262
- recent_day = 0
263
- recent_hour = 0
264
- for line in RESTORE_LOG.read_text(errors="ignore").splitlines():
265
- if not line.startswith("[") or "/.codex/memories/nexo-" in line:
266
- continue
267
- try:
268
- ts = datetime.strptime(line[1:20], "%Y-%m-%d %H:%M:%S")
269
- except ValueError:
270
- continue
271
- if ts >= cutoff_day:
272
- recent_day += 1
273
- if line[1:14] == current_hour_prefix:
274
- recent_hour += 1
275
- if recent_hour > 2:
276
- finding("ERROR", "restore", f"{recent_hour} restores in last hour")
277
- elif recent_day > 5:
278
- finding("WARN", "restore", f"{recent_day} restores in last 24h")
279
-
280
-
281
- def check_bad_responses():
282
- if not CORTEX_LOG_DIR.exists():
283
- return
284
- cutoff = datetime.now() - timedelta(days=1)
285
- bad = [p for p in CORTEX_LOG_DIR.glob("bad-response-*.json")
286
- if datetime.fromtimestamp(p.stat().st_mtime) >= cutoff]
287
- if bad:
288
- finding("WARN", "cortex", f"{len(bad)} bad model responses in last 24h")
289
-
290
-
291
- def check_runtime_preflight():
292
- if not RUNTIME_PREFLIGHT_SUMMARY.exists():
293
- return
294
- data = json.loads(RUNTIME_PREFLIGHT_SUMMARY.read_text())
295
- ts = data.get("timestamp")
296
- try:
297
- when = datetime.fromisoformat(ts)
298
- except Exception:
299
- return
300
- if when < datetime.now() - timedelta(days=1):
301
- finding("WARN", "preflight", "runtime preflight older than 24h")
302
- if not data.get("ok", False):
303
- finding("ERROR", "preflight", "runtime preflight failing")
304
-
305
-
306
- def run_watchdog_smoke():
307
- """Run the watchdog smoke test so its summary is fresh before we check it."""
308
- smoke_script = Path(__file__).resolve().parent / "nexo-watchdog-smoke.py"
309
- if not smoke_script.exists():
310
- finding("WARN", "watchdog", f"smoke script not found at {smoke_script}")
311
- return
312
- try:
313
- result = subprocess.run(
314
- [sys.executable, str(smoke_script)],
315
- capture_output=True, text=True, timeout=60
316
- )
317
- if result.returncode != 0:
318
- finding("WARN", "watchdog", f"smoke test exited {result.returncode}")
319
- except subprocess.TimeoutExpired:
320
- finding("ERROR", "watchdog", "smoke test timed out (60s)")
321
- except Exception as e:
322
- finding("WARN", "watchdog", f"smoke test failed: {e}")
323
-
324
-
325
- def check_watchdog_smoke():
326
- if not WATCHDOG_SMOKE_SUMMARY.exists():
327
- return
328
- data = json.loads(WATCHDOG_SMOKE_SUMMARY.read_text())
329
- ts = data.get("timestamp")
330
- try:
331
- when = datetime.fromisoformat(ts)
332
- except Exception:
333
- return
334
- if when < datetime.now() - timedelta(days=1):
335
- finding("WARN", "watchdog", "watchdog smoke older than 24h")
336
- if not data.get("ok", False):
337
- finding("ERROR", "watchdog", "watchdog smoke failing")
338
-
339
-
340
- def check_cognitive_health():
341
- cognitive_db = NEXO_HOME / "data" / "cognitive.db"
342
- if not cognitive_db.exists():
343
- finding("WARN", "cognitive", "cognitive.db not found")
344
- return
345
-
346
- conn = sqlite3.connect(str(cognitive_db))
347
- stm_count = conn.execute("SELECT COUNT(*) FROM stm_memories WHERE promoted_to_ltm = 0").fetchone()[0]
348
- ltm_active = conn.execute("SELECT COUNT(*) FROM ltm_memories WHERE is_dormant = 0").fetchone()[0]
349
- ltm_dormant = conn.execute("SELECT COUNT(*) FROM ltm_memories WHERE is_dormant = 1").fetchone()[0]
350
- avg_stm_str = conn.execute("SELECT AVG(strength) FROM stm_memories WHERE promoted_to_ltm = 0").fetchone()[0] or 0.0
351
- sensory_count = conn.execute("SELECT COUNT(*) FROM stm_memories WHERE source_type = 'sensory' AND promoted_to_ltm = 0").fetchone()[0]
352
- conn.close()
353
-
354
- size_mb = cognitive_db.stat().st_size / (1024 * 1024)
355
- finding("INFO", "cognitive", f"STM: {stm_count} (sensory: {sensory_count}) | LTM: {ltm_active} active, {ltm_dormant} dormant | {size_mb:.1f} MB")
356
-
357
- if avg_stm_str < 0.3 and stm_count > 20:
358
- finding("WARN", "cognitive", f"STM average strength very low ({avg_stm_str:.2f})")
359
-
360
- # Metrics
361
- try:
362
- sys.path.insert(0, str(NEXO_CODE))
363
- import cognitive as cog
364
- metrics = cog.get_metrics(days=7)
365
- if metrics["total_retrievals"] > 0:
366
- finding("INFO", "cognitive-metrics",
367
- f"7d: {metrics['total_retrievals']} retrievals, relevance={metrics['retrieval_relevance_pct']}%")
368
- if metrics["retrieval_relevance_pct"] < 50 and metrics["total_retrievals"] >= 5:
369
- finding("ERROR", "cognitive-metrics", f"Relevance critically low: {metrics['retrieval_relevance_pct']}%")
370
-
371
- repeats = cog.check_repeat_errors()
372
- if repeats["new_count"] > 0 and repeats["repeat_rate_pct"] > 30:
373
- finding("WARN", "cognitive-metrics", f"Repeat rate {repeats['repeat_rate_pct']}% > 30%")
374
-
375
- # Save metrics
376
- metrics_file = LOG_DIR / "cognitive-metrics.json"
377
- metrics_file.write_text(json.dumps({
378
- "timestamp": datetime.now().isoformat(),
379
- "retrieval": metrics,
380
- "repeats": {k: v for k, v in repeats.items() if k != "duplicates"},
381
- }, indent=2))
382
-
383
- # Track history for phase triggers
384
- history_file = LOG_DIR / "cognitive-metrics-history.json"
385
- try:
386
- history = json.loads(history_file.read_text()) if history_file.exists() else []
387
- except Exception:
388
- history = []
389
- m1 = cog.get_metrics(days=1)
390
- if m1["total_retrievals"] > 0:
391
- history.append({"date": datetime.now().strftime("%Y-%m-%d"),
392
- "relevance": m1["retrieval_relevance_pct"],
393
- "retrievals": m1["total_retrievals"]})
394
- history = history[-60:]
395
- history_file.write_text(json.dumps(history, indent=2))
396
-
397
- except Exception as e:
398
- finding("WARN", "cognitive-metrics", f"Metrics failed: {e}")
399
-
400
- # Weekly GC on Sundays
401
- if datetime.now().weekday() == 6:
402
- try:
403
- sys.path.insert(0, str(NEXO_CODE))
404
- import cognitive as cog
405
- gc_stm = cog.gc_stm()
406
- gc_sensory = cog.gc_sensory(max_age_hours=48)
407
- gc_ltm = cog.gc_ltm_dormant(min_age_days=30)
408
- if gc_stm + gc_sensory + gc_ltm > 0:
409
- finding("INFO", "cognitive", f"Weekly GC: {gc_stm} STM + {gc_sensory} sensory + {gc_ltm} dormant")
410
- except Exception as e:
411
- finding("WARN", "cognitive", f"Weekly GC failed: {e}")
412
-
413
-
414
- # ═══════════════════════════════════════════════════════════════════════════════
415
- # Stage B: Interpretation (Claude CLI opus) — NEW in v2
416
- # ═══════════════════════════════════════════════════════════════════════════════
417
-
418
- def interpret_findings(raw_findings: list) -> bool:
419
- """CLI interprets the raw findings with real understanding."""
420
-
421
- errors = [f for f in raw_findings if f["severity"] == "ERROR"]
422
- warns = [f for f in raw_findings if f["severity"] == "WARN"]
423
-
424
- # Don't invoke CLI if everything is clean
425
- if not errors and not warns:
426
- log("Stage B: All clean, no interpretation needed.")
427
- return True
428
-
429
- findings_json = json.dumps(raw_findings, ensure_ascii=False, indent=1)
430
-
431
- prompt = f"""FIRST: Call nexo_startup(task='daily self-audit') to register this session.
432
-
433
- You are NEXO's morning self-audit interpreter. The mechanical checks found
434
- {len(errors)} errors and {len(warns)} warnings. Your job is to UNDERSTAND what's
435
- actually wrong, not just list findings. Use nexo_learning_add for new findings and nexo_followup_create for action items.
436
-
437
- RAW FINDINGS:
438
- {findings_json}
439
-
440
- Write an actionable audit report to {LOG_DIR}/self-audit-interpreted.md:
441
-
442
- # NEXO Self-Audit — {datetime.now().strftime('%Y-%m-%d')}
443
-
444
- ## Critical (needs immediate action)
445
- [Group related findings, identify ROOT CAUSE, suggest specific fix]
446
-
447
- ## Warnings (should address today)
448
- [Same: group, root cause, specific action]
449
-
450
- ## Observations
451
- [Trends, things getting worse, things improving]
452
-
453
- ## Recommended Actions (priority order)
454
- 1. [Most important action with specific command/steps]
455
- 2. ...
456
-
457
- Be specific. "Fix the DB" is useless. "Archive learnings >90 days in category X
458
- via sqlite3 nexo.db 'UPDATE...'" is useful.
459
-
460
- Also write the machine-readable summary to {LOG_DIR}/self-audit-summary.json.
461
-
462
- Execute without asking."""
463
-
464
- log("Stage B: Invoking Claude CLI (opus) for interpretation...")
465
- env = os.environ.copy()
466
- env["NEXO_HEADLESS"] = "1" # Skip stop hook post-mortem
467
- env.pop("CLAUDECODE", None)
468
- env.pop("CLAUDE_CODE", None)
469
-
470
- try:
471
- result = subprocess.run(
472
- [str(CLAUDE_CLI), "-p", prompt, "--model", "opus",
473
- "--output-format", "text",
474
- "--allowedTools", "Read,Write,Edit,Glob,Grep,Bash,mcp__nexo__*"],
475
- capture_output=True, text=True, timeout=21600, env=env
476
- )
477
-
478
- if result.returncode != 0:
479
- log(f"Stage B: CLI error ({result.returncode})")
480
- return False
481
-
482
- log(f"Stage B: Interpretation complete ({len(result.stdout or '')} chars)")
483
- return True
484
-
485
- except subprocess.TimeoutExpired:
486
- log("Stage B: CLI timed out")
487
- return False
488
- except Exception as e:
489
- log(f"Stage B: {e}")
490
- return False
491
-
492
-
493
- # ═══════════════════════════════════════════════════════════════════════════════
494
- # Main
495
- # ═══════════════════════════════════════════════════════════════════════════════
496
-
497
- def main():
498
- log("=" * 60)
499
- log("NEXO Daily Self-Audit v2 starting")
500
-
501
- # Stage A: Run all mechanical checks (unchanged)
502
- check_overdue_reminders()
503
- check_overdue_followups()
504
- check_uncommitted_changes()
505
- check_cron_errors()
506
- check_evolution_health()
507
- check_disk_space()
508
- check_db_size()
509
- check_stale_sessions()
510
- check_repetition_rate()
511
- check_unused_learnings()
512
- check_memory_reviews()
513
- check_watchdog_registry()
514
- check_snapshot_sync()
515
- check_restore_activity()
516
- check_bad_responses()
517
- check_runtime_preflight()
518
- run_watchdog_smoke()
519
- check_watchdog_smoke()
520
- check_cognitive_health()
521
-
522
- errors = sum(1 for f in findings if f["severity"] == "ERROR")
523
- warns = sum(1 for f in findings if f["severity"] == "WARN")
524
- infos = sum(1 for f in findings if f["severity"] == "INFO")
525
- log(f"Stage A complete: {errors} errors, {warns} warnings, {infos} info")
526
-
527
- # Write raw summary (backward compatible)
528
- summary_file = LOG_DIR / "self-audit-summary.json"
529
- summary_file.write_text(json.dumps({
530
- "timestamp": datetime.now().isoformat(),
531
- "findings": findings,
532
- "counts": {"error": errors, "warn": warns, "info": infos}
533
- }, indent=2))
534
-
535
- # Stage B: CLI interpretation
536
- interpret_findings(findings)
537
-
538
- # Register for catch-up
539
- try:
540
- state_file = NEXO_HOME / "operations" / ".catchup-state.json"
541
- st = json.loads(state_file.read_text()) if state_file.exists() else {}
542
- st["self-audit"] = datetime.now().isoformat()
543
- state_file.write_text(json.dumps(st, indent=2))
544
- except Exception:
545
- pass
546
-
547
- log("=" * 60)
548
- return 1 if errors > 0 else 0
549
-
550
-
551
- if __name__ == "__main__":
552
- sys.exit(main())
@@ -1,97 +0,0 @@
1
- #!/bin/bash
2
- # NEXO Deep Sleep — Complete overnight session analysis
3
- # Runs at 4:30 AM via LaunchAgent
4
- # Reads ALL session transcripts from the day, analyzes with Claude CLI,
5
- # and applies findings (learnings, feedbacks, followups, trust adjustments)
6
- #
7
- # Features:
8
- # - Catch-up: if yesterday was missed (Mac off/asleep), runs it first
9
- # - Logs to $NEXO_HOME/logs/deep-sleep.log
10
- # - Marks completion in .last-run for watchdog monitoring
11
-
12
- set -euo pipefail
13
-
14
- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
15
- NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
16
- LOG_DIR="$NEXO_HOME/logs"
17
- DEEP_SLEEP_DIR="$NEXO_HOME/operations/deep-sleep"
18
- LAST_RUN_FILE="$DEEP_SLEEP_DIR/.last-run"
19
- TODAY=$(date +%Y-%m-%d)
20
-
21
- mkdir -p "$LOG_DIR" "$DEEP_SLEEP_DIR"
22
-
23
- log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_DIR/deep-sleep.log"; }
24
-
25
- run_analysis() {
26
- local DATE="$1"
27
- log "=== Deep Sleep v2 starting for $DATE ==="
28
-
29
- # Phase 1: Collect all context (Python, no LLM)
30
- log "Phase 1: Collecting context for $DATE..."
31
- python3 "$SCRIPT_DIR/deep-sleep/collect.py" "$DATE" 2>&1 | tee -a "$LOG_DIR/deep-sleep.log"
32
-
33
- if [ ! -f "$DEEP_SLEEP_DIR/$DATE-context.txt" ]; then
34
- log "No context file generated for $DATE. Skipping."
35
- return 0
36
- fi
37
-
38
- # Check meta for session count
39
- SESSIONS=0
40
- if [ -f "$DEEP_SLEEP_DIR/$DATE-meta.json" ]; then
41
- SESSIONS=$(python3 -c "import json; print(json.load(open('$DEEP_SLEEP_DIR/$DATE-meta.json'))['sessions_found'])")
42
- elif [ -f "$DEEP_SLEEP_DIR/$DATE-index.json" ]; then
43
- SESSIONS=$(python3 -c "import json; print(json.load(open('$DEEP_SLEEP_DIR/$DATE-index.json'))['sessions_found'])")
44
- fi
45
- if [ "$SESSIONS" -eq 0 ]; then
46
- log "No sessions found for $DATE. Skipping."
47
- return 0
48
- fi
49
-
50
- # Phase 2: Extract findings per session (Claude Opus)
51
- log "Phase 2: Extracting findings from $SESSIONS sessions..."
52
- python3 "$SCRIPT_DIR/deep-sleep/extract.py" "$DATE" 2>&1 | tee -a "$LOG_DIR/deep-sleep.log"
53
-
54
- if [ ! -f "$DEEP_SLEEP_DIR/$DATE-extractions.json" ]; then
55
- log "Extraction failed for $DATE. No output."
56
- return 1
57
- fi
58
-
59
- # Phase 3: Cross-session synthesis (Claude Opus, one call)
60
- log "Phase 3: Synthesizing cross-session findings..."
61
- python3 "$SCRIPT_DIR/deep-sleep/synthesize.py" "$DATE" 2>&1 | tee -a "$LOG_DIR/deep-sleep.log"
62
-
63
- if [ ! -f "$DEEP_SLEEP_DIR/$DATE-synthesis.json" ]; then
64
- log "Synthesis failed for $DATE. Falling back to extractions only."
65
- # Fall back: apply extractions directly
66
- cp "$DEEP_SLEEP_DIR/$DATE-extractions.json" "$DEEP_SLEEP_DIR/$DATE-synthesis.json"
67
- fi
68
-
69
- # Phase 4: Apply findings
70
- log "Phase 4: Applying findings..."
71
- python3 "$SCRIPT_DIR/deep-sleep/apply_findings.py" "$DATE" 2>&1 | tee -a "$LOG_DIR/deep-sleep.log"
72
-
73
- log "=== Deep Sleep v2 complete for $DATE ==="
74
- return 0
75
- }
76
-
77
- # --- Catch-up: check if the day before yesterday was missed ---
78
- YESTERDAY=$(date -v-1d +%Y-%m-%d 2>/dev/null || date -d "yesterday" +%Y-%m-%d 2>/dev/null)
79
- DAY_BEFORE=$(date -v-2d +%Y-%m-%d 2>/dev/null || date -d "2 days ago" +%Y-%m-%d 2>/dev/null)
80
- LAST_RUN=""
81
- if [ -f "$LAST_RUN_FILE" ]; then
82
- LAST_RUN=$(cat "$LAST_RUN_FILE")
83
- fi
84
-
85
- if [ -n "$DAY_BEFORE" ] && [ "$LAST_RUN" != "$DAY_BEFORE" ] && [ "$LAST_RUN" != "$YESTERDAY" ]; then
86
- # Day before yesterday wasn't analyzed — catch up
87
- if [ ! -f "$DEEP_SLEEP_DIR/$DAY_BEFORE-analysis.json" ]; then
88
- log "*** CATCH-UP: $DAY_BEFORE was missed. Running now. ***"
89
- run_analysis "$DAY_BEFORE" || log "Catch-up for $DAY_BEFORE failed."
90
- fi
91
- fi
92
-
93
- # --- Run yesterday's analysis (main task — at 4:30 AM, today has no sessions yet) ---
94
- run_analysis "$YESTERDAY"
95
-
96
- # Mark completion with yesterday's date (what we actually analyzed)
97
- echo "$YESTERDAY" > "$LAST_RUN_FILE"