eagle-mem 4.6.1 → 4.7.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
@@ -9,15 +9,18 @@
9
9
 
10
10
  **Context that survives `/compact`.**
11
11
 
12
+ **v4.7.0 adds first-class Codex support and enforced anti-regression checks.**
13
+ Claude Code and Codex can now share the same local Eagle Mem database, while every captured row records which agent created it.
14
+
12
15
  ## The Problem
13
16
 
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.
17
+ Claude Code and Codex start every session with amnesia. They don'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 agents repeat mistakes you already corrected.
15
18
 
16
19
  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.
17
20
 
18
21
  ## The Solution
19
22
 
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.
23
+ Eagle Mem is a recall and regression-control layer for Claude Code and Codex. Every session starts with context from previous sessions — summaries, decisions, memories, tasks, project overviews, and relevant code — injected automatically via hooks. Both agents share the same SQLite database at `~/.eagle-mem/memory.db`, and captured rows are source-attributed as `Claude Code` or `Codex`.
21
24
 
22
25
  **Zero per-instance overhead.** No daemon, no vector DB, no MCP server. Just bash scripts, sqlite3 (WAL mode, FTS5 full-text search), and jq.
23
26
 
@@ -41,29 +44,33 @@ npm install -g eagle-mem
41
44
  eagle-mem install
42
45
  ```
43
46
 
44
- That's it. Open Claude Code in any project directory. Eagle Mem activates automatically.
47
+ That's it. Open Claude Code or Codex in any project directory. Eagle Mem activates automatically.
45
48
 
46
49
  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.
47
50
 
51
+ For Codex, the installer enables `codex_hooks` in `~/.codex/config.toml`, registers hooks in `~/.codex/hooks.json`, and patches `~/.codex/AGENTS.md` with the Eagle Mem summary contract. For Claude Code, it keeps using `~/.claude/settings.json`, `CLAUDE.md`, and the existing Claude memory/task locations.
52
+
48
53
  ### Prerequisites
49
54
 
50
55
  - `sqlite3` with FTS5 support (ships with macOS; the installer offers to install if missing)
51
56
  - `jq` (the installer offers to install if missing)
52
- - [Claude Code](https://docs.anthropic.com/en/docs/claude-code) installed (`~/.claude/` must exist)
57
+ - [Claude Code](https://docs.anthropic.com/en/docs/claude-code), Codex, or both installed
53
58
 
54
59
  ## How It Works
55
60
 
56
- Six hooks fire automatically at different points in Claude Code's lifecycle:
61
+ Hooks fire automatically at different points in the agent lifecycle:
57
62
 
58
63
  | Hook | Fires When | What It Does |
59
64
  |------|-----------|--------------|
60
65
  | **SessionStart** | startup, resume, clear, compact | Injects overview, summaries, memories, tasks, core files, working set. Auto-provisions new projects (scan, index). |
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. |
66
+ | **PreToolUse** | before Bash/shell, Read, Edit, Write, apply_patch | Surfaces guardrails and decisions before edits. Blocks release-boundary commands while feature verification is pending. Rewrites noisy commands through RTK when available. Detects redundant reads, nudges co-edit partners, detects stuck loops. |
62
67
  | **UserPromptSubmit** | user sends a message | FTS5 search across past sessions and indexed code for relevant context |
63
68
  | **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 |
69
+ | **Stop** | agent turn ends | Extracts `<eagle-summary>` blocks for rich session summaries from Claude Code and Codex transcripts |
65
70
  | **SessionEnd** | session closes | Re-syncs tasks, marks session completed |
66
71
 
72
+ Codex shell hooks are registered for `Bash`, `exec_command`, `shell_command`, and `unified_exec` tool names so release-boundary protection works across current Codex shell paths.
73
+
67
74
  ### Background Automation
68
75
 
69
76
  These run automatically via SessionStart — no commands needed:
@@ -92,9 +99,10 @@ Eagle Mem prevents Claude from repeating past mistakes:
92
99
 
93
100
  - **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
101
  - **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`
102
+ - **Feature verification** — tracks features with smoke tests and dependencies; current git diffs create fingerprinted pending verification records, and release-boundary commands such as `git push`, `gh pr create`, and package publish are blocked until the current fingerprint is verified or waived
96
103
  - **Gotcha surfacing** — past surprises and gotchas are surfaced when editing related files
97
104
  - **Stale memory detection** — warns when edits may contradict stored memories
105
+ - **Token guard** — when `rtk` is installed, raw shell output commands are rewritten or blocked with an RTK equivalent so large output is compacted before it enters agent context
98
106
 
99
107
  ## Commands
100
108
 
@@ -129,6 +137,29 @@ eagle-mem search --stats # project statistics
129
137
  eagle-mem search --session <id> # full observation trail for one session
130
138
  ```
131
139
 
140
+ ### Feature Verification
141
+
142
+ ```bash
143
+ eagle-mem feature pending
144
+ eagle-mem feature verify "Feature name" --notes "smoke test passed"
145
+ eagle-mem feature waive 12 --reason "docs-only change, no runtime impact"
146
+ ```
147
+
148
+ Verification is tied to the current git diff fingerprint. If the same diff was already verified, release-boundary hooks do not reopen it. If the file changes again, Eagle Mem creates a new pending verification for the new fingerprint.
149
+
150
+ Dry-run validation stays unblocked. For example, `gh pr create --dry-run` and `npm publish --dry-run` are treated as validation. Explicit real commands such as `npm publish --dry-run=false` are treated as release boundaries and will enforce pending feature verification.
151
+
152
+ ### Shared Claude Code + Codex Memory
153
+
154
+ Both agents write to `~/.eagle-mem/memory.db`:
155
+
156
+ - `sessions.agent` records whether a session came from Claude Code or Codex
157
+ - `summaries.agent` records which agent produced the session summary
158
+ - mirrored memories, plans, and tasks include `origin_agent`
159
+ - SessionStart recall labels sources as `Claude Code` or `Codex`
160
+
161
+ That means opening the same project in Claude Code and Codex does not create two isolated memory worlds. They recall the same project history while preserving the source of each memory.
162
+
132
163
  ## Skills (Inside Claude Code)
133
164
 
134
165
  | Skill | What It Does |
@@ -156,6 +187,7 @@ Single SQLite database at `~/.eagle-mem/memory.db` (WAL mode, FTS5 full-text sea
156
187
  | `feature_files` | Files belonging to each feature |
157
188
  | `feature_dependencies` | Inter-feature dependency relationships |
158
189
  | `feature_smoke_tests` | Smoke test definitions for feature verification |
190
+ | `pending_feature_verifications` | Release blockers created when files tied to features change |
159
191
  | `eagle_meta` | Internal metadata (last scan, last curate, etc.) |
160
192
  | `claude_memories` | Mirror of Claude Code auto-memories |
161
193
  | `claude_plans` | Mirror of Claude Code plans |
@@ -11,8 +11,9 @@ CREATE TABLE IF NOT EXISTS guardrails (
11
11
  source TEXT NOT NULL DEFAULT 'manual',
12
12
  active INTEGER NOT NULL DEFAULT 1,
13
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)
14
+ updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
16
15
  );
17
16
 
18
17
  CREATE INDEX IF NOT EXISTS idx_guardrails_project ON guardrails(project, active);
18
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_guardrails_dedup
19
+ ON guardrails(project, COALESCE(file_pattern, ''), rule);
@@ -0,0 +1,46 @@
1
+ -- Migration 024: Normalize guardrail de-duplication
2
+ -- Older installs used UNIQUE(project, source, file_pattern, rule). Runtime
3
+ -- installs already moved to project+file_pattern+rule, so rebuild the table
4
+ -- to make fresh and upgraded databases agree.
5
+
6
+ CREATE TABLE IF NOT EXISTS guardrails_new (
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
+ );
16
+
17
+ INSERT OR IGNORE INTO guardrails_new (id, project, file_pattern, rule, source, active, created_at, updated_at)
18
+ SELECT
19
+ MIN(g.id) AS id,
20
+ g.project,
21
+ COALESCE(g.file_pattern, '') AS file_pattern,
22
+ g.rule,
23
+ COALESCE((
24
+ SELECT g2.source
25
+ FROM guardrails g2
26
+ WHERE g2.project = g.project
27
+ AND COALESCE(g2.file_pattern, '') = COALESCE(g.file_pattern, '')
28
+ AND g2.rule = g.rule
29
+ ORDER BY
30
+ CASE g2.source WHEN 'manual' THEN 0 ELSE 1 END,
31
+ g2.updated_at DESC,
32
+ g2.id DESC
33
+ LIMIT 1
34
+ ), 'manual') AS source,
35
+ MAX(g.active) AS active,
36
+ MIN(g.created_at) AS created_at,
37
+ MAX(g.updated_at) AS updated_at
38
+ FROM guardrails g
39
+ GROUP BY g.project, COALESCE(g.file_pattern, ''), g.rule;
40
+
41
+ DROP TABLE guardrails;
42
+ ALTER TABLE guardrails_new RENAME TO guardrails;
43
+
44
+ CREATE INDEX IF NOT EXISTS idx_guardrails_project ON guardrails(project, active);
45
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_guardrails_dedup
46
+ ON guardrails(project, COALESCE(file_pattern, ''), rule);
@@ -0,0 +1,30 @@
1
+ -- Migration 025: Enforced anti-regression verification state
2
+ -- Hooks record affected features here after edits. Release-boundary commands
3
+ -- are blocked while pending rows remain for the project.
4
+
5
+ CREATE TABLE IF NOT EXISTS pending_feature_verifications (
6
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
7
+ project TEXT NOT NULL,
8
+ feature_id INTEGER NOT NULL,
9
+ feature_name TEXT NOT NULL,
10
+ file_path TEXT NOT NULL DEFAULT '',
11
+ reason TEXT NOT NULL DEFAULT '',
12
+ source_session_id TEXT,
13
+ trigger_tool TEXT,
14
+ status TEXT NOT NULL DEFAULT 'pending',
15
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
16
+ updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
17
+ resolved_at TEXT,
18
+ notes TEXT,
19
+ FOREIGN KEY (feature_id) REFERENCES features(id) ON DELETE CASCADE
20
+ );
21
+
22
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_pending_feature_verifications_open
23
+ ON pending_feature_verifications(project, feature_id, file_path)
24
+ WHERE status = 'pending';
25
+
26
+ CREATE INDEX IF NOT EXISTS idx_pending_feature_verifications_project
27
+ ON pending_feature_verifications(project, status, updated_at);
28
+
29
+ CREATE INDEX IF NOT EXISTS idx_pending_feature_verifications_feature
30
+ ON pending_feature_verifications(feature_id, status);
@@ -0,0 +1,18 @@
1
+ -- Migration 026: Multi-agent source attribution
2
+ -- Eagle Mem is shared by Claude Code and Codex. Keep lifecycle source
3
+ -- (startup/resume/clear) separate from the agent that wrote each row.
4
+
5
+ ALTER TABLE sessions ADD COLUMN agent TEXT NOT NULL DEFAULT 'claude-code';
6
+ ALTER TABLE observations ADD COLUMN agent TEXT NOT NULL DEFAULT 'claude-code';
7
+ ALTER TABLE summaries ADD COLUMN agent TEXT NOT NULL DEFAULT 'claude-code';
8
+
9
+ ALTER TABLE claude_memories ADD COLUMN origin_agent TEXT NOT NULL DEFAULT 'claude-code';
10
+ ALTER TABLE claude_plans ADD COLUMN origin_agent TEXT NOT NULL DEFAULT 'claude-code';
11
+ ALTER TABLE claude_tasks ADD COLUMN origin_agent TEXT NOT NULL DEFAULT 'claude-code';
12
+
13
+ CREATE INDEX IF NOT EXISTS idx_sessions_agent ON sessions(agent);
14
+ CREATE INDEX IF NOT EXISTS idx_observations_agent ON observations(agent);
15
+ CREATE INDEX IF NOT EXISTS idx_summaries_agent ON summaries(agent);
16
+ CREATE INDEX IF NOT EXISTS idx_claude_memories_origin_agent ON claude_memories(origin_agent);
17
+ CREATE INDEX IF NOT EXISTS idx_claude_plans_origin_agent ON claude_plans(origin_agent);
18
+ CREATE INDEX IF NOT EXISTS idx_claude_tasks_origin_agent ON claude_tasks(origin_agent);
@@ -0,0 +1,9 @@
1
+ -- Migration 027: Diff-fingerprint feature verification
2
+ -- Verification must attach to the current repository change, not only to a
3
+ -- feature/file pair. This prevents release hooks from reopening already
4
+ -- verified rows when the diff has not changed.
5
+
6
+ ALTER TABLE pending_feature_verifications ADD COLUMN change_fingerprint TEXT NOT NULL DEFAULT '';
7
+
8
+ CREATE INDEX IF NOT EXISTS idx_pending_feature_verifications_fingerprint
9
+ ON pending_feature_verifications(project, feature_id, file_path, change_fingerprint, status);
@@ -19,6 +19,7 @@ input=$(eagle_read_stdin)
19
19
  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
+ agent=$(eagle_agent_source_from_json "$input")
22
23
 
23
24
  hook_event=$(echo "$input" | jq -r '.hook_event_name // empty')
24
25
 
@@ -30,7 +31,7 @@ case "$hook_event" in
30
31
  [ ! -f "$EAGLE_MEM_DB" ] && exit 0
31
32
  project=$(eagle_project_from_cwd "$cwd")
32
33
  [ -z "$project" ] && exit 0
33
- eagle_upsert_session "$session_id" "$project" "$cwd" "" ""
34
+ eagle_upsert_session "$session_id" "$project" "$cwd" "" "" "$agent"
34
35
 
35
36
  task_id=$(echo "$input" | jq -r '.task_id // empty')
36
37
  task_subject=$(echo "$input" | jq -r '.task_subject // empty')
@@ -50,14 +51,16 @@ case "$hook_event" in
50
51
  subj_sql=$(eagle_sql_escape "$task_subject")
51
52
  desc_sql=$(eagle_sql_escape "$task_desc")
52
53
  stat_sql=$(eagle_sql_escape "$local_status")
54
+ agent_sql=$(eagle_sql_escape "$agent")
53
55
 
54
56
  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
+ INSERT INTO claude_tasks (project, source_session_id, source_task_id, file_path, subject, description, status, origin_agent)
58
+ VALUES ('$proj_sql', '$sid_sql', '$tid_sql', '$fp_sql', '$subj_sql', '$desc_sql', '$stat_sql', '$agent_sql')
57
59
  ON CONFLICT(file_path) DO UPDATE SET
58
60
  subject = excluded.subject,
59
61
  description = excluded.description,
60
62
  status = excluded.status,
63
+ origin_agent = excluded.origin_agent,
61
64
  updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now');
62
65
  SQL
63
66
  fi
@@ -69,8 +72,8 @@ esac
69
72
 
70
73
  # Only track relevant tools
71
74
  case "$tool_name" in
72
- Read|Write|Edit|Bash|TaskCreate|TaskUpdate) ;;
73
- *) exit 0 ;;
75
+ Read|Write|Edit|TaskCreate|TaskUpdate|apply_patch) ;;
76
+ *) eagle_is_shell_tool "$tool_name" || exit 0 ;;
74
77
  esac
75
78
 
76
79
  [ ! -f "$EAGLE_MEM_DB" ] && exit 0
@@ -80,7 +83,7 @@ project=$(eagle_project_from_cwd "$cwd")
80
83
 
81
84
  # Ensure session row exists before inserting observations (FK constraint).
82
85
  # PostToolUse can race SessionStart — the session row might not exist yet.
83
- eagle_upsert_session "$session_id" "$project" "$cwd" "" ""
86
+ eagle_upsert_session "$session_id" "$project" "$cwd" "" "" "$agent"
84
87
 
85
88
  # ─── Extract observation data from tool call ──────────────
86
89
 
@@ -108,10 +111,22 @@ case "$tool_name" in
108
111
  [ -n "$fp" ] && files_modified=$(printf '%s' "$fp" | jq -Rsc '[.]')
109
112
  tool_summary="Edit $fp"
110
113
  ;;
111
- Bash)
112
- cmd=$(echo "$input" | jq -r '.tool_input.command // empty' | cut -c1-200)
114
+ apply_patch)
115
+ patch_cmd=$(echo "$input" | jq -r '.tool_input.command // empty')
116
+ patch_files=$(printf '%s\n' "$patch_cmd" | eagle_extract_apply_patch_files)
117
+ if [ -n "$patch_files" ]; then
118
+ fp=$(printf '%s\n' "$patch_files" | head -1)
119
+ files_modified=$(printf '%s\n' "$patch_files" | jq -Rsc 'split("\n") | map(select(. != ""))')
120
+ patch_count=$(printf '%s\n' "$patch_files" | sed '/^[[:space:]]*$/d' | wc -l | tr -d ' ')
121
+ tool_summary="apply_patch: ${patch_count} file(s)"
122
+ else
123
+ tool_summary="apply_patch"
124
+ fi
125
+ ;;
126
+ Bash|exec_command|shell_command|unified_exec)
127
+ cmd=$(eagle_tool_command_from_json "$input" | cut -c1-200)
113
128
  cmd=$(echo "$cmd" | eagle_redact)
114
- tool_summary="Bash: $cmd"
129
+ tool_summary="${tool_name}: $cmd"
115
130
 
116
131
  tool_output=$(echo "$input" | jq -r '.tool_response.stdout // empty' 2>/dev/null)
117
132
  if [ -n "$tool_output" ]; then
@@ -147,7 +162,7 @@ esac
147
162
 
148
163
  if [ -n "$fp" ] && [ -n "$session_id" ] && eagle_validate_session_id "$session_id"; then
149
164
  case "$tool_name" in
150
- Edit|Write)
165
+ Edit|Write|apply_patch)
151
166
  mod_dir="$EAGLE_MEM_DIR/mod-tracker"
152
167
  mkdir -p "$mod_dir" 2>/dev/null
153
168
  mod_file="$mod_dir/${session_id}"
@@ -167,16 +182,29 @@ if [ -n "$fp" ] && [ -n "$session_id" ] && eagle_validate_session_id "$session_i
167
182
  esac
168
183
  fi
169
184
 
185
+ # ─── Enforced anti-regression: mark affected features pending ──
186
+
187
+ case "$tool_name" in
188
+ Edit|Write|apply_patch)
189
+ if [ -n "$files_modified" ] && [ "$files_modified" != "[]" ]; then
190
+ while IFS= read -r mod_file; do
191
+ [ -z "$mod_file" ] && continue
192
+ eagle_record_current_feature_verifications_for_file "$project" "$cwd" "$mod_file" "$session_id" "$tool_name" "File changed by ${tool_name}" >/dev/null
193
+ done < <(echo "$files_modified" | jq -r '.[]?' 2>/dev/null)
194
+ fi
195
+ ;;
196
+ esac
197
+
170
198
  # ─── Dispatch to extracted responsibilities ───────────────
171
199
 
172
- eagle_posttool_mirror_writes "$tool_name" "$fp" "$session_id" "$project"
173
- eagle_posttool_mirror_tasks "$tool_name" "$session_id" "$project" "$input"
200
+ eagle_posttool_mirror_writes "$tool_name" "$fp" "$session_id" "$project" "$agent"
201
+ eagle_posttool_mirror_tasks "$tool_name" "$session_id" "$project" "$input" "$agent"
174
202
  eagle_posttool_stale_hint "$tool_name" "$fp" "$project"
175
203
  eagle_posttool_decision_surface "$tool_name" "$fp" "$project"
176
204
 
177
205
  # ─── Record observation ──────────────────────────────────
178
206
 
179
- if ! eagle_insert_observation "$session_id" "$project" "$tool_name" "$tool_summary" "$files_read" "$files_modified" "$output_bytes" "$output_lines" "$command_category"; then
207
+ if ! eagle_insert_observation "$session_id" "$project" "$tool_name" "$tool_summary" "$files_read" "$files_modified" "$output_bytes" "$output_lines" "$command_category" "$agent"; then
180
208
  eagle_log "ERROR" "PostToolUse: observation insert failed for session=$session_id tool=$tool_name"
181
209
  fi
182
210
 
@@ -21,10 +21,11 @@ input=$(eagle_read_stdin)
21
21
  [ -z "$input" ] && exit 0
22
22
 
23
23
  tool_name=$(echo "$input" | jq -r '.tool_name // empty')
24
+ agent=$(eagle_agent_source_from_json "$input")
24
25
 
25
26
  case "$tool_name" in
26
- Bash|Read|Edit|Write) ;;
27
- *) exit 0 ;;
27
+ Read|Edit|Write|apply_patch) ;;
28
+ *) eagle_is_shell_tool "$tool_name" || exit 0 ;;
28
29
  esac
29
30
 
30
31
  [ ! -f "$EAGLE_MEM_DB" ] && exit 0
@@ -38,27 +39,102 @@ context=""
38
39
  updated_input=""
39
40
 
40
41
  case "$tool_name" in
41
- Bash)
42
- cmd=$(echo "$input" | jq -r '.tool_input.command // empty')
42
+ Bash|exec_command|shell_command|unified_exec)
43
+ cmd=$(eagle_tool_command_from_json "$input")
43
44
  [ -z "$cmd" ] && exit 0
44
45
 
45
- # ─── Feature verification on git push ─────────────────────
46
+ # ─── Enforced feature verification on release boundaries ───
47
+
48
+ release_changed_files=""
49
+ if eagle_is_release_boundary_command "$cmd"; then
50
+ if [ -n "$cwd" ] && [ -d "$cwd" ]; then
51
+ release_changed_files=$(eagle_changed_files_for_release "$cwd")
52
+ eagle_reconcile_current_feature_verifications "$project" "$cwd" "$session_id" "$tool_name" "Release boundary detected for current repository diff" "$release_changed_files" >/dev/null
53
+ fi
54
+
55
+ pending_rows=$(eagle_list_current_pending_feature_verifications "$project" "$cwd" "$release_changed_files" 8 2>/dev/null)
56
+ pending_count=$(printf '%s\n' "$pending_rows" | sed '/^[[:space:]]*$/d' | wc -l | tr -d ' ')
57
+ pending_count=${pending_count:-0}
58
+ if [ "$pending_count" -gt 0 ] 2>/dev/null; then
59
+ block_reason="Eagle Mem blocked this release boundary because ${pending_count} feature verification(s) are pending.
60
+
61
+ Run the affected smoke tests, then resolve them with:
62
+ eagle-mem feature verify <name> --notes \"what passed\"
63
+
64
+ For intentional exceptions:
65
+ eagle-mem feature waive <id> --reason \"why this is safe\"
66
+
67
+ Pending checks:"
68
+ while IFS='|' read -r pid pname pfile preason _ptrigger _pcreated psmoke pfingerprint; do
69
+ [ -z "$pid" ] && continue
70
+ block_reason+="
71
+ #${pid} ${pname}"
72
+ [ -n "$pfile" ] && block_reason+=" (${pfile})"
73
+ [ -n "$preason" ] && block_reason+=" — ${preason}"
74
+ [ -n "$psmoke" ] && block_reason+=" | smoke: ${psmoke}"
75
+ [ -n "$pfingerprint" ] && block_reason+=" | diff: ${pfingerprint}"
76
+ done <<< "$pending_rows"
77
+
78
+ jq -nc --arg reason "$block_reason" '{
79
+ "decision":"block",
80
+ "reason":$reason,
81
+ "hookSpecificOutput":{
82
+ "hookEventName":"PreToolUse",
83
+ "permissionDecision":"deny",
84
+ "permissionDecisionReason":$reason
85
+ }
86
+ }'
87
+ exit 0
88
+ fi
89
+ fi
90
+
91
+ # ─── RTK command rewrite / enforcement ─────────────────
92
+
93
+ rtk_cmd=$(eagle_rtk_rewrite_command "$cmd")
94
+ if [ -n "$rtk_cmd" ]; then
95
+ if [ "$agent" = "codex" ] && ! eagle_raw_bash_unlock_active; then
96
+ reason="Eagle Mem token guard blocked raw shell output.
97
+
98
+ Use RTK so large output is compact before it enters context:
99
+ $rtk_cmd
100
+
101
+ Temporary escape hatch for one-off raw output:
102
+ touch $EAGLE_RAW_BASH_UNLOCK"
103
+ jq -nc --arg reason "$reason" '{
104
+ "decision":"block",
105
+ "reason":$reason,
106
+ "hookSpecificOutput":{
107
+ "hookEventName":"PreToolUse",
108
+ "permissionDecision":"deny",
109
+ "permissionDecisionReason":$reason
110
+ }
111
+ }'
112
+ exit 0
113
+ fi
114
+
115
+ if ! eagle_raw_bash_unlock_active; then
116
+ updated_input=$(echo "$input" | jq --arg cmd "$rtk_cmd" '.tool_input + {"command":$cmd}')
117
+ context+="Eagle Mem token guard: rewrote raw shell command through RTK to reduce context load: ${rtk_cmd}. "
118
+ fi
119
+ fi
120
+
121
+ # ─── Feature verification context for non-blocked pushes ───
46
122
 
47
123
  case "$cmd" in
48
124
  *"git push"*|*"gh pr create"*)
49
125
  has_features=$(eagle_count_active_features "$project")
50
126
  if [ "${has_features:-0}" -gt 0 ]; then
51
- changed_files=""
52
- if [ -n "$cwd" ] && [ -d "$cwd" ]; then
53
- changed_files=$(git -C "$cwd" diff --name-only HEAD 2>/dev/null)
54
- [ -z "$changed_files" ] && changed_files=$(git -C "$cwd" diff --cached --name-only 2>/dev/null)
55
- fi
127
+ changed_files="$release_changed_files"
128
+ if [ -z "$changed_files" ] && [ -n "$cwd" ] && [ -d "$cwd" ]; then
129
+ changed_files=$(eagle_changed_files_for_release "$cwd")
130
+ fi
56
131
 
57
132
  if [ -n "$changed_files" ]; then
58
133
  seen_features=""
59
134
  while IFS= read -r changed_file; do
60
135
  [ -z "$changed_file" ] && continue
61
- fname=$(basename "$changed_file")
136
+ norm_file=$(eagle_project_file_path "$cwd" "$changed_file")
137
+ fname=$(basename "$norm_file")
62
138
 
63
139
  feature_hits=$(eagle_find_feature_for_push "$project" "$fname")
64
140
 
@@ -91,6 +167,7 @@ ${context}================"
91
167
 
92
168
  # ─── Command output filtering (learned rules) ─────────────
93
169
 
170
+ if [ -z "$updated_input" ]; then
94
171
  base_cmd=$(echo "$cmd" | awk '{print $1}' | sed 's|.*/||')
95
172
  rule=$(eagle_get_command_rule "$project" "$base_cmd" "$cmd")
96
173
 
@@ -117,11 +194,18 @@ ${context}================"
117
194
  ;;
118
195
  esac
119
196
  fi
197
+ fi
120
198
  ;;
121
199
 
122
- Edit|Write)
123
- fp=$(echo "$input" | jq -r '.tool_input.file_path // empty')
124
- if [ -n "$fp" ]; then
200
+ Edit|Write|apply_patch)
201
+ target_files=$(echo "$input" | jq -r '.tool_input.file_path // empty')
202
+ if [ "$tool_name" = "apply_patch" ]; then
203
+ patch_cmd=$(echo "$input" | jq -r '.tool_input.command // empty')
204
+ target_files=$(printf '%s\n' "$patch_cmd" | eagle_extract_apply_patch_files | sed '/^[[:space:]]*$/d' | awk '!seen[$0]++')
205
+ fi
206
+ if [ -n "$target_files" ]; then
207
+ while IFS= read -r fp; do
208
+ [ -z "$fp" ] && continue
125
209
  # ─── Guardrail + decision/gotcha surfacing ────────
126
210
  fname=$(basename "$fp")
127
211
  fname_stem="${fname%.*}"
@@ -188,6 +272,7 @@ Edit|Write)
188
272
  partners=${partners%, }
189
273
  context+="Eagle Mem recall: when you change '$(basename "$fp")' you usually also touch: $partners"
190
274
  fi
275
+ done <<< "$target_files"
191
276
  fi
192
277
  ;;
193
278
 
@@ -217,6 +302,13 @@ esac
217
302
 
218
303
  [ -z "$context" ] && [ -z "$updated_input" ] && exit 0
219
304
 
305
+ if [ "$agent" = "codex" ]; then
306
+ # Codex PreToolUse currently supports deny decisions, but not advisory
307
+ # additionalContext or updatedInput. Deny paths above already returned JSON;
308
+ # non-blocking reminders are delivered through SessionStart/UserPromptSubmit.
309
+ exit 0
310
+ fi
311
+
220
312
  if [ -n "$updated_input" ]; then
221
313
  jq -nc --arg ctx "$context" --argjson ui "$updated_input" \
222
314
  '{"hookSpecificOutput":{"hookEventName":"PreToolUse","updatedInput":$ui,"additionalContext":$ctx}}'
@@ -16,6 +16,7 @@ input=$(eagle_read_stdin)
16
16
  [ -z "$input" ] && exit 0
17
17
 
18
18
  session_id=$(echo "$input" | jq -r '.session_id // empty')
19
+ agent=$(eagle_agent_source_from_json "$input")
19
20
  [ -z "$session_id" ] && exit 0
20
21
  [ ! -f "$EAGLE_MEM_DB" ] && exit 0
21
22
 
@@ -30,7 +31,7 @@ if eagle_validate_session_id "$session_id"; then
30
31
  if [ -d "$task_dir" ]; then
31
32
  for task_file in "$task_dir"/*.json; do
32
33
  [ ! -f "$task_file" ] && continue
33
- eagle_capture_claude_task "$task_file" "$session_id" "$project"
34
+ eagle_capture_claude_task "$task_file" "$session_id" "$project" "$agent"
34
35
  done
35
36
  eagle_log "INFO" "SessionEnd: re-synced tasks from $task_dir"
36
37
  fi