eagle-mem 4.4.0 → 4.6.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.
package/README.md CHANGED
@@ -1,27 +1,29 @@
1
1
  ```
2
2
  ======================================
3
- Eagle Mem
3
+ Eagle Mem
4
+ context that survives /compact
4
5
  ======================================
5
6
  ```
6
7
 
7
8
  # Eagle Mem
8
9
 
9
- Persistent memory for [Claude Code](https://docs.anthropic.com/en/docs/claude-code). Every session starts with context from previous sessions — summaries, memories, tasks, and project overviews — injected automatically via hooks.
10
+ **Context that survives `/compact`.**
10
11
 
11
- **Zero per-instance overhead.** No daemon, no vector DB, no MCP server. Just bash scripts, sqlite3, and jq.
12
+ ## The Problem
12
13
 
13
- ## Getting started
14
+ Claude Code starts every session with amnesia. It doesn't remember what you built yesterday, what decisions you made, what files matter, or what broke last time. Every `/compact` wipes context. Every new session is a cold start. You waste tokens re-explaining your project, re-reading files, and watching Claude repeat mistakes you already corrected.
14
15
 
15
- ```bash
16
- npm install -g eagle-mem
17
- eagle-mem install
18
- ```
16
+ The longer you work with Claude Code, the worse this gets. Projects accumulate history — decisions, gotchas, architectural patterns, feature dependencies — and none of it survives across sessions.
19
17
 
20
- That's it. Open Claude Code in any project directory. Eagle Mem activates automatically:
18
+ ## The Solution
19
+
20
+ Eagle Mem is a recall layer for Claude Code. Every session starts with context from previous sessions — summaries, decisions, memories, tasks, project overviews, and relevant code — injected automatically via hooks. No commands to run, no prompts to write. It just works.
21
+
22
+ **Zero per-instance overhead.** No daemon, no vector DB, no MCP server. Just bash scripts, sqlite3 (WAL mode, FTS5 full-text search), and jq.
21
23
 
22
24
  ```
23
25
  ======================================
24
- Eagle Mem Loaded
26
+ Eagle Mem Recall Ready
25
27
  ======================================
26
28
  Project | my-app
27
29
  Sessions | 42 (18 with summaries)
@@ -32,66 +34,46 @@ That's it. Open Claude Code in any project directory. Eagle Mem activates automa
32
34
  ======================================
33
35
  ```
34
36
 
35
- Everything is automatic from here. Eagle Mem scans your codebase, indexes source files, captures session summaries, mirrors Claude's memories and tasks, learns which commands are noisy, and prunes stale data — all in the background via hooks.
36
-
37
- ## Commands
38
-
39
- Six commands. Three for lifecycle, three for lookup and troubleshooting.
37
+ ## Getting Started
40
38
 
41
- | Command | What it does |
42
- |---------|-------------|
43
- | `eagle-mem install` | First-time setup: hooks, database, skills |
44
- | `eagle-mem update` | Re-deploy hooks and run migrations after `npm update` |
45
- | `eagle-mem uninstall` | Remove hooks and optionally delete data |
46
- | `eagle-mem search` | Single lookup command — see modes below |
47
- | `eagle-mem health` | Diagnose pipeline health and background automation |
48
- | `eagle-mem config` | View or change LLM provider settings |
39
+ ```bash
40
+ npm install -g eagle-mem
41
+ eagle-mem install
42
+ ```
49
43
 
50
- ### Search modes
44
+ That's it. Open Claude Code in any project directory. Eagle Mem activates automatically.
51
45
 
52
- | Mode | What it does |
53
- |------|-------------|
54
- | `eagle-mem search "query"` | FTS5 keyword search across session summaries |
55
- | `eagle-mem search --timeline` | Recent sessions in chronological order |
56
- | `eagle-mem search --overview` | View project overview |
57
- | `eagle-mem search --memories` | Mirrored Claude Code memories |
58
- | `eagle-mem search --tasks` | In-flight tasks (pending/in-progress) |
59
- | `eagle-mem search --files` | Most frequently modified files |
60
- | `eagle-mem search --stats` | Project statistics (counts) |
61
- | `eagle-mem search --session <id>` | Full observation trail for one session |
46
+ Everything is automatic from here. Eagle Mem scans your codebase, indexes source files, captures session summaries, mirrors Claude's memories and tasks, learns which commands are noisy, and prunes stale data — all in the background via hooks.
62
47
 
63
- ## Skills (inside Claude Code)
48
+ ### Prerequisites
64
49
 
65
- | Skill | What it does |
66
- |-------|-------------|
67
- | `/eagle-mem-search` | Search memory and past sessions — Claude interprets results in context |
68
- | `/eagle-mem-overview` | Build a rich project briefing from README, entry points, and git history |
69
- | `/eagle-mem-memories` | View and search mirrored Claude Code memories and plans |
70
- | `/eagle-mem-tasks` | TaskAware Compact Loop — break complex work into tasks that survive `/compact` |
50
+ - `sqlite3` with FTS5 support (ships with macOS; the installer offers to install if missing)
51
+ - `jq` (the installer offers to install if missing)
52
+ - [Claude Code](https://docs.anthropic.com/en/docs/claude-code) installed (`~/.claude/` must exist)
71
53
 
72
- ## How it works
54
+ ## How It Works
73
55
 
74
56
  Six hooks fire automatically at different points in Claude Code's lifecycle:
75
57
 
76
- | Hook | Fires when | What it does |
58
+ | Hook | Fires When | What It Does |
77
59
  |------|-----------|--------------|
78
60
  | **SessionStart** | startup, resume, clear, compact | Injects overview, summaries, memories, tasks, core files, working set. Auto-provisions new projects (scan, index). |
79
- | **PreToolUse** | before Bash, Read, Edit, and Write calls | Rewrites noisy commands (learned rules), detects redundant reads, nudges co-edit partners |
80
- | **UserPromptSubmit** | user sends a message | FTS5 search for relevant past context |
81
- | **PostToolUse** | after tool calls | Records file touches, mirrors memory/plan/task writes, tracks modifications |
82
- | **Stop** | Claude's turn ends | Extracts `<eagle-summary>`, strips `<private>` tags |
61
+ | **PreToolUse** | before Bash, Read, Edit, Write | Surfaces guardrails and decisions before edits. Rewrites noisy commands (learned rules). Detects redundant reads, nudges co-edit partners, detects stuck loops. |
62
+ | **UserPromptSubmit** | user sends a message | FTS5 search across past sessions and indexed code for relevant context |
63
+ | **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 |
64
+ | **Stop** | Claude's turn ends | Extracts `<eagle-summary>` blocks for rich session summaries |
83
65
  | **SessionEnd** | session closes | Re-syncs tasks, marks session completed |
84
66
 
85
- ### Background automation
67
+ ### Background Automation
86
68
 
87
69
  These run automatically via SessionStart — no commands needed:
88
70
 
89
71
  - **Auto-scan** — new project with no overview triggers a codebase scan
90
72
  - **Auto-index** — new or stale project triggers FTS5 source indexing
91
73
  - **Auto-prune** — observations over 10K rows trigger cleanup
92
- - **Auto-curate** — the self-learning curator analyzes observation data and generates command rules, co-edit patterns, and hot file detection (partially requires LLM provider)
74
+ - **Auto-curate** — the self-learning curator analyzes observation data and generates command rules, co-edit patterns, hot file detection, and guardrails (partially requires LLM provider)
93
75
 
94
- ### Token savings
76
+ ### Token Savings
95
77
 
96
78
  Eagle Mem actively reduces token consumption:
97
79
 
@@ -104,28 +86,98 @@ Eagle Mem actively reduces token consumption:
104
86
  - **Working set recovery** — on compact, SessionStart injects the files you were actively editing so you resume without re-reading everything
105
87
  - **Stuck loop detection** — if the same file is edited 5+ times in one session, PreToolUse nudges to reconsider the approach
106
88
 
107
- ### Data
89
+ ### Anti-Regression
90
+
91
+ Eagle Mem prevents Claude from repeating past mistakes:
92
+
93
+ - **Decision surfacing** — when you edit a file that has past decisions recorded (from `<eagle-summary>` blocks), PreToolUse reminds Claude not to revert without asking
94
+ - **Guardrails** — file-level rules (manual or curator-discovered) that fire before every Edit/Write
95
+ - **Feature verification** — tracks features with smoke tests and dependencies; reminds you to verify on `git push`
96
+ - **Gotcha surfacing** — past surprises and gotchas are surfaced when editing related files
97
+ - **Stale memory detection** — warns when edits may contradict stored memories
98
+
99
+ ## Commands
100
+
101
+ | Command | What It Does |
102
+ |---------|-------------|
103
+ | `eagle-mem install` | First-time setup: hooks, database, skills |
104
+ | `eagle-mem update` | Re-deploy hooks and run migrations after `npm update` |
105
+ | `eagle-mem uninstall` | Remove hooks and optionally delete data |
106
+ | `eagle-mem search` | Search past sessions, memories, and code |
107
+ | `eagle-mem health` | Diagnose pipeline health and background automation |
108
+ | `eagle-mem config` | View or change LLM provider settings |
109
+ | `eagle-mem guard` | Manage regression guardrails for files |
110
+ | `eagle-mem overview` | Build or view project overview |
111
+ | `eagle-mem memories` | View/sync Claude Code memories |
112
+ | `eagle-mem tasks` | View mirrored tasks |
113
+ | `eagle-mem curate` | Run curator (co-edits, hot files, guardrails) |
114
+ | `eagle-mem feature` | Track and verify features |
115
+ | `eagle-mem prune` | Clean old sessions and stale data |
116
+ | `eagle-mem scan` | Scan codebase and generate overview |
117
+ | `eagle-mem index` | Index source files for FTS5 code search |
118
+
119
+ ### Search Modes
120
+
121
+ ```bash
122
+ eagle-mem search "auth bug" # keyword search across summaries
123
+ eagle-mem search --timeline # recent sessions in chronological order
124
+ eagle-mem search --overview # project overview
125
+ eagle-mem search --memories # mirrored Claude Code memories
126
+ eagle-mem search --tasks # in-flight tasks (pending/in-progress)
127
+ eagle-mem search --files # most frequently modified files
128
+ eagle-mem search --stats # project statistics
129
+ eagle-mem search --session <id> # full observation trail for one session
130
+ ```
131
+
132
+ ## Skills (Inside Claude Code)
133
+
134
+ | Skill | What It Does |
135
+ |-------|-------------|
136
+ | `/eagle-mem-search` | Search memory and past sessions — Claude interprets results in context |
137
+ | `/eagle-mem-overview` | Build a rich project briefing from README, entry points, and git history |
138
+ | `/eagle-mem-memories` | View and search mirrored Claude Code memories and plans |
139
+ | `/eagle-mem-tasks` | TaskAware Compact Loop — break complex work into tasks that survive `/compact` |
140
+
141
+ ## Data
108
142
 
109
143
  Single SQLite database at `~/.eagle-mem/memory.db` (WAL mode, FTS5 full-text search):
110
144
 
111
- | Table | What it stores |
145
+ | Table | What It Stores |
112
146
  |-------|---------------|
113
- | sessions | Active/completed sessions per project |
114
- | summaries | Per-session summaries (FTS5-indexed) |
115
- | observations | Per-tool-use file touch records |
116
- | overviews | One overview per project (auto-scan or manual) |
117
- | code_chunks | FTS5-indexed source file chunks |
118
- | command_rules | Curator-learned command output rules |
119
- | file_hints | Curator-learned file access patterns (co-edit pairs) |
120
- | claude_memories | Mirror of Claude Code auto-memories |
121
- | claude_plans | Mirror of Claude Code plans |
122
- | claude_tasks | Mirror of Claude Code tasks |
123
-
124
- ## Prerequisites
147
+ | `sessions` | Active/completed sessions per project |
148
+ | `summaries` | Per-session summaries with decisions, gotchas, key files (FTS5-indexed) |
149
+ | `observations` | Per-tool-use file touch records |
150
+ | `overviews` | One overview per project (auto-scan or manual) |
151
+ | `code_chunks` | FTS5-indexed source file chunks |
152
+ | `command_rules` | Curator-learned command output rules |
153
+ | `file_hints` | Curator-learned file access patterns (co-edit pairs, hot files) |
154
+ | `guardrails` | File-level regression rules (manual or curator-discovered) |
155
+ | `features` | Feature tracking with names and descriptions |
156
+ | `feature_files` | Files belonging to each feature |
157
+ | `feature_dependencies` | Inter-feature dependency relationships |
158
+ | `feature_smoke_tests` | Smoke test definitions for feature verification |
159
+ | `eagle_meta` | Internal metadata (last scan, last curate, etc.) |
160
+ | `claude_memories` | Mirror of Claude Code auto-memories |
161
+ | `claude_plans` | Mirror of Claude Code plans |
162
+ | `claude_tasks` | Mirror of Claude Code tasks |
125
163
 
126
- - `sqlite3` with FTS5 support (ships with macOS; the installer offers to install if missing)
127
- - `jq` (the installer offers to install if missing)
128
- - [Claude Code](https://docs.anthropic.com/en/docs/claude-code) installed (`~/.claude/` must exist)
164
+ ### Project Identity
165
+
166
+ Projects are identified by their HOME-relative path (e.g., `personal_projects/eagle-mem`). This ensures uniqueness even when multiple projects share the same directory name. Git repositories use the repo root; non-git directories use the working directory.
167
+
168
+ ### Namespace Migration
169
+
170
+ When upgrading from older versions, `eagle-mem update` automatically migrates project data to the new namespace format. The migration preserves newer data when conflicts exist and cleans up stale entries.
171
+
172
+ ## LLM Provider (Optional)
173
+
174
+ Some features (curator auto-enrichment, overview generation) can use an LLM for richer output. Configure with:
175
+
176
+ ```bash
177
+ eagle-mem config
178
+ ```
179
+
180
+ Supported providers: Ollama (auto-detected), Anthropic, OpenAI. Eagle Mem works fully without a provider — LLM features gracefully degrade to heuristic fallbacks.
129
181
 
130
182
  ## License
131
183
 
@@ -24,14 +24,43 @@ hook_event=$(echo "$input" | jq -r '.hook_event_name // empty')
24
24
 
25
25
  if [ -z "$session_id" ]; then exit 0; fi
26
26
 
27
- # TaskCreated/TaskCompleted dedicated events — mirror tasks and exit
27
+ # TaskCreated/TaskCompleted dedicated events — parse top-level fields and exit
28
28
  case "$hook_event" in
29
29
  TaskCreated|TaskCompleted)
30
30
  [ ! -f "$EAGLE_MEM_DB" ] && exit 0
31
31
  project=$(eagle_project_from_cwd "$cwd")
32
32
  [ -z "$project" ] && exit 0
33
33
  eagle_upsert_session "$session_id" "$project" "$cwd" "" ""
34
- eagle_posttool_mirror_tasks "TaskCreate" "$session_id" "$project" "$input"
34
+
35
+ task_id=$(echo "$input" | jq -r '.task_id // empty')
36
+ task_subject=$(echo "$input" | jq -r '.task_subject // empty')
37
+ task_desc=$(echo "$input" | jq -r '.task_description // empty')
38
+
39
+ if [ -n "$task_id" ] && [ -n "$task_subject" ]; then
40
+ local_status="pending"
41
+ [ "$hook_event" = "TaskCompleted" ] && local_status="completed"
42
+
43
+ # Synthetic file_path keyed on session+task — file_path is the UNIQUE column
44
+ synthetic_fp="event://${session_id}/${task_id}"
45
+
46
+ tid_sql=$(eagle_sql_escape "$task_id")
47
+ fp_sql=$(eagle_sql_escape "$synthetic_fp")
48
+ proj_sql=$(eagle_sql_escape "$project")
49
+ sid_sql=$(eagle_sql_escape "$session_id")
50
+ subj_sql=$(eagle_sql_escape "$task_subject")
51
+ desc_sql=$(eagle_sql_escape "$task_desc")
52
+ stat_sql=$(eagle_sql_escape "$local_status")
53
+
54
+ eagle_db_pipe <<SQL
55
+ INSERT INTO claude_tasks (project, source_session_id, source_task_id, file_path, subject, description, status)
56
+ VALUES ('$proj_sql', '$sid_sql', '$tid_sql', '$fp_sql', '$subj_sql', '$desc_sql', '$stat_sql')
57
+ ON CONFLICT(file_path) DO UPDATE SET
58
+ subject = excluded.subject,
59
+ description = excluded.description,
60
+ status = excluded.status,
61
+ updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now');
62
+ SQL
63
+ fi
35
64
  exit 0
36
65
  ;;
37
66
  esac
@@ -80,8 +80,9 @@ Bash)
80
80
  done <<< "$changed_files"
81
81
 
82
82
  if [ -n "$context" ]; then
83
- context="Eagle Mem: This push affects the following features. After deploy, verify each works and run 'eagle-mem feature verify <name>'.
84
- ${context}"
83
+ context="=== Eagle Mem: Feature Guardrail ===
84
+ This push affects the following features. After deploy, verify each works and run 'eagle-mem feature verify <name>'.
85
+ ${context}================"
85
86
  fi
86
87
  fi
87
88
  fi
@@ -91,7 +92,7 @@ ${context}"
91
92
  # ─── Command output filtering (learned rules) ─────────────
92
93
 
93
94
  base_cmd=$(echo "$cmd" | awk '{print $1}' | sed 's|.*/||')
94
- rule=$(eagle_get_command_rule "$project" "$base_cmd")
95
+ rule=$(eagle_get_command_rule "$project" "$base_cmd" "$cmd")
95
96
 
96
97
  if [ -n "$rule" ]; then
97
98
  IFS='|' read -r strategy max_lines reason <<< "$rule"
@@ -100,19 +101,19 @@ ${context}"
100
101
  if [ -n "$max_lines" ] && [ "$max_lines" -gt 0 ] 2>/dev/null; then
101
102
  case "$cmd" in
102
103
  *"&&"*|*"||"*|*";"*)
103
- context+="Eagle Mem: '${base_cmd}' produces long output (${reason}). Consider: | head -${max_lines}"
104
+ context+="Eagle Mem command rule: '${base_cmd}' produces long output (${reason}). Consider: | head -${max_lines}"
104
105
  ;;
105
106
  *"| head"*|*"| tail"*|*"| wc"*|*"| grep"*|*">"*|*">>"*)
106
107
  ;;
107
108
  *)
108
- updated_input=$(jq -nc --arg cmd "${cmd} | head -${max_lines}" '{"command":$cmd}')
109
- context+="Eagle Mem: '${base_cmd}' output is typically long (${reason}). Piped through head -${max_lines}."
109
+ updated_input=$(echo "$input" | jq --arg cmd "${cmd} | head -${max_lines}" '.tool_input + {"command":$cmd}')
110
+ context+="Eagle Mem command rule: '${base_cmd}' output is typically long (${reason}). Piped through head -${max_lines}."
110
111
  ;;
111
112
  esac
112
113
  fi
113
114
  ;;
114
115
  summary)
115
- context+="Eagle Mem: '${base_cmd}' is typically noisy (${reason}). Consider piping through tail or checking exit code only."
116
+ context+="Eagle Mem command rule: '${base_cmd}' is typically noisy (${reason}). Consider piping through tail or checking exit code only."
116
117
  ;;
117
118
  esac
118
119
  fi
@@ -146,12 +147,12 @@ Edit|Write)
146
147
  while IFS= read -r ctx_line; do
147
148
  case "$ctx_line" in
148
149
  GR:*) gr_block+=" - ${ctx_line#GR:}"$'\n' ;;
149
- DEC:*) context+="Eagle Mem decisions for '${fname}': ${ctx_line#DEC:} — Do not revert without asking. " ;;
150
- GOT:*) context+="Eagle Mem gotchas for '${fname}': ${ctx_line#GOT:} " ;;
150
+ DEC:*) context+="=== Eagle Mem: Decision Recall ==="$'\n'"${fname}: ${ctx_line#DEC:} — Do not revert without asking."$'\n'"================"$'\n' ;;
151
+ GOT:*) context+="=== Eagle Mem: Gotcha Recall ==="$'\n'"${fname}: ${ctx_line#GOT:}"$'\n'"================"$'\n' ;;
151
152
  esac
152
153
  done <<< "$edit_ctx"
153
154
  if [ -n "$gr_block" ]; then
154
- context+="Eagle Mem guardrails for '${fname}':"$'\n'"${gr_block}"
155
+ context+="=== Eagle Mem: Guardrail ==="$'\n'"${fname}:"$'\n'"${gr_block}================"$'\n'
155
156
  fi
156
157
  fi
157
158
  ;;
@@ -164,9 +165,9 @@ Edit|Write)
164
165
  edit_count=$(grep -cFx -- "$fp" "$edit_tracker" 2>/dev/null)
165
166
  edit_count=${edit_count:-0}
166
167
  if [ "$edit_count" -ge 8 ]; then
167
- context+="Eagle Mem: '$(basename "$fp")' has been edited ${edit_count} times this session. You may be stuck — consider stepping back to rethink your approach before making more changes. "
168
+ context+="Eagle Mem warning: '$(basename "$fp")' has been edited ${edit_count} times this session. You may be stuck — consider stepping back to rethink your approach before making more changes. "
168
169
  elif [ "$edit_count" -ge 5 ]; then
169
- context+="Eagle Mem: '$(basename "$fp")' has been edited ${edit_count} times this session. If the changes aren't converging, consider a different approach. "
170
+ context+="Eagle Mem warning: '$(basename "$fp")' has been edited ${edit_count} times this session. If the changes aren't converging, consider a different approach. "
170
171
  fi
171
172
  fi
172
173
  fi
@@ -185,7 +186,7 @@ Edit|Write)
185
186
  [ -n "$co_file" ] && partners+="$(basename "$co_file"), "
186
187
  done
187
188
  partners=${partners%, }
188
- context+="Eagle Mem: When you change '$(basename "$fp")' you usually also touch: $partners"
189
+ context+="Eagle Mem recall: when you change '$(basename "$fp")' you usually also touch: $partners"
189
190
  fi
190
191
  fi
191
192
  ;;
@@ -197,7 +198,7 @@ Read)
197
198
  # ─── Read-after-modify detection ──────────────────────
198
199
  mod_file="$EAGLE_MEM_DIR/mod-tracker/${session_id}"
199
200
  if [ -f "$mod_file" ] && grep -qFx -- "$fp" "$mod_file" 2>/dev/null; then
200
- context+="Eagle Mem: '$(basename "$fp")' was just edited/written — the diff is already in context from the tool output. "
201
+ context+="Eagle Mem recall: '$(basename "$fp")' was just edited/written — the diff is already in context from the tool output. "
201
202
  fi
202
203
 
203
204
  # ─── Read dedup tracker (soft nudge) ──────────────────
@@ -208,7 +209,7 @@ Read)
208
209
  read_count=$(grep -cFx -- "$fp" "$tracker_file" 2>/dev/null)
209
210
  read_count=${read_count:-0}
210
211
  if [ "$read_count" -ge 3 ]; then
211
- context+="Eagle Mem: '$(basename "$fp")' has been read ${read_count} times this session. Its contents are likely already in context."
212
+ context+="Eagle Mem recall: '$(basename "$fp")' has been read ${read_count} times this session. Its contents are likely already in context."
212
213
  fi
213
214
  fi
214
215
  ;;
@@ -129,7 +129,7 @@ stat_last_display="${stat_last_summary:0:60}"
129
129
  [ ${#stat_last_summary} -gt 60 ] && stat_last_display+="..."
130
130
 
131
131
  eagle_banner="======================================
132
- Eagle Mem Loaded
132
+ Eagle Mem Recall Ready
133
133
  ======================================
134
134
  Project | $project
135
135
  Sessions | $stat_sessions ($stat_with_summaries with summaries)"
@@ -151,7 +151,8 @@ context="$eagle_banner
151
151
 
152
152
  if [ -n "$update_notice" ]; then
153
153
  context+="
154
- === $update_notice ===
154
+ === Eagle Mem: Update Available ===
155
+ $update_notice
155
156
  "
156
157
  fi
157
158
 
@@ -163,12 +164,12 @@ if [ -n "$overview" ]; then
163
164
  overview="${overview:0:497}..."
164
165
  fi
165
166
  context+="
166
- === Overview ===
167
+ === Eagle Mem: Project Overview ===
167
168
  $overview
168
169
  "
169
170
  else
170
171
  context+="
171
- === New Project ===
172
+ === Eagle Mem: New Project ===
172
173
  No overview yet — auto-scan is running. Run /eagle-mem-overview for a richer briefing.
173
174
  "
174
175
  fi
@@ -185,7 +186,7 @@ recent=$(eagle_get_recent_summaries "$project" "$_summary_limit")
185
186
 
186
187
  if [ -n "$recent" ]; then
187
188
  context+="
188
- === Recent Sessions ===
189
+ === Eagle Mem: Recent Recall ===
189
190
  "
190
191
  while IFS='|' read -r request completed learned next_steps created_at decisions gotchas key_files; do
191
192
  [ -z "$request" ] && [ -z "$completed" ] && continue
@@ -220,7 +221,7 @@ memories=$(eagle_db "SELECT memory_name, memory_type, description, file_path, up
220
221
  LIMIT 5;")
221
222
  if [ -n "$memories" ]; then
222
223
  context+="
223
- === Memories ===
224
+ === Eagle Mem: Stored Memories ===
224
225
  "
225
226
  while IFS='|' read -r mname mtype mdesc _fpath _updated days_ago; do
226
227
  [ -z "$mname" ] && continue
@@ -244,7 +245,7 @@ fi
244
245
  plans=$(eagle_list_claude_plans "$project" 3)
245
246
  if [ -n "$plans" ]; then
246
247
  context+="
247
- === Plans ===
248
+ === Eagle Mem: Plans ===
248
249
  "
249
250
  while IFS='|' read -r ptitle _pproj _fpath _updated; do
250
251
  [ -z "$ptitle" ] && continue
@@ -265,7 +266,7 @@ synced_tasks=$(eagle_db "SELECT subject, status, blocked_by FROM claude_tasks
265
266
  LIMIT 10;")
266
267
  if [ -n "$synced_tasks" ]; then
267
268
  context+="
268
- === Tasks ===
269
+ === Eagle Mem: Tasks ===
269
270
  "
270
271
  while IFS='|' read -r tsubject tstatus tblocked; do
271
272
  [ -z "$tsubject" ] && continue
@@ -283,7 +284,8 @@ fi
283
284
  hot_files=$(eagle_get_hot_files "$project")
284
285
  if [ -n "$hot_files" ]; then
285
286
  context+="
286
- === Core Files (frequently read — re-read sparingly if unchanged) ===
287
+ === Eagle Mem: Core Files ===
288
+ Frequently read — re-read sparingly if unchanged.
287
289
  "
288
290
  IFS=',' read -ra hf_arr <<< "$hot_files"
289
291
  for hf in "${hf_arr[@]}"; do
@@ -298,7 +300,8 @@ if [ "$source_type" = "compact" ] || [ "$source_type" = "clear" ]; then
298
300
  working_set=$(eagle_get_working_set "$session_id")
299
301
  if [ -n "$working_set" ]; then
300
302
  context+="
301
- === Working Set (files you were modifying before compact) ===
303
+ === Eagle Mem: Working Set ===
304
+ Files you were modifying before compact.
302
305
  "
303
306
  while IFS='|' read -r ws_path ws_edits; do
304
307
  [ -z "$ws_path" ] && continue
@@ -312,19 +315,25 @@ fi
312
315
 
313
316
  if [ "$source_type" = "compact" ] || [ "$source_type" = "clear" ]; then
314
317
  context+="
315
- === Eagle Mem ===
318
+ === Eagle Mem: Active ===
316
319
  Memory active. Attribute recalled context to Eagle Mem. Do not revert PostToolUse-surfaced decisions without asking. Emit <eagle-summary> before final response.
317
320
  "
318
321
  else
319
322
  context+="
320
- === Eagle Mem ===
323
+ === Eagle Mem: Active ===
321
324
  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.
322
325
 
323
326
  Before your final response, emit:
324
327
  <eagle-summary>
325
- request: [what user asked] | completed: [what shipped] | learned: [non-obvious discoveries]
326
- next_steps: [concrete actions] | decisions: [choice — why] | gotchas: [what surprised]
327
- key_files: [path — role] | files_read: [...] | files_modified: [...]
328
+ request: [what user asked]
329
+ completed: [what shipped]
330
+ learned: [non-obvious discoveries]
331
+ decisions: [choice — why]
332
+ gotchas: [what surprised]
333
+ next_steps: [concrete actions]
334
+ key_files: [path — role]
335
+ files_read: [path, ...]
336
+ files_modified: [path, ...]
328
337
  </eagle-summary>
329
338
  "
330
339
  fi
package/hooks/stop.sh CHANGED
@@ -231,6 +231,31 @@ else
231
231
  eagle_log "INFO" "Stop: LLM enrichment skipped — rich data already present"
232
232
  fi
233
233
 
234
+ # ─── Heuristic fallback: derive fields from tool activity when no LLM ──
235
+ # Fills key_files and completed from files_modified when both are empty
236
+
237
+ if [ -z "$key_files" ] && [ -n "$files_modified" ] && [ "$files_modified" != "[]" ]; then
238
+ key_files=$(echo "$files_modified" | jq -r '.[]?' 2>/dev/null | while read -r f; do basename "$f"; done | sort -u | head -10 | paste -sd ', ' -)
239
+ fi
240
+
241
+ if [ -z "$completed" ] && [ -n "$files_modified" ] && [ "$files_modified" != "[]" ]; then
242
+ mod_count=$(echo "$files_modified" | jq -r '.[]?' 2>/dev/null | wc -l | tr -d ' ')
243
+ mod_names=$(echo "$files_modified" | jq -r '.[]?' 2>/dev/null | while read -r f; do basename "$f"; done | sort -u | head -5 | paste -sd ', ' -)
244
+ if [ "${mod_count:-0}" -gt 0 ]; then
245
+ completed="Modified ${mod_count} files: ${mod_names}"
246
+ fi
247
+ elif [ -z "$completed" ] && [ -n "$files_read" ] && [ "$files_read" != "[]" ]; then
248
+ read_count=$(echo "$files_read" | jq -r '.[]?' 2>/dev/null | wc -l | tr -d ' ')
249
+ read_names=$(echo "$files_read" | jq -r '.[]?' 2>/dev/null | while read -r f; do basename "$f"; done | sort -u | head -5 | paste -sd ', ' -)
250
+ if [ "${read_count:-0}" -gt 0 ]; then
251
+ completed="Reviewed ${read_count} files: ${read_names}"
252
+ fi
253
+ fi
254
+
255
+ if [ -z "$key_files" ] && [ -n "$files_read" ] && [ "$files_read" != "[]" ]; then
256
+ key_files=$(echo "$files_read" | jq -r '.[]?' 2>/dev/null | while read -r f; do basename "$f"; done | sort -u | head -10 | paste -sd ', ' -)
257
+ fi
258
+
234
259
  # ─── Test reminder for guardrailed files ─────────────────
235
260
 
236
261
  if [ -n "$files_modified" ] && [ "$files_modified" != "[]" ]; then
@@ -41,14 +41,14 @@ if [ -n "$session_id" ] && eagle_validate_session_id "$session_id"; then
41
41
 
42
42
  if [ "$turn_count" -ge 30 ]; then
43
43
  context+="
44
- === EAGLE MEM Context Pressure: CRITICAL ($turn_count turns since compact) ===
44
+ === Eagle Mem: Context Pressure Critical ($turn_count turns since compact) ===
45
45
  IMMEDIATELY emit a detailed <eagle-summary> covering ALL work this session.
46
46
  Tell the user to run /compact NOW to avoid losing context.
47
47
  "
48
48
  echo "$turn_count" > "$EAGLE_MEM_DIR/.context-pressure"
49
49
  elif [ "$turn_count" -ge 20 ]; then
50
50
  context+="
51
- === EAGLE MEM Context Pressure: HIGH ($turn_count turns since compact) ===
51
+ === Eagle Mem: Context Pressure High ($turn_count turns since compact) ===
52
52
  Include a thorough <eagle-summary> in your next response — capture all decisions, gotchas, and learned context before compaction.
53
53
  Suggest the user run /compact to free context for continued work.
54
54
  "
@@ -81,7 +81,7 @@ fts_query=$(echo "$user_prompt" | tr -cs '[:alnum:]' ' ' | tr '[:upper:]' '[:low
81
81
  results=$(eagle_search_summaries "$fts_query" "$project" 3)
82
82
 
83
83
  if [ -n "$results" ]; then
84
- context+="=== EAGLE MEM Relevant Memory ===
84
+ context+="=== Eagle Mem: Relevant Recall ===
85
85
  "
86
86
  while IFS='|' read -r req completed learned _next_steps created_at _proj decisions gotchas key_files; do
87
87
  [ -z "$req" ] && [ -z "$completed" ] && continue
@@ -106,7 +106,7 @@ if [ "${has_chunks:-0}" -gt 0 ]; then
106
106
  code_results=$(eagle_search_code_chunks "$fts_query" "$project" 5)
107
107
 
108
108
  if [ -n "$code_results" ]; then
109
- context+="=== EAGLE MEM Relevant Code ===
109
+ context+="=== Eagle Mem: Relevant Code ===
110
110
  "
111
111
  while IFS='|' read -r fpath sline eline lang; do
112
112
  [ -z "$fpath" ] && continue
@@ -123,7 +123,7 @@ fi
123
123
  context+="
124
124
  IMPORTANT: When Eagle Mem finds relevant memories or code for the user's prompt, briefly mention it at the start of your response: \"Eagle Mem recalled N relevant sessions\" or \"Eagle Mem found related code in [files]\". One line max — then proceed with the answer.
125
125
 
126
- Eagle Mem (persistent memory across sessions)
126
+ === Eagle Mem: Persistent Memory ===
127
127
  "
128
128
 
129
129
  echo "$context"
package/lib/common.sh CHANGED
@@ -39,18 +39,27 @@ eagle_project_from_cwd() {
39
39
  "$HOME/Desktop"|"$HOME/Desktop/"*) echo ""; return ;;
40
40
  esac
41
41
 
42
+ local target_dir
42
43
  local git_root
43
44
  git_root=$(git -C "$cwd" rev-parse --show-toplevel 2>/dev/null)
44
45
  if [ -n "$git_root" ]; then
45
- basename "$git_root"
46
+ target_dir="$git_root"
46
47
  else
47
- local name
48
- name=$(basename "$cwd")
49
- # Reject single-character project names (likely temp dir fragments)
50
- if [ ${#name} -le 1 ]; then
51
- echo ""; return
52
- fi
53
- basename "$cwd"
48
+ target_dir="$cwd"
49
+ fi
50
+
51
+ local name
52
+ name=$(basename "$target_dir")
53
+ if [ ${#name} -le 1 ]; then
54
+ echo ""; return
55
+ fi
56
+
57
+ if [[ "$target_dir" == "$HOME/"* ]]; then
58
+ echo "${target_dir#$HOME/}"
59
+ elif [ "$target_dir" = "$HOME" ]; then
60
+ echo "$name"
61
+ else
62
+ echo "${target_dir#/}"
54
63
  fi
55
64
  }
56
65
 
@@ -66,7 +75,7 @@ eagle_sql_int() {
66
75
  }
67
76
 
68
77
  eagle_fts_sanitize() {
69
- printf '%s' "$1" | sed 's/[*"(){}^~:]/ /g' | sed 's/ */ /g; s/^ //; s/ $//'
78
+ printf '%s' "$1" | sed 's/[^A-Za-z0-9_]/ /g' | sed 's/ */ /g; s/^ //; s/ $//'
70
79
  }
71
80
 
72
81
  # Escape SQL LIKE wildcards (% and _) so literal filenames match exactly.
@@ -158,17 +167,8 @@ eagle_collect_files() {
158
167
  fi
159
168
  }
160
169
 
161
- eagle_patch_claude_md() {
162
- local claude_md="$HOME/.claude/CLAUDE.md"
163
- local marker="## Eagle Mem — Persistent Memory"
164
-
165
- if [ -f "$claude_md" ] && grep -qF "$marker" "$claude_md" 2>/dev/null; then
166
- return 1
167
- fi
168
-
169
- mkdir -p "$HOME/.claude"
170
-
171
- cat >> "$claude_md" << 'EAGLE_MD'
170
+ _eagle_claude_md_section() {
171
+ cat << 'EAGLE_MD'
172
172
 
173
173
  ---
174
174
 
@@ -180,9 +180,15 @@ Eagle Mem hooks are active in every project. SessionStart injects context (overv
180
180
 
181
181
  ```
182
182
  <eagle-summary>
183
- request: [what user asked] | completed: [what shipped] | learned: [non-obvious discoveries]
184
- next_steps: [concrete actions] | decisions: [choice — why] | gotchas: [what surprised]
185
- key_files: [path — role] | files_read: [...] | files_modified: [...]
183
+ request: [what user asked]
184
+ completed: [what shipped]
185
+ learned: [non-obvious discoveries]
186
+ decisions: [choice — why]
187
+ gotchas: [what surprised]
188
+ next_steps: [concrete actions]
189
+ key_files: [path — role]
190
+ files_read: [path, ...]
191
+ files_modified: [path, ...]
186
192
  </eagle-summary>
187
193
  ```
188
194
 
@@ -196,3 +202,34 @@ key_files: [path — role] | files_read: [...] | files_modified: [...]
196
202
  - If you contradict a loaded memory, update the memory file
197
203
  EAGLE_MD
198
204
  }
205
+
206
+ eagle_patch_claude_md() {
207
+ local claude_md="$HOME/.claude/CLAUDE.md"
208
+ local marker="## Eagle Mem — Persistent Memory"
209
+
210
+ mkdir -p "$HOME/.claude"
211
+
212
+ if [ -f "$claude_md" ] && grep -qF "$marker" "$claude_md" 2>/dev/null; then
213
+ # Check if section has outdated pipe-separated format
214
+ if grep -qF 'request: \[what user asked\] | completed:' "$claude_md" 2>/dev/null; then
215
+ # Replace the outdated section: remove old, append new
216
+ local tmp_md
217
+ tmp_md=$(mktemp)
218
+ awk -v marker="$marker" '
219
+ $0 ~ marker { skip=1; next }
220
+ skip && /^---$/ && !seen_end { seen_end=1; next }
221
+ skip && /^## / { skip=0 }
222
+ !skip { print }
223
+ ' "$claude_md" > "$tmp_md"
224
+ # Remove trailing blank lines left by section removal
225
+ sed -e :a -e '/^[[:space:]]*$/{ $d; N; ba; }' "$tmp_md" > "${tmp_md}.clean"
226
+ mv "${tmp_md}.clean" "$claude_md"
227
+ rm -f "$tmp_md"
228
+ _eagle_claude_md_section >> "$claude_md"
229
+ return 0
230
+ fi
231
+ return 1
232
+ fi
233
+
234
+ _eagle_claude_md_section >> "$claude_md"
235
+ }
@@ -39,16 +39,28 @@ eagle_backfill_projects() {
39
39
  map=$(eagle_build_session_project_map)
40
40
  [ -z "$map" ] && echo "0" && return 0
41
41
 
42
+ # Phase 1: Build old→new project mapping BEFORE mutating any rows.
43
+ # Collect from sessions table so non-session tables can be migrated.
44
+ local rename_map_file
45
+ rename_map_file=$(mktemp)
46
+ while IFS='|' read -r sid project; do
47
+ [ -z "$sid" ] || [ -z "$project" ] && continue
48
+ local sid_sql
49
+ sid_sql=$(eagle_sql_escape "$sid")
50
+ local old_project
51
+ old_project=$(eagle_db "SELECT project FROM sessions WHERE id = '$sid_sql';")
52
+ if [ -n "$old_project" ] && [ "$old_project" != "$project" ]; then
53
+ echo "$old_project|$project" >> "$rename_map_file"
54
+ fi
55
+ done <<< "$map"
56
+
57
+ # Phase 2: Update session-linked tables
42
58
  while IFS='|' read -r sid project; do
43
59
  [ -z "$sid" ] || [ -z "$project" ] && continue
44
60
  local sid_sql proj_sql
45
61
  sid_sql=$(eagle_sql_escape "$sid")
46
62
  proj_sql=$(eagle_sql_escape "$project")
47
63
 
48
- # All six tables updated atomically per session to prevent
49
- # partial backfill if the process is interrupted.
50
- # Note: total_changes() includes FTS trigger changes, so the
51
- # reported count may be higher than actual rows updated.
52
64
  local ch
53
65
  ch=$(eagle_db_pipe <<SQL
54
66
  BEGIN;
@@ -65,6 +77,52 @@ SQL
65
77
  [ "${ch:-0}" -gt 0 ] && updated=$((updated + ch))
66
78
  done <<< "$map"
67
79
 
80
+ # Phase 3: Update non-session tables using the old→new mapping.
81
+ # Skip ambiguous mappings (one old name → multiple new names).
82
+ if [ -s "$rename_map_file" ]; then
83
+ local uniq_map
84
+ uniq_map=$(sort -u "$rename_map_file")
85
+ local prev_old=""
86
+ local ambiguous=""
87
+ while IFS='|' read -r old_proj new_proj; do
88
+ [ -z "$old_proj" ] && continue
89
+ if [ "$old_proj" = "$prev_old" ]; then
90
+ ambiguous+="$old_proj|"
91
+ fi
92
+ prev_old="$old_proj"
93
+ done <<< "$(echo "$uniq_map" | sort -t'|' -k1,1)"
94
+
95
+ while IFS='|' read -r old_proj new_proj; do
96
+ [ -z "$old_proj" ] || [ -z "$new_proj" ] && continue
97
+ case "$ambiguous" in *"$old_proj|"*) continue ;; esac
98
+
99
+ local old_sql new_sql
100
+ old_sql=$(eagle_sql_escape "$old_proj")
101
+ new_sql=$(eagle_sql_escape "$new_proj")
102
+
103
+ eagle_db_pipe <<SQL 2>/dev/null
104
+ BEGIN;
105
+ UPDATE OR IGNORE overviews SET project = '$new_sql' WHERE project = '$old_sql';
106
+ DELETE FROM overviews WHERE project = '$old_sql';
107
+ DELETE FROM code_chunks WHERE project = '$old_sql'
108
+ AND EXISTS (SELECT 1 FROM code_chunks WHERE project = '$new_sql' LIMIT 1);
109
+ UPDATE code_chunks SET project = '$new_sql' WHERE project = '$old_sql';
110
+ UPDATE OR IGNORE features SET project = '$new_sql' WHERE project = '$old_sql';
111
+ DELETE FROM features WHERE project = '$old_sql';
112
+ UPDATE OR IGNORE command_rules SET project = '$new_sql' WHERE project = '$old_sql';
113
+ DELETE FROM command_rules WHERE project = '$old_sql';
114
+ UPDATE OR IGNORE eagle_meta SET project = '$new_sql' WHERE project = '$old_sql';
115
+ DELETE FROM eagle_meta WHERE project = '$old_sql';
116
+ UPDATE OR IGNORE file_hints SET project = '$new_sql' WHERE project = '$old_sql';
117
+ DELETE FROM file_hints WHERE project = '$old_sql';
118
+ UPDATE OR IGNORE guardrails SET project = '$new_sql' WHERE project = '$old_sql';
119
+ DELETE FROM guardrails WHERE project = '$old_sql';
120
+ COMMIT;
121
+ SQL
122
+ done <<< "$uniq_map"
123
+ fi
124
+ rm -f "$rename_map_file"
125
+
68
126
  echo "$updated"
69
127
  }
70
128
 
package/lib/db-mirrors.sh CHANGED
@@ -55,6 +55,10 @@ SQL
55
55
 
56
56
  eagle_search_claude_memories() {
57
57
  local query; query=$(eagle_fts_sanitize "$1")
58
+ if [ -z "$query" ]; then
59
+ echo "Search query is empty after sanitization. Try a different search term." >&2
60
+ return 1
61
+ fi
58
62
  query=$(eagle_sql_escape "$query")
59
63
  local project="${2:-}"
60
64
  local limit; limit=$(eagle_sql_int "${3:-10}")
@@ -131,6 +135,10 @@ SQL
131
135
 
132
136
  eagle_search_claude_plans() {
133
137
  local query; query=$(eagle_fts_sanitize "$1")
138
+ if [ -z "$query" ]; then
139
+ echo "Search query is empty after sanitization. Try a different search term." >&2
140
+ return 1
141
+ fi
134
142
  query=$(eagle_sql_escape "$query")
135
143
  local project="${2:-}"
136
144
  local limit; limit=$(eagle_sql_int "${3:-10}")
@@ -242,6 +250,10 @@ eagle_list_claude_tasks() {
242
250
 
243
251
  eagle_search_claude_tasks() {
244
252
  local query; query=$(eagle_fts_sanitize "$1")
253
+ if [ -z "$query" ]; then
254
+ echo "Search query is empty after sanitization. Try a different search term." >&2
255
+ return 1
256
+ fi
245
257
  query=$(eagle_sql_escape "$query")
246
258
  local project="${2:-}"
247
259
  local limit; limit=$(eagle_sql_int "${3:-10}")
@@ -46,12 +46,13 @@ eagle_prune_observations() {
46
46
 
47
47
  eagle_get_command_rule() {
48
48
  local project; project=$(eagle_sql_escape "$1")
49
- local cmd; cmd=$(eagle_sql_escape "$2")
49
+ local base_cmd; base_cmd=$(eagle_sql_escape "$2")
50
+ local full_cmd; full_cmd=$(eagle_sql_escape "${3:-$2}")
50
51
  eagle_db "SELECT strategy, max_lines, reason
51
52
  FROM command_rules
52
53
  WHERE enabled = 1
53
54
  AND (project = '$project' OR project = '')
54
- AND ('$cmd' LIKE pattern OR '$cmd' = pattern)
55
+ AND ('$base_cmd' = pattern OR '$full_cmd' = pattern OR '$full_cmd' LIKE pattern || ' %')
55
56
  ORDER BY CASE WHEN project != '' THEN 0 ELSE 1 END,
56
57
  LENGTH(pattern) DESC
57
58
  LIMIT 1;"
@@ -75,7 +75,9 @@ eagle_posttool_stale_hint() {
75
75
  local stale_hit
76
76
  stale_hit=$(eagle_search_stale_memories "$project" "$fts_query")
77
77
  if [ -n "$stale_hit" ]; then
78
- local stale_msg="Eagle Mem: Memory '${stale_hit}' may reference '${fname}'. If your edit contradicts it, update the memory."
78
+ local stale_msg="=== Eagle Mem: Memory Check ===
79
+ Memory '${stale_hit}' may reference '${fname}'. If your edit contradicts it, update the memory.
80
+ ================"
79
81
  jq -nc --arg ctx "$stale_msg" '{"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":$ctx}}'
80
82
  fi
81
83
  fi
@@ -106,7 +108,10 @@ eagle_posttool_decision_surface() {
106
108
  local decision_hit
107
109
  decision_hit=$(eagle_search_decisions_for_file "$project" "$fts_query")
108
110
  if [ -n "$decision_hit" ]; then
109
- read_context+="Eagle Mem decision history for '${fname}': ${decision_hit} Do not revert without explicit user request. "
111
+ read_context+="=== Eagle Mem: Decision Recall ===
112
+ ${fname}: ${decision_hit} — Do not revert without explicit user request.
113
+ ================
114
+ "
110
115
  fi
111
116
  fi
112
117
  fi
@@ -116,14 +121,17 @@ eagle_posttool_decision_surface() {
116
121
  if [ -n "$feature_hit" ]; then
117
122
  while IFS='|' read -r feat_name feat_desc feat_verified _role feat_deps feat_other_files feat_smoke; do
118
123
  [ -z "$feat_name" ] && continue
119
- read_context+="Eagle Mem: '${fname}' is part of feature '${feat_name}'"
124
+ read_context+="=== Eagle Mem: Feature Guardrail ===
125
+ '${fname}' is part of feature '${feat_name}'"
120
126
  [ -n "$feat_desc" ] && read_context+=" ($feat_desc)"
121
127
  read_context+="."
122
128
  [ -n "$feat_verified" ] && read_context+=" Last verified: ${feat_verified}."
123
129
  [ -n "$feat_deps" ] && read_context+=" Dependencies: ${feat_deps}."
124
130
  [ -n "$feat_other_files" ] && read_context+=" Other files in pipeline: ${feat_other_files}."
125
131
  [ -n "$feat_smoke" ] && read_context+=" Smoke tests: ${feat_smoke}."
126
- read_context+=" Changes require re-testing after deploy. "
132
+ read_context+=" Changes require re-testing after deploy.
133
+ ================
134
+ "
127
135
  done <<< "$feature_hit"
128
136
  fi
129
137
 
@@ -8,16 +8,22 @@ _EAGLE_HOOKS_SESSIONSTART_LOADED=1
8
8
 
9
9
  _state_dir="$EAGLE_MEM_DIR/state"
10
10
 
11
+ _eagle_state_slug() {
12
+ printf '%s' "$1" | shasum | cut -c1-12
13
+ }
14
+
11
15
  _eagle_state_fresh() {
12
16
  local key="$1" project="$2" max_age_days="${3:-1}"
13
- local state_file="$_state_dir/${key}-${project}"
17
+ local safe_project; safe_project=$(_eagle_state_slug "$project")
18
+ local state_file="$_state_dir/${key}-${safe_project}"
14
19
  [ -f "$state_file" ] && [ -z "$(find "$state_file" -mtime +${max_age_days} 2>/dev/null)" ]
15
20
  }
16
21
 
17
22
  _eagle_state_touch() {
18
23
  local key="$1" project="$2"
24
+ local safe_project; safe_project=$(_eagle_state_slug "$project")
19
25
  mkdir -p "$_state_dir" 2>/dev/null
20
- touch "$_state_dir/${key}-${project}"
26
+ touch "$_state_dir/${key}-${safe_project}"
21
27
  }
22
28
 
23
29
  eagle_sessionstart_auto_provision() {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "eagle-mem",
3
- "version": "4.4.0",
4
- "description": "Persistent memory for Claude Code — SQLite + FTS5, no daemon, no bloat",
3
+ "version": "4.6.1",
4
+ "description": "Context that survives /compact for Claude Code — SQLite + FTS5, no daemon, no bloat",
5
5
  "bin": {
6
6
  "eagle-mem": "bin/eagle-mem"
7
7
  },
package/scripts/health.sh CHANGED
@@ -54,8 +54,8 @@ max_score=$((max_score + 25))
54
54
 
55
55
  total_sessions=$(eagle_db "SELECT COUNT(*) FROM sessions WHERE project = '$p_esc';")
56
56
  total_summaries=$(eagle_db "SELECT COUNT(*) FROM summaries WHERE project = '$p_esc';")
57
- heuristic_summaries=$(eagle_db "SELECT COUNT(*) FROM summaries WHERE project = '$p_esc' AND completed = '(auto-captured)';")
58
- enriched_summaries=$(eagle_db "SELECT COUNT(*) FROM summaries WHERE project = '$p_esc' AND (decisions IS NOT NULL AND decisions != '' OR gotchas IS NOT NULL AND gotchas != '' OR key_files IS NOT NULL AND key_files != '');")
57
+ heuristic_summaries=$(eagle_db "SELECT COUNT(*) FROM summaries WHERE project = '$p_esc' AND (decisions IS NULL OR decisions = '') AND (gotchas IS NULL OR gotchas = '');")
58
+ enriched_summaries=$(eagle_db "SELECT COUNT(*) FROM summaries WHERE project = '$p_esc' AND (decisions IS NOT NULL AND decisions != '' OR gotchas IS NOT NULL AND gotchas != '');")
59
59
 
60
60
  if [ "${total_sessions:-0}" -eq 0 ]; then
61
61
  capture_pct=0
@@ -92,7 +92,7 @@ fi
92
92
 
93
93
  if [ "${total_summaries:-0}" -gt 0 ]; then
94
94
  if [ "$enrich_pct" -ge 50 ]; then
95
- eagle_ok "Enriched: ${enriched_summaries}/${total_summaries} (${enrich_pct}%) have decisions/gotchas/key_files"
95
+ eagle_ok "Enriched: ${enriched_summaries}/${total_summaries} (${enrich_pct}%) have decisions/gotchas"
96
96
  score=$((score + 25))
97
97
  elif [ "$enrich_pct" -ge 20 ]; then
98
98
  eagle_warn "Enriched: ${enriched_summaries}/${total_summaries} (${enrich_pct}%) — LLM extraction may need tuning"
@@ -101,9 +101,9 @@ if [ "${total_summaries:-0}" -gt 0 ]; then
101
101
  elif [ "${enriched_summaries:-0}" -gt 0 ]; then
102
102
  eagle_fail "Enriched: ${enriched_summaries}/${total_summaries} (${enrich_pct}%)"
103
103
  score=$((score + 5))
104
- issues+=("${enrich_pct}% enrichment. Decisions/gotchas/key_files mostly missing.")
104
+ issues+=("${enrich_pct}% enrichment. Decisions/gotchas mostly missing.")
105
105
  else
106
- eagle_fail "Enriched: 0/${total_summaries} — no summaries have decisions/gotchas/key_files"
106
+ eagle_fail "Enriched: 0/${total_summaries} — no summaries have decisions/gotchas"
107
107
  issues+=("Zero enrichment. Check provider config: eagle-mem config")
108
108
  fi
109
109
  else
package/scripts/help.sh CHANGED
@@ -13,7 +13,7 @@ version=$(jq -r .version "$PACKAGE_DIR/package.json" 2>/dev/null || echo "unknow
13
13
  eagle_banner
14
14
 
15
15
  echo -e " ${BOLD}Eagle Mem${RESET} ${DIM}v${version}${RESET}"
16
- echo -e " ${DIM}Persistent memory for Claude Code${RESET}"
16
+ echo -e " ${DIM}Context that survives /compact for Claude Code${RESET}"
17
17
  echo ""
18
18
  echo -e " ${BOLD}Commands:${RESET}"
19
19
  echo -e " ${CYAN}install${RESET} First-time setup: hooks, database, skills"
@@ -250,17 +250,11 @@ else
250
250
  eagle_dim " Statusline detected: $sl_file"
251
251
  eagle_dim " To add Eagle Mem, add this snippet before your ASSEMBLE section:"
252
252
  echo ""
253
- eagle_dim " # ── EAGLE MEM ──"
253
+ eagle_dim " # ── Eagle Mem ──"
254
254
  eagle_dim " em_section=\"\""
255
- eagle_dim " em_db=\"\$HOME/.eagle-mem/memory.db\""
256
- eagle_dim " if [ -f \"\$em_db\" ]; then"
257
- eagle_dim " em_proj=\$(basename \"\$project_dir\" | sed \"s/'/''/g\")"
258
- eagle_dim " em_cnt=\$(echo \".headers off"
259
- eagle_dim " SELECT COUNT(*) FROM sessions WHERE project = '\${em_proj}';\" | sqlite3 \"\$em_db\" 2>/dev/null | tr -d '[:space:]')"
260
- eagle_dim " em_mem=\$(echo \".headers off"
261
- eagle_dim " SELECT COUNT(*) FROM claude_memories WHERE project = '\${em_proj}';\" | sqlite3 \"\$em_db\" 2>/dev/null | tr -d '[:space:]')"
262
- eagle_dim " em_cnt=\${em_cnt:-0}; em_mem=\${em_mem:-0}"
263
- eagle_dim " em_section=\$(printf \"%bEagle Mem%b %b%s%b ses %b%s%b mem\" \"\$CYAN\" \"\$R\" \"\$WHT\" \"\$em_cnt\" \"\$DIM\" \"\$WHT\" \"\$em_mem\" \"\$R\")"
255
+ eagle_dim " if [ -f \"\$HOME/.eagle-mem/scripts/statusline-em.sh\" ]; then"
256
+ eagle_dim " source \"\$HOME/.eagle-mem/scripts/statusline-em.sh\""
257
+ eagle_dim " em_section=\$(eagle_mem_statusline \"\$project_dir\")"
264
258
  eagle_dim " fi"
265
259
  echo ""
266
260
  eagle_ok "Statusline ${DIM}(manual patch needed — instructions above)${RESET}"
@@ -16,8 +16,12 @@ eagle_ensure_db
16
16
 
17
17
  # ─── Parse arguments ──────────────────────────────────────
18
18
 
19
- action="${1:-list}"
20
- shift 2>/dev/null || true
19
+ action="list"
20
+ case "${1:-}" in
21
+ -*) ;; # flags parsed below
22
+ "") ;;
23
+ *) action="$1"; shift ;;
24
+ esac
21
25
 
22
26
  project=""
23
27
  limit=20
@@ -8,12 +8,14 @@ eagle_mem_statusline() {
8
8
  local em_db="$HOME/.eagle-mem/memory.db"
9
9
  [ -f "$em_db" ] || return
10
10
 
11
+ local SCRIPT_DIR; SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
+ . "$SCRIPT_DIR/../lib/common.sh"
13
+
11
14
  local proj
12
- proj=$(basename "$project_dir")
15
+ proj=$(eagle_project_from_cwd "$project_dir")
13
16
  [ -z "$proj" ] && return
14
17
 
15
- # Escape single quotes for safe SQL interpolation
16
- proj=$(printf '%s' "$proj" | sed "s/'/''/g")
18
+ proj=$(eagle_sql_escape "$proj")
17
19
 
18
20
  local cnt mem
19
21
  cnt=$(echo ".headers off
package/scripts/style.sh CHANGED
@@ -17,11 +17,13 @@ TICK="${GREEN}✓${RESET}"
17
17
  CROSS="${RED}✗${RESET}"
18
18
  ARROW="${CYAN}→${RESET}"
19
19
  DOT="${DIM}·${RESET}"
20
+ EAGLE_RULE="======================================"
21
+ EAGLE_TAGLINE="context that survives /compact"
20
22
 
21
23
  eagle_header() {
22
24
  echo ""
23
- echo -e " ${BOLD}Eagle Mem${RESET} ${DIM}$1${RESET}"
24
- echo -e " ${DIM}─────────────────────────────────────${RESET}"
25
+ echo -e " ${CYAN}${BOLD}Eagle Mem${RESET} ${DIM}$1${RESET}"
26
+ echo -e " ${CYAN}${EAGLE_RULE}${RESET}"
25
27
  echo ""
26
28
  }
27
29
 
@@ -43,9 +45,10 @@ eagle_footer() {
43
45
  }
44
46
 
45
47
  eagle_banner() {
46
- echo -e " ${CYAN}======================================${RESET}"
48
+ echo -e " ${CYAN}${EAGLE_RULE}${RESET}"
47
49
  echo -e " ${CYAN}${BOLD} Eagle Mem${RESET}"
48
- echo -e " ${CYAN}======================================${RESET}"
50
+ echo -e " ${DIM} ${EAGLE_TAGLINE}${RESET}"
51
+ echo -e " ${CYAN}${EAGLE_RULE}${RESET}"
49
52
  echo ""
50
53
  }
51
54