nexo-brain 5.3.26 → 5.3.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/package.json +1 -1
  3. package/src/server.py +3 -0
  4. package/src/tools_sessions.py +6 -1
  5. package/src/dashboard/static/favicon 2.svg +0 -32
  6. package/src/dashboard/static/nexo-logo 2.png +0 -0
  7. package/src/dashboard/static/nexo-logo 2.svg +0 -40
  8. package/src/dashboard/static/style 2.css +0 -2458
  9. package/src/dashboard/templates/adaptive 2.html +0 -118
  10. package/src/dashboard/templates/artifacts 2.html +0 -133
  11. package/src/dashboard/templates/backups 2.html +0 -136
  12. package/src/dashboard/templates/base 2.html +0 -417
  13. package/src/dashboard/templates/calendar 2.html +0 -591
  14. package/src/dashboard/templates/chat 2.html +0 -356
  15. package/src/dashboard/templates/claims 2.html +0 -259
  16. package/src/dashboard/templates/cortex 2.html +0 -321
  17. package/src/dashboard/templates/credentials 2.html +0 -128
  18. package/src/dashboard/templates/crons 2.html +0 -370
  19. package/src/dashboard/templates/dashboard 2.html +0 -494
  20. package/src/dashboard/templates/dreams 2.html +0 -252
  21. package/src/dashboard/templates/email 2.html +0 -160
  22. package/src/dashboard/templates/evolution 2.html +0 -189
  23. package/src/dashboard/templates/feed 2.html +0 -249
  24. package/src/dashboard/templates/followup_health 2.html +0 -170
  25. package/src/dashboard/templates/graph 2.html +0 -201
  26. package/src/dashboard/templates/guard 2.html +0 -259
  27. package/src/dashboard/templates/inbox 2.html +0 -251
  28. package/src/dashboard/templates/memory 2.html +0 -420
  29. package/src/dashboard/templates/operations 2.html +0 -608
  30. package/src/dashboard/templates/plugins 2.html +0 -185
  31. package/src/dashboard/templates/protocol 2.html +0 -199
  32. package/src/dashboard/templates/rules 2.html +0 -246
  33. package/src/dashboard/templates/sentiment 2.html +0 -247
  34. package/src/dashboard/templates/sessions 2.html +0 -218
  35. package/src/dashboard/templates/skills 2.html +0 -329
  36. package/src/dashboard/templates/somatic 2.html +0 -73
  37. package/src/dashboard/templates/triggers 2.html +0 -133
  38. package/src/dashboard/templates/trust 2.html +0 -360
  39. package/src/db/__init__ 2.py +0 -259
  40. package/src/db/_core 2.py +0 -437
  41. package/src/db/_credentials 2.py +0 -124
  42. package/src/db/_episodic 2.py +0 -762
  43. package/src/db/_evolution 2.py +0 -54
  44. package/src/db/_fts 2.py +0 -406
  45. package/src/db/_goal_profiles 2.py +0 -376
  46. package/src/db/_hot_context 2.py +0 -660
  47. package/src/db/_outcomes 2.py +0 -800
  48. package/src/db/_personal_scripts 2.py +0 -582
  49. package/src/db/_sessions 2.py +0 -330
  50. package/src/db/_tasks 2.py +0 -91
  51. package/src/db/_watchers 2.py +0 -173
  52. package/src/doctor/formatters 2.py +0 -52
  53. package/src/doctor/models 2.py +0 -69
  54. package/src/doctor/planes 2.py +0 -87
  55. package/src/doctor/providers/__init__ 2.py +0 -1
  56. package/src/doctor/providers/deep 2.py +0 -367
  57. package/src/evolution_cycle 2.py +0 -519
  58. package/src/hooks/auto_capture 2.py +0 -208
  59. package/src/hooks/caffeinate-guard 2.sh +0 -8
  60. package/src/hooks/capture-session 2.sh +0 -21
  61. package/src/hooks/capture-tool-logs 2.sh +0 -158
  62. package/src/hooks/daily-briefing-check 2.sh +0 -33
  63. package/src/hooks/heartbeat-enforcement 2.py +0 -90
  64. package/src/hooks/heartbeat-posttool 2.sh +0 -18
  65. package/src/hooks/inbox-hook 2.sh +0 -76
  66. package/src/hooks/post-compact 2.sh +0 -152
  67. package/src/hooks/pre-compact 2.sh +0 -169
  68. package/src/hooks/protocol-guardrail 2.sh +0 -10
  69. package/src/hooks/protocol-pretool-guardrail 2.sh +0 -9
  70. package/src/hooks/session-stop 2.sh +0 -52
  71. package/src/kg_populate 2.py +0 -292
  72. package/src/maintenance 2.py +0 -53
  73. package/src/memory_backends 2.py +0 -71
  74. package/src/migrate_embeddings 2.py +0 -124
  75. package/src/nexo_sdk 2.py +0 -103
  76. package/src/observability 2.py +0 -199
  77. package/src/plugin_loader 2.py +0 -217
  78. package/src/plugins/__init__ 2.py +0 -0
  79. package/src/plugins/artifact_registry 2.py +0 -450
  80. package/src/plugins/backup 2.py +0 -127
  81. package/src/plugins/claims_tools 2.py +0 -119
  82. package/src/plugins/cognitive_memory 2.py +0 -609
  83. package/src/plugins/core_rules 2.py +0 -252
  84. package/src/plugins/cortex 2.py +0 -1155
  85. package/src/plugins/entities 2.py +0 -67
  86. package/src/plugins/episodic_memory 2.py +0 -560
  87. package/src/plugins/evolution 2.py +0 -167
  88. package/src/plugins/goal_engine 2.py +0 -142
  89. package/src/plugins/guard 2.py +0 -862
  90. package/src/plugins/impact 2.py +0 -29
  91. package/src/plugins/knowledge_graph_tools 2.py +0 -137
  92. package/src/plugins/media_memory_tools 2.py +0 -98
  93. package/src/plugins/memory_export 2.py +0 -196
  94. package/src/plugins/outcomes 2.py +0 -130
  95. package/src/plugins/personal_scripts 2.py +0 -117
  96. package/src/plugins/preferences 2.py +0 -47
  97. package/src/plugins/protocol 2.py +0 -1449
  98. package/src/plugins/simple_api 2.py +0 -106
  99. package/src/plugins/skills 2.py +0 -341
  100. package/src/plugins/state_watchers 2.py +0 -79
  101. package/src/plugins/update 2.py +0 -986
  102. package/src/plugins/user_state_tools 2.py +0 -43
  103. package/src/plugins/workflow 2.py +0 -588
  104. package/src/protocol_settings 2.py +0 -59
  105. package/src/public_contribution 2.py +0 -466
  106. package/src/public_evolution_queue 2.py +0 -241
  107. package/src/requirements 2.txt +0 -14
  108. package/src/retroactive_learnings 2.py +0 -373
  109. package/src/rules/__init__ 2.py +0 -0
  110. package/src/rules/core-rules 2.json +0 -331
  111. package/src/rules/migrate 2.py +0 -207
  112. package/src/runtime_power 2.py +0 -874
  113. package/src/script_registry 2.py +0 -1559
  114. package/src/scripts/check-context 2.py +0 -272
  115. package/src/scripts/deep-sleep/apply_findings 2.py +0 -2327
  116. package/src/scripts/deep-sleep/collect 2.py +0 -928
  117. package/src/scripts/deep-sleep/extract 2.py +0 -330
  118. package/src/scripts/deep-sleep/extract-prompt 2.md +0 -285
  119. package/src/scripts/deep-sleep/synthesize 2.py +0 -312
  120. package/src/scripts/deep-sleep/synthesize-prompt 2.md +0 -336
  121. package/src/scripts/nexo-agent-run 2.py +0 -75
  122. package/src/scripts/nexo-auto-update 2.py +0 -6
  123. package/src/scripts/nexo-backup 2.sh +0 -25
  124. package/src/scripts/nexo-brain-activation 2.sh +0 -140
  125. package/src/scripts/nexo-catchup 2.py +0 -300
  126. package/src/scripts/nexo-cognitive-decay 2.py +0 -257
  127. package/src/scripts/nexo-cortex-cycle 2.py +0 -293
  128. package/src/scripts/nexo-cron-wrapper 2.sh +0 -53
  129. package/src/scripts/nexo-daily-self-audit 2.py +0 -2161
  130. package/src/scripts/nexo-dashboard 2.sh +0 -29
  131. package/src/scripts/nexo-deep-sleep 2.sh +0 -86
  132. package/src/scripts/nexo-evolution-run 2.py +0 -1664
  133. package/src/scripts/nexo-followup-hygiene 2.py +0 -139
  134. package/src/scripts/nexo-hook-record 2.py +0 -42
  135. package/src/scripts/nexo-immune 2.py +0 -936
  136. package/src/scripts/nexo-impact-scorer 2.py +0 -117
  137. package/src/scripts/nexo-inbox-hook 2.sh +0 -74
  138. package/src/scripts/nexo-install 2.py +0 -6
  139. package/src/scripts/nexo-learning-housekeep 2.py +0 -401
  140. package/src/scripts/nexo-learning-validator 2.py +0 -266
  141. package/src/scripts/nexo-migrate 2.py +0 -260
  142. package/src/scripts/nexo-outcome-checker 2.py +0 -127
  143. package/src/scripts/nexo-postmortem-consolidator 2.py +0 -456
  144. package/src/scripts/nexo-pre-commit 2.py +0 -120
  145. package/src/scripts/nexo-prevent-sleep 2.sh +0 -35
  146. package/src/scripts/nexo-proactive-dashboard 2.py +0 -354
  147. package/src/scripts/nexo-reflection 2.py +0 -256
  148. package/src/scripts/nexo-runtime-preflight 2.py +0 -274
  149. package/src/scripts/nexo-sleep 2.py +0 -631
  150. package/src/scripts/nexo-snapshot-restore 2.sh +0 -35
  151. package/src/scripts/nexo-sync-clients 2.py +0 -16
  152. package/src/scripts/nexo-synthesis 2.py +0 -475
  153. package/src/scripts/nexo-tcc-approve 2.sh +0 -79
  154. package/src/scripts/nexo-update 2.sh +0 -306
  155. package/src/scripts/nexo-watchdog 2.sh +0 -1207
  156. package/src/scripts/nexo-watchdog-smoke 2.py +0 -119
  157. package/src/scripts/rehydrate_learnings_from_archive 2.py +0 -245
  158. package/src/server 2.py +0 -1296
  159. package/src/skills/run-nexo-audit-phase/guide 2.md +0 -43
  160. package/src/skills/run-nexo-audit-phase/skill 2.json +0 -59
  161. package/src/skills/run-nexo-core-fix-cycle/guide 2.md +0 -17
  162. package/src/skills/run-nexo-core-fix-cycle/script 2.py +0 -276
  163. package/src/skills/run-nexo-core-fix-cycle/skill 2.json +0 -58
  164. package/src/skills/run-release-final-audit/guide 2.md +0 -16
  165. package/src/skills/run-release-final-audit/script 2.py +0 -259
  166. package/src/skills/run-release-final-audit/skill 2.json +0 -77
  167. package/src/skills/run-runtime-doctor/guide 2.md +0 -12
  168. package/src/skills/run-runtime-doctor/script 2.py +0 -21
  169. package/src/skills/run-runtime-doctor/skill 2.json +0 -25
  170. package/src/skills_runtime 2.py +0 -932
  171. package/src/state_watchers_runtime 2.py +0 -475
  172. package/src/storage_router 2.py +0 -32
  173. package/src/system_catalog 2.py +0 -786
  174. package/src/tools_coordination 2.py +0 -103
  175. package/src/tools_credentials 2.py +0 -68
  176. package/src/tools_drive 2.py +0 -487
  177. package/src/tools_hot_context 2.py +0 -163
  178. package/src/tools_learnings 2.py +0 -612
  179. package/src/tools_menu 2.py +0 -229
  180. package/src/tools_reminders 2.py +0 -88
  181. package/src/tools_reminders_crud 2.py +0 -363
  182. package/src/tools_sessions 2.py +0 -1054
  183. package/src/tools_system_catalog 2.py +0 -19
  184. package/src/tools_task_history 2.py +0 -57
  185. package/src/tools_transcripts 2.py +0 -98
  186. package/src/transcript_utils 2.py +0 -412
  187. package/src/user_context 2.py +0 -46
  188. package/src/user_data_portability 2.py +0 -328
  189. package/src/user_state_model 2.py +0 -170
  190. package/templates/CLAUDE.md 2.template +0 -108
  191. package/templates/CODEX.AGENTS.md 2.template +0 -66
  192. package/templates/launchagents/README 2.md +0 -132
  193. package/templates/launchagents/com.nexo.auto-close-sessions 2.plist +0 -39
  194. package/templates/launchagents/com.nexo.catchup 2.plist +0 -39
  195. package/templates/launchagents/com.nexo.cognitive-decay 2.plist +0 -40
  196. package/templates/launchagents/com.nexo.dashboard 2.plist +0 -43
  197. package/templates/launchagents/com.nexo.deep-sleep 2.plist +0 -43
  198. package/templates/launchagents/com.nexo.evolution 2.plist +0 -44
  199. package/templates/launchagents/com.nexo.followup-hygiene 2.plist +0 -45
  200. package/templates/launchagents/com.nexo.immune 2.plist +0 -41
  201. package/templates/launchagents/com.nexo.postmortem 2.plist +0 -45
  202. package/templates/launchagents/com.nexo.self-audit 2.plist +0 -47
  203. package/templates/launchagents/com.nexo.synthesis 2.plist +0 -45
  204. package/templates/launchagents/com.nexo.watchdog 2.plist +0 -37
  205. package/templates/nexo_helper 2.py +0 -301
  206. package/templates/openclaw 2.json +0 -13
  207. package/templates/plugin-template 2.py +0 -40
  208. package/templates/script-template 2.py +0 -59
  209. package/templates/script-template 2.sh +0 -13
  210. package/templates/skill-script-template 2.py +0 -48
  211. package/templates/skill-template 2.md +0 -33
@@ -1,456 +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 the configured automation backend, 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 (automation backend):
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
- from agent_runner import AutomationBackendUnavailableError, run_automation_prompt
40
-
41
- try:
42
- from client_preferences import resolve_user_model as _resolve_user_model
43
- _USER_MODEL = _resolve_user_model()
44
- except Exception:
45
- _USER_MODEL = ""
46
-
47
-
48
- NEXO_DB = NEXO_HOME / "data" / "nexo.db"
49
- # Memory directory — adjust to match your project's memory location
50
- MEMORY_DIR = NEXO_HOME / "memory"
51
- MEMORY_INDEX = MEMORY_DIR / "MEMORY.md"
52
- HISTORY_FILE = NEXO_HOME / "coordination" / "postmortem-history.json"
53
- CONSOLIDATION_LOG = NEXO_HOME / "logs" / "postmortem-consolidation.log"
54
- def _resolve_claude_cli() -> Path:
55
- """Find claude CLI: saved path > PATH > common locations."""
56
- import shutil as _shutil
57
- saved = NEXO_HOME / "config" / "claude-cli-path"
58
- if saved.exists():
59
- p = Path(saved.read_text().strip())
60
- if p.exists():
61
- return p
62
- found = _shutil.which("claude")
63
- if found:
64
- return Path(found)
65
- for candidate in [
66
- HOME / ".local" / "bin" / "claude",
67
- HOME / ".npm-global" / "bin" / "claude",
68
- Path("/usr/local/bin/claude"),
69
- ]:
70
- if candidate.exists():
71
- return candidate
72
- return HOME / ".local" / "bin" / "claude"
73
-
74
- CLAUDE_CLI = _resolve_claude_cli()
75
- SESSION_BUFFER = NEXO_HOME / "brain" / "session_buffer.jsonl"
76
-
77
- TODAY = date.today()
78
- TODAY_STR = TODAY.isoformat()
79
-
80
-
81
- def log(msg: str):
82
- ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
83
- line = f"[{ts}] {msg}"
84
- print(line, flush=True)
85
- CONSOLIDATION_LOG.parent.mkdir(parents=True, exist_ok=True)
86
- with open(CONSOLIDATION_LOG, "a") as f:
87
- f.write(line + "\n")
88
-
89
-
90
- # ─── Stage 1: Data Collection (Pure Python) ─────────────────────────────────
91
-
92
- def collect_data() -> dict:
93
- """Collects all data the CLI will need to decide."""
94
- data = {
95
- "date": TODAY_STR,
96
- "diaries": [],
97
- "existing_feedbacks": [],
98
- "history_summary": {},
99
- }
100
-
101
- if not NEXO_DB.exists():
102
- return data
103
-
104
- conn = sqlite3.connect(str(NEXO_DB))
105
- conn.row_factory = sqlite3.Row
106
-
107
- # Today's diaries with self-critique
108
- rows = conn.execute(
109
- "SELECT id, session_id, summary, self_critique, user_signals, "
110
- "mental_state, domain, created_at "
111
- "FROM session_diary WHERE date(created_at) = ? ORDER BY created_at",
112
- (TODAY_STR,)
113
- ).fetchall()
114
- data["diaries"] = [dict(r) for r in rows]
115
-
116
- conn.close()
117
-
118
- # Existing postmortem feedbacks (nombres, para no duplicar)
119
- data["existing_feedbacks"] = [
120
- f.stem for f in MEMORY_DIR.glob("feedback_postmortem_*.md")
121
- ]
122
-
123
- # History summary
124
- if HISTORY_FILE.exists():
125
- try:
126
- history = json.loads(HISTORY_FILE.read_text())
127
- data["history_summary"] = {
128
- "total_permanent_rules": len(history.get("permanent_rules", [])),
129
- "days_tracked": len(history.get("days", {})),
130
- "recent_rules": history.get("permanent_rules", [])[-10:],
131
- }
132
- except Exception:
133
- pass
134
-
135
- return data
136
-
137
-
138
- # ─── Stage 2: Intelligence (automation backend) ─────────────────────────────
139
-
140
- def consolidate_with_cli(data: dict) -> bool:
141
- """The brain consolidates — CLI decides what to promote."""
142
-
143
- diaries_with_critique = [
144
- d for d in data["diaries"]
145
- if d.get("self_critique") and not str(d["self_critique"]).strip().lower().startswith("no self-critique")
146
- ]
147
-
148
- if not diaries_with_critique:
149
- log("All sessions clean or trivial. Nothing to consolidate.")
150
- return True
151
-
152
- # Prepare data for CLI (truncate to avoid exceeding context)
153
- diaries_json = json.dumps(diaries_with_critique, ensure_ascii=False, indent=1)
154
- if len(diaries_json) > 12000:
155
- diaries_json = diaries_json[:12000] + "\n... (truncated)"
156
-
157
- prompt = f"""FIRST: Call nexo_startup(task='nightly postmortem consolidation') to register this session.
158
-
159
- You are NEXO's nightly consolidator. Your job is to review the self-critiques
160
- from today and decide which deserve to become permanent rules. Use nexo_learning_add for permanent rules and nexo_followup_create for action items.
161
-
162
- DATE: {data['date']}
163
- SESSIONS TODAY: {len(data['diaries'])} total, {len(diaries_with_critique)} with self-critique
164
-
165
- DIARIES WITH SELF-CRITIQUE:
166
- {diaries_json}
167
-
168
- EXISTING POSTMORTEM FEEDBACKS ({len(data['existing_feedbacks'])}):
169
- {json.dumps(data['existing_feedbacks'][:30], ensure_ascii=False)}
170
-
171
- RECENT PERMANENT RULES:
172
- {json.dumps(data['history_summary'].get('recent_rules', []), ensure_ascii=False)}
173
-
174
- INSTRUCTIONS:
175
-
176
- 1. Read each self_critique and understand its MEANING (don't count words).
177
-
178
- 2. PROMOTE to permanent feedback ONLY IF:
179
- - A pattern appears in 2+ different sessions of the day (by meaning, not literal text)
180
- - Or the user explicitly corrected (user_signals contains correction)
181
- - And the self-critique contains a CONCRETE ACTION that prevents a future error
182
- - And a similar feedback does NOT already exist in the existing ones
183
-
184
- 3. DO NOT promote if:
185
- - It's a negative response ("Nothing happened", "clean session")
186
- - It's generic without concrete action
187
- - A feedback covering the same topic already exists
188
-
189
- 4. For each rule to promote, create the file with Write en {MEMORY_DIR}/:
190
- Nombre: feedback_postmortem_[slug_descriptivo].md
191
- Formato:
192
- ---
193
- name: [descriptive title]
194
- description: Behavioral rule extracted from self-critique — recurring pattern
195
- type: feedback
196
- ---
197
-
198
- [Clear description of the pattern and rule]
199
-
200
- **Why:** [Why this matters — with evidence from sessions]
201
- **How to apply:** [When and how to apply this rule]
202
-
203
- 5. Write the daily summary en $NEXO_HOME/coordination/postmortem-daily.md:
204
- # Post-Mortem Daily — {data['date']}
205
- Sessions: X | Self-critiques: Y | Promoted: Z
206
-
207
- ## Today's self-critiques (summary)
208
- [Lista breve]
209
-
210
- ## Promoted to permanent memory
211
- [What you promoted and why]
212
-
213
- ## Discarded (and why)
214
- [What you did NOT promote and the reason]
215
-
216
- Execute without asking."""
217
-
218
- log(f"Stage 2: Invoking automation backend with {len(diaries_with_critique)} critiques...")
219
- try:
220
- result = run_automation_prompt(
221
- prompt,
222
- model=_USER_MODEL or "opus",
223
- timeout=21600,
224
- output_format="text",
225
- allowed_tools="Read,Write,Edit,Glob,Grep,Bash,mcp__nexo__*",
226
- )
227
-
228
- if result.returncode != 0:
229
- log(f"Stage 2: CLI error (code {result.returncode}): {(result.stderr or '')[:300]}")
230
- return False
231
-
232
- log(f"Stage 2: Completed. Output: {len(result.stdout or '')} chars")
233
- # Log last 500 chars of output for debugging
234
- if result.stdout:
235
- log(f"Stage 2 output tail: {result.stdout[-500:]}")
236
- return True
237
-
238
- except AutomationBackendUnavailableError as e:
239
- log(f"Stage 2: automation backend unavailable: {e}")
240
- return False
241
- except subprocess.TimeoutExpired:
242
- log("Stage 2: CLI timed out (300s)")
243
- return False
244
- except Exception as e:
245
- log(f"Stage 2: Exception: {e}")
246
- return False
247
-
248
-
249
- # ─── Stage 3: Sensory Register + Force Analysis (Pure Python) ───────────────
250
- # Kept from v1 — these are genuinely mechanical (embedding vectors, DB updates)
251
-
252
- def process_sensory_register():
253
- """Sensory Register — Atkinson-Shiffrin Layer 1. Embeds events into STM."""
254
- log("--- Sensory Register processing ---")
255
-
256
- if not SESSION_BUFFER.exists():
257
- log(" No session_buffer.jsonl found, skipping")
258
- return
259
-
260
- today_events = []
261
- try:
262
- with open(SESSION_BUFFER) as f:
263
- for line in f:
264
- line = line.strip()
265
- if not line:
266
- continue
267
- try:
268
- event = json.loads(line)
269
- if event.get("ts", "").startswith(TODAY_STR):
270
- today_events.append(event)
271
- except json.JSONDecodeError:
272
- continue
273
- except Exception as e:
274
- log(f" Error reading session_buffer: {e}")
275
- return
276
-
277
- if not today_events:
278
- log(" No events from today")
279
- return
280
-
281
- log(f" Found {len(today_events)} events")
282
-
283
- try:
284
- import cognitive
285
- except ImportError as e:
286
- log(f" Cannot import cognitive: {e}")
287
- return
288
-
289
- ingested = 0
290
- for event in today_events:
291
- source = event.get("source", "")
292
- if source == "hook-fallback":
293
- task_str = " ".join(event.get("tasks", []))
294
- if len(task_str) < 50 or "," in task_str:
295
- continue
296
-
297
- parts = []
298
- for key, label in [("tasks", "Tasks"), ("decisions", "Decisions"),
299
- ("errors_resolved", "Errors"), ("user_patterns", "the user")]:
300
- val = event.get(key, [])
301
- if val:
302
- parts.append(f"{label}: {'; '.join(str(v) for v in val[:3])}")
303
-
304
- critique = event.get("self_critique", "")
305
- if critique and "hook-fallback" not in critique:
306
- parts.append(f"Self-critique: {critique[:200]}")
307
-
308
- content = " | ".join(parts)
309
- if not content or len(content) < 20:
310
- continue
311
-
312
- try:
313
- vec = cognitive.embed(content)
314
- domain = ""
315
- lower = content.lower()
316
- # Add your project keywords for domain detection
317
- for keyword, dom in [("nexo", "nexo")]:
318
- if keyword in lower:
319
- domain = dom
320
- break
321
-
322
- cognitive.ingest_sensory(
323
- content=content, source_id=f"buffer#{event.get('ts', '')}",
324
- domain=domain, created_at=event.get("ts", "")
325
- )
326
- ingested += 1
327
- except Exception as e:
328
- log(f" Error embedding: {e}")
329
-
330
- log(f" Ingested {ingested} sensory events into STM")
331
-
332
-
333
- def analyze_force_events():
334
- """Analyze --force dissonance resolutions from today."""
335
- log("--- Force event analysis ---")
336
-
337
- try:
338
- import cognitive
339
- except ImportError:
340
- log(" Cannot import cognitive, skipping")
341
- return
342
-
343
- db = cognitive._get_db()
344
- today_forces = db.execute(
345
- """SELECT memory_id, context, created_at FROM memory_corrections
346
- WHERE correction_type = 'exception' AND context LIKE '%[FORCE]%'
347
- AND date(created_at) = ? ORDER BY created_at""",
348
- (TODAY_STR,)
349
- ).fetchall()
350
-
351
- if not today_forces:
352
- log(" No --force events today")
353
- return
354
-
355
- log(f" {len(today_forces)} --force events")
356
-
357
- from collections import Counter
358
-
359
- memory_counts = Counter(r["memory_id"] for r in today_forces)
360
- for mem_id, count in memory_counts.most_common():
361
- mem = db.execute(
362
- "SELECT content, strength FROM ltm_memories WHERE id = ?", (mem_id,)
363
- ).fetchone()
364
- if not mem:
365
- continue
366
-
367
- total = db.execute(
368
- "SELECT COUNT(*) FROM memory_corrections WHERE memory_id = ? AND context LIKE '%[FORCE]%'",
369
- (mem_id,)
370
- ).fetchone()[0]
371
-
372
- if total >= 3:
373
- log(f" PARADIGM SHIFT: LTM #{mem_id} overridden {total}x → decay to 0.3")
374
- db.execute(
375
- "UPDATE ltm_memories SET strength = 0.3, "
376
- "tags = CASE WHEN tags LIKE '%paradigm_candidate%' THEN tags "
377
- "ELSE tags || ',paradigm_candidate' END WHERE id = ?",
378
- (mem_id,)
379
- )
380
- elif count >= 2:
381
- log(f" WATCH: LTM #{mem_id} overridden {count}x today")
382
-
383
- db.commit()
384
-
385
-
386
- # ─── Main ────────────────────────────────────────────────────────────────────
387
-
388
- def already_ran_today() -> bool:
389
- """Prevent running twice on the same day."""
390
- marker = NEXO_HOME / "coordination" / "postmortem-last-run"
391
- if marker.exists():
392
- try:
393
- return marker.read_text().strip() == TODAY_STR
394
- except Exception:
395
- return False
396
- return False
397
-
398
-
399
- def mark_done():
400
- marker = NEXO_HOME / "coordination" / "postmortem-last-run"
401
- marker.parent.mkdir(parents=True, exist_ok=True)
402
- marker.write_text(TODAY_STR)
403
-
404
-
405
- def main():
406
- if already_ran_today():
407
- log("Already ran today. Skipping.")
408
- return
409
-
410
- log("=== NEXO Post-Mortem Consolidator v2 starting ===")
411
- had_errors = False
412
-
413
- # Stage 1: Collect data
414
- data = collect_data()
415
- log(f"Stage 1: {len(data['diaries'])} diaries, {len(data['existing_feedbacks'])} existing feedbacks")
416
-
417
- if not data["diaries"]:
418
- log("No session diaries today. Nothing to consolidate.")
419
- else:
420
- # Stage 2: CLI intelligence (graceful fallback: Stage 3 still runs)
421
- success = consolidate_with_cli(data)
422
- if not success:
423
- log("Stage 2 failed (CLI unavailable or error). "
424
- "Skipping intelligent consolidation. Stage 3 (sensory + force) will still run.")
425
- had_errors = True
426
-
427
- # Stage 3: Sensory Register (mechanical, kept from v1)
428
- try:
429
- process_sensory_register()
430
- except Exception as e:
431
- log(f"Sensory register failed: {e}")
432
- had_errors = True
433
-
434
- # Stage 3b: Force analysis (mechanical, kept from v1)
435
- try:
436
- analyze_force_events()
437
- except Exception as e:
438
- log(f"Force analysis failed: {e}")
439
- had_errors = True
440
-
441
- # Register successful run only if no stages failed
442
- if not had_errors:
443
- try:
444
- state_file = NEXO_HOME / "operations" / ".catchup-state.json"
445
- state = json.loads(state_file.read_text()) if state_file.exists() else {}
446
- state["postmortem"] = datetime.now().isoformat()
447
- state_file.write_text(json.dumps(state, indent=2))
448
- except Exception:
449
- pass
450
-
451
- mark_done()
452
- log("=== Consolidation v2 complete ===")
453
-
454
-
455
- if __name__ == "__main__":
456
- main()
@@ -1,120 +0,0 @@
1
- #!/usr/bin/env /Library/Frameworks/Python.framework/Versions/3.12/bin/python3
2
- """
3
- NEXO Pre-Commit Validator — Dynamic version
4
- Queries nexo.db learnings to surface relevant warnings/blockers before commit.
5
- Runs standalone (no MCP dependency, just sqlite3).
6
- """
7
- import os
8
- import sqlite3
9
- import subprocess
10
- import sys
11
- from pathlib import Path
12
-
13
- NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
14
- NEXO_DB = NEXO_HOME / "data" / "nexo.db"
15
-
16
-
17
- def get_staged_files():
18
- """Get list of staged files."""
19
- result = subprocess.run(
20
- ['git', 'diff', '--cached', '--name-only'],
21
- capture_output=True, text=True
22
- )
23
- return [f for f in result.stdout.strip().split('\n') if f]
24
-
25
-
26
- def check_file(filepath, conn):
27
- """Dynamic checks from learnings DB."""
28
- errors, warnings = [], []
29
-
30
- filename = Path(filepath).name
31
- parent_dir = Path(filepath).parent.name
32
-
33
- # 1. Find learnings mentioning this file or directory
34
- file_learnings = conn.execute(
35
- "SELECT id, title, content FROM learnings WHERE INSTR(content, ?) > 0 OR INSTR(content, ?) > 0",
36
- (filename, parent_dir)
37
- ).fetchall()
38
-
39
- # 2. Check repetition count for each matching learning
40
- for row in file_learnings:
41
- lid, title = row[0], row[1]
42
- rep_count = conn.execute(
43
- "SELECT COUNT(*) FROM error_repetitions WHERE original_learning_id = ?",
44
- (lid,)
45
- ).fetchone()[0]
46
-
47
- if rep_count >= 5:
48
- errors.append(f"BLOCKED #{lid} ({rep_count}x repeated): {title[:80]}")
49
- elif rep_count >= 3:
50
- warnings.append(f"WARNING #{lid} ({rep_count}x repeated): {title[:80]}")
51
-
52
- # 3. Universal rules (SIEMPRE/NUNCA/ANTES) matching this file
53
- universal = conn.execute(
54
- "SELECT id, title, content FROM learnings WHERE "
55
- "(content LIKE '%SIEMPRE%' OR content LIKE '%NUNCA%' OR content LIKE '%ANTES%') "
56
- "AND (INSTR(content, ?) > 0 OR INSTR(content, ?) > 0)",
57
- (filename, parent_dir)
58
- ).fetchall()
59
-
60
- for row in universal:
61
- lid, title = row[0], row[1]
62
- # Don't duplicate if already found above
63
- if not any(f"#{lid}" in e for e in errors + warnings):
64
- warnings.append(f"RULE #{lid}: {title[:80]}")
65
-
66
- return errors, warnings
67
-
68
-
69
- def main():
70
- """Run pre-commit validation."""
71
- staged = get_staged_files()
72
- if not staged:
73
- print("No staged files to check")
74
- return 0
75
-
76
- if not NEXO_DB.exists():
77
- print("nexo.db not found — skipping dynamic checks")
78
- return 0
79
-
80
- conn = sqlite3.connect(str(NEXO_DB), timeout=5)
81
-
82
- all_errors = {}
83
- all_warnings = {}
84
-
85
- for filepath in staged:
86
- errors, warnings = check_file(filepath, conn)
87
- if errors:
88
- all_errors[filepath] = errors
89
- if warnings:
90
- all_warnings[filepath] = warnings
91
-
92
- conn.close()
93
-
94
- # Print warnings (non-blocking)
95
- if all_warnings:
96
- print("\nWARNINGS:")
97
- for filepath, warns in all_warnings.items():
98
- print(f"\n {filepath}:")
99
- for w in warns:
100
- print(f" - {w}")
101
-
102
- # Print errors (blocking)
103
- if all_errors:
104
- print("\nBLOCKED — Fix these before committing:\n")
105
- for filepath, errs in all_errors.items():
106
- print(f" {filepath}:")
107
- for e in errs:
108
- print(f" - {e}")
109
- print()
110
- return 1
111
-
112
- if all_warnings:
113
- print("\nPre-commit passed with warnings\n")
114
- else:
115
- print("Pre-commit validation passed")
116
- return 0
117
-
118
-
119
- if __name__ == '__main__':
120
- sys.exit(main())
@@ -1,35 +0,0 @@
1
- #!/bin/bash
2
- # NEXO Prevent Sleep — keeps the machine awake so nocturnal processes run.
3
- #
4
- # macOS: uses native /usr/bin/caffeinate for best-effort background availability
5
- # Linux: uses systemd-inhibit or caffeine if available, otherwise no-op
6
- #
7
- # Run as LaunchAgent (KeepAlive) or systemd service.
8
-
9
- case "$(uname -s)" in
10
- Darwin)
11
- if [[ ! -x /usr/bin/caffeinate ]]; then
12
- echo "[NEXO] /usr/bin/caffeinate not found. macOS power helper unavailable."
13
- exit 1
14
- fi
15
- # Keep the helper alive as long as this service runs. On laptops with the
16
- # lid closed, actual behavior still depends on hardware and OS policy.
17
- exec /usr/bin/caffeinate -d -i -m -s /bin/bash -lc 'while :; do sleep 3600; done'
18
- ;;
19
- Linux)
20
- if command -v systemd-inhibit &>/dev/null; then
21
- exec systemd-inhibit --what=idle:sleep --who=nexo --why="NEXO nocturnal processes" sleep infinity
22
- elif command -v caffeine &>/dev/null; then
23
- exec caffeine
24
- else
25
- echo "[NEXO] No sleep prevention tool found. Install systemd-inhibit or caffeine."
26
- echo "[NEXO] Nocturnal processes may not run if the system sleeps."
27
- # Keep running so launchd/systemd doesn't restart loop
28
- exec sleep infinity
29
- fi
30
- ;;
31
- *)
32
- echo "[NEXO] Unsupported platform: $(uname -s)"
33
- exit 1
34
- ;;
35
- esac