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 +40 -8
- package/db/023_guardrails.sql +3 -2
- package/db/024_guardrails_unique.sql +46 -0
- package/db/025_pending_feature_verifications.sql +30 -0
- package/db/026_agent_source.sql +18 -0
- package/db/027_feature_verification_fingerprints.sql +9 -0
- package/hooks/post-tool-use.sh +41 -13
- package/hooks/pre-tool-use.sh +106 -14
- package/hooks/session-end.sh +2 -1
- package/hooks/session-start.sh +54 -13
- package/hooks/stop.sh +114 -21
- package/hooks/user-prompt-submit.sh +13 -5
- package/lib/codex-hooks.sh +194 -0
- package/lib/common.sh +341 -0
- package/lib/db-features.sh +222 -0
- package/lib/db-guardrails.sh +2 -1
- package/lib/db-mirrors.sh +24 -15
- package/lib/db-observations.sh +3 -2
- package/lib/db-sessions.sh +6 -2
- package/lib/db-summaries.sh +6 -3
- package/lib/hooks-posttool.sh +8 -6
- package/package.json +7 -3
- package/scripts/curate.sh +35 -25
- package/scripts/feature.sh +70 -2
- package/scripts/guard.sh +4 -1
- package/scripts/help.sh +7 -2
- package/scripts/install.sh +118 -76
- package/scripts/memories.sh +21 -18
- package/scripts/search.sh +36 -28
- package/scripts/uninstall.sh +7 -0
- package/scripts/update.sh +31 -6
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
|
|
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.
|
|
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)
|
|
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
|
-
|
|
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
|
|
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** |
|
|
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;
|
|
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 |
|
package/db/023_guardrails.sql
CHANGED
|
@@ -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);
|
package/hooks/post-tool-use.sh
CHANGED
|
@@ -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|
|
|
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
|
-
|
|
112
|
-
|
|
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="
|
|
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
|
|
package/hooks/pre-tool-use.sh
CHANGED
|
@@ -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
|
-
|
|
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=$(
|
|
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
|
-
# ───
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
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
|
-
|
|
124
|
-
if [
|
|
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}}'
|
package/hooks/session-end.sh
CHANGED
|
@@ -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
|