eagle-mem 4.10.12 → 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 (73) hide show
  1. package/CHANGELOG.md +25 -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 +11 -3
  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 +71 -8
  23. package/lib/db-events.sh +89 -0
  24. package/lib/db-features.sh +26 -23
  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.sh +2 -0
  29. package/lib/hooks.sh +12 -7
  30. package/lib/opencode-hooks.sh +105 -0
  31. package/lib/provider.sh +2 -2
  32. package/package.json +5 -2
  33. package/scripts/compaction.sh +108 -8
  34. package/scripts/dashboard.sh +372 -0
  35. package/scripts/doctor.sh +30 -3
  36. package/scripts/health.sh +40 -2
  37. package/scripts/help.sh +10 -2
  38. package/scripts/inspect.sh +285 -0
  39. package/scripts/install.sh +31 -7
  40. package/scripts/memories.sh +13 -0
  41. package/scripts/repair.sh +187 -0
  42. package/scripts/replay.sh +248 -0
  43. package/scripts/search.sh +44 -3
  44. package/scripts/statusline-em.sh +34 -7
  45. package/scripts/tasks.sh +34 -0
  46. package/scripts/test.sh +14 -0
  47. package/scripts/uninstall.sh +9 -0
  48. package/scripts/update.sh +18 -2
  49. package/skills/eagle-mem-feature/SKILL.md +3 -3
  50. package/tests/fixtures/agent-hooks/claude-statusline.json +32 -0
  51. package/tests/fixtures/agent-hooks/claude-user-prompt-submit.json +9 -0
  52. package/tests/fixtures/agent-hooks/codex-pre-tool-use.json +10 -0
  53. package/tests/fixtures/agent-hooks/codex-user-prompt-submit.json +7 -0
  54. package/tests/fixtures/agent-hooks/opencode-chat-message.json +36 -0
  55. package/tests/fixtures/agent-hooks/opencode-session-compacting.json +9 -0
  56. package/tests/fixtures/agent-hooks/opencode-todo-updated.json +13 -0
  57. package/tests/fixtures/agent-hooks/opencode-tool-execute-after.json +15 -0
  58. package/tests/fixtures/agent-hooks/opencode-tool-execute-before.json +12 -0
  59. package/tests/test_agent_compatibility_docs_gate.sh +123 -0
  60. package/tests/test_auto_orchestration_detection.sh +109 -0
  61. package/tests/test_claude_stop_hook_registration.sh +56 -0
  62. package/tests/test_codex_hooks_config.sh +73 -0
  63. package/tests/test_compaction_survival_matrix.sh +237 -0
  64. package/tests/test_dashboard.sh +96 -0
  65. package/tests/test_eagle_events.sh +96 -0
  66. package/tests/test_feature_verification_gate.sh +230 -0
  67. package/tests/test_opencode_hooks_config.sh +56 -0
  68. package/tests/test_opencode_plugin_adapter.sh +202 -0
  69. package/tests/test_recall_observability.sh +144 -0
  70. package/tests/test_reliability_guards.sh +20 -0
  71. package/tests/test_repair.sh +63 -0
  72. package/tests/test_rust_migration_plan.sh +75 -0
  73. package/tests/test_trust_surfaces.sh +123 -0
@@ -12,20 +12,117 @@ LIB_DIR="$SCRIPTS_DIR/../lib"
12
12
  . "$LIB_DIR/common.sh"
13
13
  . "$LIB_DIR/db.sh"
14
14
 
15
- eagle_ensure_db
15
+ json_output=false
16
+ project=""
16
17
 
17
- eagle_banner
18
- eagle_header "Compaction Survival"
18
+ while [ $# -gt 0 ]; do
19
+ case "$1" in
20
+ --json|-j) json_output=true; shift ;;
21
+ --project|-p) project="$2"; shift 2 ;;
22
+ --help|-h)
23
+ echo -e " ${BOLD}eagle-mem compaction${RESET} — Compaction Survival status"
24
+ echo ""
25
+ echo -e " ${BOLD}Usage:${RESET}"
26
+ echo -e " eagle-mem compaction"
27
+ echo -e " eagle-mem compaction --json"
28
+ echo -e " eagle-mem compaction --project <project>"
29
+ exit 0
30
+ ;;
31
+ *) shift ;;
32
+ esac
33
+ done
34
+
35
+ compaction_fail() {
36
+ local error_code="$1"
37
+ local message="$2"
38
+ local db_status="${3:-unknown}"
39
+ local db_detail="${4:-}"
19
40
 
20
- project=$(eagle_project_from_cwd "$(pwd)")
41
+ if [ "$json_output" = true ]; then
42
+ jq -nc \
43
+ --arg status "error" \
44
+ --arg command "compaction" \
45
+ --arg error "$error_code" \
46
+ --arg message "$message" \
47
+ --arg project "${project:-}" \
48
+ --arg db_status "$db_status" \
49
+ --arg db_detail "$db_detail" \
50
+ '{status:$status, command:$command, error:$error, message:$message,
51
+ project:$project, database:{integrity:{status:$db_status, detail:$db_detail}}}'
52
+ else
53
+ eagle_fail "$message"
54
+ [ -n "$db_detail" ] && eagle_dim " $db_detail"
55
+ fi
56
+ exit 1
57
+ }
58
+
59
+ if [ -z "$project" ]; then
60
+ project=$(eagle_project_from_cwd "$(pwd)")
61
+ fi
21
62
  project_sql=$(eagle_sql_escape "$project")
22
63
 
64
+ if ! eagle_ensure_db; then
65
+ compaction_fail "database_unavailable" "Database is unavailable; SQLite/FTS5 setup failed." "unavailable" "eagle_ensure_db failed"
66
+ fi
67
+
68
+ db_integrity_check=$(eagle_db_integrity_status "$EAGLE_MEM_DB" 2>/dev/null || true)
69
+ db_integrity_status="${db_integrity_check%%|*}"
70
+ db_integrity_detail="${db_integrity_check#*|}"
71
+ [ -n "$db_integrity_status" ] || db_integrity_status="unknown"
72
+ [ -n "$db_integrity_detail" ] || db_integrity_detail="not checked"
73
+ if [ "$db_integrity_status" != "ok" ]; then
74
+ compaction_fail "database_integrity" "Database integrity check failed; compaction state is unavailable." "$db_integrity_status" "$db_integrity_detail"
75
+ fi
76
+
23
77
  # --- Metrics ---
24
78
  enriched=$(eagle_db "SELECT COUNT(*) FROM summaries WHERE project='$project_sql' AND (learned != '' OR decisions != '' OR gotchas != '')" 2>/dev/null || echo 0)
25
79
  total_summaries=$(eagle_db "SELECT COUNT(*) FROM summaries WHERE project='$project_sql'" 2>/dev/null || echo 0)
26
80
  active_tasks=$(eagle_db "SELECT COUNT(*) FROM agent_tasks WHERE project='$project_sql' AND status IN ('pending','in_progress')" 2>/dev/null || echo 0)
27
81
  stale_tasks=$(eagle_db "SELECT COUNT(*) FROM agent_tasks WHERE project='$project_sql' AND status='in_progress' AND updated_at < datetime('now','-7 days')" 2>/dev/null || echo 0)
82
+ durable_memories=$(eagle_db "SELECT COUNT(*) FROM agent_memories WHERE project='$project_sql'" 2>/dev/null || echo 0)
83
+ active_features=$(eagle_db "SELECT COUNT(*) FROM features WHERE project='$project_sql' AND status='active'" 2>/dev/null || echo 0)
84
+ recall_events=$(eagle_db "SELECT COUNT(*) FROM recall_events WHERE project='$project_sql'" 2>/dev/null || echo 0)
85
+ semantic_graph_nodes=$(eagle_db "SELECT COUNT(*) FROM graph_nodes WHERE project='$project_sql' AND node_type IN ('feature','memory','task','session','decision')" 2>/dev/null || echo 0)
28
86
  last_capture=$(eagle_db "SELECT MAX(updated_at) FROM agent_tasks WHERE project='$project_sql'" 2>/dev/null || echo "never")
87
+ active_lanes=$(eagle_db "SELECT COUNT(*) FROM orchestration_lanes WHERE project='$project_sql' AND status NOT IN ('completed', 'cancelled')" 2>/dev/null || echo 0)
88
+
89
+ readiness="weak"
90
+ if [ "$enriched" -ge 3 ] && [ "$active_tasks" -gt 0 ]; then
91
+ readiness="strong"
92
+ elif [ "$enriched" -ge 1 ]; then
93
+ readiness="moderate"
94
+ fi
95
+
96
+ if [ "$json_output" = true ]; then
97
+ jq -nc \
98
+ --arg status "ok" \
99
+ --arg project "$project" \
100
+ --arg db_integrity_status "$db_integrity_status" \
101
+ --arg db_integrity_detail "$db_integrity_detail" \
102
+ --argjson enriched "${enriched:-0}" \
103
+ --argjson total_summaries "${total_summaries:-0}" \
104
+ --argjson active_tasks "${active_tasks:-0}" \
105
+ --argjson stale_tasks "${stale_tasks:-0}" \
106
+ --argjson durable_memories "${durable_memories:-0}" \
107
+ --argjson active_features "${active_features:-0}" \
108
+ --argjson recall_events "${recall_events:-0}" \
109
+ --argjson semantic_graph_nodes "${semantic_graph_nodes:-0}" \
110
+ --arg last_capture "${last_capture:-never}" \
111
+ --argjson active_lanes "${active_lanes:-0}" \
112
+ --arg readiness "$readiness" \
113
+ '{status:$status, project:$project,
114
+ database:{integrity:{status:$db_integrity_status, detail:$db_integrity_detail}},
115
+ metrics:{enriched_summaries:$enriched, total_summaries:$total_summaries,
116
+ active_tasks:$active_tasks, stale_tasks:$stale_tasks,
117
+ durable_memories:$durable_memories, active_features:$active_features,
118
+ recall_events:$recall_events, semantic_graph_nodes:$semantic_graph_nodes,
119
+ last_durable_update:$last_capture, active_orchestration_lanes:$active_lanes},
120
+ readiness:$readiness}'
121
+ exit 0
122
+ fi
123
+
124
+ eagle_banner
125
+ eagle_header "Compaction Survival"
29
126
 
30
127
  echo ""
31
128
  echo -e " Project: ${BOLD}$project${RESET}"
@@ -35,6 +132,10 @@ echo -e " ${BOLD}Context Survival Metrics${RESET}"
35
132
  echo -e " ─────────────────────────────────────"
36
133
  echo -e " Enriched summaries: ${GREEN}$enriched${RESET} / $total_summaries"
37
134
  echo -e " Active durable tasks: ${CYAN}$active_tasks${RESET}"
135
+ echo -e " Durable memories: ${CYAN}$durable_memories${RESET}"
136
+ echo -e " Active features: ${CYAN}$active_features${RESET}"
137
+ echo -e " Recall events: ${CYAN}$recall_events${RESET}"
138
+ echo -e " Semantic graph nodes: ${CYAN}$semantic_graph_nodes${RESET}"
38
139
  echo -e " Stale in_progress tasks:${RED}$stale_tasks${RESET}"
39
140
  echo -e " Last durable update: $last_capture"
40
141
 
@@ -46,15 +147,14 @@ else
46
147
  fi
47
148
 
48
149
  # Orchestration lanes (for cross-agent long-running work survival)
49
- active_lanes=$(eagle_db "SELECT COUNT(*) FROM orchestration_lanes WHERE project='$project_sql' AND status NOT IN ('completed', 'cancelled')" 2>/dev/null || echo 0)
50
150
  echo -e " Active orchestration lanes: ${CYAN}$active_lanes${RESET}"
51
151
  if [ "$active_lanes" -gt 0 ]; then
52
152
  eagle_info "Long-running work is tracked in durable lanes — use 'eagle-mem orchestrate' to manage"
53
153
  fi
54
154
 
55
- if [ "$enriched" -ge 3 ] && [ "$active_tasks" -gt 0 ]; then
155
+ if [ "$readiness" = "strong" ]; then
56
156
  eagle_ok "Compaction Survival: Strong — future sessions will have good context"
57
- elif [ "$enriched" -ge 1 ]; then
157
+ elif [ "$readiness" = "moderate" ]; then
58
158
  eagle_info "Compaction Survival: Moderate — add more durable tasks and summaries"
59
159
  else
60
160
  eagle_warn "Compaction Survival: Weak — start using durable tasks and <eagle-summary> blocks"
@@ -62,4 +162,4 @@ fi
62
162
 
63
163
  echo ""
64
164
  eagle_dim "Run this anytime to check how safe the project is from /compact amnesia."
65
- echo ""
165
+ echo ""
@@ -0,0 +1,372 @@
1
+ #!/usr/bin/env bash
2
+ # ═══════════════════════════════════════════════════════════
3
+ # Eagle Mem — Local Dashboard
4
+ # Generates a static HTML inspection surface from ~/.eagle-mem/memory.db.
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
+ project=""
16
+ project_was_explicit=false
17
+ cross_project=false
18
+ output_path=""
19
+ json_output=false
20
+ open_after=false
21
+
22
+ show_help() {
23
+ echo -e " ${BOLD}eagle-mem dashboard${RESET} — Generate a local HTML memory dashboard"
24
+ echo ""
25
+ echo -e " ${BOLD}Usage:${RESET}"
26
+ echo -e " eagle-mem dashboard"
27
+ echo -e " eagle-mem dashboard ${CYAN}--project <name>${RESET}"
28
+ echo -e " eagle-mem dashboard ${CYAN}--output <path>${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} Include all projects"
33
+ echo -e " ${CYAN}-o, --output${RESET} <path> Output file (default: ~/.eagle-mem/dashboard/index.html)"
34
+ echo -e " ${CYAN}--open${RESET} Open the generated dashboard"
35
+ echo -e " ${CYAN}-j, --json${RESET} Output structured JSON"
36
+ echo ""
37
+ exit 0
38
+ }
39
+
40
+ while [ $# -gt 0 ]; do
41
+ case "$1" in
42
+ --project|-p) project="$2"; project_was_explicit=true; shift 2 ;;
43
+ --all|-a) cross_project=true; shift ;;
44
+ --output|-o) output_path="$2"; shift 2 ;;
45
+ --open) open_after=true; shift ;;
46
+ --json|-j) json_output=true; shift ;;
47
+ --help|-h) show_help ;;
48
+ *)
49
+ eagle_err "Unknown option: $1"
50
+ exit 1
51
+ ;;
52
+ esac
53
+ done
54
+
55
+ dashboard_fail() {
56
+ local error_code="$1"
57
+ local message="$2"
58
+ local db_status="${3:-unknown}"
59
+ local db_detail="${4:-}"
60
+
61
+ if [ "$json_output" = true ]; then
62
+ jq -nc \
63
+ --arg status "error" \
64
+ --arg command "dashboard" \
65
+ --arg error "$error_code" \
66
+ --arg message "$message" \
67
+ --arg project "${project:-}" \
68
+ --arg output "${output_path:-}" \
69
+ --arg db_status "$db_status" \
70
+ --arg db_detail "$db_detail" \
71
+ '{status:$status, command:$command, error:$error, message:$message,
72
+ project:$project, output:$output,
73
+ database:{integrity:{status:$db_status, detail:$db_detail}}}'
74
+ else
75
+ eagle_err "$message"
76
+ [ -n "$db_detail" ] && eagle_dim " $db_detail"
77
+ fi
78
+ exit 1
79
+ }
80
+
81
+ html_escape() {
82
+ jq -Rn --arg v "${1:-}" '$v | @html'
83
+ }
84
+
85
+ json_len() {
86
+ printf '%s' "${1:-[]}" | jq 'length' 2>/dev/null || printf '0\n'
87
+ }
88
+
89
+ if ! eagle_ensure_db; then
90
+ dashboard_fail "database_unavailable" "Database is unavailable; SQLite/FTS5 setup failed." "unavailable" "eagle_ensure_db failed"
91
+ fi
92
+
93
+ db_integrity_check=$(eagle_db_integrity_status "$EAGLE_MEM_DB" 2>/dev/null || true)
94
+ db_integrity_status="${db_integrity_check%%|*}"
95
+ db_integrity_detail="${db_integrity_check#*|}"
96
+ [ -n "$db_integrity_status" ] || db_integrity_status="unknown"
97
+ [ -n "$db_integrity_detail" ] || db_integrity_detail="not checked"
98
+ if [ "$db_integrity_status" != "ok" ]; then
99
+ dashboard_fail "database_integrity" "Database integrity check failed; dashboard is unavailable." "$db_integrity_status" "$db_integrity_detail"
100
+ fi
101
+
102
+ if [ -z "$project" ] && [ "$cross_project" = false ]; then
103
+ project=$(eagle_project_from_cwd "$(pwd)")
104
+ fi
105
+ if [ "$cross_project" = false ] && [ "$project_was_explicit" = false ]; then
106
+ project=$(eagle_recall_project_scope_from_cwd "$(pwd)" "$project")
107
+ fi
108
+
109
+ project_label="$project"
110
+ if [ "$cross_project" = true ]; then
111
+ project_label="All projects"
112
+ else
113
+ project_label=$(eagle_project_scope_label "$project")
114
+ fi
115
+
116
+ [ -n "$output_path" ] || output_path="$EAGLE_MEM_DIR/dashboard/index.html"
117
+ mkdir -p "$(dirname "$output_path")"
118
+
119
+ where_project="1 = 1"
120
+ if [ "$cross_project" = false ]; then
121
+ where_project=$(eagle_sql_project_scope_condition "project" "$project")
122
+ fi
123
+
124
+ where_summary="1 = 1"
125
+ where_observation="1 = 1"
126
+ where_task="1 = 1"
127
+ where_graph="1 = 1"
128
+ if [ "$cross_project" = false ]; then
129
+ where_summary=$(eagle_sql_project_scope_condition "project" "$project")
130
+ where_observation=$(eagle_sql_project_scope_condition "project" "$project")
131
+ where_task=$(eagle_sql_project_scope_condition "project" "$project")
132
+ where_graph=$(eagle_sql_project_scope_condition "project" "$project")
133
+ fi
134
+
135
+ overview_json="[]"
136
+ if [ "$cross_project" = false ]; then
137
+ overview_json=$(eagle_db_json "SELECT content, source, updated_at
138
+ FROM overviews
139
+ WHERE $where_project
140
+ ORDER BY updated_at DESC
141
+ LIMIT 1;" 2>/dev/null || printf '[]')
142
+ fi
143
+ summary_json=$(eagle_db_json "SELECT request, completed, learned, decisions, gotchas, key_files, agent, created_at
144
+ FROM summaries
145
+ WHERE $where_summary
146
+ ORDER BY created_at DESC
147
+ LIMIT 10;" 2>/dev/null || printf '[]')
148
+ recall_json="[]"
149
+ if [ -n "$(eagle_db "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'recall_events' LIMIT 1;" 2>/dev/null || true)" ]; then
150
+ recall_json=$(eagle_db_json "SELECT prompt_snippet, fts_query, summary_matches, memory_matches, code_matches,
151
+ injected_token_estimate, status, agent, created_at
152
+ FROM recall_events
153
+ WHERE $where_project
154
+ ORDER BY created_at DESC, id DESC
155
+ LIMIT 10;" 2>/dev/null || printf '[]')
156
+ fi
157
+ files_json=$(eagle_db_json "SELECT json_each.value AS file, COUNT(*) AS touches
158
+ FROM observations, json_each(observations.files_modified)
159
+ WHERE $where_observation
160
+ GROUP BY json_each.value
161
+ ORDER BY touches DESC
162
+ LIMIT 12;" 2>/dev/null || printf '[]')
163
+ agents_json=$(eagle_db_json "SELECT agent, COUNT(*) AS sessions
164
+ FROM sessions
165
+ WHERE $where_project
166
+ GROUP BY agent
167
+ ORDER BY sessions DESC;" 2>/dev/null || printf '[]')
168
+ tasks_json=$(eagle_db_json "SELECT source_task_id, subject, status, updated_at
169
+ FROM agent_tasks
170
+ WHERE $where_task AND status IN ('pending', 'in_progress', 'blocked')
171
+ ORDER BY updated_at DESC
172
+ LIMIT 10;" 2>/dev/null || printf '[]')
173
+ graph_json=$(eagle_db_json "SELECT node_type, COUNT(*) AS nodes
174
+ FROM graph_nodes
175
+ WHERE $where_graph
176
+ GROUP BY node_type
177
+ ORDER BY nodes DESC;" 2>/dev/null || printf '[]')
178
+
179
+ overview_count=$(json_len "$overview_json")
180
+ summary_count=$(json_len "$summary_json")
181
+ recall_count=$(json_len "$recall_json")
182
+ file_count=$(json_len "$files_json")
183
+ agent_count=$(json_len "$agents_json")
184
+ task_count=$(json_len "$tasks_json")
185
+ graph_type_count=$(json_len "$graph_json")
186
+ edge_count=$(eagle_db "SELECT COUNT(*) FROM graph_edges WHERE $where_graph;" 2>/dev/null || printf '0')
187
+
188
+ render_summary_rows() {
189
+ printf '%s' "$summary_json" | jq -c '.[]' | while IFS= read -r row; do
190
+ local created request completed learned agent
191
+ created=$(printf '%s' "$row" | jq -r '.created_at // ""')
192
+ request=$(printf '%s' "$row" | jq -r '.request // "Session summary"')
193
+ completed=$(printf '%s' "$row" | jq -r '.completed // ""')
194
+ learned=$(printf '%s' "$row" | jq -r '.learned // ""')
195
+ agent=$(printf '%s' "$row" | jq -r '.agent // ""')
196
+ printf '<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>\n' \
197
+ "$(html_escape "$created")" "$(html_escape "$(eagle_agent_label "$agent")")" \
198
+ "$(html_escape "$request")" "$(html_escape "$completed")" "$(html_escape "$learned")"
199
+ done
200
+ }
201
+
202
+ render_recall_rows() {
203
+ printf '%s' "$recall_json" | jq -c '.[]' | while IFS= read -r row; do
204
+ local created prompt query summaries memories code tokens status agent
205
+ created=$(printf '%s' "$row" | jq -r '.created_at // ""')
206
+ prompt=$(printf '%s' "$row" | jq -r '.prompt_snippet // ""')
207
+ query=$(printf '%s' "$row" | jq -r '.fts_query // ""')
208
+ summaries=$(printf '%s' "$row" | jq -r '.summary_matches // 0')
209
+ memories=$(printf '%s' "$row" | jq -r '.memory_matches // 0')
210
+ code=$(printf '%s' "$row" | jq -r '.code_matches // 0')
211
+ tokens=$(printf '%s' "$row" | jq -r '.injected_token_estimate // 0')
212
+ status=$(printf '%s' "$row" | jq -r '.status // ""')
213
+ agent=$(printf '%s' "$row" | jq -r '.agent // ""')
214
+ printf '<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s/%s/%s</td><td>%s</td></tr>\n' \
215
+ "$(html_escape "$created")" "$(html_escape "$(eagle_agent_label "$agent")")" \
216
+ "$(html_escape "$status")" "$(html_escape "$prompt")" "$(html_escape "$query")" \
217
+ "$(html_escape "$summaries")" "$(html_escape "$memories")" "$(html_escape "$code")" \
218
+ "$(html_escape "$tokens")"
219
+ done
220
+ }
221
+
222
+ render_simple_rows() {
223
+ local json="$1" first="$2" second="$3"
224
+ printf '%s' "$json" | jq -c '.[]' | while IFS= read -r row; do
225
+ local a b
226
+ a=$(printf '%s' "$row" | jq -r --arg first "$first" '.[$first] // ""')
227
+ b=$(printf '%s' "$row" | jq -r --arg second "$second" '.[$second] // ""')
228
+ printf '<tr><td>%s</td><td>%s</td></tr>\n' "$(html_escape "$a")" "$(html_escape "$b")"
229
+ done
230
+ }
231
+
232
+ overview_content="No project overview recorded yet."
233
+ if [ "$overview_count" -gt 0 ]; then
234
+ overview_content=$(printf '%s' "$overview_json" | jq -r '.[0].content // "No project overview recorded yet."')
235
+ fi
236
+
237
+ cat > "$output_path" <<HTML
238
+ <!doctype html>
239
+ <html lang="en">
240
+ <head>
241
+ <meta charset="utf-8">
242
+ <meta name="viewport" content="width=device-width, initial-scale=1">
243
+ <title>Eagle Mem Dashboard - $(html_escape "$project_label")</title>
244
+ <style>
245
+ :root { color-scheme: light; --ink:#111; --muted:#626262; --line:#d8d8d8; --soft:#f6f6f3; --accent:#096b72; --warn:#8a3f00; }
246
+ * { box-sizing: border-box; }
247
+ body { margin: 0; font-family: Helvetica, Arial, sans-serif; color: var(--ink); background: #fff; line-height: 1.45; }
248
+ header { padding: 28px 36px 20px; border-bottom: 2px solid var(--ink); background: var(--soft); }
249
+ main { padding: 24px 36px 44px; display: grid; gap: 24px; }
250
+ h1 { margin: 0; font-size: 30px; letter-spacing: 0; }
251
+ h2 { margin: 0 0 12px; font-size: 18px; letter-spacing: 0; }
252
+ .meta { margin-top: 8px; color: var(--muted); font-size: 14px; }
253
+ .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; }
254
+ .metric { border: 1px solid var(--line); padding: 14px; background: #fff; }
255
+ .metric strong { display:block; font-size: 24px; }
256
+ section { border-top: 1px solid var(--line); padding-top: 18px; }
257
+ table { width: 100%; border-collapse: collapse; font-size: 13px; }
258
+ th, td { padding: 9px 8px; border-bottom: 1px solid var(--line); vertical-align: top; text-align: left; }
259
+ th { color: var(--muted); font-weight: 700; background: #fafafa; }
260
+ p { max-width: 900px; margin: 0; }
261
+ code { font-family: "Courier New", monospace; font-size: 12px; }
262
+ .empty { color: var(--muted); padding: 12px 0; }
263
+ </style>
264
+ </head>
265
+ <body>
266
+ <header>
267
+ <h1>Eagle Mem Dashboard</h1>
268
+ <div class="meta">Project: $(html_escape "$project_label") | Generated: $(date -u +%Y-%m-%dT%H:%M:%SZ) | DB integrity: $(html_escape "$db_integrity_status")</div>
269
+ </header>
270
+ <main>
271
+ <section>
272
+ <h2>Project Brain</h2>
273
+ <p>$(html_escape "$overview_content")</p>
274
+ </section>
275
+ <section>
276
+ <h2>Signals</h2>
277
+ <div class="grid">
278
+ <div class="metric"><strong>$summary_count</strong> recent summaries</div>
279
+ <div class="metric"><strong>$recall_count</strong> recall events</div>
280
+ <div class="metric"><strong>$file_count</strong> touched files</div>
281
+ <div class="metric"><strong>$task_count</strong> active tasks</div>
282
+ <div class="metric"><strong>$graph_type_count</strong> graph node types</div>
283
+ <div class="metric"><strong>$edge_count</strong> graph edges</div>
284
+ </div>
285
+ </section>
286
+ <section>
287
+ <h2>Recall Inspector</h2>
288
+ <table>
289
+ <thead><tr><th>Time</th><th>Agent</th><th>Status</th><th>Prompt</th><th>Query</th><th>Summary/Memory/Code</th><th>Token estimate</th></tr></thead>
290
+ <tbody>
291
+ $(render_recall_rows)
292
+ </tbody>
293
+ </table>
294
+ </section>
295
+ <section>
296
+ <h2>Timeline</h2>
297
+ <table>
298
+ <thead><tr><th>Time</th><th>Agent</th><th>Request</th><th>Completed</th><th>Learned</th></tr></thead>
299
+ <tbody>
300
+ $(render_summary_rows)
301
+ </tbody>
302
+ </table>
303
+ </section>
304
+ <section>
305
+ <h2>File Intelligence</h2>
306
+ <table>
307
+ <thead><tr><th>File</th><th>Touches</th></tr></thead>
308
+ <tbody>
309
+ $(render_simple_rows "$files_json" "file" "touches")
310
+ </tbody>
311
+ </table>
312
+ </section>
313
+ <section>
314
+ <h2>Agent Comparison</h2>
315
+ <table>
316
+ <thead><tr><th>Agent</th><th>Sessions</th></tr></thead>
317
+ <tbody>
318
+ $(render_simple_rows "$agents_json" "agent" "sessions")
319
+ </tbody>
320
+ </table>
321
+ </section>
322
+ <section>
323
+ <h2>Active Tasks</h2>
324
+ <table>
325
+ <thead><tr><th>Task</th><th>Status</th></tr></thead>
326
+ <tbody>
327
+ $(render_simple_rows "$tasks_json" "subject" "status")
328
+ </tbody>
329
+ </table>
330
+ </section>
331
+ <section>
332
+ <h2>Graph Readiness</h2>
333
+ <table>
334
+ <thead><tr><th>Node type</th><th>Count</th></tr></thead>
335
+ <tbody>
336
+ $(render_simple_rows "$graph_json" "node_type" "nodes")
337
+ </tbody>
338
+ </table>
339
+ </section>
340
+ </main>
341
+ </body>
342
+ </html>
343
+ HTML
344
+
345
+ if [ "$json_output" = true ]; then
346
+ jq -nc \
347
+ --arg status "ok" \
348
+ --arg command "dashboard" \
349
+ --arg project "$project_label" \
350
+ --arg output "$output_path" \
351
+ --argjson summaries "$summary_count" \
352
+ --argjson recall_events "$recall_count" \
353
+ --argjson files "$file_count" \
354
+ --argjson active_tasks "$task_count" \
355
+ --argjson graph_node_types "$graph_type_count" \
356
+ --argjson graph_edges "${edge_count:-0}" \
357
+ '{status:$status, command:$command, project:$project, output:$output,
358
+ counts:{summaries:$summaries, recall_events:$recall_events, files:$files,
359
+ active_tasks:$active_tasks, graph_node_types:$graph_node_types,
360
+ graph_edges:$graph_edges}}'
361
+ else
362
+ eagle_ok "Dashboard generated"
363
+ eagle_kv "Output:" "$output_path"
364
+ fi
365
+
366
+ if [ "$open_after" = true ]; then
367
+ if command -v open >/dev/null 2>&1; then
368
+ open "$output_path" >/dev/null 2>&1 || true
369
+ elif command -v xdg-open >/dev/null 2>&1; then
370
+ xdg-open "$output_path" >/dev/null 2>&1 || true
371
+ fi
372
+ fi
package/scripts/doctor.sh CHANGED
@@ -13,6 +13,7 @@ LIB_DIR="$SCRIPTS_DIR/../lib"
13
13
 
14
14
  . "$SCRIPTS_DIR/style.sh"
15
15
  . "$LIB_DIR/common.sh"
16
+ . "$LIB_DIR/opencode-hooks.sh"
16
17
 
17
18
  mode="install-footprint"
18
19
  json_output=false
@@ -49,7 +50,10 @@ case "$mode" in
49
50
  esac
50
51
 
51
52
  package_version=$(jq -r .version "$PACKAGE_DIR/package.json" 2>/dev/null || echo "unknown")
52
- installed_version=$(tr -d '[:space:]' < "$EAGLE_MEM_DIR/.version" 2>/dev/null || true)
53
+ installed_version=""
54
+ if [ -f "$EAGLE_MEM_DIR/.version" ]; then
55
+ installed_version=$(tr -d '[:space:]' < "$EAGLE_MEM_DIR/.version" 2>/dev/null || true)
56
+ fi
53
57
  [ -z "$installed_version" ] && installed_version="not installed"
54
58
 
55
59
  sqlite_bin=$(eagle_sqlite_path)
@@ -63,6 +67,11 @@ runtime_exists=false
63
67
  db_exists=false
64
68
  [ -d "$EAGLE_MEM_DIR" ] && runtime_exists=true
65
69
  [ -f "$EAGLE_MEM_DB" ] && db_exists=true
70
+ db_integrity_check=$(eagle_db_integrity_status "$EAGLE_MEM_DB" 2>/dev/null || true)
71
+ db_integrity_status="${db_integrity_check%%|*}"
72
+ db_integrity_detail="${db_integrity_check#*|}"
73
+ [ -n "$db_integrity_status" ] || db_integrity_status="unknown"
74
+ [ -n "$db_integrity_detail" ] || db_integrity_detail="not checked"
66
75
 
67
76
  doctor_compare_group() {
68
77
  local group="$1"
@@ -149,10 +158,12 @@ if [ -f "$EAGLE_SETTINGS" ] && command -v jq >/dev/null 2>&1; then
149
158
  fi
150
159
  fi
151
160
 
161
+ opencode_plugin=$(eagle_opencode_plugin_state)
162
+
152
163
  overall="Healthy"
153
164
  if [ "$runtime_exists" != true ] || [ "$db_exists" != true ]; then
154
165
  overall="Not installed"
155
- elif [ "$sqlite_fts5" != true ] || [ "$sum_missing" -gt 0 ] || [ "$sum_drift" -gt 0 ] || [ "$manifest_status" != "ok" ]; then
166
+ elif [ "$sqlite_fts5" != true ] || [ "$db_integrity_status" != "ok" ] || [ "$sum_missing" -gt 0 ] || [ "$sum_drift" -gt 0 ] || [ "$manifest_status" != "ok" ]; then
156
167
  overall="Needs attention"
157
168
  fi
158
169
 
@@ -167,8 +178,12 @@ if [ "$json_output" = true ]; then
167
178
  --arg sqlite_bin "${sqlite_bin:-}" \
168
179
  --arg sqlite_version "${sqlite_version:-}" \
169
180
  --argjson sqlite_fts5 "$sqlite_fts5" \
181
+ --argjson db_exists "$db_exists" \
182
+ --arg db_integrity_status "$db_integrity_status" \
183
+ --arg db_integrity_detail "$db_integrity_detail" \
170
184
  --arg claude_hooks "$claude_hooks" \
171
185
  --arg codex_hooks "$codex_hooks" \
186
+ --arg opencode_plugin "$opencode_plugin" \
172
187
  --arg statusline "$statusline_state" \
173
188
  --arg hooks_cmp "$hooks_cmp" \
174
189
  --arg lib_cmp "$lib_cmp" \
@@ -186,7 +201,8 @@ if [ "$json_output" = true ]; then
186
201
  '{overall:$overall, package_dir:$package_dir, runtime_dir:$runtime_dir, db:$db,
187
202
  versions:{package:$package_version, installed:$installed_version},
188
203
  sqlite:{path:$sqlite_bin, version:$sqlite_version, fts5:$sqlite_fts5},
189
- hooks:{claude:$claude_hooks, codex:$codex_hooks, statusline:$statusline},
204
+ database:{exists:$db_exists, integrity:{status:$db_integrity_status, detail:$db_integrity_detail}},
205
+ hooks:{claude:$claude_hooks, codex:$codex_hooks, opencode:$opencode_plugin, statusline:$statusline},
190
206
  runtime_drift:{hooks:$hooks_cmp, lib:$lib_cmp, db:$db_cmp, scripts:$scripts_cmp},
191
207
  manifest:{path:$manifest_path, status:$manifest_status, checked:$manifest_checked,
192
208
  missing:$manifest_missing, drift:$manifest_drift, version:$manifest_version,
@@ -217,11 +233,22 @@ if [ -n "$sqlite_bin" ]; then
217
233
  else
218
234
  eagle_fail "SQLite not found"
219
235
  fi
236
+ if [ "$db_exists" = true ]; then
237
+ if [ "$db_integrity_status" = "ok" ]; then
238
+ eagle_ok "Database integrity: ok"
239
+ else
240
+ eagle_fail "Database integrity: $db_integrity_status"
241
+ eagle_dim " $db_integrity_detail"
242
+ fi
243
+ else
244
+ eagle_fail "Database missing"
245
+ fi
220
246
  echo ""
221
247
 
222
248
  echo -e " ${BOLD}Hooks / Skills${RESET}"
223
249
  eagle_kv "Claude Code:" "$claude_hooks"
224
250
  eagle_kv "Codex:" "$codex_hooks"
251
+ eagle_kv "OpenCode:" "$opencode_plugin"
225
252
  [ -d "$EAGLE_GROK_DIR" ] && eagle_kv "Grok skills:" "$EAGLE_GROK_SKILLS_DIR"
226
253
  eagle_kv "Statusline:" "$statusline_state"
227
254
  echo ""