eagle-mem 4.6.2 → 4.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +49 -15
  2. package/db/023_guardrails.sql +3 -2
  3. package/db/024_guardrails_unique.sql +46 -0
  4. package/db/025_pending_feature_verifications.sql +30 -0
  5. package/db/026_agent_source.sql +18 -0
  6. package/db/027_feature_verification_fingerprints.sql +9 -0
  7. package/db/028_agent_artifact_tables.sql +124 -0
  8. package/hooks/post-tool-use.sh +42 -13
  9. package/hooks/pre-tool-use.sh +107 -14
  10. package/hooks/session-end.sh +3 -1
  11. package/hooks/session-start.sh +64 -15
  12. package/hooks/stop.sh +115 -21
  13. package/hooks/user-prompt-submit.sh +14 -5
  14. package/lib/codex-hooks.sh +194 -0
  15. package/lib/common.sh +345 -0
  16. package/lib/db-backfill.sh +3 -3
  17. package/lib/db-features.sh +222 -0
  18. package/lib/db-guardrails.sh +2 -1
  19. package/lib/db-mirrors.sh +79 -43
  20. package/lib/db-observations.sh +3 -2
  21. package/lib/db-sessions.sh +11 -7
  22. package/lib/db-summaries.sh +9 -6
  23. package/lib/hooks-posttool.sh +8 -6
  24. package/lib/provider.sh +190 -4
  25. package/package.json +7 -3
  26. package/scripts/config.sh +2 -0
  27. package/scripts/feature.sh +70 -2
  28. package/scripts/guard.sh +4 -1
  29. package/scripts/health.sh +5 -1
  30. package/scripts/help.sh +13 -8
  31. package/scripts/install.sh +130 -76
  32. package/scripts/memories.sh +71 -45
  33. package/scripts/refresh.sh +3 -3
  34. package/scripts/search.sh +57 -47
  35. package/scripts/statusline-em.sh +1 -1
  36. package/scripts/tasks.sh +186 -15
  37. package/scripts/uninstall.sh +7 -0
  38. package/scripts/update.sh +51 -7
  39. package/skills/eagle-mem-memories/SKILL.md +13 -13
  40. package/skills/eagle-mem-tasks/SKILL.md +21 -15
@@ -5,6 +5,7 @@
5
5
  # Searches memory for relevant context and injects it
6
6
  # ═══════════════════════════════════════════════════════════
7
7
  set +e
8
+ [ "${EAGLE_MEM_DISABLE_HOOKS:-}" = "1" ] && exit 0
8
9
 
9
10
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
10
11
  LIB_DIR="$SCRIPT_DIR/../lib"
@@ -20,6 +21,7 @@ input=$(eagle_read_stdin)
20
21
  session_id=$(echo "$input" | jq -r '.session_id // empty')
21
22
  cwd=$(echo "$input" | jq -r '.cwd // empty')
22
23
  user_prompt=$(echo "$input" | jq -r '.prompt // empty')
24
+ agent=$(eagle_agent_source_from_json "$input")
23
25
 
24
26
  [ -z "$user_prompt" ] && exit 0
25
27
 
@@ -60,7 +62,10 @@ fi
60
62
 
61
63
  # Skip short prompts — not enough signal for meaningful search
62
64
  word_count=$(echo "$user_prompt" | wc -w | tr -d ' ')
63
- [ "$word_count" -lt 3 ] && { [ -n "$context" ] && echo "$context"; exit 0; }
65
+ if [ "$word_count" -lt 3 ]; then
66
+ eagle_emit_context_for_agent "$agent" "UserPromptSubmit" "$context"
67
+ exit 0
68
+ fi
64
69
 
65
70
  # Build FTS5 query from significant words (drop stop words, take first 6)
66
71
  fts_query=$(echo "$user_prompt" | tr -cs '[:alnum:]' ' ' | tr '[:upper:]' '[:lower:]' | \
@@ -75,7 +80,10 @@ fts_query=$(echo "$user_prompt" | tr -cs '[:alnum:]' ' ' | tr '[:upper:]' '[:low
75
80
  }
76
81
  }')
77
82
 
78
- [ -z "$fts_query" ] && { [ -n "$context" ] && echo "$context"; exit 0; }
83
+ if [ -z "$fts_query" ]; then
84
+ eagle_emit_context_for_agent "$agent" "UserPromptSubmit" "$context"
85
+ exit 0
86
+ fi
79
87
 
80
88
  # Search for relevant past summaries (cross-session)
81
89
  results=$(eagle_search_summaries "$fts_query" "$project" 3)
@@ -83,9 +91,10 @@ results=$(eagle_search_summaries "$fts_query" "$project" 3)
83
91
  if [ -n "$results" ]; then
84
92
  context+="=== Eagle Mem: Relevant Recall ===
85
93
  "
86
- while IFS='|' read -r req completed learned _next_steps created_at _proj decisions gotchas key_files; do
94
+ while IFS='|' read -r req completed learned _next_steps created_at _proj decisions gotchas key_files summary_agent; do
87
95
  [ -z "$req" ] && [ -z "$completed" ] && continue
88
- context+="[$created_at] "
96
+ origin_label=$(eagle_agent_label "$summary_agent")
97
+ context+="[$created_at][$origin_label] "
89
98
  [ -n "$req" ] && context+="$req"
90
99
  [ -n "$completed" ] && context+=" → $completed"
91
100
  [ -n "$learned" ] && context+=" (Learned: $learned)"
@@ -126,5 +135,5 @@ IMPORTANT: When Eagle Mem finds relevant memories or code for the user's prompt,
126
135
  === Eagle Mem: Persistent Memory ===
127
136
  "
128
137
 
129
- echo "$context"
138
+ eagle_emit_context_for_agent "$agent" "UserPromptSubmit" "$context"
130
139
  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
+ }
package/lib/common.sh CHANGED
@@ -12,6 +12,13 @@ EAGLE_SKILLS_DIR="$HOME/.claude/skills"
12
12
  EAGLE_CLAUDE_PROJECTS_DIR="$HOME/.claude/projects"
13
13
  EAGLE_CLAUDE_PLANS_DIR="$HOME/.claude/plans"
14
14
  EAGLE_CLAUDE_TASKS_DIR="$HOME/.claude/tasks"
15
+ EAGLE_CODEX_DIR="${EAGLE_CODEX_DIR:-$HOME/.codex}"
16
+ EAGLE_CODEX_CONFIG="${EAGLE_CODEX_CONFIG:-$EAGLE_CODEX_DIR/config.toml}"
17
+ EAGLE_CODEX_HOOKS="${EAGLE_CODEX_HOOKS:-$EAGLE_CODEX_DIR/hooks.json}"
18
+ EAGLE_CODEX_AGENTS_MD="${EAGLE_CODEX_AGENTS_MD:-$EAGLE_CODEX_DIR/AGENTS.md}"
19
+ EAGLE_CODEX_SKILLS_DIR="${EAGLE_CODEX_SKILLS_DIR:-$EAGLE_CODEX_DIR/skills}"
20
+ EAGLE_CODEX_MEMORIES_DIR="${EAGLE_CODEX_MEMORIES_DIR:-$EAGLE_CODEX_DIR/memories}"
21
+ EAGLE_RAW_BASH_UNLOCK="${EAGLE_RAW_BASH_UNLOCK:-/tmp/eagle-mem-raw-bash-unlock}"
15
22
 
16
23
  eagle_log() {
17
24
  local level="$1"
@@ -63,6 +70,289 @@ eagle_project_from_cwd() {
63
70
  fi
64
71
  }
65
72
 
73
+ eagle_project_file_path() {
74
+ local cwd="${1:-$(pwd)}"
75
+ local file_path="${2:-}"
76
+
77
+ [ -z "$file_path" ] && return 0
78
+
79
+ case "$file_path" in
80
+ ./*) file_path="${file_path#./}" ;;
81
+ esac
82
+
83
+ case "$file_path" in
84
+ /*)
85
+ local git_root
86
+ git_root=$(git -C "$cwd" rev-parse --show-toplevel 2>/dev/null)
87
+ if [ -n "$git_root" ]; then
88
+ case "$file_path" in
89
+ "$git_root"/*)
90
+ printf '%s\n' "${file_path#$git_root/}"
91
+ return 0
92
+ ;;
93
+ esac
94
+ fi
95
+ case "$file_path" in
96
+ "$cwd"/*)
97
+ printf '%s\n' "${file_path#$cwd/}"
98
+ return 0
99
+ ;;
100
+ esac
101
+ ;;
102
+ esac
103
+
104
+ printf '%s\n' "$file_path"
105
+ }
106
+
107
+ eagle_extract_apply_patch_files() {
108
+ sed -n -E 's/^\*\*\* (Add|Update|Delete) File: //p'
109
+ }
110
+
111
+ eagle_agent_source() {
112
+ local agent="${EAGLE_AGENT_SOURCE:-${EAGLE_AGENT:-}}"
113
+ case "$agent" in
114
+ codex|openai-codex) echo "codex" ;;
115
+ claude|claude-code|cloud-code) echo "claude-code" ;;
116
+ *) echo "claude-code" ;;
117
+ esac
118
+ }
119
+
120
+ eagle_agent_source_from_json() {
121
+ local input="${1:-}"
122
+ local configured="${EAGLE_AGENT_SOURCE:-${EAGLE_AGENT:-}}"
123
+ if [ -n "$configured" ]; then
124
+ eagle_agent_source
125
+ return
126
+ fi
127
+
128
+ local transcript_path turn_id tool_name
129
+ transcript_path=$(printf '%s' "$input" | jq -r '.transcript_path // empty' 2>/dev/null)
130
+ turn_id=$(printf '%s' "$input" | jq -r '.turn_id // empty' 2>/dev/null)
131
+ tool_name=$(printf '%s' "$input" | jq -r '.tool_name // empty' 2>/dev/null)
132
+
133
+ case "$transcript_path" in
134
+ "$HOME/.codex/"*|*/.codex/*) echo "codex"; return ;;
135
+ "$HOME/.claude/"*|*/.claude/*) echo "claude-code"; return ;;
136
+ esac
137
+ [ -n "$turn_id" ] && { echo "codex"; return; }
138
+ [ "$tool_name" = "apply_patch" ] && { echo "codex"; return; }
139
+
140
+ echo "claude-code"
141
+ }
142
+
143
+ eagle_agent_label() {
144
+ case "${1:-$(eagle_agent_source)}" in
145
+ codex) echo "Codex" ;;
146
+ *) echo "Claude Code" ;;
147
+ esac
148
+ }
149
+
150
+ eagle_is_shell_tool() {
151
+ case "${1:-}" in
152
+ Bash|exec_command|shell_command|unified_exec) return 0 ;;
153
+ *) return 1 ;;
154
+ esac
155
+ }
156
+
157
+ eagle_tool_command_from_json() {
158
+ local input="${1:-}"
159
+ printf '%s' "$input" | jq -r '
160
+ .tool_input.command
161
+ // .tool_input.cmd
162
+ // .tool_input.shell_command
163
+ // .tool_input.command_line
164
+ // .tool_input.cmdline
165
+ // (if (.tool_input.argv? | type) == "array" then (.tool_input.argv | join(" ")) else empty end)
166
+ // empty
167
+ ' 2>/dev/null
168
+ }
169
+
170
+ eagle_emit_context_for_agent() {
171
+ local agent="${1:-$(eagle_agent_source)}"
172
+ local hook_event="${2:-}"
173
+ local context="${3:-}"
174
+
175
+ [ -z "$context" ] && return 0
176
+
177
+ if [ "$agent" = "codex" ]; then
178
+ jq -cn \
179
+ --arg event "$hook_event" \
180
+ --arg context "$context" \
181
+ '{
182
+ hookSpecificOutput: {
183
+ hookEventName: $event,
184
+ additionalContext: $context
185
+ }
186
+ }'
187
+ return 0
188
+ fi
189
+
190
+ printf '%s\n' "$context"
191
+ }
192
+
193
+ eagle_rtk_rewrite_command() {
194
+ local cmd="$1"
195
+ command -v rtk >/dev/null 2>&1 || return 1
196
+
197
+ case "$cmd" in
198
+ ""|rtk\ *|*" rtk "*|*"eagle-mem "*|*"git push"*|*"gh pr create"*|*"npm publish"*|*"pnpm publish"*|*"yarn npm publish"*|*"bun publish"*)
199
+ return 1
200
+ ;;
201
+ esac
202
+
203
+ local rewritten
204
+ rewritten=$(rtk rewrite "$cmd" 2>/dev/null | head -1)
205
+ [ -z "$rewritten" ] && return 1
206
+ [ "$rewritten" = "$cmd" ] && return 1
207
+ printf '%s\n' "$rewritten"
208
+ }
209
+
210
+ eagle_raw_bash_unlock_active() {
211
+ [ -f "$EAGLE_RAW_BASH_UNLOCK" ] || return 1
212
+ local now mtime age
213
+ now=$(date +%s)
214
+ mtime=$(stat -f %m "$EAGLE_RAW_BASH_UNLOCK" 2>/dev/null || stat -c %Y "$EAGLE_RAW_BASH_UNLOCK" 2>/dev/null || echo 0)
215
+ age=$((now - mtime))
216
+ [ "$age" -lt 600 ]
217
+ }
218
+
219
+ eagle_sha256_stream() {
220
+ if command -v shasum >/dev/null 2>&1; then
221
+ shasum -a 256 | awk '{print $1}'
222
+ else
223
+ sha256sum | awk '{print $1}'
224
+ fi
225
+ }
226
+
227
+ eagle_is_release_boundary_command() {
228
+ local cmd="$1"
229
+
230
+ if printf '%s\n' "$cmd" \
231
+ | tr '\n' ';' \
232
+ | sed -E 's/(&&|[|][|]|;)/\
233
+ /g' \
234
+ | awk '
235
+ function has_dry_run_flag(line) {
236
+ return line ~ /(^|[[:space:]])--dry-run([[:space:]]|$|=([Tt][Rr][Uu][Ee]|1|[Yy][Ee][Ss])([[:space:]]|$))/
237
+ }
238
+ /(^|[[:space:]])gh[[:space:]]+pr[[:space:]]+create([[:space:]]|$)/ ||
239
+ /(^|[[:space:]])npm[[:space:]]+publish([[:space:]]|$)/ ||
240
+ /(^|[[:space:]])pnpm[[:space:]]+publish([[:space:]]|$)/ ||
241
+ /(^|[[:space:]])yarn[[:space:]]+npm[[:space:]]+publish([[:space:]]|$)/ ||
242
+ /(^|[[:space:]])bun[[:space:]]+publish([[:space:]]|$)/ {
243
+ if (!has_dry_run_flag($0)) found = 1
244
+ }
245
+ END { exit(found ? 0 : 1) }
246
+ '
247
+ then
248
+ return 0
249
+ fi
250
+
251
+ if printf '%s\n' "$cmd" \
252
+ | tr '\n' ';' \
253
+ | sed -E 's/(&&|[|][|]|;)/\
254
+ /g' \
255
+ | awk '
256
+ function has_dry_run_flag(line) {
257
+ return line ~ /(^|[[:space:]])--dry-run([[:space:]]|$|=([Tt][Rr][Uu][Ee]|1|[Yy][Ee][Ss])([[:space:]]|$))/
258
+ }
259
+ /(^|[[:space:]])git[[:space:]]+push([[:space:]]|$)/ {
260
+ if (!has_dry_run_flag($0)) found = 1
261
+ }
262
+ END { exit(found ? 0 : 1) }
263
+ '
264
+ then
265
+ return 0
266
+ fi
267
+
268
+ return 1
269
+ }
270
+
271
+ eagle_changed_files_for_release() {
272
+ local cwd="${1:-$(pwd)}"
273
+ [ -d "$cwd" ] || return 0
274
+
275
+ {
276
+ git -C "$cwd" diff --name-only HEAD 2>/dev/null
277
+ git -C "$cwd" diff --cached --name-only 2>/dev/null
278
+ local default_branch
279
+ default_branch=$(git -C "$cwd" symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null | sed 's|^origin/||')
280
+ if [ -n "$default_branch" ] && git -C "$cwd" rev-parse --verify "origin/$default_branch" >/dev/null 2>&1; then
281
+ git -C "$cwd" diff --name-only "origin/$default_branch...HEAD" 2>/dev/null
282
+ fi
283
+ if git -C "$cwd" rev-parse --abbrev-ref --symbolic-full-name '@{upstream}' >/dev/null 2>&1; then
284
+ git -C "$cwd" diff --name-only '@{upstream}...HEAD' 2>/dev/null
285
+ fi
286
+ if ! git -C "$cwd" rev-parse --abbrev-ref --symbolic-full-name '@{upstream}' >/dev/null 2>&1; then
287
+ if ! git -C "$cwd" symbolic-ref --quiet --short refs/remotes/origin/HEAD >/dev/null 2>&1; then
288
+ if git -C "$cwd" rev-parse --verify HEAD~1 >/dev/null 2>&1; then
289
+ git -C "$cwd" diff --name-only HEAD~1..HEAD 2>/dev/null
290
+ fi
291
+ fi
292
+ fi
293
+ } | sed '/^[[:space:]]*$/d' | sort -u
294
+ }
295
+
296
+ eagle_change_fingerprint_for_file() {
297
+ local cwd="${1:-$(pwd)}"
298
+ local file_path="${2:-}"
299
+ [ -z "$file_path" ] && return 0
300
+ [ -d "$cwd" ] || return 0
301
+
302
+ local git_root
303
+ git_root=$(git -C "$cwd" rev-parse --show-toplevel 2>/dev/null)
304
+ [ -z "$git_root" ] && return 0
305
+
306
+ local rel_path="$file_path"
307
+ case "$rel_path" in
308
+ ./*) rel_path="${rel_path#./}" ;;
309
+ /*)
310
+ case "$rel_path" in
311
+ "$git_root"/*) rel_path="${rel_path#$git_root/}" ;;
312
+ "$cwd"/*) rel_path="${rel_path#$cwd/}" ;;
313
+ esac
314
+ ;;
315
+ esac
316
+
317
+ local base_ref=""
318
+ local default_branch
319
+ default_branch=$(git -C "$git_root" symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null | sed 's|^origin/||')
320
+ if [ -n "$default_branch" ] && git -C "$git_root" rev-parse --verify "origin/$default_branch" >/dev/null 2>&1; then
321
+ base_ref=$(git -C "$git_root" merge-base HEAD "origin/$default_branch" 2>/dev/null)
322
+ fi
323
+
324
+ if [ -z "$base_ref" ] && git -C "$git_root" rev-parse --abbrev-ref --symbolic-full-name '@{upstream}' >/dev/null 2>&1; then
325
+ base_ref=$(git -C "$git_root" merge-base HEAD '@{upstream}' 2>/dev/null)
326
+ fi
327
+
328
+ if [ -z "$base_ref" ]; then
329
+ if ! git -C "$git_root" diff --quiet HEAD -- "$rel_path" 2>/dev/null \
330
+ || ! git -C "$git_root" diff --cached --quiet HEAD -- "$rel_path" 2>/dev/null \
331
+ || git -C "$git_root" ls-files --others --exclude-standard -- "$rel_path" 2>/dev/null | grep -qxF "$rel_path"
332
+ then
333
+ base_ref="HEAD"
334
+ elif git -C "$git_root" rev-parse --verify HEAD~1 >/dev/null 2>&1; then
335
+ base_ref="HEAD~1"
336
+ else
337
+ base_ref="HEAD"
338
+ fi
339
+ fi
340
+
341
+ local base_blob="missing"
342
+ if [ -n "$base_ref" ] && git -C "$git_root" cat-file -e "$base_ref:$rel_path" 2>/dev/null; then
343
+ base_blob=$(git -C "$git_root" rev-parse "$base_ref:$rel_path" 2>/dev/null)
344
+ fi
345
+
346
+ local final_blob="missing"
347
+ if [ -f "$git_root/$rel_path" ]; then
348
+ final_blob=$(git -C "$git_root" hash-object -- "$rel_path" 2>/dev/null)
349
+ elif git -C "$git_root" cat-file -e "HEAD:$rel_path" 2>/dev/null; then
350
+ final_blob=$(git -C "$git_root" rev-parse "HEAD:$rel_path" 2>/dev/null)
351
+ fi
352
+
353
+ printf 'file:%s\nbase:%s\nfinal:%s\n' "$rel_path" "$base_blob" "$final_blob" | eagle_sha256_stream
354
+ }
355
+
66
356
  eagle_sql_escape() {
67
357
  printf '%s' "$1" | sed "s/'/''/g"
68
358
  }
@@ -189,6 +479,9 @@ next_steps: [concrete actions]
189
479
  key_files: [path — role]
190
480
  files_read: [path, ...]
191
481
  files_modified: [path, ...]
482
+ affected_features: [feature, ...]
483
+ verified_features: [feature, ...]
484
+ regression_risks: [risk, ...]
192
485
  </eagle-summary>
193
486
  ```
194
487
 
@@ -198,6 +491,7 @@ files_modified: [path, ...]
198
491
  - Emit `<eagle-summary>` before your final text response, every session
199
492
  - When Eagle Mem injects context at SessionStart, attribute it: "Eagle Mem recalls:"
200
493
  - Do not revert decisions surfaced by PostToolUse without asking the user
494
+ - If Eagle Mem reports pending feature verification, verify or waive it before push/PR/publish
201
495
  - Never put raw secrets in the summary — Eagle Mem redacts but defense in depth
202
496
  - If you contradict a loaded memory, update the memory file
203
497
  EAGLE_MD
@@ -233,3 +527,54 @@ eagle_patch_claude_md() {
233
527
 
234
528
  _eagle_claude_md_section >> "$claude_md"
235
529
  }
530
+
531
+ _eagle_codex_agents_section() {
532
+ cat << 'EAGLE_AGENTS'
533
+
534
+ ---
535
+
536
+ ## Eagle Mem — Persistent Memory
537
+
538
+ Eagle Mem hooks are active for Codex in this project. SessionStart and UserPromptSubmit inject project recall from the shared Eagle Mem database at `~/.eagle-mem/memory.db`. PostToolUse records observations and marks affected features for verification.
539
+
540
+ **Rule:** Before your final response in every session, emit an `<eagle-summary>` block so the Stop hook can capture a rich summary.
541
+
542
+ ```
543
+ <eagle-summary>
544
+ request: [what user asked]
545
+ completed: [what shipped]
546
+ learned: [non-obvious discoveries]
547
+ decisions: [choice — why]
548
+ gotchas: [what surprised]
549
+ next_steps: [concrete actions]
550
+ key_files: [path — role]
551
+ files_read: [path, ...]
552
+ files_modified: [path, ...]
553
+ affected_features: [feature, ...]
554
+ verified_features: [feature, ...]
555
+ regression_risks: [risk, ...]
556
+ </eagle-summary>
557
+ ```
558
+
559
+ **How to apply:**
560
+ - Attribute recalled context as "Eagle Mem recalls:" when it is injected
561
+ - Use the Eagle Mem skills when relevant: `eagle-mem-search`, `eagle-mem-overview`, `eagle-mem-memories`, and `eagle-mem-tasks`
562
+ - For important decisions, preferences, gotchas, or durable project facts, explicitly include them in the `<eagle-summary>` block so Codex-originated memories become available to future Claude Code and Codex sessions
563
+ - Do not revert Eagle Mem-surfaced decisions without asking the user
564
+ - If Eagle Mem reports pending feature verification, verify or waive it before push/PR/publish
565
+ - Never put raw secrets in summaries
566
+ EAGLE_AGENTS
567
+ }
568
+
569
+ eagle_patch_codex_agents_md() {
570
+ local agents_md="$EAGLE_CODEX_AGENTS_MD"
571
+ local marker="## Eagle Mem — Persistent Memory"
572
+
573
+ mkdir -p "$(dirname "$agents_md")"
574
+
575
+ if [ -f "$agents_md" ] && grep -qF "$marker" "$agents_md" 2>/dev/null; then
576
+ return 1
577
+ fi
578
+
579
+ _eagle_codex_agents_section >> "$agents_md"
580
+ }
@@ -65,9 +65,9 @@ eagle_backfill_projects() {
65
65
  ch=$(eagle_db_pipe <<SQL
66
66
  BEGIN;
67
67
  UPDATE sessions SET project = '$proj_sql' WHERE id = '$sid_sql' AND (project = '' OR project != '$proj_sql');
68
- UPDATE claude_tasks SET project = '$proj_sql' WHERE source_session_id = '$sid_sql' AND (project = '' OR project != '$proj_sql');
69
- UPDATE claude_memories SET project = '$proj_sql' WHERE origin_session_id = '$sid_sql' AND (project = '' OR project != '$proj_sql');
70
- UPDATE claude_plans SET project = '$proj_sql' WHERE origin_session_id = '$sid_sql' AND (project = '' OR project != '$proj_sql');
68
+ UPDATE agent_tasks SET project = '$proj_sql' WHERE source_session_id = '$sid_sql' AND (project = '' OR project != '$proj_sql');
69
+ UPDATE agent_memories SET project = '$proj_sql' WHERE origin_session_id = '$sid_sql' AND (project = '' OR project != '$proj_sql');
70
+ UPDATE agent_plans SET project = '$proj_sql' WHERE origin_session_id = '$sid_sql' AND (project = '' OR project != '$proj_sql');
71
71
  UPDATE summaries SET project = '$proj_sql' WHERE session_id = '$sid_sql' AND (project = '' OR project != '$proj_sql');
72
72
  UPDATE observations SET project = '$proj_sql' WHERE session_id = '$sid_sql' AND (project = '' OR project != '$proj_sql');
73
73
  SELECT total_changes();