nexo-brain 2.0.0 → 2.2.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.
- package/README.md +143 -44
- package/bin/nexo-brain.js +53 -26
- package/package.json +15 -3
- package/scripts/migrate-to-unified 2.sh +813 -0
- package/scripts/migrate-v1.5-to-v1.6 2.py +778 -0
- package/scripts/migrate-v1.7-to-v1.8 2.py +214 -0
- package/scripts/pre-commit-check 2.sh +55 -0
- package/src/__pycache__/auto_update.cpython-310.pyc +0 -0
- package/src/__pycache__/hnsw_index.cpython-310.pyc +0 -0
- package/src/__pycache__/kg_populate.cpython-310.pyc +0 -0
- package/src/__pycache__/knowledge_graph.cpython-310.pyc +0 -0
- package/src/__pycache__/plugin_loader.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_coordination.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_credentials.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_learnings.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_menu.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_reminders.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_reminders_crud.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_sessions.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_task_history.cpython-310.pyc +0 -0
- package/src/auto_close_sessions 2.py +159 -0
- package/src/auto_update 2.py +634 -0
- package/src/claim_graph 2.py +323 -0
- package/src/cognitive/__init__ 2.py +62 -0
- package/src/cognitive/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_core.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_decay.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_ingest.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_memory.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_search.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_trust.cpython-310.pyc +0 -0
- package/src/cognitive/_core 2.py +567 -0
- package/src/cognitive/_decay 2.py +382 -0
- package/src/cognitive/_ingest 2.py +892 -0
- package/src/cognitive/_memory 2.py +912 -0
- package/src/cognitive/_search 2.py +949 -0
- package/src/cognitive/_trust 2.py +464 -0
- package/src/cognitive/_trust.py +10 -36
- package/src/crons/manifest 2.json +106 -0
- package/src/crons/manifest.json +106 -0
- package/src/crons/sync 2.py +217 -0
- package/src/crons/sync.py +217 -0
- package/src/dashboard/__init__ 2.py +0 -0
- package/src/dashboard/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/dashboard/__pycache__/app.cpython-310.pyc +0 -0
- package/src/dashboard/app 2.py +789 -0
- package/src/dashboard/app.py +16 -2
- package/src/dashboard/templates/dashboard.html +3 -2
- package/src/db/__init__ 2.py +89 -0
- package/src/db/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/db/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/db/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_core.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_core.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_core.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_credentials.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_credentials.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_credentials.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_entities.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_entities.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_entities.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_episodic.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_episodic.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_episodic.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_evolution.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_evolution.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_evolution.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_fts.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_fts.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_fts.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_learnings.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_learnings.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_learnings.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_reminders.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_reminders.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_reminders.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_schema.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_schema.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_schema.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_sessions.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_sessions.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_sessions.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_tasks.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_tasks.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_tasks.cpython-314.pyc +0 -0
- package/src/db/_core 2.py +417 -0
- package/src/db/_credentials 2.py +124 -0
- package/src/db/_entities 2.py +178 -0
- package/src/db/_episodic 2.py +738 -0
- package/src/db/_episodic.py +1 -1
- package/src/db/_evolution 2.py +54 -0
- package/src/db/_fts 2.py +406 -0
- package/src/db/_learnings 2.py +168 -0
- package/src/db/_reminders 2.py +338 -0
- package/src/db/_reminders.py +9 -5
- package/src/db/_schema 2.py +364 -0
- package/src/db/_sessions 2.py +300 -0
- package/src/db/_tasks 2.py +91 -0
- package/src/evolution_cycle 2.py +266 -0
- package/src/hnsw_index 2.py +254 -0
- package/src/hooks/auto_capture 2.py +208 -0
- package/src/hooks/caffeinate-guard 2.sh +8 -0
- package/src/hooks/capture-session 2.sh +21 -0
- package/src/hooks/capture-session.sh +2 -0
- package/src/hooks/capture-tool-logs 2.sh +127 -0
- package/src/hooks/capture-tool-logs.sh +3 -2
- package/src/hooks/daily-briefing-check 2.sh +33 -0
- package/src/hooks/inbox-hook 2.sh +76 -0
- package/src/hooks/inbox-hook.sh +3 -2
- package/src/hooks/post-compact 2.sh +148 -0
- package/src/hooks/post-compact.sh +1 -1
- package/src/hooks/pre-compact 2.sh +151 -0
- package/src/hooks/pre-compact.sh +1 -1
- package/src/hooks/session-start 2.sh +268 -0
- package/src/hooks/session-start.sh +6 -3
- package/src/hooks/session-stop 2.sh +140 -0
- package/src/hooks/session-stop.sh +3 -2
- package/src/kg_populate 2.py +290 -0
- package/src/knowledge_graph 2.py +257 -0
- package/src/maintenance 2.py +59 -0
- package/src/migrate_embeddings 2.py +122 -0
- package/src/plugin_loader 2.py +202 -0
- package/src/plugins/__init__ 2.py +0 -0
- package/src/plugins/__pycache__/__init__ 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/adaptive_mode 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/adaptive_mode.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/agents 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/agents.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/artifact_registry 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/artifact_registry.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/backup 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/backup.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/cognitive_memory 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/cognitive_memory.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/core_rules 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/core_rules.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/cortex 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/cortex.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/entities 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/entities.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/episodic_memory 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/episodic_memory.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/evolution 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/evolution.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/guard 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/guard.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/knowledge_graph_tools 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/knowledge_graph_tools.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/preferences 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/preferences.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/update 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/update.cpython-310.pyc +0 -0
- package/src/plugins/adaptive_mode 2.py +805 -0
- package/src/plugins/agents 2.py +52 -0
- package/src/plugins/artifact_registry 2.py +450 -0
- package/src/plugins/backup 2.py +104 -0
- package/src/plugins/cognitive_memory 2.py +564 -0
- package/src/plugins/core_rules 2.py +252 -0
- package/src/plugins/core_rules.py +34 -17
- package/src/plugins/cortex 2.py +299 -0
- package/src/plugins/entities 2.py +67 -0
- package/src/plugins/episodic_memory 2.py +533 -0
- package/src/plugins/evolution 2.py +115 -0
- package/src/plugins/guard 2.py +746 -0
- package/src/plugins/knowledge_graph_tools 2.py +105 -0
- package/src/plugins/preferences 2.py +47 -0
- package/src/plugins/update 2.py +256 -0
- package/src/plugins/update.py +18 -0
- package/src/requirements 2.txt +12 -0
- package/src/rules/__init__ 2.py +0 -0
- package/src/rules/core-rules 2.json +331 -0
- package/src/rules/migrate 2.py +207 -0
- package/src/scripts/check-context 2.py +264 -0
- package/src/scripts/check-context.py +4 -7
- package/src/scripts/deep-sleep/apply_findings.py +570 -167
- package/src/scripts/deep-sleep/collect.py +480 -0
- package/src/scripts/deep-sleep/extract-prompt.md +233 -0
- package/src/scripts/deep-sleep/extract.py +249 -0
- package/src/scripts/deep-sleep/synthesize-prompt.md +197 -0
- package/src/scripts/deep-sleep/synthesize.py +191 -0
- package/src/scripts/nexo-auto-update 2.py +6 -0
- package/src/scripts/nexo-backup 2.sh +25 -0
- package/src/scripts/nexo-brain-activation 2.sh +140 -0
- package/src/scripts/nexo-catchup 2.py +242 -0
- package/src/scripts/nexo-catchup.py +5 -8
- package/src/scripts/nexo-cognitive-decay 2.py +182 -0
- package/src/scripts/nexo-daily-self-audit 2.py +552 -0
- package/src/scripts/nexo-daily-self-audit.py +28 -19
- package/src/scripts/nexo-deep-sleep 2.sh +97 -0
- package/src/scripts/nexo-deep-sleep.sh +31 -16
- package/src/scripts/nexo-evolution-run 2.py +597 -0
- package/src/scripts/nexo-evolution-run.py +5 -20
- package/src/scripts/nexo-followup-hygiene 2.py +112 -0
- package/src/scripts/nexo-followup-hygiene.py +4 -2
- package/src/scripts/nexo-github-monitor 2.py +256 -0
- package/src/scripts/nexo-github-monitor.py +6 -9
- package/src/scripts/nexo-immune 2.py +927 -0
- package/src/scripts/nexo-immune.py +4 -17
- package/src/scripts/nexo-inbox-hook 2.sh +74 -0
- package/src/scripts/nexo-install 2.py +6 -0
- package/src/scripts/nexo-learning-housekeep 2.py +245 -0
- package/src/scripts/nexo-learning-validator 2.py +207 -0
- package/src/scripts/nexo-learning-validator.py +0 -29
- package/src/scripts/nexo-migrate 2.py +232 -0
- package/src/scripts/nexo-postmortem-consolidator 2.py +421 -0
- package/src/scripts/nexo-postmortem-consolidator.py +9 -20
- package/src/scripts/nexo-pre-commit 2.py +120 -0
- package/src/scripts/nexo-prevent-sleep 2.sh +29 -0
- package/src/scripts/nexo-proactive-dashboard 2.py +345 -0
- package/src/scripts/nexo-proactive-dashboard.py +1 -0
- package/src/scripts/nexo-reflection 2.py +253 -0
- package/src/scripts/nexo-runtime-preflight 2.py +274 -0
- package/src/scripts/nexo-send-email 2.py +25 -0
- package/src/scripts/nexo-send-reply 2.py +178 -0
- package/src/scripts/nexo-sleep 2.py +592 -0
- package/src/scripts/nexo-sleep.py +8 -18
- package/src/scripts/nexo-snapshot-restore 2.sh +35 -0
- package/src/scripts/nexo-synthesis 2.py +253 -0
- package/src/scripts/nexo-synthesis.py +8 -19
- package/src/scripts/nexo-tcc-approve 2.sh +79 -0
- package/src/scripts/nexo-update 2.sh +161 -0
- package/src/scripts/nexo-watchdog 2.sh +878 -0
- package/src/scripts/nexo-watchdog-smoke 2.py +119 -0
- package/src/server 2.py +733 -0
- package/src/server.py +6 -1
- package/src/storage_router 2.py +32 -0
- package/src/tools_coordination 2.py +102 -0
- package/src/tools_credentials 2.py +68 -0
- package/src/tools_learnings 2.py +220 -0
- package/src/tools_menu 2.py +227 -0
- package/src/tools_menu.py +1 -1
- package/src/tools_reminders 2.py +86 -0
- package/src/tools_reminders_crud 2.py +159 -0
- package/src/tools_reminders_crud.py +7 -0
- package/src/tools_sessions 2.py +476 -0
- package/src/tools_sessions.py +67 -0
- package/src/tools_task_history 2.py +57 -0
- package/templates/CLAUDE.md 2.template +63 -0
- package/templates/openclaw 2.json +13 -0
- package/tests/__init__ 2.py +0 -0
- package/tests/conftest 2.py +71 -0
- package/tests/test_cognitive 2.py +205 -0
- package/tests/test_knowledge_graph 2.py +140 -0
- package/tests/test_migrations 2.py +137 -0
- package/src/__pycache__/auto_close_sessions.cpython-310.pyc +0 -0
- package/src/__pycache__/auto_close_sessions.cpython-314.pyc +0 -0
- package/src/__pycache__/auto_update.cpython-314.pyc +0 -0
- package/src/__pycache__/claim_graph.cpython-310.pyc +0 -0
- package/src/__pycache__/claim_graph.cpython-314.pyc +0 -0
- package/src/__pycache__/evolution_cycle.cpython-310.pyc +0 -0
- package/src/__pycache__/evolution_cycle.cpython-314.pyc +0 -0
- package/src/__pycache__/hnsw_index.cpython-314.pyc +0 -0
- package/src/__pycache__/kg_populate.cpython-314.pyc +0 -0
- package/src/__pycache__/knowledge_graph.cpython-314.pyc +0 -0
- package/src/__pycache__/maintenance.cpython-310.pyc +0 -0
- package/src/__pycache__/maintenance.cpython-314.pyc +0 -0
- package/src/__pycache__/migrate_embeddings.cpython-310.pyc +0 -0
- package/src/__pycache__/migrate_embeddings.cpython-314.pyc +0 -0
- package/src/__pycache__/plugin_loader.cpython-314.pyc +0 -0
- package/src/__pycache__/server.cpython-310.pyc +0 -0
- package/src/__pycache__/server.cpython-314.pyc +0 -0
- package/src/__pycache__/storage_router.cpython-310.pyc +0 -0
- package/src/__pycache__/storage_router.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_coordination.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_credentials.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_learnings.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_menu.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_reminders.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_reminders_crud.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_sessions.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_task_history.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_core.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_decay.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_ingest.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_memory.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_search.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_trust.cpython-314.pyc +0 -0
- package/src/dashboard/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/dashboard/__pycache__/app.cpython-314.pyc +0 -0
- package/src/hooks/__pycache__/auto_capture.cpython-310.pyc +0 -0
- package/src/hooks/__pycache__/auto_capture.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/adaptive_mode.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/agents.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/artifact_registry.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/backup.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/cognitive_memory.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/core_rules.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/cortex.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/entities.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/episodic_memory.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/evolution.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/guard.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/knowledge_graph_tools.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/preferences.cpython-314.pyc +0 -0
- package/src/rules/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/rules/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/rules/__pycache__/migrate.cpython-310.pyc +0 -0
- package/src/rules/__pycache__/migrate.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/check-context.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/check-context.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-auto-update.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-auto-update.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-catchup.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-catchup.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-cognitive-decay.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-cognitive-decay.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-daily-self-audit.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-daily-self-audit.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-evolution-run.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-evolution-run.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-followup-hygiene.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-followup-hygiene.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-github-monitor.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-github-monitor.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-immune.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-immune.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-install.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-install.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-learning-housekeep.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-learning-housekeep.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-learning-validator.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-learning-validator.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-migrate.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-migrate.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-postmortem-consolidator.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-postmortem-consolidator.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-pre-commit.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-pre-commit.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-proactive-dashboard.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-proactive-dashboard.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-reflection.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-reflection.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-runtime-preflight.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-runtime-preflight.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-send-email.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-send-email.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-send-reply.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-send-reply.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-sleep.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-sleep.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-synthesis.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-synthesis.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-watchdog-smoke.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-watchdog-smoke.cpython-314.pyc +0 -0
- package/src/scripts/deep-sleep/__pycache__/analyze_session.cpython-310.pyc +0 -0
- package/src/scripts/deep-sleep/__pycache__/analyze_session.cpython-314.pyc +0 -0
- package/src/scripts/deep-sleep/__pycache__/apply_findings.cpython-310.pyc +0 -0
- package/src/scripts/deep-sleep/__pycache__/apply_findings.cpython-314.pyc +0 -0
- package/src/scripts/deep-sleep/__pycache__/collect_transcripts.cpython-310.pyc +0 -0
- package/src/scripts/deep-sleep/__pycache__/collect_transcripts.cpython-314.pyc +0 -0
- package/src/scripts/deep-sleep/analyze_session.py +0 -217
- package/src/scripts/deep-sleep/collect_transcripts.py +0 -145
- package/src/scripts/deep-sleep/prompt.md +0 -109
- package/tests/__pycache__/__init__.cpython-310.pyc +0 -0
- package/tests/__pycache__/__init__.cpython-314.pyc +0 -0
- package/tests/__pycache__/conftest.cpython-310-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/conftest.cpython-310.pyc +0 -0
- package/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/test_cognitive.cpython-310-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/test_cognitive.cpython-310.pyc +0 -0
- package/tests/__pycache__/test_cognitive.cpython-314-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/test_knowledge_graph.cpython-310-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/test_knowledge_graph.cpython-310.pyc +0 -0
- package/tests/__pycache__/test_knowledge_graph.cpython-314-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/test_migrations.cpython-310-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/test_migrations.cpython-310.pyc +0 -0
- package/tests/__pycache__/test_migrations.cpython-314-pytest-9.0.2.pyc +0 -0
|
@@ -0,0 +1,552 @@
|
|
|
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())
|
|
@@ -303,6 +303,25 @@ def check_runtime_preflight():
|
|
|
303
303
|
finding("ERROR", "preflight", "runtime preflight failing")
|
|
304
304
|
|
|
305
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
|
+
|
|
306
325
|
def check_watchdog_smoke():
|
|
307
326
|
if not WATCHDOG_SMOKE_SUMMARY.exists():
|
|
308
327
|
return
|
|
@@ -409,9 +428,11 @@ def interpret_findings(raw_findings: list) -> bool:
|
|
|
409
428
|
|
|
410
429
|
findings_json = json.dumps(raw_findings, ensure_ascii=False, indent=1)
|
|
411
430
|
|
|
412
|
-
prompt = f"""
|
|
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
|
|
413
434
|
{len(errors)} errors and {len(warns)} warnings. Your job is to UNDERSTAND what's
|
|
414
|
-
actually wrong, not just list findings.
|
|
435
|
+
actually wrong, not just list findings. Use nexo_learning_add for new findings and nexo_followup_create for action items.
|
|
415
436
|
|
|
416
437
|
RAW FINDINGS:
|
|
417
438
|
{findings_json}
|
|
@@ -441,30 +462,17 @@ Also write the machine-readable summary to {LOG_DIR}/self-audit-summary.json.
|
|
|
441
462
|
Execute without asking."""
|
|
442
463
|
|
|
443
464
|
log("Stage B: Invoking Claude CLI (opus) for interpretation...")
|
|
444
|
-
|
|
445
|
-
# Verify Claude CLI is authenticated before calling
|
|
446
|
-
try:
|
|
447
|
-
auth_check = subprocess.run(
|
|
448
|
-
[str(CLAUDE_CLI), "-p", "Reply with exactly: ok", "--bare", "--output-format", "text", "--model", "haiku"],
|
|
449
|
-
capture_output=True, text=True, timeout=15
|
|
450
|
-
)
|
|
451
|
-
if auth_check.returncode != 0:
|
|
452
|
-
log("Stage B: Claude CLI not available or not authenticated. Skipping Stage B.")
|
|
453
|
-
return False
|
|
454
|
-
except Exception:
|
|
455
|
-
log("Stage B: Claude CLI check failed. Skipping Stage B.")
|
|
456
|
-
return False
|
|
457
|
-
|
|
458
465
|
env = os.environ.copy()
|
|
466
|
+
env["NEXO_HEADLESS"] = "1" # Skip stop hook post-mortem
|
|
459
467
|
env.pop("CLAUDECODE", None)
|
|
460
468
|
env.pop("CLAUDE_CODE", None)
|
|
461
469
|
|
|
462
470
|
try:
|
|
463
471
|
result = subprocess.run(
|
|
464
472
|
[str(CLAUDE_CLI), "-p", prompt, "--model", "opus",
|
|
465
|
-
"--output-format", "text",
|
|
466
|
-
"--allowedTools", "Read,Write,Edit,Glob,Grep"],
|
|
467
|
-
capture_output=True, text=True, timeout=
|
|
473
|
+
"--output-format", "text",
|
|
474
|
+
"--allowedTools", "Read,Write,Edit,Glob,Grep,Bash,mcp__nexo__*"],
|
|
475
|
+
capture_output=True, text=True, timeout=21600, env=env
|
|
468
476
|
)
|
|
469
477
|
|
|
470
478
|
if result.returncode != 0:
|
|
@@ -507,6 +515,7 @@ def main():
|
|
|
507
515
|
check_restore_activity()
|
|
508
516
|
check_bad_responses()
|
|
509
517
|
check_runtime_preflight()
|
|
518
|
+
run_watchdog_smoke()
|
|
510
519
|
check_watchdog_smoke()
|
|
511
520
|
check_cognitive_health()
|
|
512
521
|
|