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/README.md +31 -4
- package/architecture.html +1791 -0
- package/bin/eagle-mem +1 -0
- package/db/migrate.sh +13 -2
- package/docs/visible-surface-ux-contract.md +53 -0
- package/hooks/post-tool-use.sh +2 -2
- package/hooks/pre-tool-use.sh +6 -6
- package/hooks/session-start.sh +216 -9
- package/hooks/user-prompt-submit.sh +72 -15
- package/lib/common.sh +485 -35
- package/lib/db-mirrors.sh +9 -18
- package/lib/db-sessions.sh +22 -15
- package/lib/db-summaries.sh +19 -7
- package/lib/hooks-posttool.sh +44 -10
- package/lib/updater.sh +12 -0
- package/package.json +4 -2
- package/scripts/doctor.sh +262 -0
- package/scripts/feature.sh +37 -11
- package/scripts/health.sh +10 -7
- package/scripts/help.sh +1 -0
- package/scripts/install.sh +9 -3
- package/scripts/memories.sh +258 -65
- package/scripts/orchestrate.sh +2 -2
- package/scripts/search.sh +240 -56
- package/scripts/statusline-em.sh +130 -26
- package/scripts/uninstall.sh +67 -0
- package/scripts/update.sh +8 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
package/lib/db-sessions.sh
CHANGED
|
@@ -167,24 +167,31 @@ eagle_abandon_stale_sessions() {
|
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
eagle_get_project_stats() {
|
|
170
|
-
local
|
|
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
|
|
173
|
-
SELECT 'sessions_claude|' || COUNT(*) FROM sessions WHERE
|
|
174
|
-
SELECT 'sessions_codex|' || COUNT(*) FROM sessions WHERE
|
|
175
|
-
SELECT 'summaries|' || COUNT(*) FROM summaries WHERE
|
|
176
|
-
SELECT 'with_summaries|' || COUNT(*) FROM summaries WHERE
|
|
177
|
-
SELECT 'memories|' || COUNT(*) FROM agent_memories WHERE
|
|
178
|
-
SELECT 'plans|' || COUNT(*) FROM agent_plans WHERE
|
|
179
|
-
SELECT 'tasks_pending|' || COUNT(*) FROM agent_tasks WHERE
|
|
180
|
-
SELECT 'tasks_progress|' || COUNT(*) FROM agent_tasks WHERE
|
|
181
|
-
SELECT 'tasks_done|' || COUNT(*) FROM agent_tasks WHERE
|
|
182
|
-
SELECT 'chunks|' || COUNT(*) FROM code_chunks WHERE
|
|
183
|
-
SELECT 'observations|' || COUNT(*) FROM observations WHERE
|
|
184
|
-
SELECT 'last_active|' || COALESCE(MAX(date(COALESCE(last_activity_at, started_at))), 'never') FROM sessions WHERE
|
|
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
|
|
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
|
package/lib/db-summaries.sh
CHANGED
|
@@ -57,12 +57,16 @@ SQL
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
eagle_get_recent_summaries() {
|
|
60
|
-
local
|
|
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
|
|
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
|
|
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 "$
|
|
81
|
-
|
|
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
|
|
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
|
|
package/lib/hooks-posttool.sh
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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.
|
|
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 ""
|