eagle-mem 4.10.13 → 4.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +22 -22
  3. package/architecture.html +26 -14
  4. package/bin/eagle-mem +4 -0
  5. package/db/039_recall_events.sql +27 -0
  6. package/db/040_graph_decision_nodes.sql +21 -0
  7. package/db/041_graph_semantic_edge_types.sql +21 -0
  8. package/db/042_orchestration_auto_events.sql +23 -0
  9. package/db/043_eagle_events.sql +22 -0
  10. package/db/044_summary_capture_source.sql +12 -0
  11. package/docs/agent-compatibility/README.md +38 -0
  12. package/docs/agent-compatibility/claude-code.md +58 -0
  13. package/docs/agent-compatibility/codex.md +57 -0
  14. package/docs/agent-compatibility/opencode.md +72 -0
  15. package/hooks/post-tool-use.sh +8 -0
  16. package/hooks/pre-tool-use.sh +10 -1
  17. package/hooks/session-end.sh +3 -0
  18. package/hooks/session-start.sh +15 -17
  19. package/hooks/stop.sh +34 -5
  20. package/hooks/user-prompt-submit.sh +85 -10
  21. package/integrations/opencode_eagle_mem_plugin.js +387 -0
  22. package/lib/codex-hooks.sh +13 -6
  23. package/lib/common.sh +77 -7
  24. package/lib/db-events.sh +89 -0
  25. package/lib/db-graph.sh +154 -0
  26. package/lib/db-observations.sh +34 -0
  27. package/lib/db-orchestration.sh +149 -0
  28. package/lib/db-summaries.sh +70 -3
  29. package/lib/db.sh +2 -0
  30. package/lib/hooks.sh +41 -7
  31. package/lib/opencode-hooks.sh +105 -0
  32. package/lib/provider.sh +2 -2
  33. package/package.json +5 -2
  34. package/scripts/compaction.sh +109 -9
  35. package/scripts/dashboard.sh +372 -0
  36. package/scripts/doctor.sh +30 -3
  37. package/scripts/enrich-summary.sh +8 -2
  38. package/scripts/health.sh +40 -2
  39. package/scripts/help.sh +10 -2
  40. package/scripts/inspect.sh +285 -0
  41. package/scripts/install.sh +36 -7
  42. package/scripts/memories.sh +13 -0
  43. package/scripts/repair.sh +187 -0
  44. package/scripts/replay.sh +248 -0
  45. package/scripts/search.sh +44 -3
  46. package/scripts/session.sh +155 -18
  47. package/scripts/statusline-em.sh +34 -7
  48. package/scripts/tasks.sh +34 -0
  49. package/scripts/test.sh +13 -0
  50. package/scripts/uninstall.sh +9 -0
  51. package/scripts/update.sh +21 -2
  52. package/tests/fixtures/agent-hooks/claude-statusline.json +32 -0
  53. package/tests/fixtures/agent-hooks/claude-user-prompt-submit.json +9 -0
  54. package/tests/fixtures/agent-hooks/codex-pre-tool-use.json +10 -0
  55. package/tests/fixtures/agent-hooks/codex-user-prompt-submit.json +7 -0
  56. package/tests/fixtures/agent-hooks/opencode-chat-message.json +36 -0
  57. package/tests/fixtures/agent-hooks/opencode-session-compacting.json +9 -0
  58. package/tests/fixtures/agent-hooks/opencode-todo-updated.json +13 -0
  59. package/tests/fixtures/agent-hooks/opencode-tool-execute-after.json +15 -0
  60. package/tests/fixtures/agent-hooks/opencode-tool-execute-before.json +12 -0
  61. package/tests/test_agent_compatibility_docs_gate.sh +123 -0
  62. package/tests/test_auto_orchestration_detection.sh +109 -0
  63. package/tests/test_claude_stop_hook_registration.sh +56 -0
  64. package/tests/test_clean_session_capture.sh +105 -0
  65. package/tests/test_codex_hooks_config.sh +73 -0
  66. package/tests/test_compaction_survival_matrix.sh +237 -0
  67. package/tests/test_dashboard.sh +96 -0
  68. package/tests/test_eagle_events.sh +96 -0
  69. package/tests/test_opencode_hooks_config.sh +56 -0
  70. package/tests/test_opencode_plugin_adapter.sh +202 -0
  71. package/tests/test_recall_observability.sh +144 -0
  72. package/tests/test_repair.sh +63 -0
  73. package/tests/test_rust_migration_plan.sh +75 -0
  74. package/tests/test_trust_surfaces.sh +123 -0
@@ -19,21 +19,30 @@ show_help() {
19
19
  echo -e " eagle-mem session ${CYAN}save <text>${RESET}"
20
20
  echo ""
21
21
  echo -e " ${BOLD}Options for save:${RESET}"
22
- echo -e " ${CYAN}--summary${RESET} <text> Summary to store"
22
+ echo -e " ${CYAN}--completed${RESET} <text> What was accomplished (alias: --summary)"
23
23
  echo -e " ${CYAN}--request${RESET} <text> User request that caused the work"
24
+ echo -e " ${CYAN}--investigated${RESET} <text> What was explored or analyzed"
24
25
  echo -e " ${CYAN}--learned${RESET} <text> Non-obvious discoveries"
25
26
  echo -e " ${CYAN}--decisions${RESET} <text> Decisions and why"
26
27
  echo -e " ${CYAN}--gotchas${RESET} <text> Surprises or pitfalls"
27
28
  echo -e " ${CYAN}--next-steps${RESET} <text> Follow-up work"
28
- echo -e " ${CYAN}--key-files${RESET} <text> Important files"
29
+ echo -e " ${CYAN}--key-files${RESET} <text> Important files (path — role)"
30
+ echo -e " ${CYAN}--files-read${RESET} <list> Comma-separated files read"
31
+ echo -e " ${CYAN}--files-modified${RESET} <list> Comma-separated files modified"
32
+ echo -e " ${CYAN}--affected-features${RESET} <text> Features touched (need re-verify)"
33
+ echo -e " ${CYAN}--verified-features${RESET} <text> Features verified this session"
34
+ echo -e " ${CYAN}--regression-risks${RESET} <text> Known risks introduced"
29
35
  echo -e " ${CYAN}--notes${RESET} <text> Extra notes"
36
+ echo -e " ${CYAN}--session-id${RESET} <id> Live session id (merges into the active row;"
37
+ echo -e " omit for a standalone manual save)"
30
38
  echo -e " ${CYAN}-p, --project${RESET} <name> Project name (default: current git root)"
31
- echo -e " ${CYAN}--agent${RESET} <name> Source agent: codex or claude-code"
39
+ echo -e " ${CYAN}--agent${RESET} <name> Source: claude-code, codex, antigravity, opencode, grok"
32
40
  echo -e " ${CYAN}--cwd${RESET} <path> Working directory for project detection"
33
41
  echo -e " ${CYAN}--json${RESET} Output JSON"
34
42
  echo ""
35
- echo -e " ${DIM}This command is mainly for agent fallbacks. Normal sessions are captured${RESET}"
36
- echo -e " ${DIM}automatically by hooks when Claude Code or Codex stops a turn.${RESET}"
43
+ echo -e " ${DIM}Agents use this to capture a clean, branded session summary without printing${RESET}"
44
+ echo -e " ${DIM}raw blocks. Pass --session-id <id> to merge into the live session row. Stop${RESET}"
45
+ echo -e " ${DIM}hooks still capture automatically as a safety net when no save is made.${RESET}"
37
46
  echo ""
38
47
  }
39
48
 
@@ -49,24 +58,48 @@ json_string() {
49
58
  jq -Rn --arg v "${1:-}" '$v'
50
59
  }
51
60
 
61
+ # Comma-separated list → JSON array (matches hooks/stop.sh storage shape).
62
+ # Safe under set -e: empty input yields [] without a failing pipeline.
63
+ # Slurp the whole value (-s) so embedded newlines split into items rather than
64
+ # emitting one JSON array per line (which would corrupt the stored column).
65
+ csv_to_json_array() {
66
+ local raw="${1:-}"
67
+ [ -z "$raw" ] && { echo '[]'; return 0; }
68
+ printf '%s' "$raw" | jq -Rsc 'split("[,\n]"; "") | map(gsub("^\\s+|\\s+$";"")) | map(select(. != ""))'
69
+ }
70
+
71
+ # Count items in a field separated by ';' or newlines (for the capture banner).
72
+ count_items() {
73
+ local text="${1:-}"
74
+ [ -z "$text" ] && { echo 0; return 0; }
75
+ printf '%s' "$text" | awk 'BEGIN{RS="[;\n]"} {gsub(/^[ \t]+|[ \t]+$/,""); if($0!="") n++} END{print n+0}'
76
+ }
77
+
52
78
  save_session() {
53
79
  local summary=""
54
- local request="Manual session save"
80
+ local request=""
81
+ local investigated=""
55
82
  local learned=""
56
83
  local decisions=""
57
84
  local gotchas=""
58
85
  local next_steps=""
59
86
  local key_files=""
87
+ local files_read_raw=""
88
+ local files_modified_raw=""
89
+ local affected_features=""
90
+ local verified_features=""
91
+ local regression_risks=""
60
92
  local notes=""
61
93
  local project=""
62
94
  local cwd
63
95
  cwd="$(pwd)"
64
96
  local agent=""
97
+ local cli_session_id=""
65
98
  local json_output=false
66
99
 
67
100
  while [ $# -gt 0 ]; do
68
101
  case "$1" in
69
- --summary)
102
+ --summary|--completed)
70
103
  require_value "$1" "${2:-}"
71
104
  summary="$2"
72
105
  shift 2
@@ -76,6 +109,11 @@ save_session() {
76
109
  request="$2"
77
110
  shift 2
78
111
  ;;
112
+ --investigated)
113
+ require_value "$1" "${2:-}"
114
+ investigated="$2"
115
+ shift 2
116
+ ;;
79
117
  --learned)
80
118
  require_value "$1" "${2:-}"
81
119
  learned="$2"
@@ -101,11 +139,41 @@ save_session() {
101
139
  key_files="$2"
102
140
  shift 2
103
141
  ;;
142
+ --files-read)
143
+ require_value "$1" "${2:-}"
144
+ files_read_raw="$2"
145
+ shift 2
146
+ ;;
147
+ --files-modified)
148
+ require_value "$1" "${2:-}"
149
+ files_modified_raw="$2"
150
+ shift 2
151
+ ;;
152
+ --affected-features)
153
+ require_value "$1" "${2:-}"
154
+ affected_features="$2"
155
+ shift 2
156
+ ;;
157
+ --verified-features)
158
+ require_value "$1" "${2:-}"
159
+ verified_features="$2"
160
+ shift 2
161
+ ;;
162
+ --regression-risks)
163
+ require_value "$1" "${2:-}"
164
+ regression_risks="$2"
165
+ shift 2
166
+ ;;
104
167
  --notes)
105
168
  require_value "$1" "${2:-}"
106
169
  notes="$2"
107
170
  shift 2
108
171
  ;;
172
+ --session-id)
173
+ require_value "$1" "${2:-}"
174
+ cli_session_id="$2"
175
+ shift 2
176
+ ;;
109
177
  --project|-p)
110
178
  require_value "$1" "${2:-}"
111
179
  project="$2"
@@ -147,11 +215,25 @@ save_session() {
147
215
  esac
148
216
  done
149
217
 
150
- if [ -z "$summary" ]; then
151
- eagle_err "Nothing to save. Pass --summary <text>."
218
+ if [ -z "$summary" ] && [ -z "$learned" ] && [ -z "$decisions" ] && [ -z "$gotchas" ] \
219
+ && [ -z "$next_steps" ] && [ -z "$key_files" ] && [ -z "$investigated" ] \
220
+ && [ -z "$files_read_raw" ] && [ -z "$files_modified_raw" ] \
221
+ && [ -z "$affected_features" ] && [ -z "$verified_features" ] && [ -z "$regression_risks" ]; then
222
+ eagle_err "Nothing to save. Pass --completed/--summary (or another field)."
152
223
  exit 1
153
224
  fi
154
225
 
226
+ if [ -n "$cli_session_id" ] && ! eagle_validate_session_id "$cli_session_id"; then
227
+ eagle_err "Invalid --session-id (allowed: letters, digits, '_', '-', max 128 chars)."
228
+ exit 1
229
+ fi
230
+
231
+ # For a live session, prefer its recorded project so a save issued from a
232
+ # subdirectory (whose cwd derives a different key) does not trigger an
233
+ # unintended project rekey of the active session.
234
+ if [ -z "$project" ] && [ -n "$cli_session_id" ] && [ -f "${EAGLE_MEM_DB:-}" ]; then
235
+ project=$(eagle_db "SELECT project FROM sessions WHERE id='$(eagle_sql_escape "$cli_session_id")' LIMIT 1;" 2>/dev/null || true)
236
+ fi
155
237
  [ -z "$project" ] && project=$(eagle_project_from_cwd "$cwd")
156
238
  if [ -z "$project" ]; then
157
239
  eagle_err "Could not determine project. Re-run with --project <name>."
@@ -165,44 +247,99 @@ save_session() {
165
247
  codex|openai-codex) agent="codex" ;;
166
248
  claude|claude-code|cloud-code) agent="claude-code" ;;
167
249
  antigravity*|google-antigravity*|google_antigravity*) agent="antigravity" ;;
250
+ opencode) agent="opencode" ;;
251
+ grok|grok-cli) agent="grok" ;;
168
252
  *)
169
- eagle_err "--agent must be codex, claude-code, or antigravity"
253
+ eagle_err "--agent must be codex, claude-code, antigravity, opencode, or grok"
170
254
  exit 1
171
255
  ;;
172
256
  esac
173
257
  fi
174
258
 
259
+ # Default request only for manual (non-session-id) saves; for a live
260
+ # session leave it empty so the Stop hook's real request is preserved.
261
+ if [ -z "$request" ] && [ -z "$cli_session_id" ]; then
262
+ request="Manual session save"
263
+ fi
264
+
265
+ local files_read files_modified
266
+ files_read=$(csv_to_json_array "$files_read_raw")
267
+ files_modified=$(csv_to_json_array "$files_modified_raw")
268
+
175
269
  summary=$(printf '%s' "$summary" | eagle_redact)
176
270
  request=$(printf '%s' "$request" | eagle_redact)
271
+ investigated=$(printf '%s' "$investigated" | eagle_redact)
177
272
  learned=$(printf '%s' "$learned" | eagle_redact)
178
273
  decisions=$(printf '%s' "$decisions" | eagle_redact)
179
274
  gotchas=$(printf '%s' "$gotchas" | eagle_redact)
180
275
  next_steps=$(printf '%s' "$next_steps" | eagle_redact)
181
276
  key_files=$(printf '%s' "$key_files" | eagle_redact)
182
277
  notes=$(printf '%s' "$notes" | eagle_redact)
278
+ affected_features=$(printf '%s' "$affected_features" | eagle_redact)
279
+ verified_features=$(printf '%s' "$verified_features" | eagle_redact)
280
+ regression_risks=$(printf '%s' "$regression_risks" | eagle_redact)
281
+
282
+ # Fold feature-tracking fields into notes (same shape as hooks/stop.sh)
283
+ local regression_notes=""
284
+ [ -n "$affected_features" ] && regression_notes+="affected_features: $affected_features"
285
+ if [ -n "$verified_features" ]; then
286
+ [ -n "$regression_notes" ] && regression_notes+="; "
287
+ regression_notes+="verified_features: $verified_features"
288
+ fi
289
+ if [ -n "$regression_risks" ]; then
290
+ [ -n "$regression_notes" ] && regression_notes+="; "
291
+ regression_notes+="regression_risks: $regression_risks"
292
+ fi
293
+ if [ -n "$regression_notes" ]; then
294
+ if [ -n "$notes" ]; then
295
+ notes="${notes}; ${regression_notes}"
296
+ else
297
+ notes="$regression_notes"
298
+ fi
299
+ fi
183
300
 
184
301
  eagle_ensure_db
185
302
 
186
- local stamp session_id
187
- stamp=$(date -u +%Y%m%dT%H%M%SZ)
188
- session_id="manual-${stamp}-$$-${RANDOM:-0}"
303
+ local session_id end_after_save=1
304
+ if [ -n "$cli_session_id" ]; then
305
+ # Live session: ensure the row exists and stays active. Empty source/cwd
306
+ # preserve whatever SessionStart already recorded; do NOT end the session.
307
+ session_id="$cli_session_id"
308
+ end_after_save=0
309
+ eagle_upsert_session "$session_id" "$project" "" "" "" "$agent"
310
+ else
311
+ local stamp
312
+ stamp=$(date -u +%Y%m%dT%H%M%SZ)
313
+ session_id="manual-${stamp}-$$-${RANDOM:-0}"
314
+ eagle_upsert_session "$session_id" "$project" "$cwd" "" "manual" "$agent"
315
+ fi
316
+
317
+ # capture_source=agent: agent-authored capture is authoritative and must not
318
+ # be clobbered by later Stop-hook heuristics or background enrichment.
319
+ eagle_insert_summary "$session_id" "$project" "$request" "$investigated" "$learned" "$summary" "$next_steps" "$files_read" "$files_modified" "$notes" "$decisions" "$gotchas" "$key_files" "$agent" "agent"
320
+
321
+ [ "$end_after_save" = "1" ] && eagle_end_session "$session_id"
189
322
 
190
- eagle_upsert_session "$session_id" "$project" "$cwd" "" "manual" "$agent"
191
- eagle_insert_summary "$session_id" "$project" "$request" "" "$learned" "$summary" "$next_steps" "[]" "[]" "$notes" "$decisions" "$gotchas" "$key_files" "$agent"
192
- eagle_end_session "$session_id"
323
+ local n_dec n_got dec_word got_word
324
+ n_dec=$(count_items "$decisions")
325
+ n_got=$(count_items "$gotchas")
326
+ [ "$n_dec" = "1" ] && dec_word="decision" || dec_word="decisions"
327
+ [ "$n_got" = "1" ] && got_word="gotcha" || got_word="gotchas"
193
328
 
194
329
  if [ "$json_output" = true ]; then
195
330
  printf '{'
196
331
  printf '"session_id":%s,' "$(json_string "$session_id")"
197
332
  printf '"project":%s,' "$(json_string "$project")"
198
333
  printf '"agent":%s,' "$(json_string "$agent")"
334
+ printf '"decisions":%s,' "$n_dec"
335
+ printf '"gotchas":%s,' "$n_got"
199
336
  printf '"summary":%s' "$(json_string "$summary")"
200
337
  printf '}\n'
201
338
  else
202
- eagle_ok "Session summary saved"
339
+ printf ' %bEagle Mem%b | Session captured — %s %s, %s %s\n' \
340
+ "$CYAN" "$RESET" "$n_dec" "$dec_word" "$n_got" "$got_word"
203
341
  eagle_kv "Project:" "$project"
204
342
  eagle_kv "Source:" "$(eagle_agent_label "$agent")"
205
- eagle_kv "Session:" "$session_id"
206
343
  fi
207
344
  }
208
345
 
@@ -41,10 +41,11 @@ eagle_mem_statusline_stats() {
41
41
  local session_id="${2:-}"
42
42
  local statusline_input="${3:-}"
43
43
  local current_dir="${4:-}"
44
- local em_db="$HOME/.eagle-mem/memory.db"
44
+ local em_db="${EAGLE_MEM_DIR:-$HOME/.eagle-mem}/memory.db"
45
45
  [ -f "$em_db" ] || return
46
46
 
47
47
  _eagle_statusline_load_common
48
+ em_db="$EAGLE_MEM_DB"
48
49
 
49
50
  local sqlite_bin
50
51
  sqlite_bin=$(eagle_sqlite_path)
@@ -58,7 +59,7 @@ eagle_mem_statusline_stats() {
58
59
  [ -z "$project_dir" ] && project_dir="$(pwd)"
59
60
  [ -z "$current_dir" ] && current_dir="$project_dir"
60
61
 
61
- local project_key project_scope project_condition stats sessions memories last_raw turns version latest
62
+ local project_key project_scope project_condition stats stats_rc sessions memories last_raw turns version latest db_status db_detail
62
63
  project_key=$(eagle_project_from_statusline_input "$statusline_input" "$project_dir" "$current_dir" "$session_id")
63
64
  [ -n "$project_key" ] || return
64
65
  project_scope=$(eagle_recall_project_scope_from_cwd "${current_dir:-$project_dir}" "$project_key")
@@ -70,6 +71,20 @@ eagle_mem_statusline_stats() {
70
71
  COALESCE(MAX(COALESCE(last_activity_at, started_at)), 'never')
71
72
  FROM sessions
72
73
  WHERE $project_condition;" 2>/dev/null)
74
+ stats_rc=$?
75
+ db_status="ok"
76
+ db_detail="ok"
77
+ if [ "$stats_rc" -ne 0 ] || [ -z "$stats" ]; then
78
+ local integrity_check
79
+ integrity_check=$(eagle_db_integrity_status "$em_db" 2>/dev/null || true)
80
+ db_status="${integrity_check%%|*}"
81
+ db_detail="${integrity_check#*|}"
82
+ [ -n "$db_status" ] || db_status="error"
83
+ [ -n "$db_detail" ] || db_detail="database query failed"
84
+ if [ "$db_status" != "ok" ]; then
85
+ stats="__DB_ERROR__|0|db_error"
86
+ fi
87
+ fi
73
88
  IFS='|' read -r sessions memories last_raw <<< "${stats:-0|0|never}"
74
89
  sessions=${sessions:-0}
75
90
  memories=${memories:-0}
@@ -92,15 +107,20 @@ eagle_mem_statusline_stats() {
92
107
  [ -z "$version" ] && version="?"
93
108
  [ -z "$latest" ] && latest="$version"
94
109
 
95
- printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\n' "$project_key" "$version" "$latest" "$sessions" "$memories" "$turns" "$last_raw"
110
+ printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' "$project_key" "$version" "$latest" "$sessions" "$memories" "$turns" "$last_raw" "$db_status"
96
111
  }
97
112
 
98
113
  eagle_mem_statusline() {
99
- local stats project_key version latest sessions memories turns last_raw
114
+ local stats project_key version latest sessions memories turns last_raw db_status
100
115
  stats=$(eagle_mem_statusline_stats "${1:-}" "${2:-}" "${3:-}" "${4:-}") || return
101
- IFS=$'\t' read -r project_key version latest sessions memories turns last_raw <<< "$stats"
116
+ IFS=$'\t' read -r project_key version latest sessions memories turns last_raw db_status <<< "$stats"
102
117
 
103
118
  local R='\033[0m' CYAN='\033[96m' WHT='\033[97m' DIM='\033[2m'
119
+ if [ "${db_status:-ok}" != "ok" ]; then
120
+ printf "%bEagle%b %bv%s%b | %bDB error%b | turn %b%s%b" \
121
+ "$CYAN" "$R" "$WHT" "$version" "$DIM" "$WHT" "$DIM" "$WHT" "${turns:-0}" "$R"
122
+ return
123
+ fi
104
124
  printf "%bEagle%b %bv%s%b | %b%s%b sessions | %b%s%b memories | turn %b%s%b" \
105
125
  "$CYAN" "$R" \
106
126
  "$WHT" "$version" "$DIM" \
@@ -110,9 +130,9 @@ eagle_mem_statusline() {
110
130
  }
111
131
 
112
132
  eagle_mem_statusline_hud() {
113
- local stats project_key version latest sessions memories turns last_raw last_label
133
+ local stats project_key version latest sessions memories turns last_raw last_label db_status
114
134
  stats=$(eagle_mem_statusline_stats "${1:-}" "${2:-}" "${3:-}" "${4:-}") || return
115
- IFS=$'\t' read -r project_key version latest sessions memories turns last_raw <<< "$stats"
135
+ IFS=$'\t' read -r project_key version latest sessions memories turns last_raw db_status <<< "$stats"
116
136
  last_label=$(_eagle_statusline_relative_time "$last_raw")
117
137
 
118
138
  local R='\033[0m' CYAN='\033[96m' WHT='\033[97m' DIM='\033[2m'
@@ -136,6 +156,13 @@ eagle_mem_statusline_hud() {
136
156
  em_ver="${em_ver} ${GRN}✓${R}"
137
157
  fi
138
158
 
159
+ if [ "${db_status:-ok}" != "ok" ]; then
160
+ printf "%bEagle Mem%b %b %b│%b %bDatabase:%b %bERROR%b %b│%b %bTurns:%b %b%s/30%b %b(%s)%b" \
161
+ "$CYAN" "$R" "$em_ver" "$DIM" "$R" "$DIM" "$R" "$RED" "$R" "$DIM" "$R" \
162
+ "$DIM" "$R" "$turn_color" "$turns" "$R" "$turn_color" "$pressure_label" "$R"
163
+ return
164
+ fi
165
+
139
166
  printf "%bEagle Mem%b %b %b│%b %bSessions:%b %b%s%b %b│%b %bMemories:%b %b%s%b %b│%b %bTurns:%b %b%s/30%b %b(%s)%b %b│%b %bUpdated:%b %b%s%b" \
140
167
  "$CYAN" "$R" "$em_ver" \
141
168
  "$DIM" "$R" "$DIM" "$R" "$WHT" "${sessions:-0}" "$R" "$DIM" "$R" \
package/scripts/tasks.sh CHANGED
@@ -71,6 +71,40 @@ project_sql=$(eagle_sql_escape "$project")
71
71
  [ -z "$agent" ] && agent=$(eagle_agent_source)
72
72
  agent_sql=$(eagle_sql_escape "$agent")
73
73
 
74
+ tasks_fail() {
75
+ local error_code="$1"
76
+ local message="$2"
77
+ local db_status="${3:-unknown}"
78
+ local db_detail="${4:-}"
79
+
80
+ if [ "$json_output" = true ]; then
81
+ jq -nc \
82
+ --arg status "error" \
83
+ --arg command "tasks" \
84
+ --arg action "$action" \
85
+ --arg error "$error_code" \
86
+ --arg message "$message" \
87
+ --arg project "$project" \
88
+ --arg db_status "$db_status" \
89
+ --arg db_detail "$db_detail" \
90
+ '{status:$status, command:$command, action:$action, error:$error, message:$message,
91
+ project:$project, database:{integrity:{status:$db_status, detail:$db_detail}}}'
92
+ else
93
+ eagle_err "$message"
94
+ [ -n "$db_detail" ] && eagle_dim " $db_detail"
95
+ fi
96
+ exit 1
97
+ }
98
+
99
+ db_integrity_check=$(eagle_db_integrity_status "$EAGLE_MEM_DB" 2>/dev/null || true)
100
+ db_integrity_status="${db_integrity_check%%|*}"
101
+ db_integrity_detail="${db_integrity_check#*|}"
102
+ [ -n "$db_integrity_status" ] || db_integrity_status="unknown"
103
+ [ -n "$db_integrity_detail" ] || db_integrity_detail="not checked"
104
+ if [ "$db_integrity_status" != "ok" ]; then
105
+ tasks_fail "database_integrity" "Database integrity check failed; durable tasks are unavailable." "$db_integrity_status" "$db_integrity_detail"
106
+ fi
107
+
74
108
  # ─── List tasks ───────────────────────────────────────────
75
109
 
76
110
  tasks_list() {
package/scripts/test.sh CHANGED
@@ -55,6 +55,19 @@ run_check "Graph Memory Rebuild (isolated regression suite)" "bash \"$SCRIPTS_DI
55
55
  run_check "Dream Cycle Memory Graph Wiring (isolated regression suite)" "bash \"$SCRIPTS_DIR/../tests/test_curate_graph_memories.sh\""
56
56
  run_check "Reliability Guards (provider fallback, logs, autoscan, read scoring)" "bash \"$SCRIPTS_DIR/../tests/test_reliability_guards.sh\""
57
57
  run_check "Feature Verification Gate (monorepo path collisions)" "bash \"$SCRIPTS_DIR/../tests/test_feature_verification_gate.sh\""
58
+ run_check "Compaction Survival Matrix (summary, memory, task, feature, recall, graph)" "bash \"$SCRIPTS_DIR/../tests/test_compaction_survival_matrix.sh\""
59
+ run_check "Auto Orchestration Detection (broad prompt creates lanes/tasks)" "bash \"$SCRIPTS_DIR/../tests/test_auto_orchestration_detection.sh\""
60
+ run_check "Agent Compatibility Docs Gate (official-doc verified hook contracts)" "bash \"$SCRIPTS_DIR/../tests/test_agent_compatibility_docs_gate.sh\""
61
+ run_check "Claude Stop Hook Registration (bash wrapper)" "bash \"$SCRIPTS_DIR/../tests/test_claude_stop_hook_registration.sh\""
62
+ run_check "Codex Hooks Config (canonical features.hooks flag)" "bash \"$SCRIPTS_DIR/../tests/test_codex_hooks_config.sh\""
63
+ run_check "OpenCode Hooks Config (global plugin and skill registration)" "bash \"$SCRIPTS_DIR/../tests/test_opencode_hooks_config.sh\""
64
+ run_check "OpenCode Plugin Adapter (hook normalization)" "bash \"$SCRIPTS_DIR/../tests/test_opencode_plugin_adapter.sh\""
65
+ run_check "Rust Migration Plan (compatibility-first contract)" "bash \"$SCRIPTS_DIR/../tests/test_rust_migration_plan.sh\""
66
+ run_check "Database Repair (safe preview and recovery failure path)" "bash \"$SCRIPTS_DIR/../tests/test_repair.sh\""
67
+ run_check "Trust Surfaces (DB integrity, JSON errors, statusline)" "bash \"$SCRIPTS_DIR/../tests/test_trust_surfaces.sh\""
68
+ run_check "Recall Observability (UserPromptSubmit recall event)" "bash \"$SCRIPTS_DIR/../tests/test_recall_observability.sh\""
69
+ run_check "Eagle Event Log (hook/action observability)" "bash \"$SCRIPTS_DIR/../tests/test_eagle_events.sh\""
70
+ run_check "Dashboard Surface (local HTML memory view)" "bash \"$SCRIPTS_DIR/../tests/test_dashboard.sh\""
58
71
 
59
72
  echo ""
60
73
  if [ "$errors" -eq 0 ]; then
@@ -11,6 +11,7 @@ LIB_DIR="$SCRIPTS_DIR/../lib"
11
11
  . "$SCRIPTS_DIR/style.sh"
12
12
  . "$LIB_DIR/common.sh"
13
13
  . "$LIB_DIR/codex-hooks.sh"
14
+ . "$LIB_DIR/opencode-hooks.sh"
14
15
 
15
16
  SETTINGS="$EAGLE_SETTINGS"
16
17
  dry_run=false
@@ -66,6 +67,12 @@ else
66
67
  eagle_warn "Could not patch Codex hooks.json (jq not found or file missing)"
67
68
  fi
68
69
 
70
+ if eagle_remove_opencode_plugin; then
71
+ eagle_ok "OpenCode plugin removed"
72
+ else
73
+ eagle_info "OpenCode plugin not present or not Eagle Mem-owned"
74
+ fi
75
+
69
76
  # ─── Remove instruction blocks and statusline integration ───
70
77
 
71
78
  claude_md="$HOME/.claude/CLAUDE.md"
@@ -132,6 +139,8 @@ if [ -d "$EAGLE_GROK_SKILLS_DIR" ]; then
132
139
  done
133
140
  fi
134
141
 
142
+ eagle_remove_opencode_skills >/dev/null 2>&1 || true
143
+
135
144
  # ─── Optionally wipe data ─────────────────────────────────
136
145
 
137
146
  if [ -d "$EAGLE_MEM_DIR" ]; then
package/scripts/update.sh CHANGED
@@ -16,11 +16,13 @@ LIB_DIR="$SCRIPTS_DIR/../lib"
16
16
  . "$LIB_DIR/updater.sh"
17
17
  . "$LIB_DIR/hooks.sh"
18
18
  . "$LIB_DIR/codex-hooks.sh"
19
+ . "$LIB_DIR/opencode-hooks.sh"
19
20
 
20
21
  SETTINGS="$EAGLE_SETTINGS"
21
22
  claude_found=false
22
23
  codex_found=false
23
24
  grok_found=false
25
+ opencode_found=false
24
26
 
25
27
  eagle_header "Update"
26
28
 
@@ -43,8 +45,11 @@ fi
43
45
  if [ -d "$EAGLE_GROK_DIR" ]; then
44
46
  grok_found=true
45
47
  fi
48
+ if eagle_opencode_detected; then
49
+ opencode_found=true
50
+ fi
46
51
 
47
- eagle_runtime_change_plan "update" "$PACKAGE_DIR" "$claude_found" "$codex_found"
52
+ eagle_runtime_change_plan "update" "$PACKAGE_DIR" "$claude_found" "$codex_found" "$opencode_found"
48
53
 
49
54
  # ─── Update files ──────────────────────────────────────────
50
55
 
@@ -82,11 +87,12 @@ fi
82
87
 
83
88
  if [ "$claude_found" = true ] && [ -f "$SETTINGS" ] && command -v jq &>/dev/null; then
84
89
  # Clean old registrations before re-registering (handles matcher changes across versions)
90
+ eagle_clean_hook_entries "$SETTINGS" "Stop" "$EAGLE_MEM_DIR/hooks/stop.sh"
85
91
  eagle_clean_hook_entries "$SETTINGS" "PostToolUse" "$EAGLE_MEM_DIR/hooks/post-tool-use.sh"
86
92
  eagle_clean_hook_entries "$SETTINGS" "PreToolUse" "$EAGLE_MEM_DIR/hooks/pre-tool-use.sh"
87
93
 
88
94
  eagle_patch_hook "$SETTINGS" "SessionStart" "" "$EAGLE_MEM_DIR/hooks/session-start.sh"
89
- eagle_patch_hook "$SETTINGS" "Stop" "" "$EAGLE_MEM_DIR/hooks/stop.sh"
95
+ eagle_patch_hook "$SETTINGS" "Stop" "" "bash \"$EAGLE_MEM_DIR/hooks/stop.sh\""
90
96
  eagle_patch_hook "$SETTINGS" "PostToolUse" "Read|Write|Edit|Bash|TaskUpdate" "$EAGLE_MEM_DIR/hooks/post-tool-use.sh"
91
97
  eagle_patch_hook "$SETTINGS" "TaskCreated" "" "$EAGLE_MEM_DIR/hooks/post-tool-use.sh"
92
98
  eagle_patch_hook "$SETTINGS" "TaskCompleted" "" "$EAGLE_MEM_DIR/hooks/post-tool-use.sh"
@@ -94,6 +100,9 @@ if [ "$claude_found" = true ] && [ -f "$SETTINGS" ] && command -v jq &>/dev/null
94
100
  eagle_patch_hook "$SETTINGS" "UserPromptSubmit" "" "$EAGLE_MEM_DIR/hooks/user-prompt-submit.sh"
95
101
  eagle_patch_hook "$SETTINGS" "PreToolUse" "Bash|Read|Edit|Write" "$EAGLE_MEM_DIR/hooks/pre-tool-use.sh"
96
102
 
103
+ # Allow agent-issued session capture to run without a permission prompt
104
+ eagle_patch_permission_allow "$SETTINGS" "Bash(eagle-mem session save:*)"
105
+
97
106
  eagle_ok "Hooks registered"
98
107
  fi
99
108
 
@@ -104,6 +113,12 @@ elif [ "$codex_found" = false ]; then
104
113
  eagle_info "Codex hooks skipped ${DIM}(Codex not detected)${RESET}"
105
114
  fi
106
115
 
116
+ if [ "$opencode_found" = true ]; then
117
+ eagle_install_opencode_plugin "$PACKAGE_DIR" "0"
118
+ else
119
+ eagle_info "OpenCode plugin skipped ${DIM}(OpenCode not detected)${RESET}"
120
+ fi
121
+
107
122
  # ─── Update skill symlinks ────────────────────────────────
108
123
 
109
124
  if [ "$claude_found" = true ] && [ -d "$PACKAGE_DIR/skills" ]; then
@@ -164,6 +179,10 @@ if [ "$grok_found" = true ] && [ -d "$PACKAGE_DIR/skills" ]; then
164
179
  eagle_ok "Grok skills updated"
165
180
  fi
166
181
 
182
+ if [ "$opencode_found" = true ] && [ -d "$PACKAGE_DIR/skills" ]; then
183
+ eagle_install_opencode_skills "$PACKAGE_DIR" "0"
184
+ fi
185
+
167
186
  # ─── Refresh generated Claude statusline wrapper ───────────
168
187
 
169
188
  statusline_wrapper="$EAGLE_MEM_DIR/scripts/statusline-wrapper.sh"
@@ -0,0 +1,32 @@
1
+ {
2
+ "hook_event_name": "Status",
3
+ "session_id": "claude-status-fixture",
4
+ "transcript_path": "/tmp/eagle-mem/claude-status-fixture.jsonl",
5
+ "cwd": "/tmp/eagle-mem/project",
6
+ "model": {
7
+ "id": "claude-opus-4-1",
8
+ "display_name": "Opus"
9
+ },
10
+ "workspace": {
11
+ "current_dir": "/tmp/eagle-mem/project",
12
+ "project_dir": "/tmp/eagle-mem/project"
13
+ },
14
+ "version": "2.1.145",
15
+ "output_style": {
16
+ "name": "default"
17
+ },
18
+ "cost": {
19
+ "total_cost_usd": 0.01234,
20
+ "total_duration_ms": 45000,
21
+ "total_api_duration_ms": 2300,
22
+ "total_lines_added": 12,
23
+ "total_lines_removed": 2
24
+ },
25
+ "thinking": {
26
+ "enabled": true
27
+ },
28
+ "effort": {
29
+ "level": "xhigh"
30
+ }
31
+ }
32
+
@@ -0,0 +1,9 @@
1
+ {
2
+ "session_id": "claude-session-fixture",
3
+ "transcript_path": "/tmp/eagle-mem/claude-session-fixture.jsonl",
4
+ "cwd": "/tmp/eagle-mem/project",
5
+ "permission_mode": "default",
6
+ "hook_event_name": "UserPromptSubmit",
7
+ "prompt": "Review the OAuth memory before editing auth files"
8
+ }
9
+
@@ -0,0 +1,10 @@
1
+ {
2
+ "session_id": "codex-session-fixture",
3
+ "cwd": "/tmp/eagle-mem/project",
4
+ "hook_event_name": "PreToolUse",
5
+ "tool_name": "Bash",
6
+ "tool_input": {
7
+ "command": "git status --short"
8
+ }
9
+ }
10
+
@@ -0,0 +1,7 @@
1
+ {
2
+ "session_id": "codex-session-fixture",
3
+ "cwd": "/tmp/eagle-mem/project",
4
+ "hook_event_name": "UserPromptSubmit",
5
+ "prompt": "Review the OAuth memory before editing auth files"
6
+ }
7
+
@@ -0,0 +1,36 @@
1
+ {
2
+ "sessionID": "opencode-session-fixture",
3
+ "input": {
4
+ "sessionID": "opencode-session-fixture",
5
+ "agent": "build",
6
+ "model": {
7
+ "providerID": "openai",
8
+ "modelID": "gpt-5"
9
+ },
10
+ "messageID": "opencode-user-message-fixture"
11
+ },
12
+ "output": {
13
+ "message": {
14
+ "id": "opencode-user-message-fixture",
15
+ "sessionID": "opencode-session-fixture",
16
+ "role": "user",
17
+ "time": {
18
+ "created": 1780372800000
19
+ },
20
+ "agent": "build",
21
+ "model": {
22
+ "providerID": "openai",
23
+ "modelID": "gpt-5"
24
+ }
25
+ },
26
+ "parts": [
27
+ {
28
+ "id": "opencode-text-part-fixture",
29
+ "sessionID": "opencode-session-fixture",
30
+ "messageID": "opencode-user-message-fixture",
31
+ "type": "text",
32
+ "text": "Review the OAuth memory before editing auth files"
33
+ }
34
+ ]
35
+ }
36
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "input": {
3
+ "sessionID": "opencode-session-fixture"
4
+ },
5
+ "output": {
6
+ "context": [],
7
+ "prompt": null
8
+ }
9
+ }