eagle-mem 4.10.13 → 4.11.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 (69) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +20 -20
  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/docs/agent-compatibility/README.md +38 -0
  11. package/docs/agent-compatibility/claude-code.md +50 -0
  12. package/docs/agent-compatibility/codex.md +51 -0
  13. package/docs/agent-compatibility/opencode.md +71 -0
  14. package/hooks/post-tool-use.sh +8 -0
  15. package/hooks/pre-tool-use.sh +10 -1
  16. package/hooks/session-end.sh +3 -0
  17. package/hooks/session-start.sh +7 -0
  18. package/hooks/stop.sh +10 -1
  19. package/hooks/user-prompt-submit.sh +79 -6
  20. package/integrations/opencode_eagle_mem_plugin.js +387 -0
  21. package/lib/codex-hooks.sh +13 -6
  22. package/lib/common.sh +63 -0
  23. package/lib/db-events.sh +89 -0
  24. package/lib/db-graph.sh +154 -0
  25. package/lib/db-observations.sh +34 -0
  26. package/lib/db-orchestration.sh +149 -0
  27. package/lib/db.sh +2 -0
  28. package/lib/hooks.sh +12 -7
  29. package/lib/opencode-hooks.sh +105 -0
  30. package/lib/provider.sh +2 -2
  31. package/package.json +5 -2
  32. package/scripts/compaction.sh +108 -8
  33. package/scripts/dashboard.sh +372 -0
  34. package/scripts/doctor.sh +30 -3
  35. package/scripts/health.sh +40 -2
  36. package/scripts/help.sh +10 -2
  37. package/scripts/inspect.sh +285 -0
  38. package/scripts/install.sh +31 -7
  39. package/scripts/memories.sh +13 -0
  40. package/scripts/repair.sh +187 -0
  41. package/scripts/replay.sh +248 -0
  42. package/scripts/search.sh +44 -3
  43. package/scripts/statusline-em.sh +34 -7
  44. package/scripts/tasks.sh +34 -0
  45. package/scripts/test.sh +13 -0
  46. package/scripts/uninstall.sh +9 -0
  47. package/scripts/update.sh +18 -2
  48. package/tests/fixtures/agent-hooks/claude-statusline.json +32 -0
  49. package/tests/fixtures/agent-hooks/claude-user-prompt-submit.json +9 -0
  50. package/tests/fixtures/agent-hooks/codex-pre-tool-use.json +10 -0
  51. package/tests/fixtures/agent-hooks/codex-user-prompt-submit.json +7 -0
  52. package/tests/fixtures/agent-hooks/opencode-chat-message.json +36 -0
  53. package/tests/fixtures/agent-hooks/opencode-session-compacting.json +9 -0
  54. package/tests/fixtures/agent-hooks/opencode-todo-updated.json +13 -0
  55. package/tests/fixtures/agent-hooks/opencode-tool-execute-after.json +15 -0
  56. package/tests/fixtures/agent-hooks/opencode-tool-execute-before.json +12 -0
  57. package/tests/test_agent_compatibility_docs_gate.sh +123 -0
  58. package/tests/test_auto_orchestration_detection.sh +109 -0
  59. package/tests/test_claude_stop_hook_registration.sh +56 -0
  60. package/tests/test_codex_hooks_config.sh +73 -0
  61. package/tests/test_compaction_survival_matrix.sh +237 -0
  62. package/tests/test_dashboard.sh +96 -0
  63. package/tests/test_eagle_events.sh +96 -0
  64. package/tests/test_opencode_hooks_config.sh +56 -0
  65. package/tests/test_opencode_plugin_adapter.sh +202 -0
  66. package/tests/test_recall_observability.sh +144 -0
  67. package/tests/test_repair.sh +63 -0
  68. package/tests/test_rust_migration_plan.sh +75 -0
  69. package/tests/test_trust_surfaces.sh +123 -0
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/env bash
2
+ # ═══════════════════════════════════════════════════════════
3
+ # Eagle Mem — Database Repair
4
+ # Safe recovery workflow for malformed SQLite memory databases.
5
+ # ═══════════════════════════════════════════════════════════
6
+ set -euo pipefail
7
+
8
+ SCRIPTS_DIR="$(cd "$(dirname "$0")" && pwd)"
9
+ LIB_DIR="$SCRIPTS_DIR/../lib"
10
+ PACKAGE_DIR="$(cd "$SCRIPTS_DIR/.." && pwd)"
11
+
12
+ . "$SCRIPTS_DIR/style.sh"
13
+ . "$LIB_DIR/common.sh"
14
+
15
+ json_output=false
16
+ dry_run=true
17
+ assume_yes=false
18
+ backup_dir=""
19
+
20
+ show_help() {
21
+ echo -e " ${BOLD}eagle-mem repair${RESET} — Diagnose and recover a malformed memory database"
22
+ echo ""
23
+ echo -e " ${BOLD}Usage:${RESET}"
24
+ echo -e " eagle-mem repair ${DIM}# preview repair plan${RESET}"
25
+ echo -e " eagle-mem repair ${CYAN}--dry-run${RESET} ${DIM}# preview repair plan${RESET}"
26
+ echo -e " eagle-mem repair ${CYAN}--yes${RESET} ${DIM}# backup, recover, verify, replace${RESET}"
27
+ echo ""
28
+ echo -e " ${BOLD}Options:${RESET}"
29
+ echo -e " ${CYAN}--backup-dir${RESET} <path> Backup directory (default: ~/.eagle-mem/backups)"
30
+ echo -e " ${CYAN}-j, --json${RESET} Output structured JSON"
31
+ echo ""
32
+ exit 0
33
+ }
34
+
35
+ while [ $# -gt 0 ]; do
36
+ case "$1" in
37
+ --dry-run) dry_run=true; shift ;;
38
+ --yes|-y) assume_yes=true; dry_run=false; shift ;;
39
+ --backup-dir) backup_dir="$2"; shift 2 ;;
40
+ --json|-j) json_output=true; shift ;;
41
+ --help|-h) show_help ;;
42
+ *)
43
+ eagle_err "Unknown option: $1"
44
+ exit 1
45
+ ;;
46
+ esac
47
+ done
48
+
49
+ [ -n "$backup_dir" ] || backup_dir="$EAGLE_MEM_DIR/backups"
50
+
51
+ repair_json() {
52
+ local status="$1" message="$2" db_status="$3" db_detail="$4" backup_path="${5:-}" recovered_path="${6:-}"
53
+ jq -nc \
54
+ --arg status "$status" \
55
+ --arg command "repair" \
56
+ --arg message "$message" \
57
+ --arg db "$EAGLE_MEM_DB" \
58
+ --arg backup_dir "$backup_dir" \
59
+ --arg backup_path "$backup_path" \
60
+ --arg recovered_path "$recovered_path" \
61
+ --arg db_status "$db_status" \
62
+ --arg db_detail "$db_detail" \
63
+ '{status:$status, command:$command, message:$message, db:$db,
64
+ backup_dir:$backup_dir, backup_path:$backup_path, recovered_path:$recovered_path,
65
+ database:{integrity:{status:$db_status, detail:$db_detail}}}'
66
+ }
67
+
68
+ repair_emit() {
69
+ local status="$1" message="$2" db_status="$3" db_detail="$4" backup_path="${5:-}" recovered_path="${6:-}"
70
+ if [ "$json_output" = true ]; then
71
+ repair_json "$status" "$message" "$db_status" "$db_detail" "$backup_path" "$recovered_path"
72
+ else
73
+ case "$status" in
74
+ ok) eagle_ok "$message" ;;
75
+ repaired) eagle_ok "$message" ;;
76
+ needs_repair) eagle_warn "$message" ;;
77
+ *) eagle_fail "$message" ;;
78
+ esac
79
+ eagle_kv "Database:" "$EAGLE_MEM_DB"
80
+ eagle_kv "Integrity:" "$db_status"
81
+ [ -n "$db_detail" ] && eagle_dim " $db_detail"
82
+ [ -n "$backup_path" ] && eagle_kv "Backup:" "$backup_path"
83
+ [ -n "$recovered_path" ] && eagle_kv "Recovered:" "$recovered_path"
84
+ fi
85
+ }
86
+
87
+ sqlite_bin=$(eagle_sqlite_path)
88
+ if [ -z "$sqlite_bin" ]; then
89
+ repair_emit "error" "SQLite with FTS5 is unavailable." "unavailable" "sqlite3 with FTS5 not found"
90
+ exit 1
91
+ fi
92
+
93
+ if [ ! -f "$EAGLE_MEM_DB" ]; then
94
+ repair_emit "error" "Memory database file is missing." "missing" "database file not found"
95
+ exit 1
96
+ fi
97
+
98
+ integrity_check=$(eagle_db_integrity_status "$EAGLE_MEM_DB" 2>/dev/null || true)
99
+ integrity_status="${integrity_check%%|*}"
100
+ integrity_detail="${integrity_check#*|}"
101
+ [ -n "$integrity_status" ] || integrity_status="unknown"
102
+ [ -n "$integrity_detail" ] || integrity_detail="not checked"
103
+
104
+ if [ "$integrity_status" = "ok" ]; then
105
+ repair_emit "ok" "Database integrity is already ok; no repair needed." "$integrity_status" "$integrity_detail"
106
+ exit 0
107
+ fi
108
+
109
+ if [ "$dry_run" = true ] || [ "$assume_yes" != true ]; then
110
+ repair_emit "needs_repair" "Database needs repair. Re-run with --yes to backup, recover, verify, and replace." "$integrity_status" "$integrity_detail"
111
+ exit 0
112
+ fi
113
+
114
+ timestamp=$(date -u +%Y%m%dT%H%M%SZ)
115
+ mkdir -p "$backup_dir"
116
+ backup_path="$backup_dir/memory-$timestamp.db"
117
+ recover_dir=$(mktemp -d "$EAGLE_MEM_DIR/.repair.XXXXXX")
118
+ recover_sql="$recover_dir/recover.sql"
119
+ recovered_path="$recover_dir/memory.db"
120
+ cleanup_repair() {
121
+ rm -rf "$recover_dir" 2>/dev/null || true
122
+ }
123
+ trap cleanup_repair EXIT
124
+
125
+ cp "$EAGLE_MEM_DB" "$backup_path"
126
+ chmod 600 "$backup_path" 2>/dev/null || true
127
+ for suffix in -wal -shm; do
128
+ if [ -f "$EAGLE_MEM_DB$suffix" ]; then
129
+ cp "$EAGLE_MEM_DB$suffix" "$backup_path$suffix"
130
+ chmod 600 "$backup_path$suffix" 2>/dev/null || true
131
+ fi
132
+ done
133
+
134
+ set +e
135
+ recover_output=$("$sqlite_bin" "$EAGLE_MEM_DB" ".recover" 2>&1 > "$recover_sql")
136
+ recover_rc=$?
137
+ set -e
138
+ if [ "$recover_rc" -ne 0 ] || [ ! -s "$recover_sql" ]; then
139
+ repair_emit "error" "SQLite recovery failed; original database was left untouched." "$integrity_status" "${recover_output:-recover output was empty}" "$backup_path"
140
+ exit 1
141
+ fi
142
+
143
+ if ! "$sqlite_bin" "$recovered_path" < "$recover_sql" >/dev/null 2>"$recover_dir/import.err"; then
144
+ import_error=$(tr '\n' ' ' < "$recover_dir/import.err" | sed 's/[[:space:]][[:space:]]*/ /g')
145
+ repair_emit "error" "Recovered SQL could not be imported; original database was left untouched." "$integrity_status" "$import_error" "$backup_path"
146
+ exit 1
147
+ fi
148
+
149
+ core_tables=$("$sqlite_bin" "$recovered_path" "SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name IN ('sessions','observations','summaries','agent_memories','agent_tasks');" 2>/dev/null || printf '0')
150
+ if [ "${core_tables:-0}" -eq 0 ] 2>/dev/null; then
151
+ repair_emit "error" "Recovered SQL did not contain Eagle Mem core tables; original database was left untouched." "$integrity_status" "no recoverable Eagle Mem tables found" "$backup_path"
152
+ exit 1
153
+ fi
154
+
155
+ candidate_home="$recover_dir/home"
156
+ mkdir -p "$candidate_home"
157
+ mv "$recovered_path" "$candidate_home/memory.db"
158
+ if ! EAGLE_MEM_DIR="$candidate_home" "$PACKAGE_DIR/db/migrate.sh" >/dev/null 2>"$recover_dir/migrate.err"; then
159
+ migrate_error=$(tr '\n' ' ' < "$recover_dir/migrate.err" | sed 's/[[:space:]][[:space:]]*/ /g')
160
+ repair_emit "error" "Recovered database migrations failed; original database was left untouched." "$integrity_status" "$migrate_error" "$backup_path"
161
+ exit 1
162
+ fi
163
+ recovered_path="$candidate_home/memory.db"
164
+
165
+ post_check=$(eagle_db_integrity_status "$recovered_path" 2>/dev/null || true)
166
+ post_status="${post_check%%|*}"
167
+ post_detail="${post_check#*|}"
168
+ if [ "$post_status" != "ok" ]; then
169
+ repair_emit "error" "Recovered database failed integrity check; original database was left untouched." "$post_status" "$post_detail" "$backup_path" "$recovered_path"
170
+ exit 1
171
+ fi
172
+
173
+ cp "$recovered_path" "$EAGLE_MEM_DB"
174
+ chmod 600 "$EAGLE_MEM_DB" 2>/dev/null || true
175
+ rm -f "$EAGLE_MEM_DB-wal" "$EAGLE_MEM_DB-shm" 2>/dev/null || true
176
+
177
+ final_check=$(eagle_db_integrity_status "$EAGLE_MEM_DB" 2>/dev/null || true)
178
+ final_status="${final_check%%|*}"
179
+ final_detail="${final_check#*|}"
180
+ if [ "$final_status" != "ok" ]; then
181
+ cp "$backup_path" "$EAGLE_MEM_DB"
182
+ chmod 600 "$EAGLE_MEM_DB" 2>/dev/null || true
183
+ repair_emit "error" "Replacement failed integrity check; backup was restored." "$final_status" "$final_detail" "$backup_path"
184
+ exit 1
185
+ fi
186
+
187
+ repair_emit "repaired" "Database repaired successfully." "$final_status" "$final_detail" "$backup_path" "$EAGLE_MEM_DB"
@@ -0,0 +1,248 @@
1
+ #!/usr/bin/env bash
2
+ # ═══════════════════════════════════════════════════════════
3
+ # Eagle Mem — Replay
4
+ # Shows prompt → recall → injected context summary → observed work for a session.
5
+ # ═══════════════════════════════════════════════════════════
6
+ set -euo pipefail
7
+
8
+ SCRIPTS_DIR="$(cd "$(dirname "$0")" && pwd)"
9
+ LIB_DIR="$SCRIPTS_DIR/../lib"
10
+
11
+ . "$SCRIPTS_DIR/style.sh"
12
+ . "$LIB_DIR/common.sh"
13
+ . "$LIB_DIR/db.sh"
14
+
15
+ session_id=""
16
+ project=""
17
+ project_was_explicit=false
18
+ cross_project=false
19
+ json_output=false
20
+ last=false
21
+
22
+ show_help() {
23
+ echo -e " ${BOLD}eagle-mem replay${RESET} — Replay what Eagle Mem did in a session"
24
+ echo ""
25
+ echo -e " ${BOLD}Usage:${RESET}"
26
+ echo -e " eagle-mem replay ${CYAN}<session-id>${RESET}"
27
+ echo -e " eagle-mem replay ${CYAN}--last${RESET}"
28
+ echo -e " eagle-mem replay ${CYAN}<session-id> --json${RESET}"
29
+ echo ""
30
+ echo -e " ${BOLD}Options:${RESET}"
31
+ echo -e " ${CYAN}-p, --project${RESET} <name> Project name (default: current dir scope)"
32
+ echo -e " ${CYAN}--all${RESET} Search all projects when resolving --last"
33
+ echo -e " ${CYAN}--last${RESET} Replay latest recall event session"
34
+ echo -e " ${CYAN}-j, --json${RESET} Output structured JSON"
35
+ echo ""
36
+ exit 0
37
+ }
38
+
39
+ while [ $# -gt 0 ]; do
40
+ case "$1" in
41
+ --project|-p) project="$2"; project_was_explicit=true; shift 2 ;;
42
+ --all|-a) cross_project=true; shift ;;
43
+ --last) last=true; shift ;;
44
+ --json|-j) json_output=true; shift ;;
45
+ --help|-h) show_help ;;
46
+ -*)
47
+ eagle_err "Unknown option: $1"
48
+ exit 1
49
+ ;;
50
+ *)
51
+ session_id="$1"
52
+ shift
53
+ ;;
54
+ esac
55
+ done
56
+
57
+ replay_fail() {
58
+ local error_code="$1"
59
+ local message="$2"
60
+ local db_status="${3:-unknown}"
61
+ local db_detail="${4:-}"
62
+
63
+ if [ "$json_output" = true ]; then
64
+ jq -nc \
65
+ --arg status "error" \
66
+ --arg command "replay" \
67
+ --arg error "$error_code" \
68
+ --arg message "$message" \
69
+ --arg session_id "${session_id:-}" \
70
+ --arg project "${project:-}" \
71
+ --arg db_status "$db_status" \
72
+ --arg db_detail "$db_detail" \
73
+ '{status:$status, command:$command, error:$error, message:$message,
74
+ session_id:$session_id, project:$project,
75
+ database:{integrity:{status:$db_status, detail:$db_detail}}}'
76
+ else
77
+ eagle_err "$message"
78
+ [ -n "$db_detail" ] && eagle_dim " $db_detail"
79
+ fi
80
+ exit 1
81
+ }
82
+
83
+ json_array_or_empty() {
84
+ local value="${1:-}"
85
+ if [ -z "$value" ] || ! printf '%s' "$value" | jq -e 'type == "array"' >/dev/null 2>&1; then
86
+ printf '[]\n'
87
+ return 0
88
+ fi
89
+ printf '%s\n' "$value"
90
+ }
91
+
92
+ if ! eagle_ensure_db; then
93
+ replay_fail "database_unavailable" "Database is unavailable; SQLite/FTS5 setup failed." "unavailable" "eagle_ensure_db failed"
94
+ fi
95
+
96
+ db_integrity_check=$(eagle_db_integrity_status "$EAGLE_MEM_DB" 2>/dev/null || true)
97
+ db_integrity_status="${db_integrity_check%%|*}"
98
+ db_integrity_detail="${db_integrity_check#*|}"
99
+ [ -n "$db_integrity_status" ] || db_integrity_status="unknown"
100
+ [ -n "$db_integrity_detail" ] || db_integrity_detail="not checked"
101
+ if [ "$db_integrity_status" != "ok" ]; then
102
+ replay_fail "database_integrity" "Database integrity check failed; replay is unavailable." "$db_integrity_status" "$db_integrity_detail"
103
+ fi
104
+
105
+ if [ -z "$(eagle_db "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'recall_events' LIMIT 1;" 2>/dev/null || true)" ]; then
106
+ replay_fail "migration_missing" "Replay is unavailable until migrations create recall_events." "ok" "run: eagle-mem update"
107
+ fi
108
+
109
+ if [ -z "$project" ] && [ "$cross_project" = false ]; then
110
+ project=$(eagle_project_from_cwd "$(pwd)")
111
+ fi
112
+ if [ "$cross_project" = false ] && [ "$project_was_explicit" = false ]; then
113
+ project=$(eagle_recall_project_scope_from_cwd "$(pwd)" "$project")
114
+ fi
115
+
116
+ where_project="1 = 1"
117
+ if [ "$cross_project" = false ]; then
118
+ where_project=$(eagle_sql_project_scope_condition "project" "$project")
119
+ fi
120
+
121
+ if [ -z "$session_id" ] || [ "$last" = true ]; then
122
+ session_id=$(eagle_db "SELECT session_id
123
+ FROM recall_events
124
+ WHERE $where_project
125
+ AND session_id IS NOT NULL
126
+ AND session_id != ''
127
+ ORDER BY created_at DESC, id DESC
128
+ LIMIT 1;" 2>/dev/null | awk 'NF { print; exit }')
129
+ fi
130
+
131
+ [ -n "$session_id" ] || replay_fail "session_missing" "No session id provided and no replayable recall event was found." "ok" "run: eagle-mem replay <session-id>"
132
+
133
+ sid_sql=$(eagle_sql_escape "$session_id")
134
+
135
+ session_json=$(eagle_db_json "SELECT id, project, agent, cwd, model, source, started_at, ended_at, status
136
+ FROM sessions
137
+ WHERE id = '$sid_sql'
138
+ LIMIT 1;" 2>/dev/null || printf '[]')
139
+ summary_json=$(eagle_db_json "SELECT request, completed, learned, decisions, gotchas, key_files, agent, created_at
140
+ FROM summaries
141
+ WHERE session_id = '$sid_sql'
142
+ ORDER BY created_at DESC;" 2>/dev/null || printf '[]')
143
+ recall_json=$(eagle_db_json "SELECT prompt_snippet, fts_query, summary_matches, memory_matches, code_matches,
144
+ summary_refs, memory_refs, code_refs, injected_token_estimate,
145
+ status, error, agent, created_at
146
+ FROM recall_events
147
+ WHERE session_id = '$sid_sql'
148
+ ORDER BY created_at ASC, id ASC;" 2>/dev/null || printf '[]')
149
+ observation_json=$(eagle_db_json "SELECT tool_name, tool_input_summary, files_read, files_modified, created_at
150
+ FROM observations
151
+ WHERE session_id = '$sid_sql'
152
+ ORDER BY created_at ASC
153
+ LIMIT 100;" 2>/dev/null || printf '[]')
154
+
155
+ session_json=$(json_array_or_empty "$session_json")
156
+ summary_json=$(json_array_or_empty "$summary_json")
157
+ recall_json=$(json_array_or_empty "$recall_json")
158
+ observation_json=$(json_array_or_empty "$observation_json")
159
+
160
+ replay_json=$(jq -nc \
161
+ --arg status "ok" \
162
+ --arg command "replay" \
163
+ --arg session_id "$session_id" \
164
+ --arg db_integrity_status "$db_integrity_status" \
165
+ --arg db_integrity_detail "$db_integrity_detail" \
166
+ --argjson session "$session_json" \
167
+ --argjson summaries "$summary_json" \
168
+ --argjson recalls "$recall_json" \
169
+ --argjson observations "$observation_json" \
170
+ '{status:$status, command:$command, session_id:$session_id,
171
+ database:{integrity:{status:$db_integrity_status, detail:$db_integrity_detail}},
172
+ session:($session[0] // null),
173
+ summaries:$summaries,
174
+ recall_events:($recalls | map(
175
+ .summary_refs = ((.summary_refs // "[]") | fromjson? // []) |
176
+ .memory_refs = ((.memory_refs // "[]") | fromjson? // []) |
177
+ .code_refs = ((.code_refs // "[]") | fromjson? // [])
178
+ )),
179
+ observations:$observations}')
180
+
181
+ if [ "$json_output" = true ]; then
182
+ printf '%s\n' "$replay_json"
183
+ exit 0
184
+ fi
185
+
186
+ eagle_header "Replay"
187
+ eagle_info "Session: $session_id"
188
+ project_label=$(printf '%s' "$replay_json" | jq -r '.session.project // empty')
189
+ [ -n "$project_label" ] && eagle_info "Project: $project_label"
190
+ echo ""
191
+
192
+ recall_count=$(printf '%s' "$replay_json" | jq '.recall_events | length')
193
+ summary_count=$(printf '%s' "$replay_json" | jq '.summaries | length')
194
+ observation_count=$(printf '%s' "$replay_json" | jq '.observations | length')
195
+ eagle_kv "Recall events:" "$recall_count"
196
+ eagle_kv "Summaries:" "$summary_count"
197
+ eagle_kv "Observations:" "$observation_count"
198
+ echo ""
199
+
200
+ printf '%s' "$replay_json" | jq -c '.recall_events[]' | while IFS= read -r event; do
201
+ created=$(printf '%s' "$event" | jq -r '.created_at // ""')
202
+ agent=$(printf '%s' "$event" | jq -r '.agent // ""')
203
+ prompt=$(printf '%s' "$event" | jq -r '.prompt_snippet // ""')
204
+ query=$(printf '%s' "$event" | jq -r '.fts_query // ""')
205
+ tokens=$(printf '%s' "$event" | jq -r '.injected_token_estimate // 0')
206
+ summaries=$(printf '%s' "$event" | jq -r '.summary_matches // 0')
207
+ memories=$(printf '%s' "$event" | jq -r '.memory_matches // 0')
208
+ code=$(printf '%s' "$event" | jq -r '.code_matches // 0')
209
+
210
+ echo -e " ${BOLD}${created}${RESET} ${DIM}$(eagle_agent_label "$agent")${RESET}"
211
+ [ -n "$prompt" ] && echo -e " ${DIM}Prompt:${RESET} $prompt"
212
+ [ -n "$query" ] && echo -e " ${DIM}Query:${RESET} $query"
213
+ echo -e " ${DIM}Retrieved:${RESET} summaries=$summaries memories=$memories code=$code ${DIM}Injected:${RESET} ~${tokens} tokens"
214
+
215
+ summary_refs=$(printf '%s' "$event" | jq -r '.summary_refs[]? | "- summary: " + ((.completed // .request // "summary") | tostring)' 2>/dev/null)
216
+ memory_refs=$(printf '%s' "$event" | jq -r '.memory_refs[]? | "- memory: " + ((.name // "memory") | tostring)' 2>/dev/null)
217
+ code_refs=$(printf '%s' "$event" | jq -r '.code_refs[]? | "- code: " + ((.file // "file") | tostring) + ":" + ((.start_line // "?") | tostring) + "-" + ((.end_line // "?") | tostring)' 2>/dev/null)
218
+
219
+ if [ -n "$summary_refs$memory_refs$code_refs" ]; then
220
+ echo -e " ${DIM}Retrieved refs:${RESET}"
221
+ [ -n "$summary_refs" ] && printf '%s\n' "$summary_refs" | sed 's/^/ /'
222
+ [ -n "$memory_refs" ] && printf '%s\n' "$memory_refs" | sed 's/^/ /'
223
+ [ -n "$code_refs" ] && printf '%s\n' "$code_refs" | sed 's/^/ /'
224
+ fi
225
+ echo ""
226
+ done
227
+
228
+ if [ "$summary_count" -gt 0 ]; then
229
+ echo -e " ${BOLD}Session summaries${RESET}"
230
+ printf '%s' "$replay_json" | jq -c '.summaries[]' | while IFS= read -r row; do
231
+ completed=$(printf '%s' "$row" | jq -r '.completed // ""')
232
+ learned=$(printf '%s' "$row" | jq -r '.learned // ""')
233
+ [ -n "$completed" ] && echo -e " ${GREEN}Done:${RESET} $completed"
234
+ [ -n "$learned" ] && echo -e " ${YELLOW}Learned:${RESET} $learned"
235
+ done
236
+ echo ""
237
+ fi
238
+
239
+ if [ "$observation_count" -gt 0 ]; then
240
+ echo -e " ${BOLD}Observed tools${RESET}"
241
+ printf '%s' "$replay_json" | jq -c '.observations[-12:][]' | while IFS= read -r row; do
242
+ tool=$(printf '%s' "$row" | jq -r '.tool_name // ""')
243
+ detail=$(printf '%s' "$row" | jq -r '.tool_input_summary // ""')
244
+ created=$(printf '%s' "$row" | jq -r '.created_at // ""')
245
+ echo -e " ${DIM}${created}${RESET} ${BOLD}${tool}:${RESET} $detail"
246
+ done
247
+ echo ""
248
+ fi
package/scripts/search.sh CHANGED
@@ -12,8 +12,6 @@ LIB_DIR="$SCRIPTS_DIR/../lib"
12
12
  . "$LIB_DIR/common.sh"
13
13
  . "$LIB_DIR/db.sh"
14
14
 
15
- eagle_ensure_db
16
-
17
15
  # ─── Help ────────────────────────────────────────────────
18
16
 
19
17
  show_help() {
@@ -77,6 +75,44 @@ while [ $# -gt 0 ]; do
77
75
  esac
78
76
  done
79
77
 
78
+ search_fail() {
79
+ local error_code="$1"
80
+ local message="$2"
81
+ local db_status="${3:-unknown}"
82
+ local db_detail="${4:-}"
83
+
84
+ if [ "$json_output" = true ]; then
85
+ jq -nc \
86
+ --arg status "error" \
87
+ --arg command "search" \
88
+ --arg mode "$mode" \
89
+ --arg error "$error_code" \
90
+ --arg message "$message" \
91
+ --arg project "${project:-}" \
92
+ --arg db_status "$db_status" \
93
+ --arg db_detail "$db_detail" \
94
+ '{status:$status, command:$command, mode:$mode, error:$error, message:$message,
95
+ project:$project, database:{integrity:{status:$db_status, detail:$db_detail}}}'
96
+ else
97
+ eagle_err "$message"
98
+ [ -n "$db_detail" ] && eagle_dim " $db_detail"
99
+ fi
100
+ exit 1
101
+ }
102
+
103
+ if ! eagle_ensure_db; then
104
+ search_fail "database_unavailable" "Database is unavailable; SQLite/FTS5 setup failed." "unavailable" "eagle_ensure_db failed"
105
+ fi
106
+
107
+ db_integrity_check=$(eagle_db_integrity_status "$EAGLE_MEM_DB" 2>/dev/null || true)
108
+ db_integrity_status="${db_integrity_check%%|*}"
109
+ db_integrity_detail="${db_integrity_check#*|}"
110
+ [ -n "$db_integrity_status" ] || db_integrity_status="unknown"
111
+ [ -n "$db_integrity_detail" ] || db_integrity_detail="not checked"
112
+ if [ "$db_integrity_status" != "ok" ]; then
113
+ search_fail "database_integrity" "Database integrity check failed; memory search is unavailable." "$db_integrity_status" "$db_integrity_detail"
114
+ fi
115
+
80
116
  [ -z "$project" ] && project=$(eagle_project_from_cwd "$(pwd)")
81
117
  project_scope="$project"
82
118
  if [ "$cross_project" = false ] && [ "$project_was_explicit" = false ]; then
@@ -438,7 +474,12 @@ search_stats() {
438
474
  --argjson observations "${observations:-0}" \
439
475
  --argjson tasks "${tasks:-0}" \
440
476
  --argjson code_chunks "${chunks:-0}" \
441
- '{project: $project, sessions: $sessions, sessions_claude: $sessions_claude, sessions_codex: $sessions_codex, summaries: $summaries, observations: $observations, tasks: $tasks, code_chunks: $code_chunks}'
477
+ --arg db_integrity_status "$db_integrity_status" \
478
+ --arg db_integrity_detail "$db_integrity_detail" \
479
+ '{status:"ok", project: $project,
480
+ database:{integrity:{status:$db_integrity_status, detail:$db_integrity_detail}},
481
+ sessions: $sessions, sessions_claude: $sessions_claude, sessions_codex: $sessions_codex,
482
+ summaries: $summaries, observations: $observations, tasks: $tasks, code_chunks: $code_chunks}'
442
483
  return
443
484
  fi
444
485
 
@@ -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