eagle-mem 4.9.4 → 4.9.5

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 CHANGED
@@ -11,7 +11,7 @@
11
11
 
12
12
  Eagle Mem turns AI coding sessions into compounding project knowledge. It gives Claude Code and Codex the same local memory, labels which agent created each memory, blocks risky release commands until affected features are verified, and lets broad work split into durable worker lanes.
13
13
 
14
- **v4.8.5 hardens first-run setup:** `eagle-mem config init` now falls through cleanly when Ollama is not running, and DB-backed commands fail loudly when the active `sqlite3` lacks FTS5 support.
14
+ **v4.9.5 hardens hooks and SQLite:** Stop hooks save immediately and queue LLM enrichment in the background instead of launching nested agents during turn shutdown. SQLite calls also resolve through one FTS5-capable binary selector, so Android SDK or other PATH shims do not accidentally break Eagle Mem.
15
15
 
16
16
  **Website:** [Product](https://eagleisbatman.github.io/eagle-mem/) |
17
17
  [Architecture](https://eagleisbatman.github.io/eagle-mem/architecture.html) |
@@ -73,7 +73,7 @@ For Codex, the installer enables `codex_hooks` in `~/.codex/config.toml`, regist
73
73
 
74
74
  ### Prerequisites
75
75
 
76
- - `sqlite3` with FTS5 support (ships with macOS; the installer offers to install if missing)
76
+ - `sqlite3` with FTS5 support (ships with macOS; Eagle Mem prefers known system/Homebrew SQLite binaries before PATH shims)
77
77
  - `jq` (the installer offers to install if missing)
78
78
  - [Claude Code](https://docs.anthropic.com/en/docs/claude-code), Codex, or both installed
79
79
 
@@ -87,7 +87,7 @@ Hooks fire automatically at different points in the agent lifecycle:
87
87
  | **PreToolUse** | before Bash/shell, Read, Edit, Write, apply_patch | Surfaces guardrails and decisions before edits. Blocks release-boundary commands while feature verification is pending. Rewrites noisy commands through RTK when available. Detects redundant reads, nudges co-edit partners, detects stuck loops. |
88
88
  | **UserPromptSubmit** | user sends a message | FTS5 search across past sessions and indexed code for relevant context |
89
89
  | **PostToolUse** | after tool calls | Records file touches, mirrors memory/plan/task writes, surfaces decision history and feature impacts on reads, stale memory warnings on edits |
90
- | **Stop** | agent turn ends | Extracts `<eagle-summary>` blocks for rich session summaries from Claude Code and Codex transcripts |
90
+ | **Stop** | agent turn ends | Saves fast heuristic summaries and extracts `<eagle-summary>` blocks when present. LLM enrichment runs later in the background so the agent lifecycle is not blocked. |
91
91
  | **SessionEnd** | session closes | Re-syncs tasks, marks session completed |
92
92
 
93
93
  Codex shell hooks are registered for `Bash`, `exec_command`, `shell_command`, and `unified_exec` tool names so release-boundary protection works across current Codex shell paths.
@@ -152,6 +152,10 @@ Eagle Mem prevents Claude from repeating past mistakes:
152
152
  | `eagle-mem scan` | Scan codebase and generate overview |
153
153
  | `eagle-mem index` | Index source files for FTS5 code search |
154
154
 
155
+ ### v4.9.5 Patch
156
+
157
+ Stop hooks now use a fast path: they save heuristic summaries immediately, extract explicit summary blocks when present, and queue LLM enrichment in the background so Codex/Claude lifecycle hooks do not time out. SQLite access now goes through a shared FTS5-capable binary resolver used by migrations, DB helpers, updater backups, install checks, and the statusline, avoiding Android SDK or other PATH shims that shadow working SQLite builds.
158
+
155
159
  ### v4.9.4 Patch
156
160
 
157
161
  Project-key hardening for agents that move between folders: hooks now keep a per-session project identity instead of recalculating from every new cwd, and statuslines prefer the stored session project before falling back to folder paths. Install/update also repairs older embedded Eagle Mem statusline blocks so nested-repo projects stop showing `Memories: 0` when the session belongs to the parent workspace.
package/db/migrate.sh CHANGED
@@ -13,11 +13,12 @@ COMMON_SH="$SCRIPT_DIR/../lib/common.sh"
13
13
  if [ -f "$COMMON_SH" ]; then
14
14
  . "$COMMON_SH"
15
15
  eagle_require_sqlite_fts5 || exit 1
16
+ SQLITE_BIN=$(eagle_sqlite_path)
16
17
  else
17
- sqlite_path=$(command -v sqlite3 2>/dev/null || true)
18
- if [ -z "$sqlite_path" ] || ! sqlite3 :memory: "CREATE VIRTUAL TABLE eagle_mem_fts5_probe USING fts5(value);" >/dev/null 2>&1; then
18
+ SQLITE_BIN=$(command -v sqlite3 2>/dev/null || true)
19
+ if [ -z "$SQLITE_BIN" ] || ! "$SQLITE_BIN" :memory: "CREATE VIRTUAL TABLE eagle_mem_fts5_probe USING fts5(value);" >/dev/null 2>&1; then
19
20
  echo "Eagle Mem requires SQLite FTS5, but the active sqlite3 does not support it." >&2
20
- [ -n "$sqlite_path" ] && echo "Detected sqlite3: $sqlite_path" >&2
21
+ [ -n "$SQLITE_BIN" ] && echo "Detected sqlite3: $SQLITE_BIN" >&2
21
22
  echo "Fix PATH so an FTS5-capable sqlite3 is first, then re-run the command." >&2
22
23
  exit 1
23
24
  fi
@@ -33,7 +34,7 @@ run_migration() {
33
34
  local file="$2"
34
35
 
35
36
  local already_applied
36
- already_applied=$(sqlite3 "$DB" "SELECT COUNT(*) FROM _migrations WHERE name = '$name';" 2>/dev/null || echo "0")
37
+ already_applied=$("$SQLITE_BIN" "$DB" "SELECT COUNT(*) FROM _migrations WHERE name = '$name';" 2>/dev/null || echo "0")
37
38
 
38
39
  if [ "$already_applied" = "0" ]; then
39
40
  # Strip PRAGMAs from migration body (they can't run inside transactions
@@ -43,20 +44,20 @@ run_migration() {
43
44
 
44
45
  # Set connection PRAGMAs, then run migration body + tracking insert atomically
45
46
  # .bail on ensures sqlite3 stops on the first error instead of continuing
46
- { echo ".bail on"; echo "PRAGMA trusted_schema=ON;"; echo "PRAGMA foreign_keys=ON;"; echo "PRAGMA busy_timeout=5000;"; echo "BEGIN;"; echo "$body"; echo "INSERT INTO _migrations (name) VALUES ('$name');"; echo "COMMIT;"; } | sqlite3 "$DB"
47
+ { echo ".bail on"; echo "PRAGMA trusted_schema=ON;"; echo "PRAGMA foreign_keys=ON;"; echo "PRAGMA busy_timeout=5000;"; echo "BEGIN;"; echo "$body"; echo "INSERT INTO _migrations (name) VALUES ('$name');"; echo "COMMIT;"; } | "$SQLITE_BIN" "$DB"
47
48
  echo " applied: $name"
48
49
  fi
49
50
  }
50
51
 
51
52
  # Ensure _migrations table exists (bootstrap)
52
- sqlite3 "$DB" "CREATE TABLE IF NOT EXISTS _migrations (
53
+ "$SQLITE_BIN" "$DB" "CREATE TABLE IF NOT EXISTS _migrations (
53
54
  id INTEGER PRIMARY KEY,
54
55
  name TEXT NOT NULL UNIQUE,
55
56
  applied_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
56
57
  );"
57
58
 
58
59
  # Set PRAGMAs (these must be set on every connection)
59
- sqlite3 "$DB" "PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL; PRAGMA busy_timeout = 5000; PRAGMA foreign_keys = ON;"
60
+ "$SQLITE_BIN" "$DB" "PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL; PRAGMA busy_timeout = 5000; PRAGMA foreign_keys = ON;"
60
61
 
61
62
  # ─── Migration 001: Initial schema (special name) ──────────
62
63
  run_migration "001_initial_schema" "$SCRIPT_DIR/schema.sql"
package/hooks/stop.sh CHANGED
@@ -223,6 +223,7 @@ if echo "$request" | grep -qE '<(local-command-caveat|system-reminder|command-na
223
223
  fi
224
224
 
225
225
  needs_enrichment=0
226
+ defer_enrichment=0
226
227
  if [ "$has_rich_data" -eq 0 ]; then
227
228
  needs_enrichment=1
228
229
  elif [ "$context_pressure" -eq 1 ]; then
@@ -235,7 +236,13 @@ fi
235
236
 
236
237
  if [ "$needs_enrichment" -eq 1 ]; then
237
238
  provider=$(eagle_config_get "provider" "type" "none" 2>/dev/null)
238
- if [ "$provider" != "none" ] && [ -n "$text_content" ]; then
239
+ # Stop hooks must stay fast. Expensive LLM enrichment belongs in curate or
240
+ # another background path; nested agent_cli calls can exceed Codex/Claude
241
+ # lifecycle timeouts and make the hook look broken to users.
242
+ if [ "${EAGLE_MEM_STOP_ENRICH:-0}" != "1" ]; then
243
+ defer_enrichment=1
244
+ eagle_log "INFO" "Stop: LLM enrichment skipped — fast hook path (provider=$provider)"
245
+ elif [ "$provider" != "none" ] && [ -n "$text_content" ]; then
239
246
  excerpt=$(echo "$text_content" | tail -c 3000)
240
247
 
241
248
  enrich_prompt="Extract facts from this AI coding session. Only include items with clear evidence in the session text. Do NOT invent or repeat example content.
@@ -401,4 +408,26 @@ if [ -n "$request" ] || [ -n "$completed" ] || [ -n "$learned" ]; then
401
408
  fi
402
409
  fi
403
410
 
411
+ if [ "$defer_enrichment" -eq 1 ] && [ "${EAGLE_MEM_STOP_BACKGROUND_ENRICH:-1}" = "1" ] && [ -n "$text_content" ]; then
412
+ mkdir -p "$EAGLE_MEM_DIR/tmp" 2>/dev/null || true
413
+ enrich_job=$(mktemp "$EAGLE_MEM_DIR/tmp/summary-enrich.XXXXXX.json" 2>/dev/null)
414
+ if [ -n "$enrich_job" ]; then
415
+ jq -cn \
416
+ --arg session_id "$session_id" \
417
+ --arg project "$project" \
418
+ --arg agent "$agent" \
419
+ --arg text "$text_content" \
420
+ '{session_id:$session_id, project:$project, agent:$agent, text:$text}' > "$enrich_job"
421
+
422
+ enrich_script="$SCRIPT_DIR/../scripts/enrich-summary.sh"
423
+ if [ -x "$enrich_script" ]; then
424
+ nohup env EAGLE_MEM_DISABLE_HOOKS=1 EAGLE_AGENT_SOURCE="$agent" EAGLE_AGENT_CWD="$cwd" bash "$enrich_script" "$enrich_job" >/dev/null 2>&1 &
425
+ eagle_log "INFO" "Stop: queued background summary enrichment for session=$session_id"
426
+ else
427
+ rm -f "$enrich_job" 2>/dev/null || true
428
+ eagle_log "WARN" "Stop: background enrichment script missing: $enrich_script"
429
+ fi
430
+ fi
431
+ fi
432
+
404
433
  exit 0
@@ -163,7 +163,7 @@ eagle_register_codex_hooks() {
163
163
  "EAGLE_AGENT_SOURCE=codex bash \"$EAGLE_MEM_DIR/hooks/stop.sh\"" \
164
164
  "Codex Stop hook" \
165
165
  "Saving Eagle Mem summary" \
166
- "30"
166
+ "60"
167
167
  }
168
168
 
169
169
  eagle_remove_codex_hooks() {
package/lib/common.sh CHANGED
@@ -20,24 +20,63 @@ EAGLE_CODEX_SKILLS_DIR="${EAGLE_CODEX_SKILLS_DIR:-$EAGLE_CODEX_DIR/skills}"
20
20
  EAGLE_CODEX_MEMORIES_DIR="${EAGLE_CODEX_MEMORIES_DIR:-$EAGLE_CODEX_DIR/memories}"
21
21
  EAGLE_RAW_BASH_UNLOCK="${EAGLE_RAW_BASH_UNLOCK:-/tmp/eagle-mem-raw-bash-unlock}"
22
22
 
23
+ _eagle_sqlite_candidate_paths() {
24
+ [ -n "${EAGLE_SQLITE_BIN:-}" ] && printf '%s\n' "$EAGLE_SQLITE_BIN"
25
+ [ -f "$EAGLE_MEM_DIR/.sqlite-bin" ] && sed -n '1p' "$EAGLE_MEM_DIR/.sqlite-bin" 2>/dev/null
26
+ printf '%s\n' \
27
+ "/opt/homebrew/opt/sqlite/bin/sqlite3" \
28
+ "/usr/local/opt/sqlite/bin/sqlite3" \
29
+ "/usr/bin/sqlite3" \
30
+ "/opt/homebrew/bin/sqlite3" \
31
+ "/usr/local/bin/sqlite3"
32
+ command -v sqlite3 2>/dev/null || true
33
+ }
34
+
35
+ _eagle_sqlite_bin_supports_fts5() {
36
+ local bin="$1"
37
+ [ -n "$bin" ] && [ -x "$bin" ] || return 1
38
+ "$bin" :memory: "CREATE VIRTUAL TABLE eagle_mem_fts5_probe USING fts5(value);" >/dev/null 2>&1
39
+ }
40
+
23
41
  eagle_sqlite_path() {
42
+ local seen="" candidate
43
+ while IFS= read -r candidate; do
44
+ [ -n "$candidate" ] || continue
45
+ case "|$seen|" in *"|$candidate|"*) continue ;; esac
46
+ seen="${seen}|${candidate}"
47
+ if _eagle_sqlite_bin_supports_fts5 "$candidate"; then
48
+ printf '%s\n' "$candidate"
49
+ return 0
50
+ fi
51
+ done <<EOF
52
+ $(_eagle_sqlite_candidate_paths)
53
+ EOF
54
+
24
55
  command -v sqlite3 2>/dev/null || true
25
56
  }
26
57
 
27
58
  eagle_sqlite_version() {
28
- sqlite3 --version 2>/dev/null | awk '{print $1}'
59
+ local sqlite_bin
60
+ sqlite_bin=$(eagle_sqlite_path)
61
+ [ -n "$sqlite_bin" ] || return 0
62
+ "$sqlite_bin" --version 2>/dev/null | awk '{print $1}'
29
63
  }
30
64
 
31
65
  eagle_sqlite_supports_fts5() {
32
- command -v sqlite3 >/dev/null 2>&1 || return 1
33
- sqlite3 :memory: "CREATE VIRTUAL TABLE eagle_mem_fts5_probe USING fts5(value);" >/dev/null 2>&1
66
+ local sqlite_bin
67
+ sqlite_bin=$(eagle_sqlite_path)
68
+ _eagle_sqlite_bin_supports_fts5 "$sqlite_bin"
34
69
  }
35
70
 
36
71
  eagle_print_sqlite_fts5_error() {
37
72
  local sqlite_path sqlite_version probe_error
38
73
  sqlite_path=$(eagle_sqlite_path)
39
74
  sqlite_version=$(eagle_sqlite_version)
40
- probe_error=$(sqlite3 :memory: "CREATE VIRTUAL TABLE eagle_mem_fts5_probe USING fts5(value);" 2>&1 >/dev/null || true)
75
+ if [ -n "$sqlite_path" ]; then
76
+ probe_error=$("$sqlite_path" :memory: "CREATE VIRTUAL TABLE eagle_mem_fts5_probe USING fts5(value);" 2>&1 >/dev/null || true)
77
+ else
78
+ probe_error=""
79
+ fi
41
80
 
42
81
  printf '%s\n' "Eagle Mem requires SQLite FTS5, but the active sqlite3 does not support it." >&2
43
82
  if [ -n "$sqlite_path" ]; then
@@ -47,8 +86,8 @@ eagle_print_sqlite_fts5_error() {
47
86
  fi
48
87
  [ -n "$sqlite_version" ] && printf '%s\n' "SQLite version: $sqlite_version" >&2
49
88
  [ -n "$probe_error" ] && printf '%s\n' "SQLite error: $probe_error" >&2
50
- printf '%s\n' "Fix: put an FTS5-capable sqlite3 earlier in PATH, then re-run the command." >&2
51
- printf '%s\n' "macOS: check 'command -v sqlite3'; /usr/bin/sqlite3 usually has FTS5. If Android SDK platform-tools is first, move it later in PATH." >&2
89
+ printf '%s\n' "Fix: install an FTS5-capable sqlite3, or set EAGLE_SQLITE_BIN to its absolute path." >&2
90
+ printf '%s\n' "macOS: /usr/bin/sqlite3 usually has FTS5. Eagle Mem now prefers known system/Homebrew sqlite3 paths over Android SDK shims." >&2
52
91
  printf '%s\n' "Homebrew: install sqlite and prepend its bin directory, for example: export PATH=\"/opt/homebrew/opt/sqlite/bin:\$PATH\"" >&2
53
92
  printf '%s\n' "Linux: install a sqlite3 package compiled with ENABLE_FTS5." >&2
54
93
  }
package/lib/db-core.sh CHANGED
@@ -15,10 +15,13 @@ PRAGMA trusted_schema=ON;
15
15
  .output stdout"
16
16
 
17
17
  eagle_db() {
18
+ local _eagle_sqlite_bin
19
+ _eagle_sqlite_bin=$(eagle_sqlite_path)
20
+ [ -n "$_eagle_sqlite_bin" ] || return 1
18
21
  local _eagle_db_err
19
22
  _eagle_db_err=$(mktemp 2>/dev/null || echo "/tmp/_eagle_db_err.$$")
20
23
  local _eagle_db_out
21
- _eagle_db_out=$({ echo "$EAGLE_DB_SETUP"; echo "$*"; } | sqlite3 "$EAGLE_MEM_DB" 2>"$_eagle_db_err")
24
+ _eagle_db_out=$({ echo "$EAGLE_DB_SETUP"; echo "$*"; } | "$_eagle_sqlite_bin" "$EAGLE_MEM_DB" 2>"$_eagle_db_err")
22
25
  local _eagle_db_rc=$?
23
26
  if [ -s "$_eagle_db_err" ]; then
24
27
  cat "$_eagle_db_err" >> "$EAGLE_MEM_LOG" 2>/dev/null
@@ -29,10 +32,13 @@ eagle_db() {
29
32
  }
30
33
 
31
34
  eagle_db_pipe() {
35
+ local _eagle_sqlite_bin
36
+ _eagle_sqlite_bin=$(eagle_sqlite_path)
37
+ [ -n "$_eagle_sqlite_bin" ] || return 1
32
38
  local _eagle_db_err
33
39
  _eagle_db_err=$(mktemp 2>/dev/null || echo "/tmp/_eagle_db_pipe_err.$$")
34
40
  local _eagle_db_out
35
- _eagle_db_out=$({ echo "$EAGLE_DB_SETUP"; echo ".bail on"; cat; } | sqlite3 "$EAGLE_MEM_DB" 2>"$_eagle_db_err")
41
+ _eagle_db_out=$({ echo "$EAGLE_DB_SETUP"; echo ".bail on"; cat; } | "$_eagle_sqlite_bin" "$EAGLE_MEM_DB" 2>"$_eagle_db_err")
36
42
  local _eagle_db_rc=$?
37
43
  if [ -s "$_eagle_db_err" ]; then
38
44
  cat "$_eagle_db_err" >> "$EAGLE_MEM_LOG" 2>/dev/null
@@ -43,10 +49,13 @@ eagle_db_pipe() {
43
49
  }
44
50
 
45
51
  eagle_db_json() {
52
+ local _eagle_sqlite_bin
53
+ _eagle_sqlite_bin=$(eagle_sqlite_path)
54
+ [ -n "$_eagle_sqlite_bin" ] || return 1
46
55
  local _eagle_db_err
47
56
  _eagle_db_err=$(mktemp 2>/dev/null || echo "/tmp/_eagle_db_json_err.$$")
48
57
  local _eagle_db_out
49
- _eagle_db_out=$({ echo "$EAGLE_DB_SETUP"; echo ".mode json"; echo "$*"; } | sqlite3 "$EAGLE_MEM_DB" 2>"$_eagle_db_err")
58
+ _eagle_db_out=$({ echo "$EAGLE_DB_SETUP"; echo ".mode json"; echo "$*"; } | "$_eagle_sqlite_bin" "$EAGLE_MEM_DB" 2>"$_eagle_db_err")
50
59
  local _eagle_db_rc=$?
51
60
  if [ -s "$_eagle_db_err" ]; then
52
61
  cat "$_eagle_db_err" >> "$EAGLE_MEM_LOG" 2>/dev/null
package/lib/updater.sh CHANGED
@@ -186,8 +186,10 @@ eagle_update_backup_runtime() {
186
186
  done
187
187
 
188
188
  if [ -f "$EAGLE_MEM_DB" ]; then
189
- if command -v sqlite3 >/dev/null 2>&1; then
190
- sqlite3 "$EAGLE_MEM_DB" ".backup '$backup_dir/memory.db'" >/dev/null 2>&1 || cp "$EAGLE_MEM_DB" "$backup_dir/memory.db" 2>/dev/null || true
189
+ local sqlite_bin
190
+ sqlite_bin=$(eagle_sqlite_path)
191
+ if [ -n "$sqlite_bin" ]; then
192
+ "$sqlite_bin" "$EAGLE_MEM_DB" ".backup '$backup_dir/memory.db'" >/dev/null 2>&1 || cp "$EAGLE_MEM_DB" "$backup_dir/memory.db" 2>/dev/null || true
191
193
  else
192
194
  cp "$EAGLE_MEM_DB" "$backup_dir/memory.db" 2>/dev/null || true
193
195
  fi
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eagle-mem",
3
- "version": "4.9.4",
3
+ "version": "4.9.5",
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"
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env bash
2
+ # Eagle Mem — background summary enrichment
3
+ # Runs outside lifecycle hook timeouts. Input is a JSON job file created by Stop.
4
+ set +e
5
+ [ "${EAGLE_MEM_DISABLE_BACKGROUND_ENRICH:-}" = "1" ] && exit 0
6
+
7
+ job_file="${1:-}"
8
+ [ -n "$job_file" ] && [ -f "$job_file" ] || exit 0
9
+
10
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
11
+ LIB_DIR="$SCRIPT_DIR/../lib"
12
+
13
+ . "$LIB_DIR/common.sh"
14
+ . "$LIB_DIR/db.sh"
15
+ . "$LIB_DIR/provider.sh"
16
+
17
+ eagle_ensure_db || exit 0
18
+
19
+ session_id=$(jq -r '.session_id // empty' "$job_file" 2>/dev/null)
20
+ project=$(jq -r '.project // empty' "$job_file" 2>/dev/null)
21
+ agent=$(jq -r '.agent // empty' "$job_file" 2>/dev/null)
22
+ text_content=$(jq -r '.text // empty' "$job_file" 2>/dev/null)
23
+
24
+ if [ -z "$session_id" ] || [ -z "$project" ] || [ -z "$text_content" ]; then
25
+ rm -f "$job_file" 2>/dev/null || true
26
+ exit 0
27
+ fi
28
+
29
+ provider=$(eagle_config_get "provider" "type" "none" 2>/dev/null)
30
+ if [ "$provider" = "none" ]; then
31
+ eagle_log "INFO" "Summary enrichment skipped: no provider for session=$session_id"
32
+ rm -f "$job_file" 2>/dev/null || true
33
+ exit 0
34
+ fi
35
+
36
+ excerpt=$(printf '%s' "$text_content" | tail -c 3000)
37
+
38
+ enrich_prompt="Extract facts from this AI coding session. Only include items with clear evidence in the session text. Do NOT invent or repeat example content.
39
+
40
+ Respond with EXACTLY these sections (omit sections with no evidence):
41
+
42
+ REQUEST:
43
+ One-line summary of what the user asked for. No system tags or XML.
44
+
45
+ COMPLETED:
46
+ What was actually accomplished. Be specific about changes made.
47
+
48
+ LEARNED:
49
+ Non-obvious discoveries or insights from the session.
50
+
51
+ DECISIONS:
52
+ Each as: <what was decided> — why: <reason>
53
+
54
+ GOTCHAS:
55
+ Each as: <surprising finding or pitfall>
56
+
57
+ KEY_FILES:
58
+ Each as: <filepath>
59
+
60
+ SESSION TEXT:
61
+ $excerpt"
62
+
63
+ enrich_system="You extract structured facts from Claude Code and Codex development sessions. Output format for decisions: '- Did X — why: Y'. Output format for gotchas: '- Gotcha description'. Be concise. Only include items with clear evidence in the session text. Never fabricate content."
64
+ enrich_result=$(eagle_llm_call "$enrich_prompt" "$enrich_system" 768 2>/dev/null)
65
+ llm_rc=$?
66
+
67
+ if [ $llm_rc -ne 0 ] || [ -z "$enrich_result" ]; then
68
+ eagle_log "WARN" "Summary enrichment failed (rc=$llm_rc) for session=$session_id provider=$provider"
69
+ rm -f "$job_file" 2>/dev/null || true
70
+ exit 0
71
+ fi
72
+
73
+ extract_section() {
74
+ local result="$1" header="$2"
75
+ printf '%s\n' "$result" | awk -v h="$header:" '
76
+ $0 == h || $0 ~ "^"h { found=1; next }
77
+ found && /^[A-Z_]+:/ { exit }
78
+ found && /^[[:space:]]*$/ { next }
79
+ found && /^- / { sub(/^- /, ""); lines[++n] = $0; next }
80
+ found { lines[++n] = $0 }
81
+ END { for (i=1; i<=n; i++) { printf "%s", lines[i]; if (i<n) printf "; " } }
82
+ '
83
+ }
84
+
85
+ request=$(extract_section "$enrich_result" "REQUEST" | eagle_redact)
86
+ completed=$(extract_section "$enrich_result" "COMPLETED" | eagle_redact)
87
+ learned=$(extract_section "$enrich_result" "LEARNED" | eagle_redact)
88
+ decisions=$(extract_section "$enrich_result" "DECISIONS" | eagle_redact)
89
+ gotchas=$(extract_section "$enrich_result" "GOTCHAS" | eagle_redact)
90
+ key_files=$(extract_section "$enrich_result" "KEY_FILES" | eagle_redact)
91
+
92
+ if [ -n "$request" ] || [ -n "$completed" ] || [ -n "$learned" ] || [ -n "$decisions" ] || [ -n "$gotchas" ] || [ -n "$key_files" ]; then
93
+ eagle_insert_summary "$session_id" "$project" "$request" "" "$learned" "$completed" "" "[]" "[]" "" "$decisions" "$gotchas" "$key_files" "$agent"
94
+ eagle_log "INFO" "Summary enrichment saved for session=$session_id provider=$provider"
95
+ fi
96
+
97
+ rm -f "$job_file" 2>/dev/null || true
98
+ exit 0
@@ -80,16 +80,18 @@ echo ""
80
80
  prereqs_ok=true
81
81
 
82
82
  # sqlite3
83
- if command -v sqlite3 &>/dev/null; then
84
- sqlite_version=$(sqlite3 --version 2>/dev/null | awk '{print $1}')
85
- eagle_ok "sqlite3 ${DIM}($sqlite_version)${RESET}"
83
+ sqlite_path=$(eagle_sqlite_path)
84
+ if [ -n "$sqlite_path" ]; then
85
+ sqlite_version=$(eagle_sqlite_version)
86
+ eagle_ok "sqlite3 ${DIM}($sqlite_version at $sqlite_path)${RESET}"
86
87
  else
87
88
  eagle_fail "sqlite3 not found"
88
89
  if eagle_confirm "Install sqlite3?"; then
89
90
  install_package sqlite3
90
- if command -v sqlite3 &>/dev/null; then
91
- sqlite_version=$(sqlite3 --version 2>/dev/null | awk '{print $1}')
92
- eagle_ok "sqlite3 installed ${DIM}($sqlite_version)${RESET}"
91
+ sqlite_path=$(eagle_sqlite_path)
92
+ if [ -n "$sqlite_path" ]; then
93
+ sqlite_version=$("$sqlite_path" --version 2>/dev/null | awk '{print $1}')
94
+ eagle_ok "sqlite3 installed ${DIM}($sqlite_version at $sqlite_path)${RESET}"
93
95
  else
94
96
  eagle_fail "sqlite3 installation failed"
95
97
  prereqs_ok=false
@@ -100,8 +102,7 @@ else
100
102
  fi
101
103
 
102
104
  # FTS5 support
103
- if command -v sqlite3 &>/dev/null; then
104
- sqlite_path=$(eagle_sqlite_path)
105
+ if [ -n "$sqlite_path" ]; then
105
106
  if eagle_sqlite_supports_fts5; then
106
107
  eagle_ok "FTS5 support ${DIM}($sqlite_path)${RESET}"
107
108
  else
@@ -109,9 +110,9 @@ if command -v sqlite3 &>/dev/null; then
109
110
  eagle_dim "Detected sqlite3: $sqlite_path"
110
111
  sqlite_version=$(eagle_sqlite_version)
111
112
  [ -n "$sqlite_version" ] && eagle_dim "SQLite version: $sqlite_version"
112
- eagle_dim "Run: command -v sqlite3"
113
- eagle_dim "Fix PATH so an FTS5-capable sqlite3 is first."
114
- eagle_dim "macOS: /usr/bin/sqlite3 usually has FTS5; move Android SDK platform-tools later if it shadows sqlite3."
113
+ eagle_dim "Run: eagle-mem health, or set EAGLE_SQLITE_BIN=/absolute/path/to/sqlite3."
114
+ eagle_dim "Eagle Mem prefers known system/Homebrew SQLite paths before PATH shims."
115
+ eagle_dim "macOS: /usr/bin/sqlite3 usually has FTS5; Android SDK sqlite3 shims are ignored when a better binary exists."
115
116
  eagle_dim "Homebrew: brew install sqlite, then prepend /opt/homebrew/opt/sqlite/bin or /usr/local/opt/sqlite/bin."
116
117
  eagle_dim "Linux: install a sqlite3 package compiled with ENABLE_FTS5."
117
118
  prereqs_ok=false
@@ -12,6 +12,9 @@ eagle_mem_statusline() {
12
12
 
13
13
  local SCRIPT_DIR; SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
14
  . "$SCRIPT_DIR/../lib/common.sh"
15
+ local sqlite_bin
16
+ sqlite_bin=$(eagle_sqlite_path)
17
+ [ -n "$sqlite_bin" ] || return
15
18
 
16
19
  local proj
17
20
  [ -z "$project_dir" ] && project_dir="$(pwd)"
@@ -24,9 +27,9 @@ eagle_mem_statusline() {
24
27
  version=$(tr -d '[:space:]' < "$HOME/.eagle-mem/.version" 2>/dev/null)
25
28
  [ -z "$version" ] && version="?"
26
29
  cnt=$(echo ".headers off
27
- SELECT COUNT(*) FROM sessions WHERE project = '${proj}';" | sqlite3 "$em_db" 2>/dev/null | tr -d '[:space:]')
30
+ SELECT COUNT(*) FROM sessions WHERE project = '${proj}';" | "$sqlite_bin" "$em_db" 2>/dev/null | tr -d '[:space:]')
28
31
  mem=$(echo ".headers off
29
- SELECT COUNT(*) FROM agent_memories WHERE project = '${proj}';" | sqlite3 "$em_db" 2>/dev/null | tr -d '[:space:]')
32
+ SELECT COUNT(*) FROM agent_memories WHERE project = '${proj}';" | "$sqlite_bin" "$em_db" 2>/dev/null | tr -d '[:space:]')
30
33
  if [ -n "$session_id" ] && [ -f "$HOME/.eagle-mem/.turn-counter.${session_id}" ]; then
31
34
  turns=$(tr -d '[:space:]' < "$HOME/.eagle-mem/.turn-counter.${session_id}" 2>/dev/null)
32
35
  else