eagle-mem 4.9.1 → 4.9.3
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 +10 -0
- package/bin/eagle-mem +1 -0
- package/db/003_code_chunks.sql +3 -1
- package/db/005_claude_memories.sql +3 -1
- package/db/006_claude_plans.sql +3 -1
- package/db/007_claude_tasks.sql +3 -1
- package/db/008_summary_upsert.sql +3 -1
- package/db/012_enriched_summaries.sql +3 -1
- package/db/028_agent_artifact_tables.sql +9 -3
- package/db/033_safe_project_rekey_triggers.sql +57 -0
- package/db/schema.sql +3 -1
- package/hooks/post-tool-use.sh +2 -2
- package/hooks/pre-tool-use.sh +1 -1
- package/hooks/session-end.sh +1 -1
- package/hooks/session-start.sh +1 -1
- package/hooks/stop.sh +1 -1
- package/hooks/user-prompt-submit.sh +1 -1
- package/lib/common.sh +184 -38
- package/lib/db-backfill.sh +98 -33
- package/lib/db-mirrors.sh +75 -37
- package/package.json +1 -1
- package/scripts/help.sh +1 -0
- package/scripts/install.sh +27 -2
- package/scripts/memories.sh +11 -7
- package/scripts/statusline-em.sh +30 -5
- package/scripts/update.sh +16 -0
package/README.md
CHANGED
|
@@ -152,6 +152,16 @@ Eagle Mem prevents Claude from repeating past mistakes:
|
|
|
152
152
|
| `eagle-mem scan` | Scan codebase and generate overview |
|
|
153
153
|
| `eagle-mem index` | Index source files for FTS5 code search |
|
|
154
154
|
|
|
155
|
+
### v4.9.3 Patch
|
|
156
|
+
|
|
157
|
+
Follow-up hardening for the v4.9.2 project-key repair: Claude transcript workspace detection now reads complete early JSONL records instead of a fixed byte slice, so large SessionStart hook context cannot hide the first `cwd`. Metadata-only memory/plan/task repairs also avoid touching FTS-indexed columns, preventing SQLite FTS update triggers from firing during safe project/source rekeys.
|
|
158
|
+
|
|
159
|
+
### v4.9.2 Patch
|
|
160
|
+
|
|
161
|
+
Nested-repo Claude Code projects now use one stable project key. When a Claude workspace contains a git repo subdirectory, hooks prefer the Claude transcript workspace root while repo-local CLI commands can still use git-root keys where appropriate. Memory sync and backfill also repair unchanged memory rows whose content hash stayed the same but whose project key was stale. FTS5 update triggers now ignore metadata-only project rekeys, avoiding SQLite virtual-table errors during safe repairs.
|
|
162
|
+
|
|
163
|
+
Installer parity also improved: first-time install now auto-provisions RTK when Cargo is available, the Eagle Mem statusline shows version/session/memory/turn counts, `eagle-mem statusline` is available as a CLI command, and Codex instructions explicitly call out that Codex currently has hook recall plus the statusline command rather than Claude Code's persistent custom statusline UI.
|
|
164
|
+
|
|
155
165
|
### v4.9.1 Patch
|
|
156
166
|
|
|
157
167
|
`eagle-mem updates status` now refreshes the npm version live, and install/update seed the local latest-version cache with the installed version. This avoids confusing status output immediately after an update.
|
package/bin/eagle-mem
CHANGED
|
@@ -23,6 +23,7 @@ case "$command" in
|
|
|
23
23
|
health) bash "$SCRIPTS_DIR/health.sh" "$@" ;;
|
|
24
24
|
config) bash "$SCRIPTS_DIR/config.sh" "$@" ;;
|
|
25
25
|
updates) bash "$SCRIPTS_DIR/updates.sh" "$@" ;;
|
|
26
|
+
statusline) "$SCRIPTS_DIR/statusline-em.sh" "$@" ;;
|
|
26
27
|
guard) bash "$SCRIPTS_DIR/guard.sh" "$@" ;;
|
|
27
28
|
overview) bash "$SCRIPTS_DIR/overview.sh" "$@" ;;
|
|
28
29
|
session|sessions)
|
package/db/003_code_chunks.sql
CHANGED
|
@@ -38,7 +38,9 @@ CREATE TRIGGER IF NOT EXISTS chunks_ad AFTER DELETE ON code_chunks BEGIN
|
|
|
38
38
|
VALUES ('delete', old.id, old.file_path, old.content);
|
|
39
39
|
END;
|
|
40
40
|
|
|
41
|
-
CREATE TRIGGER IF NOT EXISTS chunks_au
|
|
41
|
+
CREATE TRIGGER IF NOT EXISTS chunks_au
|
|
42
|
+
AFTER UPDATE OF file_path, content ON code_chunks
|
|
43
|
+
BEGIN
|
|
42
44
|
INSERT INTO code_chunks_fts(code_chunks_fts, rowid, file_path, content)
|
|
43
45
|
VALUES ('delete', old.id, old.file_path, old.content);
|
|
44
46
|
INSERT INTO code_chunks_fts(rowid, file_path, content)
|
|
@@ -41,7 +41,9 @@ CREATE TRIGGER IF NOT EXISTS claude_memories_ad AFTER DELETE ON claude_memories
|
|
|
41
41
|
VALUES ('delete', old.id, old.memory_name, old.description, old.content);
|
|
42
42
|
END;
|
|
43
43
|
|
|
44
|
-
CREATE TRIGGER IF NOT EXISTS claude_memories_au
|
|
44
|
+
CREATE TRIGGER IF NOT EXISTS claude_memories_au
|
|
45
|
+
AFTER UPDATE OF memory_name, description, content ON claude_memories
|
|
46
|
+
BEGIN
|
|
45
47
|
INSERT INTO claude_memories_fts(claude_memories_fts, rowid, memory_name, description, content)
|
|
46
48
|
VALUES ('delete', old.id, old.memory_name, old.description, old.content);
|
|
47
49
|
INSERT INTO claude_memories_fts(rowid, memory_name, description, content)
|
package/db/006_claude_plans.sql
CHANGED
|
@@ -37,7 +37,9 @@ CREATE TRIGGER IF NOT EXISTS claude_plans_ad AFTER DELETE ON claude_plans BEGIN
|
|
|
37
37
|
VALUES ('delete', old.id, old.title, old.content);
|
|
38
38
|
END;
|
|
39
39
|
|
|
40
|
-
CREATE TRIGGER IF NOT EXISTS claude_plans_au
|
|
40
|
+
CREATE TRIGGER IF NOT EXISTS claude_plans_au
|
|
41
|
+
AFTER UPDATE OF title, content ON claude_plans
|
|
42
|
+
BEGIN
|
|
41
43
|
INSERT INTO claude_plans_fts(claude_plans_fts, rowid, title, content)
|
|
42
44
|
VALUES ('delete', old.id, old.title, old.content);
|
|
43
45
|
INSERT INTO claude_plans_fts(rowid, title, content)
|
package/db/007_claude_tasks.sql
CHANGED
|
@@ -42,7 +42,9 @@ CREATE TRIGGER IF NOT EXISTS claude_tasks_ad AFTER DELETE ON claude_tasks BEGIN
|
|
|
42
42
|
VALUES ('delete', old.id, old.subject, old.description);
|
|
43
43
|
END;
|
|
44
44
|
|
|
45
|
-
CREATE TRIGGER IF NOT EXISTS claude_tasks_au
|
|
45
|
+
CREATE TRIGGER IF NOT EXISTS claude_tasks_au
|
|
46
|
+
AFTER UPDATE OF subject, description ON claude_tasks
|
|
47
|
+
BEGIN
|
|
46
48
|
INSERT INTO claude_tasks_fts(claude_tasks_fts, rowid, subject, description)
|
|
47
49
|
VALUES ('delete', old.id, old.subject, old.description);
|
|
48
50
|
INSERT INTO claude_tasks_fts(rowid, subject, description)
|
|
@@ -49,7 +49,9 @@ CREATE TRIGGER summaries_ad AFTER DELETE ON summaries BEGIN
|
|
|
49
49
|
VALUES ('delete', old.id, old.request, old.investigated, old.learned, old.completed, old.next_steps, old.notes);
|
|
50
50
|
END;
|
|
51
51
|
|
|
52
|
-
CREATE TRIGGER summaries_au
|
|
52
|
+
CREATE TRIGGER summaries_au
|
|
53
|
+
AFTER UPDATE OF request, investigated, learned, completed, next_steps, notes ON summaries
|
|
54
|
+
BEGIN
|
|
53
55
|
INSERT INTO summaries_fts(summaries_fts, rowid, request, investigated, learned, completed, next_steps, notes)
|
|
54
56
|
VALUES ('delete', old.id, old.request, old.investigated, old.learned, old.completed, old.next_steps, old.notes);
|
|
55
57
|
INSERT INTO summaries_fts(rowid, request, investigated, learned, completed, next_steps, notes)
|
|
@@ -43,7 +43,9 @@ CREATE TRIGGER summaries_ad AFTER DELETE ON summaries BEGIN
|
|
|
43
43
|
VALUES ('delete', old.id, old.request, old.investigated, old.learned, old.completed, old.next_steps, old.notes, old.decisions, old.gotchas, old.key_files);
|
|
44
44
|
END;
|
|
45
45
|
|
|
46
|
-
CREATE TRIGGER summaries_au
|
|
46
|
+
CREATE TRIGGER summaries_au
|
|
47
|
+
AFTER UPDATE OF request, investigated, learned, completed, next_steps, notes, decisions, gotchas, key_files ON summaries
|
|
48
|
+
BEGIN
|
|
47
49
|
INSERT INTO summaries_fts(summaries_fts, rowid, request, investigated, learned, completed, next_steps, notes, decisions, gotchas, key_files)
|
|
48
50
|
VALUES ('delete', old.id, old.request, old.investigated, old.learned, old.completed, old.next_steps, old.notes, old.decisions, old.gotchas, old.key_files);
|
|
49
51
|
INSERT INTO summaries_fts(rowid, request, investigated, learned, completed, next_steps, notes, decisions, gotchas, key_files)
|
|
@@ -78,7 +78,9 @@ CREATE TRIGGER IF NOT EXISTS agent_memories_ad AFTER DELETE ON agent_memories BE
|
|
|
78
78
|
VALUES ('delete', old.id, old.memory_name, old.description, old.content);
|
|
79
79
|
END;
|
|
80
80
|
|
|
81
|
-
CREATE TRIGGER IF NOT EXISTS agent_memories_au
|
|
81
|
+
CREATE TRIGGER IF NOT EXISTS agent_memories_au
|
|
82
|
+
AFTER UPDATE OF memory_name, description, content ON agent_memories
|
|
83
|
+
BEGIN
|
|
82
84
|
INSERT INTO agent_memories_fts(agent_memories_fts, rowid, memory_name, description, content)
|
|
83
85
|
VALUES ('delete', old.id, old.memory_name, old.description, old.content);
|
|
84
86
|
INSERT INTO agent_memories_fts(rowid, memory_name, description, content)
|
|
@@ -95,7 +97,9 @@ CREATE TRIGGER IF NOT EXISTS agent_plans_ad AFTER DELETE ON agent_plans BEGIN
|
|
|
95
97
|
VALUES ('delete', old.id, old.title, old.content);
|
|
96
98
|
END;
|
|
97
99
|
|
|
98
|
-
CREATE TRIGGER IF NOT EXISTS agent_plans_au
|
|
100
|
+
CREATE TRIGGER IF NOT EXISTS agent_plans_au
|
|
101
|
+
AFTER UPDATE OF title, content ON agent_plans
|
|
102
|
+
BEGIN
|
|
99
103
|
INSERT INTO agent_plans_fts(agent_plans_fts, rowid, title, content)
|
|
100
104
|
VALUES ('delete', old.id, old.title, old.content);
|
|
101
105
|
INSERT INTO agent_plans_fts(rowid, title, content)
|
|
@@ -112,7 +116,9 @@ CREATE TRIGGER IF NOT EXISTS agent_tasks_ad AFTER DELETE ON agent_tasks BEGIN
|
|
|
112
116
|
VALUES ('delete', old.id, old.subject, old.description);
|
|
113
117
|
END;
|
|
114
118
|
|
|
115
|
-
CREATE TRIGGER IF NOT EXISTS agent_tasks_au
|
|
119
|
+
CREATE TRIGGER IF NOT EXISTS agent_tasks_au
|
|
120
|
+
AFTER UPDATE OF subject, description ON agent_tasks
|
|
121
|
+
BEGIN
|
|
116
122
|
INSERT INTO agent_tasks_fts(agent_tasks_fts, rowid, subject, description)
|
|
117
123
|
VALUES ('delete', old.id, old.subject, old.description);
|
|
118
124
|
INSERT INTO agent_tasks_fts(rowid, subject, description)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
-- Migration 033: Make FTS triggers safe for project-key repairs.
|
|
2
|
+
--
|
|
3
|
+
-- SQLite FTS5 external-content tables can throw unsafe virtual-table errors
|
|
4
|
+
-- when UPDATE triggers try to delete/reinsert FTS rows during metadata-only
|
|
5
|
+
-- repairs. Project rekeys do not change searchable text, so the FTS UPDATE
|
|
6
|
+
-- triggers should only run when indexed columns change.
|
|
7
|
+
|
|
8
|
+
DROP TRIGGER IF EXISTS summaries_au;
|
|
9
|
+
DROP TRIGGER IF EXISTS chunks_au;
|
|
10
|
+
DROP TRIGGER IF EXISTS agent_memories_au;
|
|
11
|
+
DROP TRIGGER IF EXISTS agent_plans_au;
|
|
12
|
+
DROP TRIGGER IF EXISTS agent_tasks_au;
|
|
13
|
+
|
|
14
|
+
CREATE TRIGGER summaries_au
|
|
15
|
+
AFTER UPDATE OF request, investigated, learned, completed, next_steps, notes, decisions, gotchas, key_files ON summaries
|
|
16
|
+
BEGIN
|
|
17
|
+
INSERT INTO summaries_fts(summaries_fts, rowid, request, investigated, learned, completed, next_steps, notes, decisions, gotchas, key_files)
|
|
18
|
+
VALUES ('delete', old.id, old.request, old.investigated, old.learned, old.completed, old.next_steps, old.notes, old.decisions, old.gotchas, old.key_files);
|
|
19
|
+
INSERT INTO summaries_fts(rowid, request, investigated, learned, completed, next_steps, notes, decisions, gotchas, key_files)
|
|
20
|
+
VALUES (new.id, new.request, new.investigated, new.learned, new.completed, new.next_steps, new.notes, new.decisions, new.gotchas, new.key_files);
|
|
21
|
+
END;
|
|
22
|
+
|
|
23
|
+
CREATE TRIGGER chunks_au
|
|
24
|
+
AFTER UPDATE OF file_path, content ON code_chunks
|
|
25
|
+
BEGIN
|
|
26
|
+
INSERT INTO code_chunks_fts(code_chunks_fts, rowid, file_path, content)
|
|
27
|
+
VALUES ('delete', old.id, old.file_path, old.content);
|
|
28
|
+
INSERT INTO code_chunks_fts(rowid, file_path, content)
|
|
29
|
+
VALUES (new.id, new.file_path, new.content);
|
|
30
|
+
END;
|
|
31
|
+
|
|
32
|
+
CREATE TRIGGER agent_memories_au
|
|
33
|
+
AFTER UPDATE OF memory_name, description, content ON agent_memories
|
|
34
|
+
BEGIN
|
|
35
|
+
INSERT INTO agent_memories_fts(agent_memories_fts, rowid, memory_name, description, content)
|
|
36
|
+
VALUES ('delete', old.id, old.memory_name, old.description, old.content);
|
|
37
|
+
INSERT INTO agent_memories_fts(rowid, memory_name, description, content)
|
|
38
|
+
VALUES (new.id, new.memory_name, new.description, new.content);
|
|
39
|
+
END;
|
|
40
|
+
|
|
41
|
+
CREATE TRIGGER agent_plans_au
|
|
42
|
+
AFTER UPDATE OF title, content ON agent_plans
|
|
43
|
+
BEGIN
|
|
44
|
+
INSERT INTO agent_plans_fts(agent_plans_fts, rowid, title, content)
|
|
45
|
+
VALUES ('delete', old.id, old.title, old.content);
|
|
46
|
+
INSERT INTO agent_plans_fts(rowid, title, content)
|
|
47
|
+
VALUES (new.id, new.title, new.content);
|
|
48
|
+
END;
|
|
49
|
+
|
|
50
|
+
CREATE TRIGGER agent_tasks_au
|
|
51
|
+
AFTER UPDATE OF subject, description ON agent_tasks
|
|
52
|
+
BEGIN
|
|
53
|
+
INSERT INTO agent_tasks_fts(agent_tasks_fts, rowid, subject, description)
|
|
54
|
+
VALUES ('delete', old.id, old.subject, old.description);
|
|
55
|
+
INSERT INTO agent_tasks_fts(rowid, subject, description)
|
|
56
|
+
VALUES (new.id, new.subject, new.description);
|
|
57
|
+
END;
|
package/db/schema.sql
CHANGED
|
@@ -99,7 +99,9 @@ CREATE TRIGGER IF NOT EXISTS summaries_ad AFTER DELETE ON summaries BEGIN
|
|
|
99
99
|
VALUES ('delete', old.id, old.request, old.investigated, old.learned, old.completed, old.next_steps, old.notes);
|
|
100
100
|
END;
|
|
101
101
|
|
|
102
|
-
CREATE TRIGGER IF NOT EXISTS summaries_au
|
|
102
|
+
CREATE TRIGGER IF NOT EXISTS summaries_au
|
|
103
|
+
AFTER UPDATE OF request, investigated, learned, completed, next_steps, notes ON summaries
|
|
104
|
+
BEGIN
|
|
103
105
|
INSERT INTO summaries_fts(summaries_fts, rowid, request, investigated, learned, completed, next_steps, notes)
|
|
104
106
|
VALUES ('delete', old.id, old.request, old.investigated, old.learned, old.completed, old.next_steps, old.notes);
|
|
105
107
|
INSERT INTO summaries_fts(rowid, request, investigated, learned, completed, next_steps, notes)
|
package/hooks/post-tool-use.sh
CHANGED
|
@@ -30,7 +30,7 @@ if [ -z "$session_id" ]; then exit 0; fi
|
|
|
30
30
|
case "$hook_event" in
|
|
31
31
|
TaskCreated|TaskCompleted)
|
|
32
32
|
[ ! -f "$EAGLE_MEM_DB" ] && exit 0
|
|
33
|
-
project=$(
|
|
33
|
+
project=$(eagle_project_from_hook_input "$input")
|
|
34
34
|
[ -z "$project" ] && exit 0
|
|
35
35
|
eagle_upsert_session "$session_id" "$project" "$cwd" "" "" "$agent"
|
|
36
36
|
|
|
@@ -79,7 +79,7 @@ esac
|
|
|
79
79
|
|
|
80
80
|
[ ! -f "$EAGLE_MEM_DB" ] && exit 0
|
|
81
81
|
|
|
82
|
-
project=$(
|
|
82
|
+
project=$(eagle_project_from_hook_input "$input")
|
|
83
83
|
[ -z "$project" ] && exit 0
|
|
84
84
|
|
|
85
85
|
# Ensure session row exists before inserting observations (FK constraint).
|
package/hooks/pre-tool-use.sh
CHANGED
|
@@ -33,7 +33,7 @@ esac
|
|
|
33
33
|
|
|
34
34
|
session_id=$(echo "$input" | jq -r '.session_id // empty')
|
|
35
35
|
cwd=$(echo "$input" | jq -r '.cwd // empty')
|
|
36
|
-
project=$(
|
|
36
|
+
project=$(eagle_project_from_hook_input "$input")
|
|
37
37
|
[ -z "$project" ] && exit 0
|
|
38
38
|
|
|
39
39
|
context=""
|
package/hooks/session-end.sh
CHANGED
|
@@ -22,7 +22,7 @@ agent=$(eagle_agent_source_from_json "$input")
|
|
|
22
22
|
[ ! -f "$EAGLE_MEM_DB" ] && exit 0
|
|
23
23
|
|
|
24
24
|
cwd=$(echo "$input" | jq -r '.cwd // empty')
|
|
25
|
-
project=$(
|
|
25
|
+
project=$(eagle_project_from_hook_input "$input")
|
|
26
26
|
[ -z "$project" ] && exit 0
|
|
27
27
|
|
|
28
28
|
# Final sweep: re-capture all task files to catch status changes
|
package/hooks/session-start.sh
CHANGED
package/hooks/stop.sh
CHANGED
|
@@ -33,7 +33,7 @@ agent=$(eagle_agent_source_from_json "$input")
|
|
|
33
33
|
agent_type=$(echo "$input" | jq -r '.agent_type // empty')
|
|
34
34
|
[ -n "$agent_type" ] && [ "$agent_type" != "main" ] && exit 0
|
|
35
35
|
|
|
36
|
-
project=$(
|
|
36
|
+
project=$(eagle_project_from_hook_input "$input")
|
|
37
37
|
[ -z "$project" ] && exit 0
|
|
38
38
|
|
|
39
39
|
eagle_log "INFO" "Stop: session=$session_id project=$project transcript=$transcript_path agent=$agent"
|
package/lib/common.sh
CHANGED
|
@@ -8,10 +8,10 @@ EAGLE_MEM_DIR="${EAGLE_MEM_DIR:-$HOME/.eagle-mem}"
|
|
|
8
8
|
EAGLE_MEM_DB="$EAGLE_MEM_DIR/memory.db"
|
|
9
9
|
EAGLE_MEM_LOG="$EAGLE_MEM_DIR/eagle-mem.log"
|
|
10
10
|
EAGLE_SETTINGS="${EAGLE_SETTINGS:-$HOME/.claude/settings.json}"
|
|
11
|
-
EAGLE_SKILLS_DIR="$HOME/.claude/skills"
|
|
12
|
-
EAGLE_CLAUDE_PROJECTS_DIR="$HOME/.claude/projects"
|
|
13
|
-
EAGLE_CLAUDE_PLANS_DIR="$HOME/.claude/plans"
|
|
14
|
-
EAGLE_CLAUDE_TASKS_DIR="$HOME/.claude/tasks"
|
|
11
|
+
EAGLE_SKILLS_DIR="${EAGLE_SKILLS_DIR:-$HOME/.claude/skills}"
|
|
12
|
+
EAGLE_CLAUDE_PROJECTS_DIR="${EAGLE_CLAUDE_PROJECTS_DIR:-$HOME/.claude/projects}"
|
|
13
|
+
EAGLE_CLAUDE_PLANS_DIR="${EAGLE_CLAUDE_PLANS_DIR:-$HOME/.claude/plans}"
|
|
14
|
+
EAGLE_CLAUDE_TASKS_DIR="${EAGLE_CLAUDE_TASKS_DIR:-$HOME/.claude/tasks}"
|
|
15
15
|
EAGLE_CODEX_DIR="${EAGLE_CODEX_DIR:-$HOME/.codex}"
|
|
16
16
|
EAGLE_CODEX_CONFIG="${EAGLE_CODEX_CONFIG:-$EAGLE_CODEX_DIR/config.toml}"
|
|
17
17
|
EAGLE_CODEX_HOOKS="${EAGLE_CODEX_HOOKS:-$EAGLE_CODEX_DIR/hooks.json}"
|
|
@@ -71,30 +71,52 @@ eagle_log() {
|
|
|
71
71
|
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] [$level] $*" >> "$EAGLE_MEM_LOG" 2>/dev/null || true
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
echo "$EAGLE_MEM_PROJECT"
|
|
77
|
-
return
|
|
78
|
-
fi
|
|
74
|
+
eagle_normalize_project_path() {
|
|
75
|
+
local path="${1:-$(pwd)}"
|
|
79
76
|
|
|
80
|
-
|
|
81
|
-
|
|
77
|
+
# Normalize macOS /private prefixes.
|
|
78
|
+
case "$path" in /private/tmp*) path="/tmp${path#/private/tmp}" ;; esac
|
|
79
|
+
case "$path" in /private/var/*) path="/var${path#/private/var}" ;; esac
|
|
80
|
+
|
|
81
|
+
printf '%s\n' "$path"
|
|
82
|
+
}
|
|
82
83
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
case "$resolved" in /private/var/*) resolved="/var${resolved#/private/var}" ;; esac
|
|
84
|
+
eagle_is_ephemeral_project_path() {
|
|
85
|
+
local path="${1:-}"
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
/
|
|
90
|
-
/
|
|
91
|
-
"$HOME/
|
|
92
|
-
"$HOME/Desktop"|"$HOME/Desktop/"*) echo ""; return ;;
|
|
87
|
+
case "$path" in
|
|
88
|
+
/tmp|/tmp/*|/var/tmp|/var/tmp/*) return 0 ;;
|
|
89
|
+
/var/folders|/var/folders/*) return 0 ;;
|
|
90
|
+
"$HOME/Downloads"|"$HOME/Downloads/"*) return 0 ;;
|
|
91
|
+
"$HOME/Desktop"|"$HOME/Desktop/"*) return 0 ;;
|
|
93
92
|
esac
|
|
94
93
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
94
|
+
return 1
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
eagle_project_key_from_target_dir() {
|
|
98
|
+
local target_dir="${1:-}"
|
|
99
|
+
[ -z "$target_dir" ] && return 1
|
|
100
|
+
|
|
101
|
+
local name
|
|
102
|
+
name=$(basename "$target_dir")
|
|
103
|
+
if [ ${#name} -le 1 ]; then
|
|
104
|
+
echo ""
|
|
105
|
+
return 0
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
if [[ "$target_dir" == "$HOME/"* ]]; then
|
|
109
|
+
echo "${target_dir#$HOME/}"
|
|
110
|
+
elif [ "$target_dir" = "$HOME" ]; then
|
|
111
|
+
echo "$name"
|
|
112
|
+
else
|
|
113
|
+
echo "${target_dir#/}"
|
|
114
|
+
fi
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
eagle_project_key_for_worktree_path() {
|
|
118
|
+
local resolved="${1:-}"
|
|
119
|
+
|
|
98
120
|
case "$resolved" in
|
|
99
121
|
"$HOME"/*/.eagle-worktrees/*)
|
|
100
122
|
local worktree_parent worktree_tail worktree_repo worktree_project
|
|
@@ -103,34 +125,156 @@ eagle_project_from_cwd() {
|
|
|
103
125
|
worktree_repo="${worktree_tail%%/*}"
|
|
104
126
|
worktree_project="$worktree_parent/$worktree_repo"
|
|
105
127
|
if [ -n "$worktree_repo" ] && [ -d "$worktree_project" ]; then
|
|
106
|
-
|
|
107
|
-
return
|
|
128
|
+
eagle_project_key_from_target_dir "$worktree_project"
|
|
129
|
+
return 0
|
|
108
130
|
fi
|
|
109
131
|
;;
|
|
110
132
|
esac
|
|
111
133
|
|
|
134
|
+
return 1
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
eagle_project_from_path_no_git() {
|
|
138
|
+
if [ -n "${EAGLE_MEM_PROJECT:-}" ]; then
|
|
139
|
+
echo "$EAGLE_MEM_PROJECT"
|
|
140
|
+
return
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
local path="${1:-$(pwd)}"
|
|
144
|
+
local resolved
|
|
145
|
+
resolved=$(eagle_normalize_project_path "$path")
|
|
146
|
+
|
|
147
|
+
if eagle_is_ephemeral_project_path "$resolved"; then
|
|
148
|
+
echo ""
|
|
149
|
+
return
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
local worktree_project
|
|
153
|
+
if worktree_project=$(eagle_project_key_for_worktree_path "$resolved"); then
|
|
154
|
+
echo "$worktree_project"
|
|
155
|
+
return
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
eagle_project_key_from_target_dir "$resolved"
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
eagle_project_from_cwd() {
|
|
162
|
+
if [ -n "${EAGLE_MEM_PROJECT:-}" ]; then
|
|
163
|
+
echo "$EAGLE_MEM_PROJECT"
|
|
164
|
+
return
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
local cwd="${1:-$(pwd)}"
|
|
168
|
+
local resolved
|
|
169
|
+
resolved=$(eagle_normalize_project_path "$cwd")
|
|
170
|
+
|
|
171
|
+
# Skip ephemeral directories — return empty so hooks early-exit.
|
|
172
|
+
if eagle_is_ephemeral_project_path "$resolved"; then
|
|
173
|
+
echo ""
|
|
174
|
+
return
|
|
175
|
+
fi
|
|
176
|
+
|
|
177
|
+
# Eagle Mem worker lanes run in sibling git worktrees under
|
|
178
|
+
# <parent>/.eagle-worktrees/<repo>/<lane>. Keep their observations attached
|
|
179
|
+
# to the real project, not to the disposable worktree path.
|
|
180
|
+
local worktree_project
|
|
181
|
+
if worktree_project=$(eagle_project_key_for_worktree_path "$resolved"); then
|
|
182
|
+
echo "$worktree_project"
|
|
183
|
+
return
|
|
184
|
+
fi
|
|
185
|
+
|
|
112
186
|
local target_dir
|
|
113
187
|
local git_root
|
|
114
|
-
git_root=$(git -C "$
|
|
188
|
+
git_root=$(git -C "$resolved" rev-parse --show-toplevel 2>/dev/null)
|
|
115
189
|
if [ -n "$git_root" ]; then
|
|
116
190
|
target_dir="$git_root"
|
|
117
191
|
else
|
|
118
|
-
target_dir="$
|
|
192
|
+
target_dir="$resolved"
|
|
119
193
|
fi
|
|
120
194
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
195
|
+
eagle_project_key_from_target_dir "$target_dir"
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
eagle_path_is_same_or_child() {
|
|
199
|
+
local parent child
|
|
200
|
+
parent=$(eagle_normalize_project_path "${1:-}")
|
|
201
|
+
child=$(eagle_normalize_project_path "${2:-}")
|
|
202
|
+
|
|
203
|
+
[ -z "$parent" ] || [ -z "$child" ] && return 1
|
|
204
|
+
[ "$child" = "$parent" ] && return 0
|
|
205
|
+
case "$child" in "$parent"/*) return 0 ;; esac
|
|
206
|
+
return 1
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
eagle_transcript_first_cwd() {
|
|
210
|
+
local transcript_path="${1:-}"
|
|
211
|
+
[ -f "$transcript_path" ] || return 1
|
|
212
|
+
|
|
213
|
+
local cwd
|
|
214
|
+
cwd=$(sed -n '1,200p' "$transcript_path" 2>/dev/null \
|
|
215
|
+
| jq -r 'select((.cwd? // "") != "") | .cwd' 2>/dev/null \
|
|
216
|
+
| awk 'NF { print; exit }' || true)
|
|
217
|
+
[ -n "$cwd" ] || return 1
|
|
218
|
+
printf '%s\n' "$cwd"
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
eagle_project_from_claude_project_dir() {
|
|
222
|
+
local project_dir="${1:-}"
|
|
223
|
+
project_dir="${project_dir%/}"
|
|
224
|
+
[ -d "$project_dir" ] || return 1
|
|
225
|
+
|
|
226
|
+
local jsonl cwd project
|
|
227
|
+
for jsonl in "$project_dir"/*.jsonl; do
|
|
228
|
+
[ -f "$jsonl" ] || continue
|
|
229
|
+
cwd=$(eagle_transcript_first_cwd "$jsonl")
|
|
230
|
+
[ -z "$cwd" ] && continue
|
|
231
|
+
project=$(eagle_project_from_path_no_git "$cwd")
|
|
232
|
+
[ -n "$project" ] && { printf '%s\n' "$project"; return 0; }
|
|
233
|
+
done
|
|
234
|
+
|
|
235
|
+
return 1
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
eagle_project_from_claude_transcript() {
|
|
239
|
+
local transcript_path="${1:-}"
|
|
240
|
+
local cwd="${2:-}"
|
|
241
|
+
|
|
242
|
+
case "$transcript_path" in
|
|
243
|
+
"$EAGLE_CLAUDE_PROJECTS_DIR"/*/*.jsonl) ;;
|
|
244
|
+
*) return 1 ;;
|
|
245
|
+
esac
|
|
246
|
+
|
|
247
|
+
local transcript_cwd project
|
|
248
|
+
transcript_cwd=$(eagle_transcript_first_cwd "$transcript_path")
|
|
249
|
+
[ -n "$transcript_cwd" ] || return 1
|
|
250
|
+
|
|
251
|
+
if [ -n "$cwd" ] && ! eagle_path_is_same_or_child "$transcript_cwd" "$cwd"; then
|
|
252
|
+
return 1
|
|
125
253
|
fi
|
|
126
254
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
255
|
+
project=$(eagle_project_from_path_no_git "$transcript_cwd")
|
|
256
|
+
[ -n "$project" ] || return 1
|
|
257
|
+
printf '%s\n' "$project"
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
eagle_project_from_hook_input() {
|
|
261
|
+
local input="${1:-}"
|
|
262
|
+
|
|
263
|
+
if [ -n "${EAGLE_MEM_PROJECT:-}" ]; then
|
|
264
|
+
echo "$EAGLE_MEM_PROJECT"
|
|
265
|
+
return
|
|
133
266
|
fi
|
|
267
|
+
|
|
268
|
+
local cwd transcript_path project
|
|
269
|
+
cwd=$(printf '%s' "$input" | jq -r '.cwd // empty' 2>/dev/null)
|
|
270
|
+
transcript_path=$(printf '%s' "$input" | jq -r '.transcript_path // empty' 2>/dev/null)
|
|
271
|
+
|
|
272
|
+
if project=$(eagle_project_from_claude_transcript "$transcript_path" "$cwd"); then
|
|
273
|
+
printf '%s\n' "$project"
|
|
274
|
+
return
|
|
275
|
+
fi
|
|
276
|
+
|
|
277
|
+
eagle_project_from_cwd "$cwd"
|
|
134
278
|
}
|
|
135
279
|
|
|
136
280
|
eagle_project_file_path() {
|
|
@@ -761,6 +905,7 @@ Eagle Mem hooks are active for Codex in this project. SessionStart and UserPromp
|
|
|
761
905
|
- Attribute recalled context as "Eagle Mem recalls:" when it is injected
|
|
762
906
|
- Use the Eagle Mem skills when relevant: `eagle-mem-search`, `eagle-mem-overview`, `eagle-mem-memories`, `eagle-mem-tasks`, and `eagle-mem-orchestrate`
|
|
763
907
|
- For broad multi-agent work, YOU run `eagle-mem orchestrate`; do not ask the user to run these commands
|
|
908
|
+
- Codex does not currently expose a persistent custom statusline like Claude Code; if the user asks for Eagle Mem status, run `eagle-mem statusline`
|
|
764
909
|
- For important decisions, preferences, gotchas, or durable project facts, include them briefly in normal prose. Eagle Mem will extract them from the transcript.
|
|
765
910
|
- Do not revert Eagle Mem-surfaced decisions without asking the user
|
|
766
911
|
- If Eagle Mem reports pending feature verification, verify or waive it before push/PR/publish
|
|
@@ -779,7 +924,8 @@ eagle_patch_codex_agents_md() {
|
|
|
779
924
|
|| grep -qF 'emit an <eagle-summary> block' "$agents_md" 2>/dev/null \
|
|
780
925
|
|| grep -qF 'explicitly include them in the `<eagle-summary>` block' "$agents_md" 2>/dev/null \
|
|
781
926
|
|| grep -qF 'explicitly include them in the <eagle-summary> block' "$agents_md" 2>/dev/null \
|
|
782
|
-
|| ! grep -qF 'Keep Codex final answers clean' "$agents_md" 2>/dev/null
|
|
927
|
+
|| ! grep -qF 'Keep Codex final answers clean' "$agents_md" 2>/dev/null \
|
|
928
|
+
|| ! grep -qF 'eagle-mem statusline' "$agents_md" 2>/dev/null; then
|
|
783
929
|
local tmp_md
|
|
784
930
|
tmp_md=$(mktemp)
|
|
785
931
|
awk -v marker="$marker" '
|
package/lib/db-backfill.sh
CHANGED
|
@@ -6,22 +6,41 @@
|
|
|
6
6
|
_EAGLE_DB_BACKFILL_LOADED=1
|
|
7
7
|
|
|
8
8
|
eagle_build_session_project_map() {
|
|
9
|
+
local session_filter_file="${1:-}"
|
|
9
10
|
local claude_projects_dir="$EAGLE_CLAUDE_PROJECTS_DIR"
|
|
10
11
|
[ ! -d "$claude_projects_dir" ] && return 0
|
|
11
12
|
|
|
13
|
+
if [ -n "$session_filter_file" ] && [ -s "$session_filter_file" ]; then
|
|
14
|
+
local transcript_index project_cache
|
|
15
|
+
transcript_index=$(mktemp)
|
|
16
|
+
project_cache=$(mktemp)
|
|
17
|
+
find "$claude_projects_dir" -mindepth 2 -maxdepth 2 -name "*.jsonl" -print > "$transcript_index" 2>/dev/null || true
|
|
18
|
+
|
|
19
|
+
local sid jsonl proj_dir project cached
|
|
20
|
+
while IFS= read -r sid; do
|
|
21
|
+
[ -z "$sid" ] && continue
|
|
22
|
+
jsonl=$(grep -m 1 "/$sid.jsonl\$" "$transcript_index" 2>/dev/null || true)
|
|
23
|
+
[ -f "$jsonl" ] || continue
|
|
24
|
+
|
|
25
|
+
proj_dir=$(dirname "$jsonl")
|
|
26
|
+
cached=$(grep -m 1 "^$proj_dir|" "$project_cache" 2>/dev/null || true)
|
|
27
|
+
if [ -n "$cached" ]; then
|
|
28
|
+
project="${cached#*|}"
|
|
29
|
+
else
|
|
30
|
+
project=$(eagle_project_from_claude_project_dir "$proj_dir" 2>/dev/null || true)
|
|
31
|
+
printf '%s|%s\n' "$proj_dir" "$project" >> "$project_cache"
|
|
32
|
+
fi
|
|
33
|
+
[ -n "$project" ] && echo "$sid|$project"
|
|
34
|
+
done < "$session_filter_file"
|
|
35
|
+
rm -f "$transcript_index" "$project_cache"
|
|
36
|
+
return 0
|
|
37
|
+
fi
|
|
38
|
+
|
|
12
39
|
for proj_dir in "$claude_projects_dir"/*/; do
|
|
13
40
|
[ ! -d "$proj_dir" ] && continue
|
|
14
41
|
|
|
15
42
|
local project=""
|
|
16
|
-
|
|
17
|
-
sample_jsonl=$(ls "$proj_dir"*.jsonl 2>/dev/null | head -1)
|
|
18
|
-
if [ -n "$sample_jsonl" ] && [ -f "$sample_jsonl" ]; then
|
|
19
|
-
local cwd
|
|
20
|
-
cwd=$(head -10 "$sample_jsonl" | jq -r 'select(.cwd != null) | .cwd' 2>/dev/null | head -1)
|
|
21
|
-
if [ -n "$cwd" ]; then
|
|
22
|
-
project=$(eagle_project_from_cwd "$cwd")
|
|
23
|
-
fi
|
|
24
|
-
fi
|
|
43
|
+
project=$(eagle_project_from_claude_project_dir "$proj_dir" 2>/dev/null || true)
|
|
25
44
|
[ -z "$project" ] && continue
|
|
26
45
|
|
|
27
46
|
for jsonl in "$proj_dir"*.jsonl; do
|
|
@@ -33,36 +52,55 @@ eagle_build_session_project_map() {
|
|
|
33
52
|
done
|
|
34
53
|
}
|
|
35
54
|
|
|
55
|
+
eagle_build_claude_project_dir_map() {
|
|
56
|
+
local claude_projects_dir="$EAGLE_CLAUDE_PROJECTS_DIR"
|
|
57
|
+
[ ! -d "$claude_projects_dir" ] && return 0
|
|
58
|
+
|
|
59
|
+
for proj_dir in "$claude_projects_dir"/*/; do
|
|
60
|
+
[ ! -d "$proj_dir" ] && continue
|
|
61
|
+
|
|
62
|
+
local project
|
|
63
|
+
project=$(eagle_project_from_claude_project_dir "$proj_dir" 2>/dev/null || true)
|
|
64
|
+
[ -z "$project" ] && continue
|
|
65
|
+
|
|
66
|
+
printf '%s|%s\n' "${proj_dir%/}" "$project"
|
|
67
|
+
done
|
|
68
|
+
}
|
|
69
|
+
|
|
36
70
|
eagle_backfill_projects() {
|
|
37
71
|
local updated=0
|
|
38
|
-
local map
|
|
39
|
-
|
|
40
|
-
|
|
72
|
+
local session_filter_file map
|
|
73
|
+
session_filter_file=$(mktemp)
|
|
74
|
+
eagle_db "SELECT id FROM sessions;" > "$session_filter_file" 2>/dev/null || true
|
|
75
|
+
map=$(eagle_build_session_project_map "$session_filter_file")
|
|
41
76
|
|
|
42
77
|
# Phase 1: Build old→new project mapping BEFORE mutating any rows.
|
|
43
78
|
# Collect from sessions table so non-session tables can be migrated.
|
|
44
79
|
local rename_map_file
|
|
45
80
|
rename_map_file=$(mktemp)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
81
|
+
if [ -n "$map" ]; then
|
|
82
|
+
while IFS='|' read -r sid project; do
|
|
83
|
+
[ -z "$sid" ] || [ -z "$project" ] && continue
|
|
84
|
+
local sid_sql
|
|
85
|
+
sid_sql=$(eagle_sql_escape "$sid")
|
|
86
|
+
local old_project
|
|
87
|
+
old_project=$(eagle_db "SELECT project FROM sessions WHERE id = '$sid_sql';")
|
|
88
|
+
if [ -n "$old_project" ] && [ "$old_project" != "$project" ]; then
|
|
89
|
+
echo "$old_project|$project" >> "$rename_map_file"
|
|
90
|
+
fi
|
|
91
|
+
done <<< "$map"
|
|
92
|
+
fi
|
|
56
93
|
|
|
57
94
|
# Phase 2: Update session-linked tables
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
95
|
+
if [ -n "$map" ]; then
|
|
96
|
+
while IFS='|' read -r sid project; do
|
|
97
|
+
[ -z "$sid" ] || [ -z "$project" ] && continue
|
|
98
|
+
local sid_sql proj_sql
|
|
99
|
+
sid_sql=$(eagle_sql_escape "$sid")
|
|
100
|
+
proj_sql=$(eagle_sql_escape "$project")
|
|
101
|
+
|
|
102
|
+
local ch
|
|
103
|
+
ch=$(eagle_db_pipe <<SQL
|
|
66
104
|
BEGIN;
|
|
67
105
|
UPDATE sessions SET project = '$proj_sql' WHERE id = '$sid_sql' AND (project = '' OR project != '$proj_sql');
|
|
68
106
|
UPDATE agent_tasks SET project = '$proj_sql' WHERE source_session_id = '$sid_sql' AND (project = '' OR project != '$proj_sql');
|
|
@@ -73,11 +111,38 @@ UPDATE observations SET project = '$proj_sql' WHERE session_id = '$sid_sql' AND
|
|
|
73
111
|
SELECT total_changes();
|
|
74
112
|
COMMIT;
|
|
75
113
|
SQL
|
|
114
|
+
)
|
|
115
|
+
[ "${ch:-0}" -gt 0 ] && updated=$((updated + ch))
|
|
116
|
+
done <<< "$map"
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
# Phase 3: Repair Claude memory rows that have no session id but live under
|
|
120
|
+
# a Claude project directory. These rows used to keep stale project keys
|
|
121
|
+
# when their file content did not change.
|
|
122
|
+
local dir_map
|
|
123
|
+
dir_map=$(eagle_build_claude_project_dir_map)
|
|
124
|
+
while IFS='|' read -r proj_dir project; do
|
|
125
|
+
[ -z "$proj_dir" ] || [ -z "$project" ] && continue
|
|
126
|
+
local proj_sql prefix_sql
|
|
127
|
+
proj_sql=$(eagle_sql_escape "$project")
|
|
128
|
+
prefix_sql=$(eagle_sql_escape "$proj_dir/memory/")
|
|
129
|
+
|
|
130
|
+
local ch
|
|
131
|
+
ch=$(eagle_db_pipe <<SQL
|
|
132
|
+
BEGIN;
|
|
133
|
+
UPDATE agent_memories
|
|
134
|
+
SET project = '$proj_sql'
|
|
135
|
+
WHERE file_path >= '$prefix_sql'
|
|
136
|
+
AND file_path < ('$prefix_sql' || char(0x10ffff))
|
|
137
|
+
AND (project = '' OR project != '$proj_sql');
|
|
138
|
+
SELECT total_changes();
|
|
139
|
+
COMMIT;
|
|
140
|
+
SQL
|
|
76
141
|
)
|
|
77
142
|
[ "${ch:-0}" -gt 0 ] && updated=$((updated + ch))
|
|
78
|
-
done <<< "$
|
|
143
|
+
done <<< "$dir_map"
|
|
79
144
|
|
|
80
|
-
# Phase
|
|
145
|
+
# Phase 4: Update non-session tables using the old→new mapping.
|
|
81
146
|
# Skip ambiguous mappings (one old name → multiple new names).
|
|
82
147
|
if [ -s "$rename_map_file" ]; then
|
|
83
148
|
local uniq_map
|
|
@@ -121,7 +186,7 @@ COMMIT;
|
|
|
121
186
|
SQL
|
|
122
187
|
done <<< "$uniq_map"
|
|
123
188
|
fi
|
|
124
|
-
rm -f "$rename_map_file"
|
|
189
|
+
rm -f "$rename_map_file" "$session_filter_file"
|
|
125
190
|
|
|
126
191
|
echo "$updated"
|
|
127
192
|
}
|
package/lib/db-mirrors.sh
CHANGED
|
@@ -63,18 +63,32 @@ eagle_capture_agent_memory() {
|
|
|
63
63
|
agent_sql=$(eagle_sql_escape "$agent")
|
|
64
64
|
|
|
65
65
|
eagle_db_pipe <<SQL
|
|
66
|
-
INSERT INTO agent_memories (project, file_path, memory_name, description, memory_type, content, content_hash, origin_session_id, origin_agent)
|
|
67
|
-
VALUES ('$proj_sql', '$fp_sql', '$name_sql', '$desc_sql', '$type_sql', '$content_sql', '$hash_sql', '$origin_sql', '$agent_sql')
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
66
|
+
INSERT OR IGNORE INTO agent_memories (project, file_path, memory_name, description, memory_type, content, content_hash, origin_session_id, origin_agent)
|
|
67
|
+
VALUES ('$proj_sql', '$fp_sql', '$name_sql', '$desc_sql', '$type_sql', '$content_sql', '$hash_sql', '$origin_sql', '$agent_sql');
|
|
68
|
+
|
|
69
|
+
UPDATE agent_memories
|
|
70
|
+
SET memory_name = '$name_sql',
|
|
71
|
+
description = '$desc_sql',
|
|
72
|
+
memory_type = '$type_sql',
|
|
73
|
+
content = '$content_sql',
|
|
74
|
+
content_hash = '$hash_sql',
|
|
75
|
+
origin_session_id = COALESCE(NULLIF('$origin_sql', ''), origin_session_id),
|
|
76
|
+
origin_agent = COALESCE(NULLIF('$agent_sql', ''), origin_agent),
|
|
77
|
+
project = CASE WHEN '$proj_sql' != '' THEN '$proj_sql' ELSE project END,
|
|
78
|
+
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
79
|
+
WHERE file_path = '$fp_sql'
|
|
80
|
+
AND content_hash != '$hash_sql';
|
|
81
|
+
|
|
82
|
+
UPDATE agent_memories
|
|
83
|
+
SET origin_session_id = COALESCE(NULLIF('$origin_sql', ''), origin_session_id),
|
|
84
|
+
origin_agent = COALESCE(NULLIF('$agent_sql', ''), origin_agent),
|
|
85
|
+
project = CASE WHEN '$proj_sql' != '' THEN '$proj_sql' ELSE project END,
|
|
86
|
+
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
87
|
+
WHERE file_path = '$fp_sql'
|
|
88
|
+
AND content_hash = '$hash_sql'
|
|
89
|
+
AND (('$proj_sql' != '' AND project != '$proj_sql')
|
|
90
|
+
OR ('$origin_sql' != '' AND origin_session_id != '$origin_sql')
|
|
91
|
+
OR ('$agent_sql' != '' AND origin_agent != '$agent_sql'));
|
|
78
92
|
SQL
|
|
79
93
|
}
|
|
80
94
|
|
|
@@ -147,17 +161,30 @@ eagle_capture_agent_plan() {
|
|
|
147
161
|
agent_sql=$(eagle_sql_escape "$agent")
|
|
148
162
|
|
|
149
163
|
eagle_db_pipe <<SQL
|
|
150
|
-
INSERT INTO agent_plans (project, file_path, title, content, content_hash, origin_session_id, origin_agent)
|
|
151
|
-
VALUES ('$proj_sql', '$fp_sql', '$title_sql', '$content_sql', '$hash_sql', '$origin_sql', '$agent_sql')
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
164
|
+
INSERT OR IGNORE INTO agent_plans (project, file_path, title, content, content_hash, origin_session_id, origin_agent)
|
|
165
|
+
VALUES ('$proj_sql', '$fp_sql', '$title_sql', '$content_sql', '$hash_sql', '$origin_sql', '$agent_sql');
|
|
166
|
+
|
|
167
|
+
UPDATE agent_plans
|
|
168
|
+
SET title = '$title_sql',
|
|
169
|
+
content = '$content_sql',
|
|
170
|
+
content_hash = '$hash_sql',
|
|
171
|
+
origin_session_id = COALESCE(NULLIF('$origin_sql', ''), origin_session_id),
|
|
172
|
+
origin_agent = COALESCE(NULLIF('$agent_sql', ''), origin_agent),
|
|
173
|
+
project = CASE WHEN '$proj_sql' != '' THEN '$proj_sql' ELSE project END,
|
|
174
|
+
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
175
|
+
WHERE file_path = '$fp_sql'
|
|
176
|
+
AND content_hash != '$hash_sql';
|
|
177
|
+
|
|
178
|
+
UPDATE agent_plans
|
|
179
|
+
SET origin_session_id = COALESCE(NULLIF('$origin_sql', ''), origin_session_id),
|
|
180
|
+
origin_agent = COALESCE(NULLIF('$agent_sql', ''), origin_agent),
|
|
181
|
+
project = CASE WHEN '$proj_sql' != '' THEN '$proj_sql' ELSE project END,
|
|
182
|
+
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
183
|
+
WHERE file_path = '$fp_sql'
|
|
184
|
+
AND content_hash = '$hash_sql'
|
|
185
|
+
AND (('$proj_sql' != '' AND project != '$proj_sql')
|
|
186
|
+
OR ('$origin_sql' != '' AND origin_session_id != '$origin_sql')
|
|
187
|
+
OR ('$agent_sql' != '' AND origin_agent != '$agent_sql'));
|
|
161
188
|
SQL
|
|
162
189
|
}
|
|
163
190
|
|
|
@@ -245,20 +272,31 @@ eagle_capture_agent_task() {
|
|
|
245
272
|
agent_sql=$(eagle_sql_escape "$agent")
|
|
246
273
|
|
|
247
274
|
eagle_db_pipe <<SQL
|
|
248
|
-
INSERT INTO agent_tasks (project, source_session_id, source_task_id, file_path, subject, description, active_form, status, blocks, blocked_by, content_hash, origin_agent)
|
|
249
|
-
VALUES ('$proj_sql', '$sid_sql', '$tid_sql', '$fp_sql', '$subj_sql', '$desc_sql', '$af_sql', '$status_sql', '$blocks_sql', '$bb_sql', '$hash_sql', '$agent_sql')
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
275
|
+
INSERT OR IGNORE INTO agent_tasks (project, source_session_id, source_task_id, file_path, subject, description, active_form, status, blocks, blocked_by, content_hash, origin_agent)
|
|
276
|
+
VALUES ('$proj_sql', '$sid_sql', '$tid_sql', '$fp_sql', '$subj_sql', '$desc_sql', '$af_sql', '$status_sql', '$blocks_sql', '$bb_sql', '$hash_sql', '$agent_sql');
|
|
277
|
+
|
|
278
|
+
UPDATE agent_tasks
|
|
279
|
+
SET subject = '$subj_sql',
|
|
280
|
+
description = '$desc_sql',
|
|
281
|
+
active_form = '$af_sql',
|
|
282
|
+
status = '$status_sql',
|
|
283
|
+
blocks = '$blocks_sql',
|
|
284
|
+
blocked_by = '$bb_sql',
|
|
285
|
+
content_hash = '$hash_sql',
|
|
286
|
+
origin_agent = COALESCE(NULLIF('$agent_sql', ''), origin_agent),
|
|
287
|
+
project = CASE WHEN '$proj_sql' != '' THEN '$proj_sql' ELSE project END,
|
|
288
|
+
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
289
|
+
WHERE file_path = '$fp_sql'
|
|
290
|
+
AND content_hash != '$hash_sql';
|
|
291
|
+
|
|
292
|
+
UPDATE agent_tasks
|
|
293
|
+
SET origin_agent = COALESCE(NULLIF('$agent_sql', ''), origin_agent),
|
|
294
|
+
project = CASE WHEN '$proj_sql' != '' THEN '$proj_sql' ELSE project END,
|
|
295
|
+
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
296
|
+
WHERE file_path = '$fp_sql'
|
|
297
|
+
AND content_hash = '$hash_sql'
|
|
298
|
+
AND (('$proj_sql' != '' AND project != '$proj_sql')
|
|
299
|
+
OR ('$agent_sql' != '' AND origin_agent != '$agent_sql'));
|
|
262
300
|
SQL
|
|
263
301
|
}
|
|
264
302
|
|
package/package.json
CHANGED
package/scripts/help.sh
CHANGED
|
@@ -32,6 +32,7 @@ echo -e " ${BOLD}Safety and token controls:${RESET}"
|
|
|
32
32
|
echo -e " ${CYAN}feature${RESET} Track, verify, and unblock feature changes"
|
|
33
33
|
echo -e " ${CYAN}guard${RESET} Manage regression guardrails for files"
|
|
34
34
|
echo -e " ${CYAN}config${RESET} View/change providers, RTK, and token guard settings"
|
|
35
|
+
echo -e " ${CYAN}statusline${RESET} Print compact Eagle Mem statusline"
|
|
35
36
|
echo ""
|
|
36
37
|
echo -e " ${BOLD}Automation and coordination:${RESET}"
|
|
37
38
|
echo -e " ${CYAN}curate${RESET} Run curator (co-edits, hot files, guardrails)"
|
package/scripts/install.sh
CHANGED
|
@@ -52,6 +52,26 @@ install_package() {
|
|
|
52
52
|
esac
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
ensure_rtk() {
|
|
56
|
+
if command -v rtk &>/dev/null; then
|
|
57
|
+
rtk_version=$(rtk --version 2>/dev/null | head -1)
|
|
58
|
+
eagle_ok "RTK ${DIM}(${rtk_version:-installed})${RESET}"
|
|
59
|
+
return 0
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
eagle_warn "RTK not found ${DIM}(token guard will be advisory until installed)${RESET}"
|
|
63
|
+
if command -v cargo &>/dev/null; then
|
|
64
|
+
eagle_info "Installing RTK with Cargo: cargo install rtk"
|
|
65
|
+
if cargo install rtk; then
|
|
66
|
+
eagle_ok "RTK installed"
|
|
67
|
+
else
|
|
68
|
+
eagle_warn "RTK install failed ${DIM}(continuing; run 'cargo install rtk' later)${RESET}"
|
|
69
|
+
fi
|
|
70
|
+
else
|
|
71
|
+
eagle_dim "Install Rust/Cargo, then run: cargo install rtk"
|
|
72
|
+
fi
|
|
73
|
+
}
|
|
74
|
+
|
|
55
75
|
# ─── Check prerequisites ───────────────────────────────────
|
|
56
76
|
|
|
57
77
|
echo -e " ${BOLD}Checking prerequisites...${RESET}"
|
|
@@ -118,6 +138,10 @@ else
|
|
|
118
138
|
fi
|
|
119
139
|
fi
|
|
120
140
|
|
|
141
|
+
# RTK is optional, but Eagle Mem can use it for both Claude Code and Codex
|
|
142
|
+
# token-guard behavior when it is available on PATH.
|
|
143
|
+
ensure_rtk
|
|
144
|
+
|
|
121
145
|
# Claude Code / Codex
|
|
122
146
|
claude_found=false
|
|
123
147
|
codex_found=false
|
|
@@ -283,8 +307,9 @@ if [ "$claude_found" = true ]; then
|
|
|
283
307
|
#!/usr/bin/env bash
|
|
284
308
|
input=$(cat)
|
|
285
309
|
project_dir=$(echo "$input" | jq -r '.workspace.project_dir // .workspace.current_dir // .cwd // ""' 2>/dev/null)
|
|
310
|
+
session_id=$(echo "$input" | jq -r '.session_id // .session.id // ""' 2>/dev/null)
|
|
286
311
|
source "$HOME/.eagle-mem/scripts/statusline-em.sh"
|
|
287
|
-
eagle_mem_statusline "$project_dir"
|
|
312
|
+
eagle_mem_statusline "$project_dir" "$session_id"
|
|
288
313
|
WRAPPER
|
|
289
314
|
chmod +x "$wrapper"
|
|
290
315
|
tmp=$(mktemp)
|
|
@@ -303,7 +328,7 @@ WRAPPER
|
|
|
303
328
|
eagle_dim " em_section=\"\""
|
|
304
329
|
eagle_dim " if [ -f \"\$HOME/.eagle-mem/scripts/statusline-em.sh\" ]; then"
|
|
305
330
|
eagle_dim " source \"\$HOME/.eagle-mem/scripts/statusline-em.sh\""
|
|
306
|
-
eagle_dim " em_section=\$(eagle_mem_statusline \"\$project_dir\")"
|
|
331
|
+
eagle_dim " em_section=\$(eagle_mem_statusline \"\$project_dir\" \"\$session_id\")"
|
|
307
332
|
eagle_dim " fi"
|
|
308
333
|
echo ""
|
|
309
334
|
eagle_ok "Statusline ${DIM}(manual patch needed — instructions above)${RESET}"
|
package/scripts/memories.sh
CHANGED
|
@@ -455,17 +455,20 @@ memories_sync() {
|
|
|
455
455
|
base=$(basename "$memfile")
|
|
456
456
|
[ "$base" = "MEMORY.md" ] && continue
|
|
457
457
|
|
|
458
|
-
local existing_hash
|
|
459
|
-
|
|
458
|
+
local mem_project mem_project_dir existing_row existing_hash existing_project
|
|
459
|
+
mem_project_dir="${memfile%/memory/*}"
|
|
460
|
+
mem_project=$(eagle_project_from_claude_project_dir "$mem_project_dir" 2>/dev/null || true)
|
|
461
|
+
existing_row=$(eagle_db "SELECT content_hash, project FROM agent_memories WHERE file_path = '$(eagle_sql_escape "$memfile")';")
|
|
462
|
+
IFS='|' read -r existing_hash existing_project <<< "$existing_row"
|
|
460
463
|
local new_hash
|
|
461
464
|
new_hash=$(shasum -a 256 "$memfile" | awk '{print $1}')
|
|
462
465
|
|
|
463
|
-
if [ "$existing_hash" = "$new_hash" ]; then
|
|
466
|
+
if [ "$existing_hash" = "$new_hash" ] && { [ -z "$mem_project" ] || [ "$existing_project" = "$mem_project" ]; }; then
|
|
464
467
|
mem_skipped=$((mem_skipped + 1))
|
|
465
468
|
continue
|
|
466
469
|
fi
|
|
467
470
|
|
|
468
|
-
eagle_capture_agent_memory "$memfile" "" ""
|
|
471
|
+
eagle_capture_agent_memory "$memfile" "" "$mem_project" "claude-code"
|
|
469
472
|
mem_synced=$((mem_synced + 1))
|
|
470
473
|
eagle_ok "Memory: $base"
|
|
471
474
|
done < <(find "$claude_mem_root" -path "*/memory/*.md" -print0 2>/dev/null)
|
|
@@ -478,12 +481,13 @@ memories_sync() {
|
|
|
478
481
|
for memfile in "$codex_mem_root/MEMORY.md" "$codex_mem_root/memory_summary.md"; do
|
|
479
482
|
[ ! -f "$memfile" ] && continue
|
|
480
483
|
|
|
481
|
-
local existing_hash
|
|
482
|
-
|
|
484
|
+
local existing_row existing_hash existing_project
|
|
485
|
+
existing_row=$(eagle_db "SELECT content_hash, project FROM agent_memories WHERE file_path = '$(eagle_sql_escape "$memfile")';")
|
|
486
|
+
IFS='|' read -r existing_hash existing_project <<< "$existing_row"
|
|
483
487
|
local new_hash
|
|
484
488
|
new_hash=$(shasum -a 256 "$memfile" | awk '{print $1}')
|
|
485
489
|
|
|
486
|
-
if [ "$existing_hash" = "$new_hash" ]; then
|
|
490
|
+
if [ "$existing_hash" = "$new_hash" ] && [ "$existing_project" = "$codex_project" ]; then
|
|
487
491
|
mem_skipped=$((mem_skipped + 1))
|
|
488
492
|
continue
|
|
489
493
|
fi
|
package/scripts/statusline-em.sh
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
eagle_mem_statusline() {
|
|
7
7
|
local project_dir="${1:-}"
|
|
8
|
+
local session_id="${2:-}"
|
|
8
9
|
local em_db="$HOME/.eagle-mem/memory.db"
|
|
9
10
|
[ -f "$em_db" ] || return
|
|
10
11
|
|
|
@@ -12,25 +13,49 @@ eagle_mem_statusline() {
|
|
|
12
13
|
. "$SCRIPT_DIR/../lib/common.sh"
|
|
13
14
|
|
|
14
15
|
local proj
|
|
16
|
+
[ -z "$project_dir" ] && project_dir="$(pwd)"
|
|
15
17
|
proj=$(eagle_project_from_cwd "$project_dir")
|
|
16
18
|
[ -z "$proj" ] && return
|
|
17
19
|
|
|
18
20
|
proj=$(eagle_sql_escape "$proj")
|
|
19
21
|
|
|
20
|
-
local cnt mem
|
|
22
|
+
local version cnt mem turns
|
|
23
|
+
version=$(tr -d '[:space:]' < "$HOME/.eagle-mem/.version" 2>/dev/null)
|
|
24
|
+
[ -z "$version" ] && version="?"
|
|
21
25
|
cnt=$(echo ".headers off
|
|
22
26
|
SELECT COUNT(*) FROM sessions WHERE project = '${proj}';" | sqlite3 "$em_db" 2>/dev/null | tr -d '[:space:]')
|
|
23
27
|
mem=$(echo ".headers off
|
|
24
28
|
SELECT COUNT(*) FROM agent_memories WHERE project = '${proj}';" | sqlite3 "$em_db" 2>/dev/null | tr -d '[:space:]')
|
|
29
|
+
if [ -n "$session_id" ] && [ -f "$HOME/.eagle-mem/.turn-counter.${session_id}" ]; then
|
|
30
|
+
turns=$(tr -d '[:space:]' < "$HOME/.eagle-mem/.turn-counter.${session_id}" 2>/dev/null)
|
|
31
|
+
else
|
|
32
|
+
turns=$(find "$HOME/.eagle-mem" -name '.turn-counter.*' -type f -mtime -1 -print 2>/dev/null \
|
|
33
|
+
| while IFS= read -r f; do
|
|
34
|
+
tr -d '[:space:]' < "$f" 2>/dev/null
|
|
35
|
+
echo ""
|
|
36
|
+
done \
|
|
37
|
+
| awk '($1+0)>max{max=$1+0} END{print max+0}')
|
|
38
|
+
fi
|
|
25
39
|
cnt=${cnt:-0}; mem=${mem:-0}
|
|
40
|
+
turns=${turns:-0}
|
|
26
41
|
|
|
27
42
|
local R='\033[0m' CYAN='\033[96m' WHT='\033[97m' DIM='\033[2m'
|
|
28
|
-
printf "%
|
|
43
|
+
printf "%bEM%b %bv%s%b ses %b%s%b mem %b%s%b turns %b%s%b" \
|
|
44
|
+
"$CYAN" "$R" \
|
|
45
|
+
"$WHT" "$version" "$DIM" \
|
|
46
|
+
"$WHT" "$cnt" "$DIM" \
|
|
47
|
+
"$WHT" "$mem" "$DIM" \
|
|
48
|
+
"$WHT" "$turns" "$R"
|
|
29
49
|
}
|
|
30
50
|
|
|
31
51
|
# When run directly, read project_dir from stdin JSON (statusline format)
|
|
32
52
|
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
53
|
+
if [ -t 0 ]; then
|
|
54
|
+
input=""
|
|
55
|
+
else
|
|
56
|
+
input=$(cat)
|
|
57
|
+
fi
|
|
58
|
+
project_dir=$(echo "$input" | jq -r '.workspace.project_dir // .workspace.current_dir // .cwd // empty' 2>/dev/null)
|
|
59
|
+
session_id=$(echo "$input" | jq -r '.session_id // .session.id // empty' 2>/dev/null)
|
|
60
|
+
eagle_mem_statusline "${project_dir:-$(pwd)}" "$session_id"
|
|
36
61
|
fi
|
package/scripts/update.sh
CHANGED
|
@@ -143,6 +143,22 @@ if [ "$codex_found" = true ] && [ -d "$PACKAGE_DIR/skills" ]; then
|
|
|
143
143
|
eagle_ok "Codex skills updated"
|
|
144
144
|
fi
|
|
145
145
|
|
|
146
|
+
# ─── Refresh generated Claude statusline wrapper ───────────
|
|
147
|
+
|
|
148
|
+
statusline_wrapper="$EAGLE_MEM_DIR/scripts/statusline-wrapper.sh"
|
|
149
|
+
if [ -f "$statusline_wrapper" ]; then
|
|
150
|
+
cat > "$statusline_wrapper" << 'WRAPPER'
|
|
151
|
+
#!/usr/bin/env bash
|
|
152
|
+
input=$(cat)
|
|
153
|
+
project_dir=$(echo "$input" | jq -r '.workspace.project_dir // .workspace.current_dir // .cwd // ""' 2>/dev/null)
|
|
154
|
+
session_id=$(echo "$input" | jq -r '.session_id // .session.id // ""' 2>/dev/null)
|
|
155
|
+
source "$HOME/.eagle-mem/scripts/statusline-em.sh"
|
|
156
|
+
eagle_mem_statusline "$project_dir" "$session_id"
|
|
157
|
+
WRAPPER
|
|
158
|
+
chmod +x "$statusline_wrapper"
|
|
159
|
+
eagle_ok "Statusline wrapper updated"
|
|
160
|
+
fi
|
|
161
|
+
|
|
146
162
|
# ─── Backfill project names ───────────────────────────────
|
|
147
163
|
|
|
148
164
|
backfilled=$(eagle_backfill_projects 2>/dev/null)
|