eagle-mem 3.2.0 → 4.0.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.
- package/README.md +45 -131
- package/bin/eagle-mem +2 -11
- package/db/020_drop_seeded_rules.sql +12 -0
- package/hooks/post-tool-use.sh +20 -1
- package/hooks/pre-tool-use.sh +115 -69
- package/hooks/session-start.sh +53 -69
- package/lib/db-observations.sh +2 -1
- package/lib/db-sessions.sh +3 -3
- package/lib/hooks-sessionstart.sh +89 -0
- package/lib/hooks.sh +9 -1
- package/lib/provider.sh +2 -2
- package/package.json +1 -1
- package/scripts/curate.sh +7 -0
- package/scripts/help.sh +8 -20
- package/scripts/install.sh +5 -1
- package/scripts/update.sh +1 -0
- package/skills/eagle-mem-overview/SKILL.md +8 -8
- package/skills/eagle-mem-index/SKILL.md +0 -120
- package/skills/eagle-mem-prune/SKILL.md +0 -131
- package/skills/eagle-mem-scan/SKILL.md +0 -116
package/hooks/session-start.sh
CHANGED
|
@@ -13,6 +13,7 @@ SCRIPTS_DIR="$SCRIPT_DIR/../scripts"
|
|
|
13
13
|
. "$LIB_DIR/common.sh"
|
|
14
14
|
. "$LIB_DIR/db.sh"
|
|
15
15
|
. "$LIB_DIR/provider.sh"
|
|
16
|
+
. "$LIB_DIR/hooks-sessionstart.sh"
|
|
16
17
|
|
|
17
18
|
eagle_ensure_db
|
|
18
19
|
|
|
@@ -27,8 +28,6 @@ model=$(echo "$input" | jq -r '.model // empty')
|
|
|
27
28
|
[ -z "$session_id" ] && exit 0
|
|
28
29
|
|
|
29
30
|
project=$(eagle_project_from_cwd "$cwd")
|
|
30
|
-
|
|
31
|
-
# Skip ephemeral directories (tmp, Downloads, etc.) — no tracking
|
|
32
31
|
[ -z "$project" ] && exit 0
|
|
33
32
|
|
|
34
33
|
p_esc=$(eagle_sql_escape "$project")
|
|
@@ -36,32 +35,18 @@ p_esc=$(eagle_sql_escape "$project")
|
|
|
36
35
|
eagle_log "INFO" "SessionStart: session=$session_id project=$project source=$source_type"
|
|
37
36
|
|
|
38
37
|
eagle_upsert_session "$session_id" "$project" "$cwd" "$model" "$source_type"
|
|
39
|
-
|
|
40
|
-
# ─── Sweep stuck sessions (no activity for 7 days) ─────────
|
|
41
|
-
# Uses last_activity_at (updated by trigger on every observation insert)
|
|
42
|
-
# so long-lived sessions with regular compactions aren't falsely abandoned
|
|
43
38
|
eagle_abandon_stale_sessions "$session_id"
|
|
44
39
|
|
|
45
|
-
# ───
|
|
46
|
-
# Moved here from SessionEnd because SessionEnd rarely fires in long-lived sessions.
|
|
47
|
-
# SessionStart fires on every new session, resume, and compaction — reliable trigger.
|
|
48
|
-
curator_schedule=$(eagle_config_get "curator" "schedule" "manual")
|
|
49
|
-
if [ "$curator_schedule" = "auto" ]; then
|
|
50
|
-
_curator_provider=$(eagle_config_get "provider" "type" "none")
|
|
51
|
-
if [ "$_curator_provider" != "none" ]; then
|
|
52
|
-
_min_sessions=$(eagle_config_get "curator" "min_sessions" "5")
|
|
53
|
-
_min_sessions=$(eagle_sql_int "$_min_sessions")
|
|
54
|
-
_last_curated=$(eagle_meta_get "last_curated_at" "$project")
|
|
55
|
-
_since="${_last_curated:-1970-01-01T00:00:00Z}"
|
|
56
|
-
_sessions_since=$(eagle_count_sessions_since "$project" "$_since")
|
|
57
|
-
if [ "${_sessions_since:-0}" -ge "$_min_sessions" ]; then
|
|
58
|
-
eagle_log "INFO" "SessionStart: auto-curate triggered (${_sessions_since} sessions since last curate)"
|
|
59
|
-
nohup bash "$SCRIPTS_DIR/curate.sh" -p "$project" >> "$EAGLE_MEM_LOG" 2>&1 &
|
|
60
|
-
fi
|
|
61
|
-
fi
|
|
62
|
-
fi
|
|
40
|
+
# ─── Background automation (non-blocking) ────────────────
|
|
63
41
|
|
|
64
|
-
|
|
42
|
+
eagle_sessionstart_auto_provision "$project" "$cwd" "$SCRIPTS_DIR"
|
|
43
|
+
eagle_sessionstart_auto_prune "$project" "$SCRIPTS_DIR" "$(eagle_db "SELECT COUNT(*) FROM observations WHERE session_id IN (SELECT id FROM sessions WHERE project='$p_esc');")"
|
|
44
|
+
eagle_sessionstart_auto_curate "$project" "$SCRIPTS_DIR"
|
|
45
|
+
|
|
46
|
+
find "$EAGLE_MEM_DIR/read-tracker" -type f -mtime +1 -delete 2>/dev/null &
|
|
47
|
+
find "$EAGLE_MEM_DIR/mod-tracker" -type f -mtime +1 -delete 2>/dev/null &
|
|
48
|
+
|
|
49
|
+
# ─── Version check (non-blocking) ────────────────────────
|
|
65
50
|
|
|
66
51
|
update_notice=""
|
|
67
52
|
version_file="$EAGLE_MEM_DIR/.version"
|
|
@@ -89,7 +74,7 @@ if [ -f "$version_file" ] && [ -s "$version_file" ]; then
|
|
|
89
74
|
fi
|
|
90
75
|
fi
|
|
91
76
|
|
|
92
|
-
# ─── Gather stats
|
|
77
|
+
# ─── Gather stats ────────────────────────────────────────
|
|
93
78
|
|
|
94
79
|
stat_sessions=0; stat_summaries=0; stat_with_summaries=0; stat_memories=0
|
|
95
80
|
stat_tasks_pending=0; stat_tasks_progress=0; stat_tasks_done=0
|
|
@@ -113,7 +98,8 @@ while IFS='|' read -r key val; do
|
|
|
113
98
|
esac
|
|
114
99
|
done <<< "$(eagle_get_project_stats "$project")"
|
|
115
100
|
|
|
116
|
-
# Build
|
|
101
|
+
# ─── Build compressed banner (elide zero-value lines) ────
|
|
102
|
+
|
|
117
103
|
task_parts=""
|
|
118
104
|
[ "$stat_tasks_progress" -gt 0 ] && task_parts="${stat_tasks_progress} in progress"
|
|
119
105
|
if [ "$stat_tasks_pending" -gt 0 ]; then
|
|
@@ -124,27 +110,26 @@ if [ "$stat_tasks_done" -gt 0 ]; then
|
|
|
124
110
|
[ -n "$task_parts" ] && task_parts+=", "
|
|
125
111
|
task_parts+="${stat_tasks_done} completed"
|
|
126
112
|
fi
|
|
127
|
-
[ -z "$task_parts" ] && task_parts="none"
|
|
128
113
|
|
|
129
|
-
# Truncate last summary for display
|
|
130
114
|
stat_last_display="${stat_last_summary:0:60}"
|
|
131
115
|
[ ${#stat_last_summary} -gt 60 ] && stat_last_display+="..."
|
|
132
|
-
[ -z "$stat_last_display" ] && stat_last_display="(no sessions yet)"
|
|
133
|
-
|
|
134
|
-
# ─── Build context injection ────────────────────────────────
|
|
135
116
|
|
|
136
117
|
eagle_banner="======================================
|
|
137
118
|
Eagle Mem Loaded
|
|
138
119
|
======================================
|
|
139
120
|
Project | $project
|
|
140
|
-
Sessions | $stat_sessions
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
121
|
+
Sessions | $stat_sessions ($stat_with_summaries with summaries)"
|
|
122
|
+
[ "$stat_memories" -gt 0 ] && eagle_banner+="
|
|
123
|
+
Memories | $stat_memories stored"
|
|
124
|
+
[ "$stat_plans" -gt 0 ] && eagle_banner+="
|
|
125
|
+
Plans | $stat_plans saved"
|
|
126
|
+
[ -n "$task_parts" ] && eagle_banner+="
|
|
127
|
+
Tasks | $task_parts"
|
|
128
|
+
[ "$stat_chunks" -gt 0 ] && eagle_banner+="
|
|
129
|
+
Code Index | $stat_chunks chunks"
|
|
130
|
+
[ -n "$stat_last_display" ] && eagle_banner+="
|
|
131
|
+
Last Work | $stat_last_display"
|
|
132
|
+
eagle_banner+="
|
|
148
133
|
======================================"
|
|
149
134
|
|
|
150
135
|
context="$eagle_banner
|
|
@@ -156,22 +141,33 @@ if [ -n "$update_notice" ]; then
|
|
|
156
141
|
"
|
|
157
142
|
fi
|
|
158
143
|
|
|
159
|
-
# Project overview
|
|
144
|
+
# ─── Project overview (capped at 500 chars) ──────────────
|
|
145
|
+
|
|
160
146
|
overview=$(eagle_get_overview "$project")
|
|
161
147
|
if [ -n "$overview" ]; then
|
|
148
|
+
if [ ${#overview} -gt 500 ]; then
|
|
149
|
+
overview="${overview:0:497}..."
|
|
150
|
+
fi
|
|
162
151
|
context+="
|
|
163
|
-
===
|
|
152
|
+
=== Overview ===
|
|
164
153
|
$overview
|
|
165
154
|
"
|
|
166
155
|
else
|
|
167
156
|
context+="
|
|
168
|
-
===
|
|
169
|
-
No overview
|
|
157
|
+
=== New Project ===
|
|
158
|
+
No overview yet — auto-scan is running. Run /eagle-mem-overview for a richer briefing.
|
|
170
159
|
"
|
|
171
160
|
fi
|
|
172
161
|
|
|
173
|
-
# Recent
|
|
174
|
-
|
|
162
|
+
# ─── Recent sessions (1 on compact, 3 on startup) ────────
|
|
163
|
+
|
|
164
|
+
if [ "$source_type" = "compact" ] || [ "$source_type" = "clear" ]; then
|
|
165
|
+
_summary_limit=1
|
|
166
|
+
else
|
|
167
|
+
_summary_limit=3
|
|
168
|
+
fi
|
|
169
|
+
|
|
170
|
+
recent=$(eagle_get_recent_summaries "$project" "$_summary_limit")
|
|
175
171
|
|
|
176
172
|
if [ -n "$recent" ]; then
|
|
177
173
|
context+="
|
|
@@ -200,7 +196,7 @@ Next steps: $next_steps"
|
|
|
200
196
|
"
|
|
201
197
|
fi
|
|
202
198
|
|
|
203
|
-
# ───
|
|
199
|
+
# ─── Memories (skip if none) ─────────────────────────────
|
|
204
200
|
|
|
205
201
|
memories=$(eagle_db "SELECT memory_name, memory_type, description, file_path, updated_at,
|
|
206
202
|
CAST(julianday('now') - julianday(updated_at) AS INTEGER) as days_ago
|
|
@@ -229,7 +225,7 @@ if [ -n "$memories" ]; then
|
|
|
229
225
|
done <<< "$memories"
|
|
230
226
|
fi
|
|
231
227
|
|
|
232
|
-
# ───
|
|
228
|
+
# ─── Plans (skip if none) ────────────────────────────────
|
|
233
229
|
|
|
234
230
|
plans=$(eagle_list_claude_plans "$project" 3)
|
|
235
231
|
if [ -n "$plans" ]; then
|
|
@@ -243,7 +239,7 @@ if [ -n "$plans" ]; then
|
|
|
243
239
|
done <<< "$plans"
|
|
244
240
|
fi
|
|
245
241
|
|
|
246
|
-
# ───
|
|
242
|
+
# ─── Tasks (skip if none) ────────────────────────────────
|
|
247
243
|
|
|
248
244
|
synced_tasks=$(eagle_db "SELECT subject, status, blocked_by FROM claude_tasks
|
|
249
245
|
WHERE project = '$p_esc'
|
|
@@ -268,39 +264,27 @@ if [ -n "$synced_tasks" ]; then
|
|
|
268
264
|
done <<< "$synced_tasks"
|
|
269
265
|
fi
|
|
270
266
|
|
|
271
|
-
# ─── Instructions (
|
|
267
|
+
# ─── Instructions (compressed) ───────────────────────────
|
|
272
268
|
|
|
273
269
|
if [ "$source_type" = "compact" ] || [ "$source_type" = "clear" ]; then
|
|
274
270
|
context+="
|
|
275
|
-
=== Eagle Mem
|
|
276
|
-
|
|
271
|
+
=== Eagle Mem ===
|
|
272
|
+
Memory active. Attribute recalled context to Eagle Mem. Do not revert PostToolUse-surfaced decisions without asking. Emit <eagle-summary> before final response.
|
|
277
273
|
"
|
|
278
274
|
else
|
|
279
275
|
context+="
|
|
280
276
|
=== Eagle Mem ===
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
Emit an <eagle-summary> block before your FINAL response:
|
|
277
|
+
Memory active for '$project'. Scan, index, prune, and self-learning run automatically — never ask the user to run these. Attribute recalled context: \"Eagle Mem recalls:\" Do not revert PostToolUse-surfaced decisions without user request. No raw secrets in summaries. If you contradict a loaded memory, update the memory file.
|
|
284
278
|
|
|
279
|
+
Before your final response, emit:
|
|
285
280
|
<eagle-summary>
|
|
286
|
-
request: [what
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
completed: [what shipped — be specific]
|
|
290
|
-
next_steps: [concrete actions for next session]
|
|
291
|
-
decisions:
|
|
292
|
-
- [choice] Why: [reason]
|
|
293
|
-
gotchas:
|
|
294
|
-
- [what failed or surprised — \"X doesn't work because Y\"]
|
|
295
|
-
key_files:
|
|
296
|
-
- [path] — [role in this work]
|
|
297
|
-
files_read: [file1, file2]
|
|
298
|
-
files_modified: [file1, file2]
|
|
281
|
+
request: [what user asked] | completed: [what shipped] | learned: [non-obvious discoveries]
|
|
282
|
+
next_steps: [concrete actions] | decisions: [choice — why] | gotchas: [what surprised]
|
|
283
|
+
key_files: [path — role] | files_read: [...] | files_modified: [...]
|
|
299
284
|
</eagle-summary>
|
|
300
285
|
"
|
|
301
286
|
fi
|
|
302
287
|
|
|
303
|
-
# Output context (plain text stdout = additionalContext for SessionStart)
|
|
304
288
|
if [ -n "$context" ]; then
|
|
305
289
|
echo "$context"
|
|
306
290
|
fi
|
package/lib/db-observations.sh
CHANGED
|
@@ -52,7 +52,8 @@ eagle_get_command_rule() {
|
|
|
52
52
|
WHERE enabled = 1
|
|
53
53
|
AND (project = '$project' OR project = '')
|
|
54
54
|
AND ('$cmd' LIKE pattern OR '$cmd' = pattern)
|
|
55
|
-
ORDER BY CASE WHEN project != '' THEN 0 ELSE 1 END
|
|
55
|
+
ORDER BY CASE WHEN project != '' THEN 0 ELSE 1 END,
|
|
56
|
+
LENGTH(pattern) DESC
|
|
56
57
|
LIMIT 1;"
|
|
57
58
|
}
|
|
58
59
|
|
package/lib/db-sessions.sh
CHANGED
|
@@ -15,9 +15,9 @@ eagle_upsert_session() {
|
|
|
15
15
|
eagle_db "INSERT INTO sessions (id, project, cwd, model, source, last_activity_at)
|
|
16
16
|
VALUES ('$session_id', '$project', '$cwd', '$model', '$source', strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
|
17
17
|
ON CONFLICT(id) DO UPDATE SET
|
|
18
|
-
cwd = COALESCE(excluded.cwd, sessions.cwd),
|
|
19
|
-
model = COALESCE(excluded.model, sessions.model),
|
|
20
|
-
source = COALESCE(excluded.source, sessions.source),
|
|
18
|
+
cwd = COALESCE(NULLIF(excluded.cwd, ''), sessions.cwd),
|
|
19
|
+
model = COALESCE(NULLIF(excluded.model, ''), sessions.model),
|
|
20
|
+
source = COALESCE(NULLIF(excluded.source, ''), sessions.source),
|
|
21
21
|
status = 'active',
|
|
22
22
|
last_activity_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now');"
|
|
23
23
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ═══════════════════════════════════════════════════════════
|
|
3
|
+
# Eagle Mem — SessionStart extracted automation
|
|
4
|
+
# Source from hooks/session-start.sh after common.sh + db.sh
|
|
5
|
+
# ═══════════════════════════════════════════════════════════
|
|
6
|
+
[ -n "${_EAGLE_HOOKS_SESSIONSTART_LOADED:-}" ] && return 0
|
|
7
|
+
_EAGLE_HOOKS_SESSIONSTART_LOADED=1
|
|
8
|
+
|
|
9
|
+
_state_dir="$EAGLE_MEM_DIR/state"
|
|
10
|
+
|
|
11
|
+
_eagle_state_fresh() {
|
|
12
|
+
local key="$1" project="$2" max_age_days="${3:-1}"
|
|
13
|
+
local state_file="$_state_dir/${key}-${project}"
|
|
14
|
+
[ -f "$state_file" ] && [ -z "$(find "$state_file" -mtime +${max_age_days} 2>/dev/null)" ]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
_eagle_state_touch() {
|
|
18
|
+
local key="$1" project="$2"
|
|
19
|
+
mkdir -p "$_state_dir" 2>/dev/null
|
|
20
|
+
touch "$_state_dir/${key}-${project}"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
eagle_sessionstart_auto_provision() {
|
|
24
|
+
local project="$1" cwd="$2" scripts_dir="$3"
|
|
25
|
+
local needs_scan=false needs_index=false
|
|
26
|
+
|
|
27
|
+
# Auto-scan: no overview exists
|
|
28
|
+
local overview
|
|
29
|
+
overview=$(eagle_get_overview "$project")
|
|
30
|
+
if [ -z "$overview" ] && ! _eagle_state_fresh "scan" "$project" 1; then
|
|
31
|
+
needs_scan=true
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# Auto-index: 0 chunks or stale > 7 days
|
|
35
|
+
local chunk_count
|
|
36
|
+
chunk_count=$(eagle_db "SELECT COUNT(*) FROM code_chunks WHERE project = '$(eagle_sql_escape "$project")';" 2>/dev/null)
|
|
37
|
+
chunk_count=${chunk_count:-0}
|
|
38
|
+
if [ "$chunk_count" -eq 0 ] && ! _eagle_state_fresh "index" "$project" 1; then
|
|
39
|
+
needs_index=true
|
|
40
|
+
elif [ "$chunk_count" -gt 0 ] && ! _eagle_state_fresh "index" "$project" 7; then
|
|
41
|
+
needs_index=true
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
if [ "$needs_scan" = true ] && [ "$needs_index" = true ]; then
|
|
45
|
+
eagle_log "INFO" "SessionStart: first-session provision — scan then index"
|
|
46
|
+
_eagle_state_touch "scan" "$project"
|
|
47
|
+
_eagle_state_touch "index" "$project"
|
|
48
|
+
nohup bash -c "bash '$scripts_dir/scan.sh' '$cwd' >> '$EAGLE_MEM_LOG' 2>&1; bash '$scripts_dir/index.sh' '$cwd' >> '$EAGLE_MEM_LOG' 2>&1" &
|
|
49
|
+
elif [ "$needs_scan" = true ]; then
|
|
50
|
+
eagle_log "INFO" "SessionStart: auto-scan triggered"
|
|
51
|
+
_eagle_state_touch "scan" "$project"
|
|
52
|
+
nohup bash "$scripts_dir/scan.sh" "$cwd" >> "$EAGLE_MEM_LOG" 2>&1 &
|
|
53
|
+
elif [ "$needs_index" = true ]; then
|
|
54
|
+
eagle_log "INFO" "SessionStart: auto-index triggered"
|
|
55
|
+
_eagle_state_touch "index" "$project"
|
|
56
|
+
nohup bash "$scripts_dir/index.sh" "$cwd" >> "$EAGLE_MEM_LOG" 2>&1 &
|
|
57
|
+
fi
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
eagle_sessionstart_auto_prune() {
|
|
61
|
+
local project="$1" scripts_dir="$2" observation_count="$3"
|
|
62
|
+
if [ "${observation_count:-0}" -gt 10000 ] && ! _eagle_state_fresh "prune" "$project" 1; then
|
|
63
|
+
eagle_log "INFO" "SessionStart: auto-prune triggered (${observation_count} observations)"
|
|
64
|
+
_eagle_state_touch "prune" "$project"
|
|
65
|
+
nohup bash "$scripts_dir/prune.sh" -p "$project" >> "$EAGLE_MEM_LOG" 2>&1 &
|
|
66
|
+
fi
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
eagle_sessionstart_auto_curate() {
|
|
70
|
+
local project="$1" scripts_dir="$2"
|
|
71
|
+
local curator_schedule
|
|
72
|
+
curator_schedule=$(eagle_config_get "curator" "schedule" "manual")
|
|
73
|
+
if [ "$curator_schedule" = "auto" ]; then
|
|
74
|
+
local _provider
|
|
75
|
+
_provider=$(eagle_config_get "provider" "type" "none")
|
|
76
|
+
if [ "$_provider" != "none" ]; then
|
|
77
|
+
local _min_sessions _last_curated _since _sessions_since
|
|
78
|
+
_min_sessions=$(eagle_config_get "curator" "min_sessions" "5")
|
|
79
|
+
_min_sessions=$(eagle_sql_int "$_min_sessions")
|
|
80
|
+
_last_curated=$(eagle_meta_get "last_curated_at" "$project")
|
|
81
|
+
_since="${_last_curated:-1970-01-01T00:00:00Z}"
|
|
82
|
+
_sessions_since=$(eagle_count_sessions_since "$project" "$_since")
|
|
83
|
+
if [ "${_sessions_since:-0}" -ge "$_min_sessions" ]; then
|
|
84
|
+
eagle_log "INFO" "SessionStart: auto-curate triggered (${_sessions_since} sessions since last curate)"
|
|
85
|
+
nohup bash "$scripts_dir/curate.sh" -p "$project" >> "$EAGLE_MEM_LOG" 2>&1 &
|
|
86
|
+
fi
|
|
87
|
+
fi
|
|
88
|
+
fi
|
|
89
|
+
}
|
package/lib/hooks.sh
CHANGED
|
@@ -11,7 +11,15 @@ eagle_patch_hook() {
|
|
|
11
11
|
local command="$4"
|
|
12
12
|
local description="${5:-}"
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
# Check both command AND matcher to avoid skipping entries with different matchers
|
|
15
|
+
# (e.g. PreToolUse with "Bash" vs "Read" matcher using the same script)
|
|
16
|
+
local match_query
|
|
17
|
+
if [ -n "$matcher" ]; then
|
|
18
|
+
match_query=".hooks.${event}[]? | select(.matcher == \"$matcher\" and (.hooks[]?.command == \"$command\"))"
|
|
19
|
+
else
|
|
20
|
+
match_query=".hooks.${event}[]? | select(.matcher == null and (.hooks[]?.command == \"$command\"))"
|
|
21
|
+
fi
|
|
22
|
+
if jq -e "$match_query" "$settings" &>/dev/null; then
|
|
15
23
|
[ -n "$description" ] && eagle_ok "$description ${DIM}(already registered)${RESET}"
|
|
16
24
|
return 0
|
|
17
25
|
fi
|
package/lib/provider.sh
CHANGED
|
@@ -151,8 +151,8 @@ model = "claude-haiku-4-5-20251001"
|
|
|
151
151
|
model = "gpt-4o-mini"
|
|
152
152
|
|
|
153
153
|
[curator]
|
|
154
|
-
# "auto" = triggers at session
|
|
155
|
-
schedule = "
|
|
154
|
+
# "auto" = triggers at session start after min_sessions; "manual" = CLI only
|
|
155
|
+
schedule = "auto"
|
|
156
156
|
min_sessions = 5
|
|
157
157
|
|
|
158
158
|
[redaction]
|
package/package.json
CHANGED
package/scripts/curate.sh
CHANGED
|
@@ -222,6 +222,13 @@ If no rules needed, output: NONE"
|
|
|
222
222
|
eagle_log "WARN" "Curator: skipping RULE with invalid strategy '$strategy'"
|
|
223
223
|
continue
|
|
224
224
|
;; esac
|
|
225
|
+
# Guard: reject dangerous LLM-generated patterns that match everything
|
|
226
|
+
# Require at least 2 literal characters (not just wildcards)
|
|
227
|
+
_literal_chars=$(printf '%s' "$pattern" | sed 's/[%_]//g')
|
|
228
|
+
if [ ${#_literal_chars} -lt 2 ]; then
|
|
229
|
+
eagle_log "WARN" "Curator: skipping overly broad pattern '$pattern' (needs >=2 literal chars)"
|
|
230
|
+
continue
|
|
231
|
+
fi
|
|
225
232
|
|
|
226
233
|
[ "$max_lines" = "-" ] && max_lines=""
|
|
227
234
|
|
package/scripts/help.sh
CHANGED
|
@@ -16,38 +16,26 @@ echo -e " ${BOLD}Eagle Mem${RESET} ${DIM}v${version}${RESET}"
|
|
|
16
16
|
echo -e " ${DIM}Persistent memory for Claude Code${RESET}"
|
|
17
17
|
echo ""
|
|
18
18
|
echo -e " ${BOLD}Commands:${RESET}"
|
|
19
|
-
echo -e " ${CYAN}search${RESET} Search past sessions, memories, and code"
|
|
20
|
-
echo -e " ${CYAN}overview${RESET} View or set project overview"
|
|
21
|
-
echo -e " ${CYAN}scan${RESET} Analyze codebase structure"
|
|
22
|
-
echo -e " ${CYAN}index${RESET} Index source files for FTS5 code search"
|
|
23
|
-
echo -e " ${CYAN}memories${RESET} View/sync mirrored Claude Code memories"
|
|
24
|
-
echo -e " ${CYAN}tasks${RESET} View mirrored Claude Code tasks"
|
|
25
|
-
echo -e " ${CYAN}refresh${RESET} Full project sync: scan (if needed) + index + memories"
|
|
26
|
-
echo -e " ${CYAN}prune${RESET} Remove old observations and orphaned chunks"
|
|
27
|
-
echo -e " ${CYAN}config${RESET} View or change LLM provider settings"
|
|
28
|
-
echo -e " ${CYAN}curate${RESET} Run the self-learning curator (LLM-powered analysis)"
|
|
29
|
-
echo -e " ${CYAN}feature${RESET} Manage feature graph (list/show/verify/add)"
|
|
30
|
-
echo -e " ${CYAN}health${RESET} Diagnose self-learning pipeline health"
|
|
31
19
|
echo -e " ${CYAN}install${RESET} First-time setup: hooks, database, skills"
|
|
32
20
|
echo -e " ${CYAN}update${RESET} Re-deploy hooks and run migrations"
|
|
33
21
|
echo -e " ${CYAN}uninstall${RESET} Remove hooks and optionally delete data"
|
|
22
|
+
echo -e " ${CYAN}search${RESET} Search past sessions, memories, and code"
|
|
23
|
+
echo -e " ${CYAN}health${RESET} Diagnose pipeline health and background automation"
|
|
24
|
+
echo -e " ${CYAN}config${RESET} View or change LLM provider settings"
|
|
34
25
|
echo ""
|
|
35
26
|
echo -e " ${BOLD}Examples:${RESET}"
|
|
36
|
-
echo -e " ${DIM}\$${RESET} eagle-mem search \"auth bug\" ${DIM}# keyword search${RESET}"
|
|
37
|
-
echo -e " ${DIM}\$${RESET} eagle-mem search --timeline ${DIM}# recent sessions${RESET}"
|
|
38
|
-
echo -e " ${DIM}\$${RESET} eagle-mem refresh ${DIM}# full project sync${RESET}"
|
|
39
|
-
echo -e " ${DIM}\$${RESET} eagle-mem overview ${DIM}# view overview${RESET}"
|
|
40
|
-
echo -e " ${DIM}\$${RESET} eagle-mem prune --dry-run ${DIM}# preview cleanup${RESET}"
|
|
41
27
|
echo -e " ${DIM}\$${RESET} eagle-mem install ${DIM}# first-time setup${RESET}"
|
|
28
|
+
echo -e " ${DIM}\$${RESET} eagle-mem search \"auth bug\" ${DIM}# keyword search${RESET}"
|
|
29
|
+
echo -e " ${DIM}\$${RESET} eagle-mem health ${DIM}# check pipeline${RESET}"
|
|
42
30
|
echo ""
|
|
43
31
|
echo -e " ${BOLD}Skills${RESET} ${DIM}(inside Claude Code sessions):${RESET}"
|
|
44
32
|
echo -e " ${CYAN}/eagle-mem-search${RESET} Search memory and past sessions"
|
|
45
33
|
echo -e " ${CYAN}/eagle-mem-overview${RESET} Build or update project overview"
|
|
46
|
-
echo -e " ${CYAN}/eagle-mem-scan${RESET} Analyze codebase structure"
|
|
47
|
-
echo -e " ${CYAN}/eagle-mem-index${RESET} Index source files for code search"
|
|
48
34
|
echo -e " ${CYAN}/eagle-mem-memories${RESET} View/sync Claude Code memories"
|
|
49
35
|
echo -e " ${CYAN}/eagle-mem-tasks${RESET} TaskAware Compact Loop for multi-step work"
|
|
50
|
-
echo
|
|
36
|
+
echo ""
|
|
37
|
+
echo -e " ${DIM}Everything else is automatic — scan, index, prune, and${RESET}"
|
|
38
|
+
echo -e " ${DIM}curator all run in the background via hooks.${RESET}"
|
|
51
39
|
echo ""
|
|
52
40
|
echo -e " ${DIM}https://github.com/eagleisbatman/eagle-mem${RESET}"
|
|
53
41
|
echo ""
|
package/scripts/install.sh
CHANGED
|
@@ -186,7 +186,11 @@ eagle_patch_hook "$SETTINGS" "UserPromptSubmit" "" \
|
|
|
186
186
|
|
|
187
187
|
eagle_patch_hook "$SETTINGS" "PreToolUse" "Bash" \
|
|
188
188
|
"$EAGLE_MEM_DIR/hooks/pre-tool-use.sh" \
|
|
189
|
-
"PreToolUse hook"
|
|
189
|
+
"PreToolUse hook (Bash)"
|
|
190
|
+
|
|
191
|
+
eagle_patch_hook "$SETTINGS" "PreToolUse" "Read" \
|
|
192
|
+
"$EAGLE_MEM_DIR/hooks/pre-tool-use.sh" \
|
|
193
|
+
"PreToolUse hook (Read)"
|
|
190
194
|
|
|
191
195
|
# ─── Install skills ────────────────────────────────────────
|
|
192
196
|
|
package/scripts/update.sh
CHANGED
|
@@ -76,6 +76,7 @@ if [ -f "$SETTINGS" ] && command -v jq &>/dev/null; then
|
|
|
76
76
|
eagle_patch_hook "$SETTINGS" "SessionEnd" "" "$EAGLE_MEM_DIR/hooks/session-end.sh"
|
|
77
77
|
eagle_patch_hook "$SETTINGS" "UserPromptSubmit" "" "$EAGLE_MEM_DIR/hooks/user-prompt-submit.sh"
|
|
78
78
|
eagle_patch_hook "$SETTINGS" "PreToolUse" "Bash" "$EAGLE_MEM_DIR/hooks/pre-tool-use.sh"
|
|
79
|
+
eagle_patch_hook "$SETTINGS" "PreToolUse" "Read" "$EAGLE_MEM_DIR/hooks/pre-tool-use.sh"
|
|
79
80
|
|
|
80
81
|
eagle_ok "Hooks registered"
|
|
81
82
|
fi
|
|
@@ -17,11 +17,12 @@ description: >
|
|
|
17
17
|
## Judgment
|
|
18
18
|
|
|
19
19
|
**Build an overview when:**
|
|
20
|
-
- SessionStart
|
|
21
|
-
- The current overview reads like scan metadata (file counts, directory listings) — upgrade it
|
|
20
|
+
- SessionStart shows a scan-generated overview (file counts, directory listings) — upgrade it to a rich briefing
|
|
22
21
|
- The user asks: "update overview", "summarize this project", "what is this project"
|
|
23
22
|
- The project has changed significantly since the last overview (new major feature, architecture shift)
|
|
24
23
|
|
|
24
|
+
Note: New projects are auto-scanned at SessionStart. The scan produces structural metadata. This skill upgrades that into a briefing with intent, architecture, and current state.
|
|
25
|
+
|
|
25
26
|
**Don't rebuild when:**
|
|
26
27
|
- The overview is already rich and current — just use it
|
|
27
28
|
- The user needs help with a specific task — do the task first, update overview after if needed
|
|
@@ -64,11 +65,7 @@ If the output is empty or doesn't match what you wrote, the save failed. Retry o
|
|
|
64
65
|
|
|
65
66
|
### 4. Fallback for empty repos
|
|
66
67
|
|
|
67
|
-
If no readable source files exist (fresh repo, no README),
|
|
68
|
-
```bash
|
|
69
|
-
eagle-mem scan .
|
|
70
|
-
```
|
|
71
|
-
Then tell the user: "I've generated a structural overview from scan. Once you add a README or source code, re-run `/eagle-mem-overview` for a richer briefing."
|
|
68
|
+
If no readable source files exist (fresh repo, no README), Eagle Mem's auto-scan has already generated a structural overview in the background. Tell the user: "Auto-scan has captured the project structure. Once you add a README or source code, re-run `/eagle-mem-overview` for a richer briefing."
|
|
72
69
|
|
|
73
70
|
## What makes a good overview
|
|
74
71
|
|
|
@@ -86,6 +83,10 @@ A good overview lets a fresh Claude Code context window give useful answers with
|
|
|
86
83
|
**Bad:**
|
|
87
84
|
> eagle-mem: Node.js project (42 files, ~5k lines). Structure: bin/ (1), db/ (10), hooks/ (5), lib/ (3), scripts/ (13), skills/ (7). Entry: bin/eagle-mem. No tests detected.
|
|
88
85
|
|
|
86
|
+
## How automation works
|
|
87
|
+
|
|
88
|
+
Eagle Mem v4.0 auto-scans new projects at SessionStart — no user action needed. This skill exists to *upgrade* that scan into a rich, multi-paragraph briefing that captures intent, architecture, and current state.
|
|
89
|
+
|
|
89
90
|
## Reference
|
|
90
91
|
|
|
91
92
|
```bash
|
|
@@ -93,5 +94,4 @@ eagle-mem overview # view current overview
|
|
|
93
94
|
eagle-mem overview set "..." # save new overview
|
|
94
95
|
eagle-mem overview list # all projects with overviews
|
|
95
96
|
eagle-mem overview delete # remove current project's overview
|
|
96
|
-
eagle-mem scan . # structural scan (fallback only)
|
|
97
97
|
```
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: eagle-mem-index
|
|
3
|
-
description: >
|
|
4
|
-
Index source files into FTS5-searchable chunks. Use when: 'eagle index', 'index this project',
|
|
5
|
-
'make code searchable', 'reindex', 'update index', 'index after pull',
|
|
6
|
-
'code search setup'. Uses the eagle-mem CLI.
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# Eagle Mem — Index
|
|
10
|
-
|
|
11
|
-
## Purpose
|
|
12
|
-
|
|
13
|
-
**For the user:** Claude Code can find code they wrote weeks ago -- across sessions, without needing to remember which file it was in. Every prompt automatically surfaces relevant code chunks from the index, so past work stays accessible.
|
|
14
|
-
|
|
15
|
-
**For you (Claude Code):** The UserPromptSubmit hook searches `code_chunks` on every user prompt and injects matching file references into your context. You don't call a search command for this -- it happens automatically. But the index must exist and be current for it to work. That's what this skill manages.
|
|
16
|
-
|
|
17
|
-
## Judgment
|
|
18
|
-
|
|
19
|
-
**Index when:**
|
|
20
|
-
- First time setting up Eagle Mem on a project (`eagle-mem index .`)
|
|
21
|
-
- After a `git pull` or branch switch that brought in significant changes
|
|
22
|
-
- After a major refactor (renamed files, moved directories, new modules)
|
|
23
|
-
- The user says "index this project" or "make code searchable"
|
|
24
|
-
- You notice the UserPromptSubmit hook isn't surfacing relevant code -- the index may be stale
|
|
25
|
-
|
|
26
|
-
**Don't index when:**
|
|
27
|
-
- You just need to read a specific file -- use Read directly
|
|
28
|
-
- The project was recently indexed and no files changed (mtime dedup handles this, but don't waste time running it)
|
|
29
|
-
- You're in the middle of a task -- index at session boundaries, not mid-work
|
|
30
|
-
|
|
31
|
-
**Decision rule:** Index is a maintenance task, not a search tool. Run it to *prepare* the database, then rely on the automatic hook to *use* it. If the hook isn't surfacing what you expect, the index is stale -- re-run.
|
|
32
|
-
|
|
33
|
-
## Steps
|
|
34
|
-
|
|
35
|
-
### 1. Run the indexer
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
eagle-mem index .
|
|
39
|
-
```
|
|
40
|
-
Indexes the current directory. The script:
|
|
41
|
-
- Collects source files (respects `.gitignore`, skips binaries/lockfiles)
|
|
42
|
-
- Filters to known source extensions (sh, js, ts, py, go, rs, java, etc.)
|
|
43
|
-
- Skips files over 1MB
|
|
44
|
-
- Compares mtime against stored mtime -- only re-indexes changed files
|
|
45
|
-
- Chunks each file into segments (default 80 lines) and inserts into `code_chunks` with FTS5
|
|
46
|
-
|
|
47
|
-
The mtime check makes re-running cheap. On a project with 200 files where 5 changed, it processes only those 5.
|
|
48
|
-
|
|
49
|
-
### 2. Understand chunk size
|
|
50
|
-
|
|
51
|
-
Default: 80 lines per chunk. Override with:
|
|
52
|
-
```bash
|
|
53
|
-
EAGLE_MEM_CHUNK_SIZE=40 eagle-mem index .
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
**Smaller chunks (30-50):** More precise search matches. Better for codebases with many small, distinct functions. Produces more chunks -- larger DB.
|
|
57
|
-
|
|
58
|
-
**Larger chunks (100-150):** More context per match. Better for files with long, cohesive blocks. Fewer chunks -- smaller DB.
|
|
59
|
-
|
|
60
|
-
**When to change:** If the hook is surfacing irrelevant matches (chunks too big, mixing unrelated code), shrink. If matches lack context (cutting functions in half), grow. The default of 80 works well for most projects.
|
|
61
|
-
|
|
62
|
-
### 3. Know what gets indexed
|
|
63
|
-
|
|
64
|
-
The indexer processes files matching these extensions:
|
|
65
|
-
`sh bash zsh js jsx mjs cjs ts tsx mts py rb go rs java kt kts swift c h cpp cc cxx hpp cs php sql html htm css scss vue svelte dart ex exs zig lua r scala yaml yml toml json md`
|
|
66
|
-
|
|
67
|
-
Silently skipped: images, binaries, lockfiles (`package-lock.json` etc. are over 1MB), `.git/` contents, anything in `.gitignore`.
|
|
68
|
-
|
|
69
|
-
### 4. Understand how indexed code reaches the user
|
|
70
|
-
|
|
71
|
-
The flow is:
|
|
72
|
-
1. You run `eagle-mem index .` -- code is chunked and stored in `code_chunks` table
|
|
73
|
-
2. User submits a prompt in any future session
|
|
74
|
-
3. UserPromptSubmit hook extracts keywords from the prompt
|
|
75
|
-
4. Hook searches `code_chunks` FTS5 index for matches
|
|
76
|
-
5. Matching file paths + line ranges are injected into your context as "EAGLE MEM — Relevant Code"
|
|
77
|
-
|
|
78
|
-
There is no manual "search code" CLI command. The index feeds the hook, and the hook feeds you. Your job is to keep the index current.
|
|
79
|
-
|
|
80
|
-
### 5. Verify the index
|
|
81
|
-
|
|
82
|
-
After indexing, confirm it worked:
|
|
83
|
-
```bash
|
|
84
|
-
eagle-mem search --stats
|
|
85
|
-
```
|
|
86
|
-
Check the "Code chunks" count. For a typical project:
|
|
87
|
-
- 50-file project ~ 200-500 chunks
|
|
88
|
-
- 200-file project ~ 800-2000 chunks
|
|
89
|
-
- 0 chunks after indexing = something went wrong (check file extensions, directory path)
|
|
90
|
-
|
|
91
|
-
Also verify the hook is using it: on the next user prompt with 3+ words, you should see "EAGLE MEM — Relevant Code" injected if any chunks match.
|
|
92
|
-
|
|
93
|
-
### 6. When indexing gets stale
|
|
94
|
-
|
|
95
|
-
The index reflects the state of files *when you last ran it*. It goes stale when:
|
|
96
|
-
- `git pull` brings in changes from others
|
|
97
|
-
- `git checkout` switches branches (different file contents, possibly different files)
|
|
98
|
-
- A major refactor renames or moves files (old paths stay in the index, new paths are missing)
|
|
99
|
-
- Dependencies are updated and source files regenerate
|
|
100
|
-
|
|
101
|
-
**After any of these, re-run `eagle-mem index .`** The mtime check ensures only changed files are re-processed. Old chunks for unchanged files are kept. Chunks for changed files are atomically replaced (DELETE + INSERT in a transaction).
|
|
102
|
-
|
|
103
|
-
## What makes a good indexing practice
|
|
104
|
-
|
|
105
|
-
**Good:**
|
|
106
|
-
> Indexed the project after pulling the team's changes from main. Stats show 1,247 chunks across 189 files. The UserPromptSubmit hook should now surface the new API routes the team added.
|
|
107
|
-
|
|
108
|
-
**Bad:**
|
|
109
|
-
> Indexed once when Eagle Mem was installed and never again. The hook keeps surfacing stale code from deleted files, and misses the 30 new files added over the past month.
|
|
110
|
-
|
|
111
|
-
## Reference
|
|
112
|
-
|
|
113
|
-
| Command / Flag | What it does |
|
|
114
|
-
|---|---|
|
|
115
|
-
| `eagle-mem index .` | Index current directory (incremental via mtime) |
|
|
116
|
-
| `eagle-mem index /path/to/dir` | Index a specific directory |
|
|
117
|
-
| `EAGLE_MEM_CHUNK_SIZE=N` | Override chunk size (default: 80 lines) |
|
|
118
|
-
| `eagle-mem search --stats` | Verify chunk count after indexing |
|
|
119
|
-
| Max file size | 1MB (larger files silently skipped) |
|
|
120
|
-
| Dedup | mtime-based -- re-running is cheap and safe |
|