eagle-mem 4.9.6 → 4.9.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/db-mirrors.sh CHANGED
@@ -82,8 +82,7 @@ WHERE file_path = '$fp_sql'
82
82
  UPDATE agent_memories
83
83
  SET origin_session_id = COALESCE(NULLIF('$origin_sql', ''), origin_session_id),
84
84
  origin_agent = COALESCE(NULLIF('$agent_sql', ''), origin_agent),
85
- project = CASE WHEN '$proj_sql' != '' THEN '$proj_sql' ELSE project END,
86
- updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
85
+ project = CASE WHEN '$proj_sql' != '' THEN '$proj_sql' ELSE project END
87
86
  WHERE file_path = '$fp_sql'
88
87
  AND content_hash = '$hash_sql'
89
88
  AND (('$proj_sql' != '' AND project != '$proj_sql')
@@ -104,8 +103,7 @@ eagle_search_agent_memories() {
104
103
 
105
104
  local where_clause=""
106
105
  if [ -n "$project" ]; then
107
- project=$(eagle_sql_escape "$project")
108
- where_clause="AND m.project = '$project'"
106
+ where_clause="AND $(eagle_sql_project_scope_condition "m.project" "$project")"
109
107
  fi
110
108
 
111
109
  eagle_db "SELECT m.memory_name, m.memory_type, m.description,
@@ -125,8 +123,7 @@ eagle_list_agent_memories() {
125
123
 
126
124
  local where_clause=""
127
125
  if [ -n "$project" ]; then
128
- project=$(eagle_sql_escape "$project")
129
- where_clause="WHERE project = '$project'"
126
+ where_clause="WHERE $(eagle_sql_project_scope_condition "project" "$project")"
130
127
  fi
131
128
 
132
129
  eagle_db "SELECT memory_name, memory_type, description, file_path, updated_at, origin_agent
@@ -178,8 +175,7 @@ WHERE file_path = '$fp_sql'
178
175
  UPDATE agent_plans
179
176
  SET origin_session_id = COALESCE(NULLIF('$origin_sql', ''), origin_session_id),
180
177
  origin_agent = COALESCE(NULLIF('$agent_sql', ''), origin_agent),
181
- project = CASE WHEN '$proj_sql' != '' THEN '$proj_sql' ELSE project END,
182
- updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
178
+ project = CASE WHEN '$proj_sql' != '' THEN '$proj_sql' ELSE project END
183
179
  WHERE file_path = '$fp_sql'
184
180
  AND content_hash = '$hash_sql'
185
181
  AND (('$proj_sql' != '' AND project != '$proj_sql')
@@ -200,8 +196,7 @@ eagle_search_agent_plans() {
200
196
 
201
197
  local where_clause=""
202
198
  if [ -n "$project" ]; then
203
- project=$(eagle_sql_escape "$project")
204
- where_clause="AND p.project = '$project'"
199
+ where_clause="AND $(eagle_sql_project_scope_condition "p.project" "$project")"
205
200
  fi
206
201
 
207
202
  eagle_db "SELECT p.title, p.project,
@@ -221,8 +216,7 @@ eagle_list_agent_plans() {
221
216
 
222
217
  local where_clause=""
223
218
  if [ -n "$project" ]; then
224
- project=$(eagle_sql_escape "$project")
225
- where_clause="WHERE project = '$project'"
219
+ where_clause="WHERE $(eagle_sql_project_scope_condition "project" "$project")"
226
220
  fi
227
221
 
228
222
  eagle_db "SELECT title, project, file_path, updated_at, origin_agent
@@ -291,8 +285,7 @@ WHERE file_path = '$fp_sql'
291
285
 
292
286
  UPDATE agent_tasks
293
287
  SET origin_agent = COALESCE(NULLIF('$agent_sql', ''), origin_agent),
294
- project = CASE WHEN '$proj_sql' != '' THEN '$proj_sql' ELSE project END,
295
- updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
288
+ project = CASE WHEN '$proj_sql' != '' THEN '$proj_sql' ELSE project END
296
289
  WHERE file_path = '$fp_sql'
297
290
  AND content_hash = '$hash_sql'
298
291
  AND (('$proj_sql' != '' AND project != '$proj_sql')
@@ -306,8 +299,7 @@ eagle_list_agent_tasks() {
306
299
 
307
300
  local where_clause=""
308
301
  if [ -n "$project" ]; then
309
- project=$(eagle_sql_escape "$project")
310
- where_clause="WHERE project = '$project'"
302
+ where_clause="WHERE $(eagle_sql_project_scope_condition "project" "$project")"
311
303
  fi
312
304
 
313
305
  eagle_db "SELECT subject, status, source_session_id, source_task_id, updated_at, origin_agent
@@ -329,8 +321,7 @@ eagle_search_agent_tasks() {
329
321
 
330
322
  local where_clause=""
331
323
  if [ -n "$project" ]; then
332
- project=$(eagle_sql_escape "$project")
333
- where_clause="AND t.project = '$project'"
324
+ where_clause="AND $(eagle_sql_project_scope_condition "t.project" "$project")"
334
325
  fi
335
326
 
336
327
  eagle_db "SELECT t.subject, t.status,
@@ -167,24 +167,31 @@ eagle_abandon_stale_sessions() {
167
167
  }
168
168
 
169
169
  eagle_get_project_stats() {
170
- local project; project=$(eagle_sql_escape "$1")
170
+ local project_scope="${1:-}"
171
+ local session_filter memory_filter plan_filter task_filter chunk_filter observation_filter
172
+ session_filter=$(eagle_sql_project_scope_condition "project" "$project_scope")
173
+ memory_filter=$(eagle_sql_project_scope_condition "project" "$project_scope")
174
+ plan_filter=$(eagle_sql_project_scope_condition "project" "$project_scope")
175
+ task_filter=$(eagle_sql_project_scope_condition "project" "$project_scope")
176
+ chunk_filter=$(eagle_sql_project_scope_condition "project" "$project_scope")
177
+ observation_filter=$(eagle_sql_project_scope_condition "project" "$project_scope")
171
178
  eagle_db_pipe <<SQL
172
- SELECT 'sessions|' || COUNT(*) FROM sessions WHERE project = '$project';
173
- SELECT 'sessions_claude|' || COUNT(*) FROM sessions WHERE project = '$project' AND agent = 'claude-code';
174
- SELECT 'sessions_codex|' || COUNT(*) FROM sessions WHERE project = '$project' AND agent = 'codex';
175
- SELECT 'summaries|' || COUNT(*) FROM summaries WHERE project = '$project';
176
- SELECT 'with_summaries|' || COUNT(*) FROM summaries WHERE project = '$project' AND request IS NOT NULL AND request != '';
177
- SELECT 'memories|' || COUNT(*) FROM agent_memories WHERE project = '$project';
178
- SELECT 'plans|' || COUNT(*) FROM agent_plans WHERE project = '$project';
179
- SELECT 'tasks_pending|' || COUNT(*) FROM agent_tasks WHERE project = '$project' AND status = 'pending';
180
- SELECT 'tasks_progress|' || COUNT(*) FROM agent_tasks WHERE project = '$project' AND status = 'in_progress';
181
- SELECT 'tasks_done|' || COUNT(*) FROM agent_tasks WHERE project = '$project' AND status = 'completed';
182
- SELECT 'chunks|' || COUNT(*) FROM code_chunks WHERE project = '$project';
183
- SELECT 'observations|' || COUNT(*) FROM observations WHERE session_id IN (SELECT id FROM sessions WHERE project = '$project');
184
- SELECT 'last_active|' || COALESCE(MAX(date(COALESCE(last_activity_at, started_at))), 'never') FROM sessions WHERE project = '$project';
179
+ SELECT 'sessions|' || COUNT(*) FROM sessions WHERE $session_filter;
180
+ SELECT 'sessions_claude|' || COUNT(*) FROM sessions WHERE $session_filter AND agent = 'claude-code';
181
+ SELECT 'sessions_codex|' || COUNT(*) FROM sessions WHERE $session_filter AND agent = 'codex';
182
+ SELECT 'summaries|' || COUNT(*) FROM summaries WHERE $session_filter;
183
+ SELECT 'with_summaries|' || COUNT(*) FROM summaries WHERE $session_filter AND request IS NOT NULL AND request != '';
184
+ SELECT 'memories|' || COUNT(*) FROM agent_memories WHERE $memory_filter;
185
+ SELECT 'plans|' || COUNT(*) FROM agent_plans WHERE $plan_filter;
186
+ SELECT 'tasks_pending|' || COUNT(*) FROM agent_tasks WHERE $task_filter AND status = 'pending';
187
+ SELECT 'tasks_progress|' || COUNT(*) FROM agent_tasks WHERE $task_filter AND status = 'in_progress';
188
+ SELECT 'tasks_done|' || COUNT(*) FROM agent_tasks WHERE $task_filter AND status = 'completed';
189
+ SELECT 'chunks|' || COUNT(*) FROM code_chunks WHERE $chunk_filter;
190
+ SELECT 'observations|' || COUNT(*) FROM observations WHERE $observation_filter;
191
+ SELECT 'last_active|' || COALESCE(MAX(date(COALESCE(last_activity_at, started_at))), 'never') FROM sessions WHERE $session_filter;
185
192
  SELECT 'last_summary|' || COALESCE((SELECT substr(request, 1, 60)
186
193
  FROM summaries
187
- WHERE project = '$project'
194
+ WHERE $session_filter
188
195
  AND COALESCE(request, '') NOT LIKE '# AGENTS.md instructions%'
189
196
  AND COALESCE(request, '') NOT LIKE '<environment_context>%'
190
197
  ORDER BY created_at DESC
@@ -57,12 +57,16 @@ SQL
57
57
  }
58
58
 
59
59
  eagle_get_recent_summaries() {
60
- local project; project=$(eagle_sql_escape "$1")
60
+ local project_scope="${1:-}"
61
61
  local limit; limit=$(eagle_sql_int "${2:-5}")
62
+ local project_filter="1 = 1"
63
+ if [ -n "$project_scope" ]; then
64
+ project_filter=$(eagle_sql_project_scope_condition "s.project" "$project_scope")
65
+ fi
62
66
 
63
67
  eagle_db "SELECT s.request, s.completed, s.learned, s.next_steps, s.created_at, s.decisions, s.gotchas, s.key_files, s.agent
64
68
  FROM summaries s
65
- WHERE s.project = '$project'
69
+ WHERE $project_filter
66
70
  AND COALESCE(s.request, '') NOT LIKE '%<local-command-caveat>%'
67
71
  AND COALESCE(s.request, '') NOT LIKE '# AGENTS.md instructions%'
68
72
  AND COALESCE(s.request, '') NOT LIKE '<environment_context>%'
@@ -73,13 +77,12 @@ eagle_get_recent_summaries() {
73
77
  eagle_search_summaries() {
74
78
  local query; query=$(eagle_fts_sanitize "$1")
75
79
  query=$(eagle_sql_escape "$query")
76
- local project="${2:-}"
80
+ local project_scope="${2:-}"
77
81
  local limit; limit=$(eagle_sql_int "${3:-10}")
78
82
 
79
83
  local where_clause=""
80
- if [ -n "$project" ]; then
81
- project=$(eagle_sql_escape "$project")
82
- where_clause="AND s.project = '$project'"
84
+ if [ -n "$project_scope" ]; then
85
+ where_clause="AND $(eagle_sql_project_scope_condition "s.project" "$project_scope")"
83
86
  fi
84
87
 
85
88
  eagle_db "SELECT s.request, s.completed, s.learned, s.next_steps, s.created_at, s.project, s.decisions, s.gotchas, s.key_files, s.agent
@@ -89,8 +92,17 @@ eagle_search_summaries() {
89
92
  AND COALESCE(s.request, '') NOT LIKE '%<local-command-caveat>%'
90
93
  AND COALESCE(s.request, '') NOT LIKE '# AGENTS.md instructions%'
91
94
  AND COALESCE(s.request, '') NOT LIKE '<environment_context>%'
95
+ AND COALESCE(s.request, '') NOT LIKE '<subagent_notification>%'
96
+ AND COALESCE(s.request, '') NOT LIKE '</subagent_notification>%'
92
97
  $where_clause
93
- ORDER BY rank
98
+ ORDER BY
99
+ CASE
100
+ WHEN julianday('now') - julianday(s.created_at) <= 14 THEN 0
101
+ WHEN julianday('now') - julianday(s.created_at) <= 45 THEN 1
102
+ ELSE 2
103
+ END,
104
+ s.created_at DESC,
105
+ rank
94
106
  LIMIT $limit;"
95
107
  }
96
108
 
@@ -60,6 +60,7 @@ eagle_posttool_mirror_tasks() {
60
60
 
61
61
  eagle_posttool_stale_hint() {
62
62
  local tool_name="$1" fp="$2" project="$3"
63
+ local agent="${4:-$(eagle_agent_source)}"
63
64
 
64
65
  case "$tool_name" in
65
66
  Write|Edit|apply_patch)
@@ -77,9 +78,16 @@ eagle_posttool_stale_hint() {
77
78
  local stale_hit
78
79
  stale_hit=$(eagle_search_stale_memories "$project" "$fts_query")
79
80
  if [ -n "$stale_hit" ]; then
80
- local stale_msg="=== Eagle Mem: Memory Check ===
81
+ local stale_msg
82
+ if [ "$agent" = "codex" ]; then
83
+ stale_msg="Eagle Mem memory check:
84
+ - Memory '${stale_hit}' may reference '${fname}'.
85
+ - If this edit contradicts that memory, update or correct the memory before relying on it."
86
+ else
87
+ stale_msg="=== Eagle Mem: Memory Check ===
81
88
  Memory '${stale_hit}' may reference '${fname}'. If your edit contradicts it, update the memory.
82
89
  ================"
90
+ fi
83
91
  jq -nc --arg ctx "$stale_msg" '{"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":$ctx}}'
84
92
  fi
85
93
  fi
@@ -93,6 +101,7 @@ Memory '${stale_hit}' may reference '${fname}'. If your edit contradicts it, upd
93
101
 
94
102
  eagle_posttool_decision_surface() {
95
103
  local tool_name="$1" fp="$2" project="$3"
104
+ local agent="${4:-$(eagle_agent_source)}"
96
105
 
97
106
  case "$tool_name" in
98
107
  Read)
@@ -110,10 +119,17 @@ eagle_posttool_decision_surface() {
110
119
  local decision_hit
111
120
  decision_hit=$(eagle_search_decisions_for_file "$project" "$fts_query")
112
121
  if [ -n "$decision_hit" ]; then
113
- read_context+="=== Eagle Mem: Decision Recall ===
122
+ if [ "$agent" = "codex" ]; then
123
+ read_context+="Eagle Mem decision recall:
124
+ - ${fname}: ${decision_hit}
125
+ - Do not revert without explicit user request.
126
+ "
127
+ else
128
+ read_context+="=== Eagle Mem: Decision Recall ===
114
129
  ${fname}: ${decision_hit} — Do not revert without explicit user request.
115
130
  ================
116
131
  "
132
+ fi
117
133
  fi
118
134
  fi
119
135
  fi
@@ -123,17 +139,35 @@ ${fname}: ${decision_hit} — Do not revert without explicit user request.
123
139
  if [ -n "$feature_hit" ]; then
124
140
  while IFS='|' read -r feat_name feat_desc feat_verified _role feat_deps feat_other_files feat_smoke; do
125
141
  [ -z "$feat_name" ] && continue
126
- read_context+="=== Eagle Mem: Feature Guardrail ===
142
+ if [ "$agent" = "codex" ]; then
143
+ read_context+="Eagle Mem feature guardrail:
144
+ - '${fname}' is part of '${feat_name}'"
145
+ [ -n "$feat_desc" ] && read_context+=": ${feat_desc}"
146
+ read_context+=".
147
+ "
148
+ [ -n "$feat_verified" ] && read_context+="- Last verified: ${feat_verified}.
149
+ "
150
+ [ -n "$feat_deps" ] && read_context+="- Dependencies: ${feat_deps}.
151
+ "
152
+ [ -n "$feat_other_files" ] && read_context+="- Related files: ${feat_other_files}.
153
+ "
154
+ [ -n "$feat_smoke" ] && read_context+="- Smoke tests: ${feat_smoke}.
155
+ "
156
+ read_context+="- Retest after changes before release.
157
+ "
158
+ else
159
+ read_context+="=== Eagle Mem: Feature Guardrail ===
127
160
  '${fname}' is part of feature '${feat_name}'"
128
- [ -n "$feat_desc" ] && read_context+=" ($feat_desc)"
129
- read_context+="."
130
- [ -n "$feat_verified" ] && read_context+=" Last verified: ${feat_verified}."
131
- [ -n "$feat_deps" ] && read_context+=" Dependencies: ${feat_deps}."
132
- [ -n "$feat_other_files" ] && read_context+=" Other files in pipeline: ${feat_other_files}."
133
- [ -n "$feat_smoke" ] && read_context+=" Smoke tests: ${feat_smoke}."
134
- read_context+=" Changes require re-testing after deploy.
161
+ [ -n "$feat_desc" ] && read_context+=" ($feat_desc)"
162
+ read_context+="."
163
+ [ -n "$feat_verified" ] && read_context+=" Last verified: ${feat_verified}."
164
+ [ -n "$feat_deps" ] && read_context+=" Dependencies: ${feat_deps}."
165
+ [ -n "$feat_other_files" ] && read_context+=" Other files in pipeline: ${feat_other_files}."
166
+ [ -n "$feat_smoke" ] && read_context+=" Smoke tests: ${feat_smoke}."
167
+ read_context+=" Changes require re-testing after deploy.
135
168
  ================
136
169
  "
170
+ fi
137
171
  done <<< "$feature_hit"
138
172
  fi
139
173
 
package/lib/updater.sh CHANGED
@@ -185,6 +185,12 @@ eagle_update_backup_runtime() {
185
185
  fi
186
186
  done
187
187
 
188
+ for item in .version .latest-version config.toml install-manifest.json .last-update.json; do
189
+ if [ -f "$EAGLE_MEM_DIR/$item" ]; then
190
+ cp "$EAGLE_MEM_DIR/$item" "$backup_dir/$item" 2>/dev/null || true
191
+ fi
192
+ done
193
+
188
194
  if [ -f "$EAGLE_MEM_DB" ]; then
189
195
  local sqlite_bin
190
196
  sqlite_bin=$(eagle_sqlite_path)
@@ -205,6 +211,12 @@ eagle_update_restore_runtime() {
205
211
  fi
206
212
  done
207
213
 
214
+ for item in .version .latest-version config.toml install-manifest.json .last-update.json; do
215
+ if [ -f "$backup_dir/$item" ]; then
216
+ cp "$backup_dir/$item" "$EAGLE_MEM_DIR/$item" 2>/dev/null || true
217
+ fi
218
+ done
219
+
208
220
  if [ -f "$backup_dir/memory.db" ]; then
209
221
  cp "$backup_dir/memory.db" "$EAGLE_MEM_DB" 2>/dev/null || true
210
222
  fi
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eagle-mem",
3
- "version": "4.9.6",
3
+ "version": "4.9.8",
4
4
  "description": "Shared memory, release guardrails, RTK token protection, and worker lanes for Claude Code and Codex",
5
5
  "bin": {
6
6
  "eagle-mem": "bin/eagle-mem"
@@ -11,7 +11,9 @@
11
11
  "hooks/",
12
12
  "lib/",
13
13
  "db/",
14
- "skills/"
14
+ "skills/",
15
+ "docs/",
16
+ "architecture.html"
15
17
  ],
16
18
  "keywords": [
17
19
  "claude-code",
@@ -0,0 +1,262 @@
1
+ #!/usr/bin/env bash
2
+ # ═══════════════════════════════════════════════════════════
3
+ # Eagle Mem — Doctor
4
+ # Read-only trust and install footprint checks.
5
+ # ═══════════════════════════════════════════════════════════
6
+ set -euo pipefail
7
+
8
+ PACKAGE_DIR="${1:-.}"
9
+ shift 2>/dev/null || true
10
+
11
+ SCRIPTS_DIR="$(cd "$(dirname "$0")" && pwd)"
12
+ LIB_DIR="$SCRIPTS_DIR/../lib"
13
+
14
+ . "$SCRIPTS_DIR/style.sh"
15
+ . "$LIB_DIR/common.sh"
16
+
17
+ mode="install-footprint"
18
+ json_output=false
19
+
20
+ while [ $# -gt 0 ]; do
21
+ case "$1" in
22
+ --json|-j) json_output=true; shift ;;
23
+ --help|-h)
24
+ echo -e " ${BOLD}eagle-mem doctor${RESET} — Trust and install diagnostics"
25
+ echo ""
26
+ echo -e " ${BOLD}Usage:${RESET}"
27
+ echo -e " eagle-mem doctor"
28
+ echo -e " eagle-mem doctor install-footprint"
29
+ echo ""
30
+ echo -e " ${BOLD}Options:${RESET}"
31
+ echo -e " ${CYAN}-j, --json${RESET} Output structured JSON"
32
+ exit 0
33
+ ;;
34
+ install-footprint|footprint)
35
+ mode="$1"
36
+ shift
37
+ ;;
38
+ *) shift ;;
39
+ esac
40
+ done
41
+
42
+ case "$mode" in
43
+ install-footprint|footprint|"") ;;
44
+ *)
45
+ eagle_err "Unknown doctor check: $mode"
46
+ eagle_dim "Run: eagle-mem doctor --help"
47
+ exit 1
48
+ ;;
49
+ esac
50
+
51
+ 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
+ [ -z "$installed_version" ] && installed_version="not installed"
54
+
55
+ sqlite_bin=$(eagle_sqlite_path)
56
+ sqlite_version=$(eagle_sqlite_version)
57
+ sqlite_fts5=false
58
+ if eagle_sqlite_supports_fts5; then
59
+ sqlite_fts5=true
60
+ fi
61
+
62
+ runtime_exists=false
63
+ db_exists=false
64
+ [ -d "$EAGLE_MEM_DIR" ] && runtime_exists=true
65
+ [ -f "$EAGLE_MEM_DB" ] && db_exists=true
66
+
67
+ doctor_compare_group() {
68
+ local group="$1"
69
+ local checked=0 missing=0 drift=0
70
+ local src dst
71
+ for src in "$PACKAGE_DIR/$group"/*; do
72
+ [ -f "$src" ] || continue
73
+ case "$group" in
74
+ db) ;;
75
+ *)
76
+ case "$src" in
77
+ *.sh|*/eagle-mem) ;;
78
+ *) continue ;;
79
+ esac
80
+ ;;
81
+ esac
82
+ checked=$((checked + 1))
83
+ dst="$EAGLE_MEM_DIR/$group/$(basename "$src")"
84
+ if [ ! -f "$dst" ]; then
85
+ missing=$((missing + 1))
86
+ elif ! cmp -s "$src" "$dst"; then
87
+ drift=$((drift + 1))
88
+ fi
89
+ done
90
+ printf '%s|%s|%s\n' "$checked" "$missing" "$drift"
91
+ }
92
+
93
+ hooks_cmp=$(doctor_compare_group hooks)
94
+ lib_cmp=$(doctor_compare_group lib)
95
+ db_cmp=$(doctor_compare_group db)
96
+ scripts_cmp=$(doctor_compare_group scripts)
97
+ manifest_path=$(eagle_runtime_manifest_path)
98
+ manifest_check=$(eagle_runtime_manifest_check)
99
+ manifest_status="${manifest_check%%|*}"
100
+ manifest_checked=$(printf '%s' "$manifest_check" | cut -d'|' -f2)
101
+ manifest_missing=$(printf '%s' "$manifest_check" | cut -d'|' -f3)
102
+ manifest_drift=$(printf '%s' "$manifest_check" | cut -d'|' -f4)
103
+ manifest_version=$(eagle_runtime_manifest_field '.package.version' 2>/dev/null || true)
104
+ manifest_action=$(eagle_runtime_manifest_field '.action' 2>/dev/null || true)
105
+ manifest_generated_at=$(eagle_runtime_manifest_field '.generated_at' 2>/dev/null || true)
106
+ manifest_package_dir=$(eagle_runtime_manifest_field '.package.dir' 2>/dev/null || true)
107
+
108
+ sum_missing=0
109
+ sum_drift=0
110
+ for row in "$hooks_cmp" "$lib_cmp" "$db_cmp" "$scripts_cmp"; do
111
+ IFS='|' read -r _checked missing drift <<< "$row"
112
+ sum_missing=$((sum_missing + missing))
113
+ sum_drift=$((sum_drift + drift))
114
+ done
115
+
116
+ claude_hooks="not found"
117
+ if [ -f "$EAGLE_SETTINGS" ] && command -v jq >/dev/null 2>&1; then
118
+ if jq -e '.. | objects | .command? // empty | select(test("eagle-mem|\\.eagle-mem"))' "$EAGLE_SETTINGS" >/dev/null 2>&1; then
119
+ claude_hooks="registered"
120
+ else
121
+ claude_hooks="not registered"
122
+ fi
123
+ fi
124
+
125
+ codex_hooks="not found"
126
+ if [ -f "$EAGLE_CODEX_HOOKS" ] && command -v jq >/dev/null 2>&1; then
127
+ if jq -e '.. | objects | .command? // empty | select(test("eagle-mem|\\.eagle-mem"))' "$EAGLE_CODEX_HOOKS" >/dev/null 2>&1; then
128
+ codex_hooks="registered"
129
+ else
130
+ codex_hooks="not registered"
131
+ fi
132
+ fi
133
+
134
+ statusline_state="not configured"
135
+ statusline_command=""
136
+ if [ -f "$EAGLE_SETTINGS" ] && command -v jq >/dev/null 2>&1; then
137
+ statusline_command=$(jq -r '.statusLine.command // .statusline.command // empty' "$EAGLE_SETTINGS" 2>/dev/null)
138
+ if [ -n "$statusline_command" ]; then
139
+ if printf '%s' "$statusline_command" | grep -qE 'eagle-mem|\.eagle-mem'; then
140
+ statusline_state="registered"
141
+ else
142
+ sl_file=$(eagle_statusline_script_from_command "$statusline_command" 2>/dev/null || true)
143
+ if [ -n "$sl_file" ] && grep -qE 'eagle_mem_statusline|\.eagle-mem/scripts/statusline-em' "$sl_file" 2>/dev/null; then
144
+ statusline_state="registered"
145
+ else
146
+ statusline_state="custom"
147
+ fi
148
+ fi
149
+ fi
150
+ fi
151
+
152
+ overall="Healthy"
153
+ if [ "$runtime_exists" != true ] || [ "$db_exists" != true ]; then
154
+ overall="Not installed"
155
+ elif [ "$sqlite_fts5" != true ] || [ "$sum_missing" -gt 0 ] || [ "$sum_drift" -gt 0 ] || [ "$manifest_status" != "ok" ]; then
156
+ overall="Needs attention"
157
+ fi
158
+
159
+ if [ "$json_output" = true ]; then
160
+ jq -nc \
161
+ --arg overall "$overall" \
162
+ --arg package_dir "$PACKAGE_DIR" \
163
+ --arg runtime_dir "$EAGLE_MEM_DIR" \
164
+ --arg db "$EAGLE_MEM_DB" \
165
+ --arg package_version "$package_version" \
166
+ --arg installed_version "$installed_version" \
167
+ --arg sqlite_bin "${sqlite_bin:-}" \
168
+ --arg sqlite_version "${sqlite_version:-}" \
169
+ --argjson sqlite_fts5 "$sqlite_fts5" \
170
+ --arg claude_hooks "$claude_hooks" \
171
+ --arg codex_hooks "$codex_hooks" \
172
+ --arg statusline "$statusline_state" \
173
+ --arg hooks_cmp "$hooks_cmp" \
174
+ --arg lib_cmp "$lib_cmp" \
175
+ --arg db_cmp "$db_cmp" \
176
+ --arg scripts_cmp "$scripts_cmp" \
177
+ --arg manifest_path "$manifest_path" \
178
+ --arg manifest_status "$manifest_status" \
179
+ --argjson manifest_checked "${manifest_checked:-0}" \
180
+ --argjson manifest_missing "${manifest_missing:-0}" \
181
+ --argjson manifest_drift "${manifest_drift:-0}" \
182
+ --arg manifest_version "${manifest_version:-}" \
183
+ --arg manifest_action "${manifest_action:-}" \
184
+ --arg manifest_generated_at "${manifest_generated_at:-}" \
185
+ --arg manifest_package_dir "${manifest_package_dir:-}" \
186
+ '{overall:$overall, package_dir:$package_dir, runtime_dir:$runtime_dir, db:$db,
187
+ versions:{package:$package_version, installed:$installed_version},
188
+ sqlite:{path:$sqlite_bin, version:$sqlite_version, fts5:$sqlite_fts5},
189
+ hooks:{claude:$claude_hooks, codex:$codex_hooks, statusline:$statusline},
190
+ runtime_drift:{hooks:$hooks_cmp, lib:$lib_cmp, db:$db_cmp, scripts:$scripts_cmp},
191
+ manifest:{path:$manifest_path, status:$manifest_status, checked:$manifest_checked,
192
+ missing:$manifest_missing, drift:$manifest_drift, version:$manifest_version,
193
+ action:$manifest_action, generated_at:$manifest_generated_at,
194
+ package_dir:$manifest_package_dir}}'
195
+ exit 0
196
+ fi
197
+
198
+ eagle_header "Doctor"
199
+ echo -e " ${BOLD}Overall:${RESET} $overall"
200
+ echo ""
201
+ echo -e " ${BOLD}Install footprint${RESET}"
202
+ eagle_kv "Package:" "$PACKAGE_DIR"
203
+ eagle_kv "Runtime:" "$EAGLE_MEM_DIR"
204
+ eagle_kv "Database:" "$EAGLE_MEM_DB"
205
+ eagle_kv "Version:" "package $package_version, installed $installed_version"
206
+ echo ""
207
+
208
+ echo -e " ${BOLD}SQLite${RESET}"
209
+ if [ -n "$sqlite_bin" ]; then
210
+ eagle_kv "Binary:" "$sqlite_bin"
211
+ eagle_kv "Version:" "${sqlite_version:-unknown}"
212
+ if [ "$sqlite_fts5" = true ]; then
213
+ eagle_ok "FTS5 available"
214
+ else
215
+ eagle_fail "FTS5 unavailable"
216
+ fi
217
+ else
218
+ eagle_fail "SQLite not found"
219
+ fi
220
+ echo ""
221
+
222
+ echo -e " ${BOLD}Hooks${RESET}"
223
+ eagle_kv "Claude Code:" "$claude_hooks"
224
+ eagle_kv "Codex:" "$codex_hooks"
225
+ eagle_kv "Statusline:" "$statusline_state"
226
+ echo ""
227
+
228
+ echo -e " ${BOLD}Install manifest${RESET}"
229
+ eagle_kv "Path:" "$manifest_path"
230
+ if [ "$manifest_status" = "ok" ]; then
231
+ eagle_ok "${manifest_checked:-0} files match manifest"
232
+ else
233
+ eagle_warn "status=$manifest_status, checked=${manifest_checked:-0}, missing=${manifest_missing:-0}, drift=${manifest_drift:-0}"
234
+ fi
235
+ [ -n "$manifest_version" ] && eagle_kv "Version:" "$manifest_version"
236
+ [ -n "$manifest_action" ] && eagle_kv "Action:" "$manifest_action"
237
+ [ -n "$manifest_generated_at" ] && eagle_kv "Generated:" "$manifest_generated_at"
238
+ echo ""
239
+
240
+ echo -e " ${BOLD}Runtime drift${RESET}"
241
+ for label_row in "hooks:$hooks_cmp" "lib:$lib_cmp" "db:$db_cmp" "scripts:$scripts_cmp"; do
242
+ label="${label_row%%:*}"
243
+ row="${label_row#*:}"
244
+ IFS='|' read -r checked missing drift <<< "$row"
245
+ if [ "$missing" -eq 0 ] && [ "$drift" -eq 0 ] 2>/dev/null; then
246
+ eagle_ok "$label: $checked checked, no drift"
247
+ else
248
+ eagle_warn "$label: $checked checked, $missing missing, $drift drifted"
249
+ fi
250
+ done
251
+ echo ""
252
+
253
+ echo -e " ${BOLD}Next${RESET}"
254
+ if [ "$overall" = "Healthy" ]; then
255
+ eagle_ok "Installed runtime matches this package."
256
+ else
257
+ [ "$runtime_exists" != true ] && eagle_info "Run: eagle-mem install"
258
+ [ "$manifest_status" != "ok" ] && eagle_info "Run: eagle-mem update to refresh the install manifest."
259
+ [ "$sum_missing" -gt 0 ] || [ "$sum_drift" -gt 0 ] && eagle_info "Run: eagle-mem update"
260
+ [ "$sqlite_fts5" != true ] && eagle_info "Set EAGLE_SQLITE_BIN to an FTS5-capable sqlite3."
261
+ fi
262
+ echo ""