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 +119 -67
- package/hooks/post-tool-use.sh +31 -2
- package/hooks/pre-tool-use.sh +16 -15
- package/hooks/session-start.sh +24 -15
- package/hooks/stop.sh +25 -0
- package/hooks/user-prompt-submit.sh +5 -5
- package/lib/common.sh +60 -23
- package/lib/db-backfill.sh +62 -4
- package/lib/db-mirrors.sh +12 -0
- package/lib/db-observations.sh +3 -2
- package/lib/hooks-posttool.sh +12 -4
- package/lib/hooks-sessionstart.sh +8 -2
- package/package.json +2 -2
- package/scripts/health.sh +5 -5
- package/scripts/help.sh +1 -1
- package/scripts/install.sh +4 -10
- package/scripts/memories.sh +6 -2
- package/scripts/statusline-em.sh +5 -3
- package/scripts/style.sh +7 -4
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
|
-
|
|
10
|
+
**Context that survives `/compact`.**
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
## The Problem
|
|
12
13
|
|
|
13
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
## Commands
|
|
38
|
-
|
|
39
|
-
Six commands. Three for lifecycle, three for lookup and troubleshooting.
|
|
37
|
+
## Getting Started
|
|
40
38
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
44
|
+
That's it. Open Claude Code in any project directory. Eagle Mem activates automatically.
|
|
51
45
|
|
|
52
|
-
|
|
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
|
-
|
|
48
|
+
### Prerequisites
|
|
64
49
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
|
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
|
|
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
|
|
80
|
-
| **UserPromptSubmit** | user sends a message | FTS5 search for relevant
|
|
81
|
-
| **PostToolUse** | after tool calls | Records file touches, mirrors memory/plan/task writes,
|
|
82
|
-
| **Stop** | Claude's turn ends | Extracts `<eagle-summary
|
|
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
|
|
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,
|
|
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
|
|
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
|
-
###
|
|
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
|
|
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
|
-
|
|
|
121
|
-
|
|
|
122
|
-
|
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
-
|
|
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
|
|
package/hooks/post-tool-use.sh
CHANGED
|
@@ -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 —
|
|
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
|
-
|
|
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
|
package/hooks/pre-tool-use.sh
CHANGED
|
@@ -80,8 +80,9 @@ Bash)
|
|
|
80
80
|
done <<< "$changed_files"
|
|
81
81
|
|
|
82
82
|
if [ -n "$context" ]; then
|
|
83
|
-
context="Eagle Mem:
|
|
84
|
-
|
|
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
|
|
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
|
|
150
|
-
GOT:*) context+="Eagle Mem
|
|
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
|
|
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:
|
|
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
|
;;
|
package/hooks/session-start.sh
CHANGED
|
@@ -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
|
|
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
|
-
===
|
|
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
|
|
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
|
|
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
|
|
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]
|
|
326
|
-
|
|
327
|
-
|
|
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
|
-
===
|
|
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
|
-
===
|
|
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+="===
|
|
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+="===
|
|
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
|
-
|
|
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
|
-
|
|
46
|
+
target_dir="$git_root"
|
|
46
47
|
else
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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/[
|
|
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
|
-
|
|
162
|
-
|
|
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]
|
|
184
|
-
|
|
185
|
-
|
|
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
|
+
}
|
package/lib/db-backfill.sh
CHANGED
|
@@ -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}")
|
package/lib/db-observations.sh
CHANGED
|
@@ -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
|
|
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 ('$
|
|
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;"
|
package/lib/hooks-posttool.sh
CHANGED
|
@@ -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
|
|
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
|
|
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:
|
|
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
|
|
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}-${
|
|
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
|
-
"description": "
|
|
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
|
|
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 != ''
|
|
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
|
|
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
|
|
104
|
+
issues+=("${enrich_pct}% enrichment. Decisions/gotchas mostly missing.")
|
|
105
105
|
else
|
|
106
|
-
eagle_fail "Enriched: 0/${total_summaries} — no summaries have decisions/gotchas
|
|
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}
|
|
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"
|
package/scripts/install.sh
CHANGED
|
@@ -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 " # ──
|
|
253
|
+
eagle_dim " # ── Eagle Mem ──"
|
|
254
254
|
eagle_dim " em_section=\"\""
|
|
255
|
-
eagle_dim "
|
|
256
|
-
eagle_dim "
|
|
257
|
-
eagle_dim "
|
|
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}"
|
package/scripts/memories.sh
CHANGED
|
@@ -16,8 +16,12 @@ eagle_ensure_db
|
|
|
16
16
|
|
|
17
17
|
# ─── Parse arguments ──────────────────────────────────────
|
|
18
18
|
|
|
19
|
-
action="
|
|
20
|
-
|
|
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
|
package/scripts/statusline-em.sh
CHANGED
|
@@ -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=$(
|
|
15
|
+
proj=$(eagle_project_from_cwd "$project_dir")
|
|
13
16
|
[ -z "$proj" ] && return
|
|
14
17
|
|
|
15
|
-
|
|
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 " ${
|
|
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}
|
|
48
|
+
echo -e " ${CYAN}${EAGLE_RULE}${RESET}"
|
|
47
49
|
echo -e " ${CYAN}${BOLD} Eagle Mem${RESET}"
|
|
48
|
-
echo -e " ${
|
|
50
|
+
echo -e " ${DIM} ${EAGLE_TAGLINE}${RESET}"
|
|
51
|
+
echo -e " ${CYAN}${EAGLE_RULE}${RESET}"
|
|
49
52
|
echo ""
|
|
50
53
|
}
|
|
51
54
|
|