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