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 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
- 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.
9
+ ## The Problem
10
10
 
11
- **Zero per-instance overhead.** No daemon, no vector DB, no MCP server. Just bash scripts, sqlite3, and jq.
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
- ## Getting started
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
- ```bash
16
- npm install -g eagle-mem
17
- eagle-mem install
18
- ```
15
+ ## The Solution
19
16
 
20
- That's it. Open Claude Code in any project directory. Eagle Mem activates automatically:
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
- 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.
34
+ ## Getting Started
40
35
 
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 |
36
+ ```bash
37
+ npm install -g eagle-mem
38
+ eagle-mem install
39
+ ```
49
40
 
50
- ### Search modes
41
+ That's it. Open Claude Code in any project directory. Eagle Mem activates automatically.
51
42
 
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 |
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
- ## Skills (inside Claude Code)
45
+ ### Prerequisites
64
46
 
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` |
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 it works
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 when | What it does |
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 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 |
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 automation
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, and hot file detection (partially requires LLM provider)
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 savings
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
- ### Data
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 it stores |
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
- | 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
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
- - `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)
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);
@@ -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
- if [ -z "$session_id" ] || [ -z "$tool_name" ]; then exit 0; fi
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
@@ -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 ===
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 -nc --arg cmd "${cmd} | head -${max_lines}" '{"command":$cmd}')
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}"
@@ -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] | 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: [...]
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 | head -1 | cut -c1-500)
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: fill in decisions/gotchas/key_files ─────
128
- # Check both local vars (from eagle-summary block) AND existing DB enrichment.
129
- # Skip LLM call if either source already has enrichment data.
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
- existing_enrichment=""
138
- if [ -z "$decisions" ] && [ -z "$gotchas" ] && [ -z "$key_files" ]; then
139
- s_esc=$(eagle_sql_escape "$session_id")
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
- if [ -z "$decisions" ] && [ -z "$gotchas" ] && [ -z "$key_files" ] && { [ -z "$existing_enrichment" ] || [ "$context_pressure" -eq 1 ]; }; then
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 2000)
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
- enrich_prompt="Extract from this Claude Code session excerpt:
149
- 1. DECISIONS: architectural or design choices made (with WHY). One per line.
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
- SESSION EXCERPT:
154
- $excerpt
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
- - <decision> — why: <reason>
182
+ Each as: <what was decided> — why: <reason>
183
+
159
184
  GOTCHAS:
160
- - <gotcha>
185
+ Each as: <surprising finding or pitfall>
186
+
161
187
  KEY_FILES:
162
- - <filepath>"
188
+ Each as: <filepath>
163
189
 
164
- enrich_result=$(eagle_llm_call "$enrich_prompt" "Extract structured facts from development sessions. Be concise. Only include items with clear evidence." 512 2>/dev/null) || true
190
+ SESSION TEXT:
191
+ $excerpt"
165
192
 
166
- if [ -n "$enrich_result" ]; then
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 && /^- / { sub(/^- /, ""); lines[++n] = $0 }
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
- decisions=$(extract_section "$enrich_result" "DECISIONS")
177
- gotchas=$(extract_section "$enrich_result" "GOTCHAS")
178
- key_files=$(extract_section "$enrich_result" "KEY_FILES")
179
- [ -n "$decisions" ] || [ -n "$gotchas" ] || [ -n "$key_files" ] && eagle_log "INFO" "Stop: LLM enrichment extracted for session=$session_id"
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