eagle-mem 4.6.1 → 4.7.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 +40 -8
- package/db/023_guardrails.sql +3 -2
- package/db/024_guardrails_unique.sql +46 -0
- package/db/025_pending_feature_verifications.sql +30 -0
- package/db/026_agent_source.sql +18 -0
- package/db/027_feature_verification_fingerprints.sql +9 -0
- package/hooks/post-tool-use.sh +41 -13
- package/hooks/pre-tool-use.sh +106 -14
- package/hooks/session-end.sh +2 -1
- package/hooks/session-start.sh +54 -13
- package/hooks/stop.sh +114 -21
- package/hooks/user-prompt-submit.sh +13 -5
- package/lib/codex-hooks.sh +194 -0
- package/lib/common.sh +341 -0
- package/lib/db-features.sh +222 -0
- package/lib/db-guardrails.sh +2 -1
- package/lib/db-mirrors.sh +24 -15
- package/lib/db-observations.sh +3 -2
- package/lib/db-sessions.sh +6 -2
- package/lib/db-summaries.sh +6 -3
- package/lib/hooks-posttool.sh +8 -6
- package/package.json +7 -3
- package/scripts/curate.sh +35 -25
- package/scripts/feature.sh +70 -2
- package/scripts/guard.sh +4 -1
- package/scripts/help.sh +7 -2
- package/scripts/install.sh +118 -76
- package/scripts/memories.sh +21 -18
- package/scripts/search.sh +36 -28
- package/scripts/uninstall.sh +7 -0
- package/scripts/update.sh +31 -6
package/hooks/session-start.sh
CHANGED
|
@@ -24,6 +24,8 @@ session_id=$(echo "$input" | jq -r '.session_id // empty')
|
|
|
24
24
|
cwd=$(echo "$input" | jq -r '.cwd // empty')
|
|
25
25
|
source_type=$(echo "$input" | jq -r '.source // empty')
|
|
26
26
|
model=$(echo "$input" | jq -r '.model // empty')
|
|
27
|
+
agent=$(eagle_agent_source_from_json "$input")
|
|
28
|
+
agent_label=$(eagle_agent_label "$agent")
|
|
27
29
|
|
|
28
30
|
[ -z "$session_id" ] && exit 0
|
|
29
31
|
|
|
@@ -32,9 +34,9 @@ project=$(eagle_project_from_cwd "$cwd")
|
|
|
32
34
|
|
|
33
35
|
p_esc=$(eagle_sql_escape "$project")
|
|
34
36
|
|
|
35
|
-
eagle_log "INFO" "SessionStart: session=$session_id project=$project source=$source_type"
|
|
37
|
+
eagle_log "INFO" "SessionStart: session=$session_id project=$project source=$source_type agent=$agent"
|
|
36
38
|
|
|
37
|
-
eagle_upsert_session "$session_id" "$project" "$cwd" "$model" "$source_type"
|
|
39
|
+
eagle_upsert_session "$session_id" "$project" "$cwd" "$model" "$source_type" "$agent"
|
|
38
40
|
eagle_abandon_stale_sessions "$session_id"
|
|
39
41
|
|
|
40
42
|
# ─── Reset turn counter on compact/clear ─────────────────
|
|
@@ -90,7 +92,8 @@ fi
|
|
|
90
92
|
|
|
91
93
|
# ─── Gather stats ────────────────────────────────────────
|
|
92
94
|
|
|
93
|
-
stat_sessions=0;
|
|
95
|
+
stat_sessions=0; stat_sessions_claude=0; stat_sessions_codex=0
|
|
96
|
+
stat_summaries=0; stat_with_summaries=0; stat_memories=0
|
|
94
97
|
stat_tasks_pending=0; stat_tasks_progress=0; stat_tasks_done=0
|
|
95
98
|
stat_chunks=0; stat_observations=0; stat_plans=0
|
|
96
99
|
stat_last_active="never"; stat_last_summary=""
|
|
@@ -98,6 +101,8 @@ stat_last_active="never"; stat_last_summary=""
|
|
|
98
101
|
while IFS='|' read -r key val; do
|
|
99
102
|
case "$key" in
|
|
100
103
|
sessions) stat_sessions="$val" ;;
|
|
104
|
+
sessions_claude) stat_sessions_claude="$val" ;;
|
|
105
|
+
sessions_codex) stat_sessions_codex="$val" ;;
|
|
101
106
|
summaries) stat_summaries="$val" ;;
|
|
102
107
|
with_summaries) stat_with_summaries="$val" ;;
|
|
103
108
|
memories) stat_memories="$val" ;;
|
|
@@ -132,7 +137,12 @@ eagle_banner="======================================
|
|
|
132
137
|
Eagle Mem Recall Ready
|
|
133
138
|
======================================
|
|
134
139
|
Project | $project
|
|
140
|
+
Agent | $agent_label
|
|
135
141
|
Sessions | $stat_sessions ($stat_with_summaries with summaries)"
|
|
142
|
+
if [ "$stat_sessions_codex" -gt 0 ] || [ "$stat_sessions_claude" -gt 0 ]; then
|
|
143
|
+
eagle_banner+="
|
|
144
|
+
Sources | Claude $stat_sessions_claude, Codex $stat_sessions_codex"
|
|
145
|
+
fi
|
|
136
146
|
[ "$stat_memories" -gt 0 ] && eagle_banner+="
|
|
137
147
|
Memories | $stat_memories stored"
|
|
138
148
|
[ "$stat_plans" -gt 0 ] && eagle_banner+="
|
|
@@ -188,10 +198,13 @@ if [ -n "$recent" ]; then
|
|
|
188
198
|
context+="
|
|
189
199
|
=== Eagle Mem: Recent Recall ===
|
|
190
200
|
"
|
|
191
|
-
while IFS='|' read -r request completed learned next_steps created_at decisions gotchas key_files; do
|
|
201
|
+
while IFS='|' read -r request completed learned next_steps created_at decisions gotchas key_files summary_agent; do
|
|
192
202
|
[ -z "$request" ] && [ -z "$completed" ] && continue
|
|
203
|
+
summary_agent_label=$(eagle_agent_label "$summary_agent")
|
|
193
204
|
context+="
|
|
194
205
|
--- $created_at ---"
|
|
206
|
+
[ -n "$summary_agent" ] && context+="
|
|
207
|
+
Source: $summary_agent_label"
|
|
195
208
|
[ -n "$request" ] && context+="
|
|
196
209
|
Request: $request"
|
|
197
210
|
[ -n "$completed" ] && context+="
|
|
@@ -213,7 +226,7 @@ fi
|
|
|
213
226
|
|
|
214
227
|
# ─── Memories (skip if none) ─────────────────────────────
|
|
215
228
|
|
|
216
|
-
memories=$(eagle_db "SELECT memory_name, memory_type, description, file_path, updated_at,
|
|
229
|
+
memories=$(eagle_db "SELECT memory_name, memory_type, description, file_path, updated_at, origin_agent,
|
|
217
230
|
CAST(julianday('now') - julianday(updated_at) AS INTEGER) as days_ago
|
|
218
231
|
FROM claude_memories
|
|
219
232
|
WHERE project = '$p_esc'
|
|
@@ -223,8 +236,9 @@ if [ -n "$memories" ]; then
|
|
|
223
236
|
context+="
|
|
224
237
|
=== Eagle Mem: Stored Memories ===
|
|
225
238
|
"
|
|
226
|
-
while IFS='|' read -r mname mtype mdesc _fpath _updated days_ago; do
|
|
239
|
+
while IFS='|' read -r mname mtype mdesc _fpath _updated morigin days_ago; do
|
|
227
240
|
[ -z "$mname" ] && continue
|
|
241
|
+
origin_label=$(eagle_agent_label "$morigin")
|
|
228
242
|
age_label=""
|
|
229
243
|
if [ -n "$days_ago" ] && [ "$days_ago" -gt 0 ] 2>/dev/null; then
|
|
230
244
|
if [ "$days_ago" -eq 1 ]; then
|
|
@@ -235,7 +249,7 @@ if [ -n "$memories" ]; then
|
|
|
235
249
|
else
|
|
236
250
|
age_label=" (today)"
|
|
237
251
|
fi
|
|
238
|
-
context+=" - [$mtype] $mname: $mdesc$age_label
|
|
252
|
+
context+=" - [$mtype][$origin_label] $mname: $mdesc$age_label
|
|
239
253
|
"
|
|
240
254
|
done <<< "$memories"
|
|
241
255
|
fi
|
|
@@ -247,16 +261,17 @@ if [ -n "$plans" ]; then
|
|
|
247
261
|
context+="
|
|
248
262
|
=== Eagle Mem: Plans ===
|
|
249
263
|
"
|
|
250
|
-
while IFS='|' read -r ptitle _pproj _fpath _updated; do
|
|
264
|
+
while IFS='|' read -r ptitle _pproj _fpath _updated porigin; do
|
|
251
265
|
[ -z "$ptitle" ] && continue
|
|
252
|
-
|
|
266
|
+
origin_label=$(eagle_agent_label "$porigin")
|
|
267
|
+
context+=" - [$origin_label] $ptitle
|
|
253
268
|
"
|
|
254
269
|
done <<< "$plans"
|
|
255
270
|
fi
|
|
256
271
|
|
|
257
272
|
# ─── Tasks (skip if none) ────────────────────────────────
|
|
258
273
|
|
|
259
|
-
synced_tasks=$(eagle_db "SELECT subject, status, blocked_by FROM claude_tasks
|
|
274
|
+
synced_tasks=$(eagle_db "SELECT subject, status, blocked_by, origin_agent FROM claude_tasks
|
|
260
275
|
WHERE project = '$p_esc'
|
|
261
276
|
AND status IN ('in_progress', 'pending')
|
|
262
277
|
AND updated_at > strftime('%Y-%m-%dT%H:%M:%fZ', 'now', '-7 days')
|
|
@@ -268,17 +283,40 @@ if [ -n "$synced_tasks" ]; then
|
|
|
268
283
|
context+="
|
|
269
284
|
=== Eagle Mem: Tasks ===
|
|
270
285
|
"
|
|
271
|
-
while IFS='|' read -r tsubject tstatus tblocked; do
|
|
286
|
+
while IFS='|' read -r tsubject tstatus tblocked torigin; do
|
|
272
287
|
[ -z "$tsubject" ] && continue
|
|
288
|
+
origin_label=$(eagle_agent_label "$torigin")
|
|
273
289
|
block_marker=""
|
|
274
290
|
if [ "$tblocked" != "[]" ] && [ -n "$tblocked" ]; then
|
|
275
291
|
block_marker=" (blocked)"
|
|
276
292
|
fi
|
|
277
|
-
context+=" - [$tstatus] $tsubject$block_marker
|
|
293
|
+
context+=" - [$tstatus][$origin_label] $tsubject$block_marker
|
|
278
294
|
"
|
|
279
295
|
done <<< "$synced_tasks"
|
|
280
296
|
fi
|
|
281
297
|
|
|
298
|
+
# ─── Pending feature verifications ───────────────────────
|
|
299
|
+
|
|
300
|
+
pending_features=$(eagle_list_pending_feature_verifications "$project" 10 2>/dev/null)
|
|
301
|
+
if [ -n "$pending_features" ]; then
|
|
302
|
+
context+="
|
|
303
|
+
=== Eagle Mem: Pending Feature Verification ===
|
|
304
|
+
Release-boundary commands are blocked until these are verified or waived.
|
|
305
|
+
"
|
|
306
|
+
while IFS='|' read -r pf_id pf_name pf_file pf_reason _pf_trigger _pf_created pf_smoke pfingerprint; do
|
|
307
|
+
[ -z "$pf_id" ] && continue
|
|
308
|
+
context+=" - #${pf_id} ${pf_name}"
|
|
309
|
+
[ -n "$pf_file" ] && context+=" (${pf_file})"
|
|
310
|
+
[ -n "$pf_reason" ] && context+=" — ${pf_reason}"
|
|
311
|
+
[ -n "$pf_smoke" ] && context+=" | smoke: ${pf_smoke}"
|
|
312
|
+
[ -n "$pfingerprint" ] && context+=" | diff: ${pfingerprint}"
|
|
313
|
+
context+="
|
|
314
|
+
"
|
|
315
|
+
done <<< "$pending_features"
|
|
316
|
+
context+="Resolve with: eagle-mem feature verify <name> --notes \"what passed\"; or eagle-mem feature waive <id> --reason \"why safe\".
|
|
317
|
+
"
|
|
318
|
+
fi
|
|
319
|
+
|
|
282
320
|
# ─── Core files (hot file hints from curator) ───────────
|
|
283
321
|
|
|
284
322
|
hot_files=$(eagle_get_hot_files "$project")
|
|
@@ -334,12 +372,15 @@ next_steps: [concrete actions]
|
|
|
334
372
|
key_files: [path — role]
|
|
335
373
|
files_read: [path, ...]
|
|
336
374
|
files_modified: [path, ...]
|
|
375
|
+
affected_features: [feature, ...]
|
|
376
|
+
verified_features: [feature, ...]
|
|
377
|
+
regression_risks: [risk, ...]
|
|
337
378
|
</eagle-summary>
|
|
338
379
|
"
|
|
339
380
|
fi
|
|
340
381
|
|
|
341
382
|
if [ -n "$context" ]; then
|
|
342
|
-
|
|
383
|
+
eagle_emit_context_for_agent "$agent" "SessionStart" "$context"
|
|
343
384
|
fi
|
|
344
385
|
|
|
345
386
|
exit 0
|
package/hooks/stop.sh
CHANGED
|
@@ -23,6 +23,8 @@ input=$(eagle_read_stdin)
|
|
|
23
23
|
session_id=$(echo "$input" | jq -r '.session_id // empty')
|
|
24
24
|
cwd=$(echo "$input" | jq -r '.cwd // empty')
|
|
25
25
|
transcript_path=$(echo "$input" | jq -r '.transcript_path // empty')
|
|
26
|
+
last_assistant_message=$(echo "$input" | jq -r '.last_assistant_message // empty')
|
|
27
|
+
agent=$(eagle_agent_source_from_json "$input")
|
|
26
28
|
|
|
27
29
|
[ -z "$session_id" ] && exit 0
|
|
28
30
|
|
|
@@ -33,25 +35,62 @@ agent_type=$(echo "$input" | jq -r '.agent_type // empty')
|
|
|
33
35
|
project=$(eagle_project_from_cwd "$cwd")
|
|
34
36
|
[ -z "$project" ] && exit 0
|
|
35
37
|
|
|
36
|
-
eagle_log "INFO" "Stop: session=$session_id project=$project transcript=$transcript_path"
|
|
38
|
+
eagle_log "INFO" "Stop: session=$session_id project=$project transcript=$transcript_path agent=$agent"
|
|
37
39
|
|
|
38
40
|
# Ensure session exists (may not if SessionStart didn't fire)
|
|
39
|
-
eagle_upsert_session "$session_id" "$project" "$cwd" "" ""
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
eagle_upsert_session "$session_id" "$project" "$cwd" "" "" "$agent"
|
|
42
|
+
|
|
43
|
+
# Reconcile from git diff, not only from edit-tool hooks. This keeps
|
|
44
|
+
# anti-regression agent-agnostic: Claude, Codex, manual edits, and script edits
|
|
45
|
+
# all become visible before a release boundary.
|
|
46
|
+
if [ -n "$cwd" ] && [ -d "$cwd" ]; then
|
|
47
|
+
changed_files=$(eagle_changed_files_for_release "$cwd")
|
|
48
|
+
if [ -n "$changed_files" ]; then
|
|
49
|
+
eagle_reconcile_current_feature_verifications "$project" "$cwd" "$session_id" "Stop" "Repository diff detected at turn end" "$changed_files" >/dev/null
|
|
50
|
+
fi
|
|
51
|
+
fi
|
|
42
52
|
|
|
43
53
|
# ─── Primary: heuristic extraction from transcript ───────────
|
|
44
54
|
|
|
45
|
-
request
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
|
51
|
-
|
|
55
|
+
request=""
|
|
56
|
+
heuristic_reads=""
|
|
57
|
+
heuristic_writes=""
|
|
58
|
+
|
|
59
|
+
if [ -n "$transcript_path" ] && [ -f "$transcript_path" ]; then
|
|
60
|
+
request=$(jq -r 'select(.type == "user") | .message.content | if type == "string" then . elif type == "array" then [.[] | select(.type == "text") | .text] | join(" ") else "" end' "$transcript_path" 2>/dev/null \
|
|
61
|
+
| grep -v '<local-command-caveat>' \
|
|
62
|
+
| grep -v '<system-reminder>' \
|
|
63
|
+
| grep -v '<command-name>' \
|
|
64
|
+
| grep -v '<command-message>' \
|
|
65
|
+
| grep -v '^\[{' \
|
|
66
|
+
| head -1 | cut -c1-500)
|
|
67
|
+
|
|
68
|
+
if [ -z "$request" ]; then
|
|
69
|
+
request=$(jq -r '
|
|
70
|
+
select(.type == "response_item" and .payload.role == "user")
|
|
71
|
+
| .payload.content
|
|
72
|
+
| if type == "string" then .
|
|
73
|
+
elif type == "array" then [.[]? | select(.type == "input_text" or .type == "text") | .text] | join(" ")
|
|
74
|
+
else "" end
|
|
75
|
+
' "$transcript_path" 2>/dev/null \
|
|
76
|
+
| grep -v '<local-command-caveat>' \
|
|
77
|
+
| grep -v '<system-reminder>' \
|
|
78
|
+
| grep -v '<command-name>' \
|
|
79
|
+
| grep -v '<command-message>' \
|
|
80
|
+
| grep -v '^\[{' \
|
|
81
|
+
| head -1 | cut -c1-500)
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
if [ -z "$request" ]; then
|
|
85
|
+
request=$(jq -r 'select(.type == "event_msg" and .payload.type == "user_message") | .payload.message // empty' "$transcript_path" 2>/dev/null \
|
|
86
|
+
| grep -v '<local-command-caveat>' \
|
|
87
|
+
| grep -v '<system-reminder>' \
|
|
88
|
+
| head -1 | cut -c1-500)
|
|
89
|
+
fi
|
|
52
90
|
|
|
53
|
-
heuristic_reads=$(jq -r 'select(.type == "assistant") | .message.content[]? | select(.type == "tool_use") | select(.name == "Read") | .input.file_path // empty' "$transcript_path" 2>/dev/null | sort -u | head -20)
|
|
54
|
-
heuristic_writes=$(jq -r 'select(.type == "assistant") | .message.content[]? | select(.type == "tool_use") | select(.name == "Write" or .name == "Edit") | .input.file_path // empty' "$transcript_path" 2>/dev/null | sort -u | head -20)
|
|
91
|
+
heuristic_reads=$(jq -r 'select(.type == "assistant") | .message.content[]? | select(.type == "tool_use") | select(.name == "Read") | .input.file_path // empty' "$transcript_path" 2>/dev/null | sort -u | head -20)
|
|
92
|
+
heuristic_writes=$(jq -r 'select(.type == "assistant") | .message.content[]? | select(.type == "tool_use") | select(.name == "Write" or .name == "Edit") | .input.file_path // empty' "$transcript_path" 2>/dev/null | sort -u | head -20)
|
|
93
|
+
fi
|
|
55
94
|
|
|
56
95
|
files_read="[]"
|
|
57
96
|
files_modified="[]"
|
|
@@ -70,17 +109,46 @@ notes=""
|
|
|
70
109
|
decisions=""
|
|
71
110
|
gotchas=""
|
|
72
111
|
key_files=""
|
|
112
|
+
affected_features=""
|
|
113
|
+
verified_features=""
|
|
114
|
+
regression_risks=""
|
|
73
115
|
|
|
74
116
|
eagle_log "INFO" "Stop: heuristic extraction complete"
|
|
75
117
|
|
|
76
118
|
# ─── Bonus: eagle-summary block overrides where present ──────
|
|
77
119
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
120
|
+
if [ -n "$transcript_path" ] && [ -f "$transcript_path" ]; then
|
|
121
|
+
text_content=$(jq -r -s '
|
|
122
|
+
[.[] | select(.type == "assistant")] | last |
|
|
123
|
+
if . then
|
|
124
|
+
[.message.content[]? | select(.type == "text") | .text] | join("\n")
|
|
125
|
+
else "" end
|
|
126
|
+
' "$transcript_path" 2>/dev/null)
|
|
127
|
+
|
|
128
|
+
if [ -z "$text_content" ]; then
|
|
129
|
+
text_content=$(jq -r -s '
|
|
130
|
+
def content_text:
|
|
131
|
+
if type == "string" then .
|
|
132
|
+
elif type == "array" then
|
|
133
|
+
[.[]? | select(.type == "output_text" or .type == "text") | (.text // empty)] | join("\n")
|
|
134
|
+
else "" end;
|
|
135
|
+
|
|
136
|
+
(
|
|
137
|
+
[.[] | select(.type == "response_item" and .payload.role == "assistant") | (.payload.content | content_text)]
|
|
138
|
+
| map(select(. != ""))
|
|
139
|
+
| last
|
|
140
|
+
) // (
|
|
141
|
+
[.[] | select(.type == "event_msg" and .payload.type == "agent_message") | (.payload.message // "")]
|
|
142
|
+
| map(select(. != ""))
|
|
143
|
+
| last
|
|
144
|
+
) // ""
|
|
145
|
+
' "$transcript_path" 2>/dev/null)
|
|
146
|
+
fi
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
if [ -z "$text_content" ]; then
|
|
150
|
+
text_content="$last_assistant_message"
|
|
151
|
+
fi
|
|
84
152
|
|
|
85
153
|
# Strip <private>...</private> blocks
|
|
86
154
|
text_content=$(echo "$text_content" | sed -E '/<[Pp][Rr][Ii][Vv][Aa][Tt][Ee][^>]*>/,/<\/[Pp][Rr][Ii][Vv][Aa][Tt][Ee][[:space:]]*>/d')
|
|
@@ -99,7 +167,7 @@ if [ -n "$summary_block" ]; then
|
|
|
99
167
|
$0 ~ "^"f":" {
|
|
100
168
|
sub("^"f":[[:space:]]*", ""); found=1; val=$0; next
|
|
101
169
|
}
|
|
102
|
-
found && /^(request|investigated|learned|completed|next_steps|files_read|files_modified|notes|decisions|gotchas|key_files):/ { exit }
|
|
170
|
+
found && /^(request|investigated|learned|completed|next_steps|files_read|files_modified|notes|decisions|gotchas|key_files|affected_features|verified_features|regression_risks):/ { exit }
|
|
103
171
|
found { val = val " " $0 }
|
|
104
172
|
END { if (found) print val }
|
|
105
173
|
'
|
|
@@ -114,6 +182,9 @@ if [ -n "$summary_block" ]; then
|
|
|
114
182
|
_val=$(parse_field "$summary_block" "decisions"); [ -n "$_val" ] && decisions="$_val"
|
|
115
183
|
_val=$(parse_field "$summary_block" "gotchas"); [ -n "$_val" ] && gotchas="$_val"
|
|
116
184
|
_val=$(parse_field "$summary_block" "key_files"); [ -n "$_val" ] && key_files="$_val"
|
|
185
|
+
_val=$(parse_field "$summary_block" "affected_features"); [ -n "$_val" ] && affected_features="$_val"
|
|
186
|
+
_val=$(parse_field "$summary_block" "verified_features"); [ -n "$_val" ] && verified_features="$_val"
|
|
187
|
+
_val=$(parse_field "$summary_block" "regression_risks"); [ -n "$_val" ] && regression_risks="$_val"
|
|
117
188
|
|
|
118
189
|
# Convert bracket-list to JSON array
|
|
119
190
|
to_json_array() {
|
|
@@ -295,11 +366,33 @@ next_steps=$(echo "$next_steps" | eagle_redact)
|
|
|
295
366
|
decisions=$(echo "$decisions" | eagle_redact)
|
|
296
367
|
gotchas=$(echo "$gotchas" | eagle_redact)
|
|
297
368
|
key_files=$(echo "$key_files" | eagle_redact)
|
|
369
|
+
notes=$(echo "$notes" | eagle_redact)
|
|
370
|
+
affected_features=$(echo "$affected_features" | eagle_redact)
|
|
371
|
+
verified_features=$(echo "$verified_features" | eagle_redact)
|
|
372
|
+
regression_risks=$(echo "$regression_risks" | eagle_redact)
|
|
373
|
+
|
|
374
|
+
regression_notes=""
|
|
375
|
+
[ -n "$affected_features" ] && regression_notes+="affected_features: $affected_features"
|
|
376
|
+
if [ -n "$verified_features" ]; then
|
|
377
|
+
[ -n "$regression_notes" ] && regression_notes+="; "
|
|
378
|
+
regression_notes+="verified_features: $verified_features"
|
|
379
|
+
fi
|
|
380
|
+
if [ -n "$regression_risks" ]; then
|
|
381
|
+
[ -n "$regression_notes" ] && regression_notes+="; "
|
|
382
|
+
regression_notes+="regression_risks: $regression_risks"
|
|
383
|
+
fi
|
|
384
|
+
if [ -n "$regression_notes" ]; then
|
|
385
|
+
if [ -n "$notes" ]; then
|
|
386
|
+
notes="${notes}; ${regression_notes}"
|
|
387
|
+
else
|
|
388
|
+
notes="$regression_notes"
|
|
389
|
+
fi
|
|
390
|
+
fi
|
|
298
391
|
|
|
299
392
|
# ─── Write to database ─────────────────────────────────────
|
|
300
393
|
|
|
301
394
|
if [ -n "$request" ] || [ -n "$completed" ] || [ -n "$learned" ]; then
|
|
302
|
-
if eagle_insert_summary "$session_id" "$project" "$request" "$investigated" "$learned" "$completed" "$next_steps" "$files_read" "$files_modified" "$notes" "$decisions" "$gotchas" "$key_files"; then
|
|
395
|
+
if eagle_insert_summary "$session_id" "$project" "$request" "$investigated" "$learned" "$completed" "$next_steps" "$files_read" "$files_modified" "$notes" "$decisions" "$gotchas" "$key_files" "$agent"; then
|
|
303
396
|
eagle_log "INFO" "Stop: summary saved for session=$session_id"
|
|
304
397
|
else
|
|
305
398
|
eagle_log "ERROR" "Stop: summary insert FAILED for session=$session_id — check DB constraints"
|
|
@@ -20,6 +20,7 @@ input=$(eagle_read_stdin)
|
|
|
20
20
|
session_id=$(echo "$input" | jq -r '.session_id // empty')
|
|
21
21
|
cwd=$(echo "$input" | jq -r '.cwd // empty')
|
|
22
22
|
user_prompt=$(echo "$input" | jq -r '.prompt // empty')
|
|
23
|
+
agent=$(eagle_agent_source_from_json "$input")
|
|
23
24
|
|
|
24
25
|
[ -z "$user_prompt" ] && exit 0
|
|
25
26
|
|
|
@@ -60,7 +61,10 @@ fi
|
|
|
60
61
|
|
|
61
62
|
# Skip short prompts — not enough signal for meaningful search
|
|
62
63
|
word_count=$(echo "$user_prompt" | wc -w | tr -d ' ')
|
|
63
|
-
[ "$word_count" -lt 3 ]
|
|
64
|
+
if [ "$word_count" -lt 3 ]; then
|
|
65
|
+
eagle_emit_context_for_agent "$agent" "UserPromptSubmit" "$context"
|
|
66
|
+
exit 0
|
|
67
|
+
fi
|
|
64
68
|
|
|
65
69
|
# Build FTS5 query from significant words (drop stop words, take first 6)
|
|
66
70
|
fts_query=$(echo "$user_prompt" | tr -cs '[:alnum:]' ' ' | tr '[:upper:]' '[:lower:]' | \
|
|
@@ -75,7 +79,10 @@ fts_query=$(echo "$user_prompt" | tr -cs '[:alnum:]' ' ' | tr '[:upper:]' '[:low
|
|
|
75
79
|
}
|
|
76
80
|
}')
|
|
77
81
|
|
|
78
|
-
[ -z "$fts_query" ]
|
|
82
|
+
if [ -z "$fts_query" ]; then
|
|
83
|
+
eagle_emit_context_for_agent "$agent" "UserPromptSubmit" "$context"
|
|
84
|
+
exit 0
|
|
85
|
+
fi
|
|
79
86
|
|
|
80
87
|
# Search for relevant past summaries (cross-session)
|
|
81
88
|
results=$(eagle_search_summaries "$fts_query" "$project" 3)
|
|
@@ -83,9 +90,10 @@ results=$(eagle_search_summaries "$fts_query" "$project" 3)
|
|
|
83
90
|
if [ -n "$results" ]; then
|
|
84
91
|
context+="=== Eagle Mem: Relevant Recall ===
|
|
85
92
|
"
|
|
86
|
-
while IFS='|' read -r req completed learned _next_steps created_at _proj decisions gotchas key_files; do
|
|
93
|
+
while IFS='|' read -r req completed learned _next_steps created_at _proj decisions gotchas key_files summary_agent; do
|
|
87
94
|
[ -z "$req" ] && [ -z "$completed" ] && continue
|
|
88
|
-
|
|
95
|
+
origin_label=$(eagle_agent_label "$summary_agent")
|
|
96
|
+
context+="[$created_at][$origin_label] "
|
|
89
97
|
[ -n "$req" ] && context+="$req"
|
|
90
98
|
[ -n "$completed" ] && context+=" → $completed"
|
|
91
99
|
[ -n "$learned" ] && context+=" (Learned: $learned)"
|
|
@@ -126,5 +134,5 @@ IMPORTANT: When Eagle Mem finds relevant memories or code for the user's prompt,
|
|
|
126
134
|
=== Eagle Mem: Persistent Memory ===
|
|
127
135
|
"
|
|
128
136
|
|
|
129
|
-
|
|
137
|
+
eagle_emit_context_for_agent "$agent" "UserPromptSubmit" "$context"
|
|
130
138
|
exit 0
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ═══════════════════════════════════════════════════════════
|
|
3
|
+
# Eagle Mem — Codex hook registration helpers
|
|
4
|
+
# Shared by install.sh, update.sh, and uninstall.sh
|
|
5
|
+
# ═══════════════════════════════════════════════════════════
|
|
6
|
+
[ -n "${_EAGLE_CODEX_HOOKS_LOADED:-}" ] && return 0
|
|
7
|
+
_EAGLE_CODEX_HOOKS_LOADED=1
|
|
8
|
+
|
|
9
|
+
eagle_enable_codex_hooks() {
|
|
10
|
+
local config="$EAGLE_CODEX_CONFIG"
|
|
11
|
+
mkdir -p "$(dirname "$config")"
|
|
12
|
+
|
|
13
|
+
if [ ! -f "$config" ]; then
|
|
14
|
+
cat > "$config" << 'TOML'
|
|
15
|
+
[features]
|
|
16
|
+
codex_hooks = true
|
|
17
|
+
TOML
|
|
18
|
+
chmod 600 "$config" 2>/dev/null || true
|
|
19
|
+
return 0
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
local tmp
|
|
23
|
+
tmp=$(mktemp)
|
|
24
|
+
awk '
|
|
25
|
+
BEGIN { in_features=0; saw_features=0; saw_flag=0; inserted=0 }
|
|
26
|
+
/^[[:space:]]*\[features\][[:space:]]*$/ {
|
|
27
|
+
saw_features=1
|
|
28
|
+
in_features=1
|
|
29
|
+
print
|
|
30
|
+
next
|
|
31
|
+
}
|
|
32
|
+
/^[[:space:]]*\[/ && in_features {
|
|
33
|
+
if (!saw_flag && !inserted) {
|
|
34
|
+
print "codex_hooks = true"
|
|
35
|
+
inserted=1
|
|
36
|
+
}
|
|
37
|
+
in_features=0
|
|
38
|
+
}
|
|
39
|
+
in_features && /^[[:space:]]*codex_hooks[[:space:]]*=/ {
|
|
40
|
+
print "codex_hooks = true"
|
|
41
|
+
saw_flag=1
|
|
42
|
+
next
|
|
43
|
+
}
|
|
44
|
+
{ print }
|
|
45
|
+
END {
|
|
46
|
+
if (in_features && !saw_flag && !inserted) {
|
|
47
|
+
print "codex_hooks = true"
|
|
48
|
+
inserted=1
|
|
49
|
+
}
|
|
50
|
+
if (!saw_features) {
|
|
51
|
+
print ""
|
|
52
|
+
print "[features]"
|
|
53
|
+
print "codex_hooks = true"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
' "$config" > "$tmp" && mv "$tmp" "$config"
|
|
57
|
+
chmod 600 "$config" 2>/dev/null || true
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
eagle_patch_codex_hook() {
|
|
61
|
+
local hooks_file="$1"
|
|
62
|
+
local event="$2"
|
|
63
|
+
local matcher="$3"
|
|
64
|
+
local command="$4"
|
|
65
|
+
local description="${5:-}"
|
|
66
|
+
local status_message="${6:-}"
|
|
67
|
+
local timeout="${7:-}"
|
|
68
|
+
local script_path="$command"
|
|
69
|
+
script_path="${script_path#EAGLE_AGENT_SOURCE=codex bash \"}"
|
|
70
|
+
script_path="${script_path#bash \"}"
|
|
71
|
+
script_path="${script_path%\"}"
|
|
72
|
+
|
|
73
|
+
mkdir -p "$(dirname "$hooks_file")"
|
|
74
|
+
if [ ! -f "$hooks_file" ]; then
|
|
75
|
+
printf '{"hooks":{}}\n' > "$hooks_file"
|
|
76
|
+
chmod 600 "$hooks_file" 2>/dev/null || true
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
local match_query
|
|
80
|
+
if [ -n "$matcher" ]; then
|
|
81
|
+
match_query='.hooks[$event][]? | select(.matcher == $matcher and (.hooks[]?.command == $command))'
|
|
82
|
+
else
|
|
83
|
+
match_query='.hooks[$event][]? | select((.matcher == null or .matcher == "") and (.hooks[]?.command == $command))'
|
|
84
|
+
fi
|
|
85
|
+
if jq -e --arg event "$event" --arg matcher "$matcher" --arg command "$command" "$match_query" "$hooks_file" &>/dev/null; then
|
|
86
|
+
[ -n "$description" ] && eagle_ok "$description ${DIM}(already registered)${RESET}"
|
|
87
|
+
return 0
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
if jq -e --arg event "$event" --arg matcher "$matcher" --arg script "$script_path" '
|
|
91
|
+
.hooks[$event][]?
|
|
92
|
+
| select((($matcher == "" and (.matcher == null or .matcher == "")) or .matcher == $matcher)
|
|
93
|
+
and any(.hooks[]?; (.command // "") | contains($script)))
|
|
94
|
+
' "$hooks_file" &>/dev/null; then
|
|
95
|
+
local tmp_existing
|
|
96
|
+
tmp_existing=$(mktemp)
|
|
97
|
+
jq --arg event "$event" --arg matcher "$matcher" --arg script "$script_path" --arg command "$command" '
|
|
98
|
+
.hooks[$event] |= map(
|
|
99
|
+
if ((($matcher == "" and (.matcher == null or .matcher == "")) or .matcher == $matcher)
|
|
100
|
+
and any(.hooks[]?; (.command // "") | contains($script)))
|
|
101
|
+
then .hooks |= map(if ((.command // "") | contains($script)) then .command = $command else . end)
|
|
102
|
+
else .
|
|
103
|
+
end
|
|
104
|
+
)
|
|
105
|
+
' "$hooks_file" > "$tmp_existing" && mv "$tmp_existing" "$hooks_file"
|
|
106
|
+
[ -n "$description" ] && eagle_ok "$description ${DIM}(updated)${RESET}"
|
|
107
|
+
return 0
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
local entry
|
|
111
|
+
entry=$(jq -nc \
|
|
112
|
+
--arg m "$matcher" \
|
|
113
|
+
--arg c "$command" \
|
|
114
|
+
--arg s "$status_message" \
|
|
115
|
+
--arg timeout "$timeout" '
|
|
116
|
+
{
|
|
117
|
+
hooks: [
|
|
118
|
+
{
|
|
119
|
+
type: "command",
|
|
120
|
+
command: $c
|
|
121
|
+
}
|
|
122
|
+
+ (if $s == "" then {} else {statusMessage: $s} end)
|
|
123
|
+
+ (if $timeout == "" then {} else {timeout: ($timeout | tonumber)} end)
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
+ (if $m == "" then {} else {matcher: $m} end)')
|
|
127
|
+
|
|
128
|
+
local tmp
|
|
129
|
+
tmp=$(mktemp)
|
|
130
|
+
jq --argjson entry "$entry" ".hooks.${event} = ((.hooks.${event} // []) + [\$entry])" "$hooks_file" > "$tmp" && mv "$tmp" "$hooks_file"
|
|
131
|
+
chmod 600 "$hooks_file" 2>/dev/null || true
|
|
132
|
+
[ -n "$description" ] && eagle_ok "$description"
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
eagle_register_codex_hooks() {
|
|
136
|
+
eagle_enable_codex_hooks
|
|
137
|
+
|
|
138
|
+
eagle_patch_codex_hook "$EAGLE_CODEX_HOOKS" "SessionStart" "startup|resume|clear" \
|
|
139
|
+
"EAGLE_AGENT_SOURCE=codex bash \"$EAGLE_MEM_DIR/hooks/session-start.sh\"" \
|
|
140
|
+
"Codex SessionStart hook" \
|
|
141
|
+
"Loading Eagle Mem recall" \
|
|
142
|
+
"30"
|
|
143
|
+
|
|
144
|
+
eagle_patch_codex_hook "$EAGLE_CODEX_HOOKS" "UserPromptSubmit" "" \
|
|
145
|
+
"EAGLE_AGENT_SOURCE=codex bash \"$EAGLE_MEM_DIR/hooks/user-prompt-submit.sh\"" \
|
|
146
|
+
"Codex UserPromptSubmit hook" \
|
|
147
|
+
"Searching Eagle Mem" \
|
|
148
|
+
"30"
|
|
149
|
+
|
|
150
|
+
eagle_patch_codex_hook "$EAGLE_CODEX_HOOKS" "PreToolUse" "^(Bash|exec_command|shell_command|unified_exec|apply_patch|Edit|Write)$" \
|
|
151
|
+
"EAGLE_AGENT_SOURCE=codex bash \"$EAGLE_MEM_DIR/hooks/pre-tool-use.sh\"" \
|
|
152
|
+
"Codex PreToolUse hook" \
|
|
153
|
+
"Checking Eagle Mem guardrails" \
|
|
154
|
+
"30"
|
|
155
|
+
|
|
156
|
+
eagle_patch_codex_hook "$EAGLE_CODEX_HOOKS" "PostToolUse" "^(Bash|exec_command|shell_command|unified_exec|apply_patch|Edit|Write)$" \
|
|
157
|
+
"EAGLE_AGENT_SOURCE=codex bash \"$EAGLE_MEM_DIR/hooks/post-tool-use.sh\"" \
|
|
158
|
+
"Codex PostToolUse hook" \
|
|
159
|
+
"Recording Eagle Mem observation" \
|
|
160
|
+
"30"
|
|
161
|
+
|
|
162
|
+
eagle_patch_codex_hook "$EAGLE_CODEX_HOOKS" "Stop" "" \
|
|
163
|
+
"EAGLE_AGENT_SOURCE=codex bash \"$EAGLE_MEM_DIR/hooks/stop.sh\"" \
|
|
164
|
+
"Codex Stop hook" \
|
|
165
|
+
"Saving Eagle Mem summary" \
|
|
166
|
+
"30"
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
eagle_remove_codex_hooks() {
|
|
170
|
+
local hooks_file="$EAGLE_CODEX_HOOKS"
|
|
171
|
+
[ -f "$hooks_file" ] || return 1
|
|
172
|
+
command -v jq &>/dev/null || return 1
|
|
173
|
+
|
|
174
|
+
local tmp
|
|
175
|
+
tmp=$(mktemp)
|
|
176
|
+
jq '
|
|
177
|
+
def without_eagle_mem_handlers:
|
|
178
|
+
.hooks = ((.hooks // [])
|
|
179
|
+
| map(select(((.command // "") | contains(".eagle-mem/hooks/")) | not)));
|
|
180
|
+
|
|
181
|
+
if .hooks then
|
|
182
|
+
.hooks |= with_entries(
|
|
183
|
+
.value = [
|
|
184
|
+
.value[]?
|
|
185
|
+
| without_eagle_mem_handlers
|
|
186
|
+
| select((.hooks // []) | length > 0)
|
|
187
|
+
]
|
|
188
|
+
| select(.value != [])
|
|
189
|
+
)
|
|
190
|
+
else . end
|
|
191
|
+
| if .hooks == {} then del(.hooks) else . end
|
|
192
|
+
' "$hooks_file" > "$tmp" && mv "$tmp" "$hooks_file"
|
|
193
|
+
chmod 600 "$hooks_file" 2>/dev/null || true
|
|
194
|
+
}
|