eagle-mem 4.2.1 → 4.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -66
- package/bin/eagle-mem +9 -0
- package/db/022_cleanup_fossils.sql +12 -0
- package/db/023_guardrails.sql +18 -0
- package/hooks/post-tool-use.sh +46 -1
- package/hooks/pre-tool-use.sh +41 -4
- package/hooks/session-start.sh +9 -3
- package/hooks/stop.sh +132 -28
- package/hooks/user-prompt-submit.sh +3 -3
- package/lib/common.sh +60 -23
- package/lib/db-backfill.sh +62 -4
- package/lib/db-core.sh +2 -1
- package/lib/db-guardrails.sh +107 -0
- package/lib/db-mirrors.sh +12 -0
- package/lib/db-observations.sh +3 -2
- package/lib/db-summaries.sh +15 -0
- package/lib/db.sh +1 -0
- package/lib/hooks-posttool.sh +12 -4
- package/lib/hooks-sessionstart.sh +8 -2
- package/lib/provider.sh +17 -8
- package/package.json +1 -1
- package/scripts/curate.sh +2 -1
- package/scripts/guard.sh +160 -0
- package/scripts/health.sh +5 -5
- package/scripts/help.sh +7 -0
- package/scripts/install.sh +8 -0
- package/scripts/memories.sh +6 -2
- package/scripts/overview.sh +8 -4
- package/scripts/search.sh +21 -3
- package/scripts/statusline-em.sh +5 -3
- package/scripts/tasks.sh +15 -6
- package/scripts/update.sh +2 -0
package/README.md
CHANGED
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
```
|
|
2
2
|
======================================
|
|
3
|
-
Eagle Mem
|
|
3
|
+
Eagle Mem
|
|
4
4
|
======================================
|
|
5
5
|
```
|
|
6
6
|
|
|
7
7
|
# Eagle Mem
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## The Problem
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
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.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
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.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
npm install -g eagle-mem
|
|
17
|
-
eagle-mem install
|
|
18
|
-
```
|
|
15
|
+
## The Solution
|
|
19
16
|
|
|
20
|
-
|
|
17
|
+
Eagle Mem gives Claude Code persistent memory. 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.
|
|
18
|
+
|
|
19
|
+
**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
20
|
|
|
22
21
|
```
|
|
23
22
|
======================================
|
|
@@ -32,66 +31,46 @@ That's it. Open Claude Code in any project directory. Eagle Mem activates automa
|
|
|
32
31
|
======================================
|
|
33
32
|
```
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
## Commands
|
|
38
|
-
|
|
39
|
-
Six commands. Three for lifecycle, three for lookup and troubleshooting.
|
|
34
|
+
## Getting Started
|
|
40
35
|
|
|
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 |
|
|
36
|
+
```bash
|
|
37
|
+
npm install -g eagle-mem
|
|
38
|
+
eagle-mem install
|
|
39
|
+
```
|
|
49
40
|
|
|
50
|
-
|
|
41
|
+
That's it. Open Claude Code in any project directory. Eagle Mem activates automatically.
|
|
51
42
|
|
|
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 |
|
|
43
|
+
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
44
|
|
|
63
|
-
|
|
45
|
+
### Prerequisites
|
|
64
46
|
|
|
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` |
|
|
47
|
+
- `sqlite3` with FTS5 support (ships with macOS; the installer offers to install if missing)
|
|
48
|
+
- `jq` (the installer offers to install if missing)
|
|
49
|
+
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) installed (`~/.claude/` must exist)
|
|
71
50
|
|
|
72
|
-
## How
|
|
51
|
+
## How It Works
|
|
73
52
|
|
|
74
53
|
Six hooks fire automatically at different points in Claude Code's lifecycle:
|
|
75
54
|
|
|
76
|
-
| Hook | Fires
|
|
55
|
+
| Hook | Fires When | What It Does |
|
|
77
56
|
|------|-----------|--------------|
|
|
78
57
|
| **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
|
|
58
|
+
| **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. |
|
|
59
|
+
| **UserPromptSubmit** | user sends a message | FTS5 search across past sessions and indexed code for relevant context |
|
|
60
|
+
| **PostToolUse** | after tool calls | Records file touches, mirrors memory/plan/task writes, surfaces decision history on reads |
|
|
61
|
+
| **Stop** | Claude's turn ends | Extracts `<eagle-summary>` blocks for rich session summaries |
|
|
83
62
|
| **SessionEnd** | session closes | Re-syncs tasks, marks session completed |
|
|
84
63
|
|
|
85
|
-
### Background
|
|
64
|
+
### Background Automation
|
|
86
65
|
|
|
87
66
|
These run automatically via SessionStart — no commands needed:
|
|
88
67
|
|
|
89
68
|
- **Auto-scan** — new project with no overview triggers a codebase scan
|
|
90
69
|
- **Auto-index** — new or stale project triggers FTS5 source indexing
|
|
91
70
|
- **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,
|
|
71
|
+
- **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
72
|
|
|
94
|
-
### Token
|
|
73
|
+
### Token Savings
|
|
95
74
|
|
|
96
75
|
Eagle Mem actively reduces token consumption:
|
|
97
76
|
|
|
@@ -104,28 +83,94 @@ Eagle Mem actively reduces token consumption:
|
|
|
104
83
|
- **Working set recovery** — on compact, SessionStart injects the files you were actively editing so you resume without re-reading everything
|
|
105
84
|
- **Stuck loop detection** — if the same file is edited 5+ times in one session, PreToolUse nudges to reconsider the approach
|
|
106
85
|
|
|
107
|
-
###
|
|
86
|
+
### Anti-Regression
|
|
87
|
+
|
|
88
|
+
Eagle Mem prevents Claude from repeating past mistakes:
|
|
89
|
+
|
|
90
|
+
- **Decision surfacing** — when you edit a file that has past decisions recorded (from `<eagle-summary>` blocks), PreToolUse reminds Claude not to revert without asking
|
|
91
|
+
- **Guardrails** — file-level rules (manual or curator-discovered) that fire before every Edit/Write
|
|
92
|
+
- **Feature verification** — tracks features with smoke tests and dependencies; reminds you to verify on `git push`
|
|
93
|
+
- **Gotcha surfacing** — past surprises and gotchas are surfaced when editing related files
|
|
94
|
+
|
|
95
|
+
## Commands
|
|
96
|
+
|
|
97
|
+
| Command | What It Does |
|
|
98
|
+
|---------|-------------|
|
|
99
|
+
| `eagle-mem install` | First-time setup: hooks, database, skills |
|
|
100
|
+
| `eagle-mem update` | Re-deploy hooks and run migrations after `npm update` |
|
|
101
|
+
| `eagle-mem uninstall` | Remove hooks and optionally delete data |
|
|
102
|
+
| `eagle-mem search` | Search past sessions, memories, and code |
|
|
103
|
+
| `eagle-mem health` | Diagnose pipeline health and background automation |
|
|
104
|
+
| `eagle-mem config` | View or change LLM provider settings |
|
|
105
|
+
| `eagle-mem guard` | Manage regression guardrails for files |
|
|
106
|
+
| `eagle-mem overview` | Build or view project overview |
|
|
107
|
+
| `eagle-mem memories` | View/sync Claude Code memories |
|
|
108
|
+
| `eagle-mem tasks` | View mirrored tasks |
|
|
109
|
+
| `eagle-mem curate` | Run curator (co-edits, hot files, guardrails) |
|
|
110
|
+
| `eagle-mem feature` | Track and verify features |
|
|
111
|
+
| `eagle-mem prune` | Clean old sessions and stale data |
|
|
112
|
+
| `eagle-mem scan` | Scan codebase and generate overview |
|
|
113
|
+
| `eagle-mem index` | Index source files for FTS5 code search |
|
|
114
|
+
|
|
115
|
+
### Search Modes
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
eagle-mem search "auth bug" # keyword search across summaries
|
|
119
|
+
eagle-mem search --timeline # recent sessions in chronological order
|
|
120
|
+
eagle-mem search --overview # project overview
|
|
121
|
+
eagle-mem search --memories # mirrored Claude Code memories
|
|
122
|
+
eagle-mem search --tasks # in-flight tasks (pending/in-progress)
|
|
123
|
+
eagle-mem search --files # most frequently modified files
|
|
124
|
+
eagle-mem search --stats # project statistics
|
|
125
|
+
eagle-mem search --session <id> # full observation trail for one session
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Skills (Inside Claude Code)
|
|
129
|
+
|
|
130
|
+
| Skill | What It Does |
|
|
131
|
+
|-------|-------------|
|
|
132
|
+
| `/eagle-mem-search` | Search memory and past sessions — Claude interprets results in context |
|
|
133
|
+
| `/eagle-mem-overview` | Build a rich project briefing from README, entry points, and git history |
|
|
134
|
+
| `/eagle-mem-memories` | View and search mirrored Claude Code memories and plans |
|
|
135
|
+
| `/eagle-mem-tasks` | TaskAware Compact Loop — break complex work into tasks that survive `/compact` |
|
|
136
|
+
|
|
137
|
+
## Data
|
|
108
138
|
|
|
109
139
|
Single SQLite database at `~/.eagle-mem/memory.db` (WAL mode, FTS5 full-text search):
|
|
110
140
|
|
|
111
|
-
| Table | What
|
|
141
|
+
| Table | What It Stores |
|
|
112
142
|
|-------|---------------|
|
|
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
|
-
|
|
143
|
+
| `sessions` | Active/completed sessions per project |
|
|
144
|
+
| `summaries` | Per-session summaries with decisions, gotchas, key files (FTS5-indexed) |
|
|
145
|
+
| `observations` | Per-tool-use file touch records |
|
|
146
|
+
| `overviews` | One overview per project (auto-scan or manual) |
|
|
147
|
+
| `code_chunks` | FTS5-indexed source file chunks |
|
|
148
|
+
| `command_rules` | Curator-learned command output rules |
|
|
149
|
+
| `file_hints` | Curator-learned file access patterns (co-edit pairs, hot files) |
|
|
150
|
+
| `guardrails` | File-level regression rules (manual or curator-discovered) |
|
|
151
|
+
| `features` | Feature tracking with smoke tests and dependencies |
|
|
152
|
+
| `eagle_meta` | Internal metadata (last scan, last curate, etc.) |
|
|
153
|
+
| `claude_memories` | Mirror of Claude Code auto-memories |
|
|
154
|
+
| `claude_plans` | Mirror of Claude Code plans |
|
|
155
|
+
| `claude_tasks` | Mirror of Claude Code tasks |
|
|
125
156
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
-
|
|
157
|
+
### Project Identity
|
|
158
|
+
|
|
159
|
+
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.
|
|
160
|
+
|
|
161
|
+
### Namespace Migration
|
|
162
|
+
|
|
163
|
+
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.
|
|
164
|
+
|
|
165
|
+
## LLM Provider (Optional)
|
|
166
|
+
|
|
167
|
+
Some features (curator auto-enrichment, overview generation) can use an LLM for richer output. Configure with:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
eagle-mem config
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Supported providers: Ollama (auto-detected), Anthropic, OpenAI. Eagle Mem works fully without a provider — LLM features gracefully degrade to heuristic fallbacks.
|
|
129
174
|
|
|
130
175
|
## License
|
|
131
176
|
|
package/bin/eagle-mem
CHANGED
|
@@ -22,6 +22,15 @@ case "$command" in
|
|
|
22
22
|
search) bash "$SCRIPTS_DIR/search.sh" "$@" ;;
|
|
23
23
|
health) bash "$SCRIPTS_DIR/health.sh" "$@" ;;
|
|
24
24
|
config) bash "$SCRIPTS_DIR/config.sh" "$@" ;;
|
|
25
|
+
guard) bash "$SCRIPTS_DIR/guard.sh" "$@" ;;
|
|
26
|
+
overview) bash "$SCRIPTS_DIR/overview.sh" "$@" ;;
|
|
27
|
+
memories) bash "$SCRIPTS_DIR/memories.sh" "$@" ;;
|
|
28
|
+
tasks) bash "$SCRIPTS_DIR/tasks.sh" "$@" ;;
|
|
29
|
+
curate) bash "$SCRIPTS_DIR/curate.sh" "$@" ;;
|
|
30
|
+
feature) bash "$SCRIPTS_DIR/feature.sh" "$@" ;;
|
|
31
|
+
prune) bash "$SCRIPTS_DIR/prune.sh" "$@" ;;
|
|
32
|
+
scan) bash "$SCRIPTS_DIR/scan.sh" "$@" ;;
|
|
33
|
+
index) bash "$SCRIPTS_DIR/index.sh" "$@" ;;
|
|
25
34
|
help|--help|-h)
|
|
26
35
|
bash "$SCRIPTS_DIR/help.sh" ;;
|
|
27
36
|
version|--version|-v|-V)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
-- Clear fossil placeholder data that the COALESCE upsert preserves forever.
|
|
2
|
+
-- These rows block real data from being written on future Stop hook fires.
|
|
3
|
+
PRAGMA trusted_schema=ON;
|
|
4
|
+
|
|
5
|
+
UPDATE summaries SET completed = ''
|
|
6
|
+
WHERE completed LIKE '%(auto-captured%';
|
|
7
|
+
|
|
8
|
+
UPDATE summaries SET request = ''
|
|
9
|
+
WHERE request LIKE '%<local-command-caveat>%';
|
|
10
|
+
|
|
11
|
+
UPDATE summaries SET request = ''
|
|
12
|
+
WHERE request LIKE '%<system-reminder>%';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
-- ═══════════════════════════════════════════════════════════
|
|
2
|
+
-- Migration 023: Guardrails table
|
|
3
|
+
-- Persistent per-project rules surfaced at Edit/Write time
|
|
4
|
+
-- ═══════════════════════════════════════════════════════════
|
|
5
|
+
|
|
6
|
+
CREATE TABLE IF NOT EXISTS guardrails (
|
|
7
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
8
|
+
project TEXT NOT NULL,
|
|
9
|
+
file_pattern TEXT NOT NULL DEFAULT '',
|
|
10
|
+
rule TEXT NOT NULL,
|
|
11
|
+
source TEXT NOT NULL DEFAULT 'manual',
|
|
12
|
+
active INTEGER NOT NULL DEFAULT 1,
|
|
13
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
14
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
15
|
+
UNIQUE(project, source, file_pattern, rule)
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
CREATE INDEX IF NOT EXISTS idx_guardrails_project ON guardrails(project, active);
|
package/hooks/post-tool-use.sh
CHANGED
|
@@ -20,7 +20,52 @@ session_id=$(echo "$input" | jq -r '.session_id // empty')
|
|
|
20
20
|
cwd=$(echo "$input" | jq -r '.cwd // empty')
|
|
21
21
|
tool_name=$(echo "$input" | jq -r '.tool_name // empty')
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
hook_event=$(echo "$input" | jq -r '.hook_event_name // empty')
|
|
24
|
+
|
|
25
|
+
if [ -z "$session_id" ]; then exit 0; fi
|
|
26
|
+
|
|
27
|
+
# TaskCreated/TaskCompleted dedicated events — parse top-level fields and exit
|
|
28
|
+
case "$hook_event" in
|
|
29
|
+
TaskCreated|TaskCompleted)
|
|
30
|
+
[ ! -f "$EAGLE_MEM_DB" ] && exit 0
|
|
31
|
+
project=$(eagle_project_from_cwd "$cwd")
|
|
32
|
+
[ -z "$project" ] && exit 0
|
|
33
|
+
eagle_upsert_session "$session_id" "$project" "$cwd" "" ""
|
|
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
|
|
64
|
+
exit 0
|
|
65
|
+
;;
|
|
66
|
+
esac
|
|
67
|
+
|
|
68
|
+
[ -z "$tool_name" ] && exit 0
|
|
24
69
|
|
|
25
70
|
# Only track relevant tools
|
|
26
71
|
case "$tool_name" in
|
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 ===
|
|
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"
|
|
@@ -105,7 +106,7 @@ ${context}"
|
|
|
105
106
|
*"| head"*|*"| tail"*|*"| wc"*|*"| grep"*|*">"*|*">>"*)
|
|
106
107
|
;;
|
|
107
108
|
*)
|
|
108
|
-
updated_input=$(jq
|
|
109
|
+
updated_input=$(echo "$input" | jq --arg cmd "${cmd} | head -${max_lines}" '.tool_input + {"command":$cmd}')
|
|
109
110
|
context+="Eagle Mem: '${base_cmd}' output is typically long (${reason}). Piped through head -${max_lines}."
|
|
110
111
|
;;
|
|
111
112
|
esac
|
|
@@ -121,6 +122,42 @@ ${context}"
|
|
|
121
122
|
Edit|Write)
|
|
122
123
|
fp=$(echo "$input" | jq -r '.tool_input.file_path // empty')
|
|
123
124
|
if [ -n "$fp" ]; then
|
|
125
|
+
# ─── Guardrail + decision/gotcha surfacing ────────
|
|
126
|
+
fname=$(basename "$fp")
|
|
127
|
+
fname_stem="${fname%.*}"
|
|
128
|
+
case "$fp" in
|
|
129
|
+
"$HOME/.claude/"*) ;;
|
|
130
|
+
*)
|
|
131
|
+
# Guardrails use GLOB on full filename — no stem length minimum needed.
|
|
132
|
+
# FTS decision/gotcha lookups need a meaningful stem (>= 3 chars).
|
|
133
|
+
if [ ${#fname_stem} -ge 3 ]; then
|
|
134
|
+
fts_query=$(eagle_fts_sanitize "$fname_stem")
|
|
135
|
+
fts_query=${fts_query:-"$fname_stem"}
|
|
136
|
+
edit_ctx=$(eagle_get_edit_context "$project" "$fname" "$fts_query" 2>/dev/null)
|
|
137
|
+
else
|
|
138
|
+
# Short stem (e.g. db.sh) — only fetch guardrails, skip FTS queries
|
|
139
|
+
edit_ctx=$(eagle_get_guardrails_for_file "$project" "$fname" 2>/dev/null)
|
|
140
|
+
if [ -n "$edit_ctx" ]; then
|
|
141
|
+
# Prefix with GR: to match batched output format; strip empty lines
|
|
142
|
+
edit_ctx=$(echo "$edit_ctx" | grep -v '^$' | sed 's/^/GR:/')
|
|
143
|
+
fi
|
|
144
|
+
fi
|
|
145
|
+
if [ -n "$edit_ctx" ]; then
|
|
146
|
+
gr_block=""
|
|
147
|
+
while IFS= read -r ctx_line; do
|
|
148
|
+
case "$ctx_line" in
|
|
149
|
+
GR:*) gr_block+=" - ${ctx_line#GR:}"$'\n' ;;
|
|
150
|
+
DEC:*) context+="=== Eagle Mem ==="$'\n'"Decisions for '${fname}': ${ctx_line#DEC:} — Do not revert without asking."$'\n'"================"$'\n' ;;
|
|
151
|
+
GOT:*) context+="=== Eagle Mem ==="$'\n'"Gotchas for '${fname}': ${ctx_line#GOT:}"$'\n'"================"$'\n' ;;
|
|
152
|
+
esac
|
|
153
|
+
done <<< "$edit_ctx"
|
|
154
|
+
if [ -n "$gr_block" ]; then
|
|
155
|
+
context+="=== Eagle Mem ==="$'\n'"Guardrails for '${fname}':"$'\n'"${gr_block}================"$'\n'
|
|
156
|
+
fi
|
|
157
|
+
fi
|
|
158
|
+
;;
|
|
159
|
+
esac
|
|
160
|
+
|
|
124
161
|
# ─── Stuck loop detection ─────────────────────────
|
|
125
162
|
if [ -n "$session_id" ] && eagle_validate_session_id "$session_id"; then
|
|
126
163
|
edit_tracker="$EAGLE_MEM_DIR/edit-tracker/${session_id}"
|
package/hooks/session-start.sh
CHANGED
|
@@ -322,9 +322,15 @@ Memory active for '$project'. Scan, index, prune, and self-learning run automati
|
|
|
322
322
|
|
|
323
323
|
Before your final response, emit:
|
|
324
324
|
<eagle-summary>
|
|
325
|
-
request: [what user asked]
|
|
326
|
-
|
|
327
|
-
|
|
325
|
+
request: [what user asked]
|
|
326
|
+
completed: [what shipped]
|
|
327
|
+
learned: [non-obvious discoveries]
|
|
328
|
+
decisions: [choice — why]
|
|
329
|
+
gotchas: [what surprised]
|
|
330
|
+
next_steps: [concrete actions]
|
|
331
|
+
key_files: [path — role]
|
|
332
|
+
files_read: [path, ...]
|
|
333
|
+
files_modified: [path, ...]
|
|
328
334
|
</eagle-summary>
|
|
329
335
|
"
|
|
330
336
|
fi
|
package/hooks/stop.sh
CHANGED
|
@@ -42,7 +42,13 @@ eagle_upsert_session "$session_id" "$project" "$cwd" "" ""
|
|
|
42
42
|
|
|
43
43
|
# ─── Primary: heuristic extraction from transcript ───────────
|
|
44
44
|
|
|
45
|
-
request=$(jq -r 'select(.type == "user") | .message.content | if type == "string" then . elif type == "array" then [.[] | select(.type == "text") | .text] | join(" ") else "" end' "$transcript_path" 2>/dev/null
|
|
45
|
+
request=$(jq -r 'select(.type == "user") | .message.content | if type == "string" then . elif type == "array" then [.[] | select(.type == "text") | .text] | join(" ") else "" end' "$transcript_path" 2>/dev/null \
|
|
46
|
+
| grep -v '<local-command-caveat>' \
|
|
47
|
+
| grep -v '<system-reminder>' \
|
|
48
|
+
| grep -v '<command-name>' \
|
|
49
|
+
| grep -v '<command-message>' \
|
|
50
|
+
| grep -v '^\[{' \
|
|
51
|
+
| head -1 | cut -c1-500)
|
|
46
52
|
|
|
47
53
|
heuristic_reads=$(jq -r 'select(.type == "assistant") | .message.content[]? | select(.type == "tool_use") | select(.name == "Read") | .input.file_path // empty' "$transcript_path" 2>/dev/null | sort -u | head -20)
|
|
48
54
|
heuristic_writes=$(jq -r 'select(.type == "assistant") | .message.content[]? | select(.type == "tool_use") | select(.name == "Write" or .name == "Edit") | .input.file_path // empty' "$transcript_path" 2>/dev/null | sort -u | head -20)
|
|
@@ -124,59 +130,157 @@ if [ -n "$summary_block" ]; then
|
|
|
124
130
|
eagle_log "INFO" "Stop: eagle-summary block merged over heuristic data"
|
|
125
131
|
fi
|
|
126
132
|
|
|
127
|
-
# ─── LLM enrichment:
|
|
128
|
-
#
|
|
129
|
-
#
|
|
130
|
-
# Exception: under context pressure, force re-enrichment for richest summary.
|
|
133
|
+
# ─── LLM enrichment: extract structured data when eagle-summary absent ──
|
|
134
|
+
# Runs when: (a) no eagle-summary block, OR (b) heuristic data is thin
|
|
135
|
+
# Skips when: eagle-summary already provided rich data, OR no text to analyze
|
|
131
136
|
|
|
132
137
|
context_pressure=0
|
|
133
138
|
if [ -f "$EAGLE_MEM_DIR/.context-pressure" ]; then
|
|
134
139
|
context_pressure=1
|
|
135
140
|
fi
|
|
136
141
|
|
|
137
|
-
|
|
138
|
-
if [ -
|
|
139
|
-
|
|
140
|
-
existing_enrichment=$(eagle_db "SELECT decisions||gotchas||key_files FROM summaries WHERE session_id='$s_esc';")
|
|
142
|
+
has_rich_data=0
|
|
143
|
+
if [ -n "$decisions" ] || [ -n "$gotchas" ] || [ -n "$key_files" ]; then
|
|
144
|
+
has_rich_data=1
|
|
141
145
|
fi
|
|
142
146
|
|
|
143
|
-
|
|
147
|
+
request_is_polluted=0
|
|
148
|
+
if echo "$request" | grep -qE '<(local-command-caveat|system-reminder|command-name)>' 2>/dev/null; then
|
|
149
|
+
request_is_polluted=1
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
needs_enrichment=0
|
|
153
|
+
if [ "$has_rich_data" -eq 0 ]; then
|
|
154
|
+
needs_enrichment=1
|
|
155
|
+
elif [ "$context_pressure" -eq 1 ]; then
|
|
156
|
+
needs_enrichment=1
|
|
157
|
+
elif [ -z "$completed" ] && [ -z "$learned" ]; then
|
|
158
|
+
needs_enrichment=1
|
|
159
|
+
elif [ "$request_is_polluted" -eq 1 ]; then
|
|
160
|
+
needs_enrichment=1
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
if [ "$needs_enrichment" -eq 1 ]; then
|
|
144
164
|
provider=$(eagle_config_get "provider" "type" "none" 2>/dev/null)
|
|
145
165
|
if [ "$provider" != "none" ] && [ -n "$text_content" ]; then
|
|
146
|
-
excerpt=$(echo "$text_content" | tail -c
|
|
166
|
+
excerpt=$(echo "$text_content" | tail -c 3000)
|
|
167
|
+
|
|
168
|
+
enrich_prompt="Extract facts from this Claude Code session. Only include items with clear evidence in the session text. Do NOT invent or repeat example content.
|
|
169
|
+
|
|
170
|
+
Respond with EXACTLY these sections (omit sections with no evidence):
|
|
147
171
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
2. GOTCHAS: non-obvious pitfalls, bugs found, things that surprised. One per line.
|
|
151
|
-
3. KEY_FILES: important files that were central to the work. One per line.
|
|
172
|
+
REQUEST:
|
|
173
|
+
One-line summary of what the user asked for. No system tags or XML.
|
|
152
174
|
|
|
153
|
-
|
|
154
|
-
|
|
175
|
+
COMPLETED:
|
|
176
|
+
What was actually accomplished. Be specific about changes made.
|
|
177
|
+
|
|
178
|
+
LEARNED:
|
|
179
|
+
Non-obvious discoveries or insights from the session.
|
|
155
180
|
|
|
156
|
-
Output EXACTLY this format (omit sections with nothing to report):
|
|
157
181
|
DECISIONS:
|
|
158
|
-
|
|
182
|
+
Each as: <what was decided> — why: <reason>
|
|
183
|
+
|
|
159
184
|
GOTCHAS:
|
|
160
|
-
|
|
185
|
+
Each as: <surprising finding or pitfall>
|
|
186
|
+
|
|
161
187
|
KEY_FILES:
|
|
162
|
-
|
|
188
|
+
Each as: <filepath>
|
|
163
189
|
|
|
164
|
-
|
|
190
|
+
SESSION TEXT:
|
|
191
|
+
$excerpt"
|
|
165
192
|
|
|
166
|
-
|
|
193
|
+
enrich_system="You extract structured facts from development sessions. Output format for decisions: '- Did X — why: Y'. Output format for gotchas: '- Gotcha description'. Be concise. Only include items with clear evidence in the session text. Never fabricate content."
|
|
194
|
+
enrich_result=$(eagle_llm_call "$enrich_prompt" "$enrich_system" 768 2>/dev/null)
|
|
195
|
+
llm_rc=$?
|
|
196
|
+
|
|
197
|
+
if [ $llm_rc -ne 0 ] || [ -z "$enrich_result" ]; then
|
|
198
|
+
eagle_log "WARN" "Stop: LLM enrichment failed (rc=$llm_rc) for session=$session_id provider=$provider"
|
|
199
|
+
else
|
|
167
200
|
extract_section() {
|
|
168
201
|
local result="$1" header="$2"
|
|
169
202
|
echo "$result" | awk -v h="$header:" '
|
|
170
203
|
$0 == h || $0 ~ "^"h { found=1; next }
|
|
171
204
|
found && /^[A-Z_]+:/ { exit }
|
|
172
|
-
found &&
|
|
205
|
+
found && /^[[:space:]]*$/ { next }
|
|
206
|
+
found && /^- / { sub(/^- /, ""); lines[++n] = $0; next }
|
|
207
|
+
found { lines[++n] = $0 }
|
|
173
208
|
END { for (i=1; i<=n; i++) { printf "%s", lines[i]; if (i<n) printf "; " } }
|
|
174
209
|
'
|
|
175
210
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
211
|
+
_req=$(extract_section "$enrich_result" "REQUEST")
|
|
212
|
+
_comp=$(extract_section "$enrich_result" "COMPLETED")
|
|
213
|
+
_learn=$(extract_section "$enrich_result" "LEARNED")
|
|
214
|
+
_dec=$(extract_section "$enrich_result" "DECISIONS")
|
|
215
|
+
_got=$(extract_section "$enrich_result" "GOTCHAS")
|
|
216
|
+
_kf=$(extract_section "$enrich_result" "KEY_FILES")
|
|
217
|
+
|
|
218
|
+
[ -z "$request" ] || [ "$request_is_polluted" -eq 1 ] && [ -n "$_req" ] && request="$_req"
|
|
219
|
+
[ -z "$completed" ] && [ -n "$_comp" ] && completed="$_comp"
|
|
220
|
+
[ -z "$learned" ] && [ -n "$_learn" ] && learned="$_learn"
|
|
221
|
+
[ -z "$decisions" ] && [ -n "$_dec" ] && decisions="$_dec"
|
|
222
|
+
[ -z "$gotchas" ] && [ -n "$_got" ] && gotchas="$_got"
|
|
223
|
+
[ -z "$key_files" ] && [ -n "$_kf" ] && key_files="$_kf"
|
|
224
|
+
|
|
225
|
+
eagle_log "INFO" "Stop: LLM enrichment extracted for session=$session_id (req=${#_req} comp=${#_comp} dec=${#_dec})"
|
|
226
|
+
fi
|
|
227
|
+
else
|
|
228
|
+
eagle_log "INFO" "Stop: LLM enrichment skipped — provider=$provider text_len=${#text_content}"
|
|
229
|
+
fi
|
|
230
|
+
else
|
|
231
|
+
eagle_log "INFO" "Stop: LLM enrichment skipped — rich data already present"
|
|
232
|
+
fi
|
|
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
|
+
|
|
259
|
+
# ─── Test reminder for guardrailed files ─────────────────
|
|
260
|
+
|
|
261
|
+
if [ -n "$files_modified" ] && [ "$files_modified" != "[]" ]; then
|
|
262
|
+
# Short-circuit: skip per-file loop if project has no guardrails at all
|
|
263
|
+
has_gr=$(eagle_has_any_guardrails "$project" 2>/dev/null)
|
|
264
|
+
if [ -n "$has_gr" ]; then
|
|
265
|
+
guardrailed_files=""
|
|
266
|
+
while IFS= read -r mod_file; do
|
|
267
|
+
[ -z "$mod_file" ] && continue
|
|
268
|
+
mod_basename=$(basename "$mod_file")
|
|
269
|
+
gr_check=$(eagle_get_guardrails_for_file "$project" "$mod_basename" 2>/dev/null)
|
|
270
|
+
if [ -n "$gr_check" ]; then
|
|
271
|
+
guardrailed_files+="${mod_basename}, "
|
|
272
|
+
fi
|
|
273
|
+
done < <(echo "$files_modified" | jq -r '.[]?' 2>/dev/null)
|
|
274
|
+
|
|
275
|
+
if [ -n "$guardrailed_files" ]; then
|
|
276
|
+
guardrailed_files=${guardrailed_files%, }
|
|
277
|
+
test_reminder="Run affected tests for guardrailed files: ${guardrailed_files}"
|
|
278
|
+
if [ -n "$next_steps" ]; then
|
|
279
|
+
next_steps="${next_steps}; ${test_reminder}"
|
|
280
|
+
else
|
|
281
|
+
next_steps="$test_reminder"
|
|
282
|
+
fi
|
|
283
|
+
eagle_log "INFO" "Stop: added test reminder for guardrailed files: $guardrailed_files"
|
|
180
284
|
fi
|
|
181
285
|
fi
|
|
182
286
|
fi
|