eagle-mem 1.5.1 → 1.6.1
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/bin/eagle-mem +1 -1
- package/db/009_drop_dead_tasks.sql +29 -0
- package/db/migrate.sh +5 -1
- package/db/schema.sql +0 -49
- package/hooks/post-tool-use.sh +4 -0
- package/hooks/session-start.sh +18 -61
- package/hooks/stop.sh +0 -8
- package/lib/common.sh +2 -0
- package/lib/db.sh +19 -50
- package/package.json +1 -1
- package/scripts/help.sh +6 -6
- package/scripts/index.sh +0 -1
- package/scripts/prune.sh +28 -25
- package/scripts/search.sh +23 -19
- package/scripts/tasks.sh +75 -171
- package/scripts/update.sh +1 -1
- package/skills/eagle-mem-tasks/SKILL.md +55 -63
package/bin/eagle-mem
CHANGED
|
@@ -29,7 +29,7 @@ case "$command" in
|
|
|
29
29
|
help|--help|-h)
|
|
30
30
|
bash "$SCRIPTS_DIR/help.sh" ;;
|
|
31
31
|
version|--version|-v|-V)
|
|
32
|
-
version=$(
|
|
32
|
+
version=$(jq -r .version "$PACKAGE_DIR/package.json" 2>/dev/null || echo "unknown")
|
|
33
33
|
echo -e " ${BOLD}Eagle Mem${RESET} v${version}"
|
|
34
34
|
;;
|
|
35
35
|
*)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
-- ═══════════════════════════════════════════════════════════
|
|
2
|
+
-- Migration 009: Drop dead tasks table
|
|
3
|
+
-- The original tasks table (from schema.sql / migration 001)
|
|
4
|
+
-- was replaced by claude_tasks (migration 007). No code
|
|
5
|
+
-- references the old table. This cleans up the vestigial
|
|
6
|
+
-- schema: 3 FTS triggers, the FTS virtual table, 3 indexes,
|
|
7
|
+
-- and the table itself.
|
|
8
|
+
--
|
|
9
|
+
-- NOTE: schema.sql (migration 001) still contains the CREATE
|
|
10
|
+
-- TABLE tasks definition. We do NOT edit applied migrations.
|
|
11
|
+
-- Fresh installs will CREATE (001) then DROP (009). Existing
|
|
12
|
+
-- installs just run 009. Verified 0 rows in production.
|
|
13
|
+
-- ═══════════════════════════════════════════════════════════
|
|
14
|
+
|
|
15
|
+
-- Drop FTS sync triggers first (they reference the tasks table)
|
|
16
|
+
DROP TRIGGER IF EXISTS tasks_ai;
|
|
17
|
+
DROP TRIGGER IF EXISTS tasks_ad;
|
|
18
|
+
DROP TRIGGER IF EXISTS tasks_au;
|
|
19
|
+
|
|
20
|
+
-- Drop the FTS virtual table
|
|
21
|
+
DROP TABLE IF EXISTS tasks_fts;
|
|
22
|
+
|
|
23
|
+
-- Drop indexes (implicit in DROP TABLE, but explicit for clarity)
|
|
24
|
+
DROP INDEX IF EXISTS idx_tasks_project;
|
|
25
|
+
DROP INDEX IF EXISTS idx_tasks_status;
|
|
26
|
+
DROP INDEX IF EXISTS idx_tasks_parent;
|
|
27
|
+
|
|
28
|
+
-- Drop the dead tasks table
|
|
29
|
+
DROP TABLE IF EXISTS tasks;
|
package/db/migrate.sh
CHANGED
|
@@ -25,7 +25,8 @@ run_migration() {
|
|
|
25
25
|
body=$(grep -v -E '^[[:space:]]*PRAGMA ' "$file")
|
|
26
26
|
|
|
27
27
|
# Set connection PRAGMAs, then run migration body + tracking insert atomically
|
|
28
|
-
|
|
28
|
+
# .bail on ensures sqlite3 stops on the first error instead of continuing
|
|
29
|
+
{ echo ".bail on"; echo "PRAGMA trusted_schema=ON;"; echo "PRAGMA foreign_keys=ON;"; echo "PRAGMA busy_timeout=5000;"; echo "BEGIN;"; echo "$body"; echo "INSERT INTO _migrations (name) VALUES ('$name');"; echo "COMMIT;"; } | sqlite3 "$DB"
|
|
29
30
|
echo " applied: $name"
|
|
30
31
|
fi
|
|
31
32
|
}
|
|
@@ -64,4 +65,7 @@ run_migration "007_claude_tasks" "$SCRIPT_DIR/007_claude_tasks.sql"
|
|
|
64
65
|
# ─── Migration 008: Summary UPSERT (unique session_id) ───
|
|
65
66
|
run_migration "008_summary_upsert" "$SCRIPT_DIR/008_summary_upsert.sql"
|
|
66
67
|
|
|
68
|
+
# ─── Migration 009: Drop dead tasks table ────────────────
|
|
69
|
+
run_migration "009_drop_dead_tasks" "$SCRIPT_DIR/009_drop_dead_tasks.sql"
|
|
70
|
+
|
|
67
71
|
echo " Eagle Mem database ready: $DB"
|
package/db/schema.sql
CHANGED
|
@@ -75,28 +75,6 @@ CREATE TABLE IF NOT EXISTS summaries (
|
|
|
75
75
|
CREATE INDEX IF NOT EXISTS idx_summaries_session ON summaries(session_id);
|
|
76
76
|
CREATE INDEX IF NOT EXISTS idx_summaries_project ON summaries(project);
|
|
77
77
|
|
|
78
|
-
-- ─── Tasks (TaskAware Compact Loop) ───────────────────────
|
|
79
|
-
-- Subtasks for multi-step work with compaction between each
|
|
80
|
-
|
|
81
|
-
CREATE TABLE IF NOT EXISTS tasks (
|
|
82
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
83
|
-
project TEXT NOT NULL,
|
|
84
|
-
session_id TEXT REFERENCES sessions(id),
|
|
85
|
-
parent_id INTEGER REFERENCES tasks(id),
|
|
86
|
-
title TEXT NOT NULL,
|
|
87
|
-
instructions TEXT,
|
|
88
|
-
context_snapshot TEXT,
|
|
89
|
-
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'active', 'done', 'blocked', 'cancelled')),
|
|
90
|
-
ordinal INTEGER NOT NULL DEFAULT 0,
|
|
91
|
-
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
92
|
-
started_at TEXT,
|
|
93
|
-
completed_at TEXT
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
CREATE INDEX IF NOT EXISTS idx_tasks_project ON tasks(project);
|
|
97
|
-
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
|
|
98
|
-
CREATE INDEX IF NOT EXISTS idx_tasks_parent ON tasks(parent_id);
|
|
99
|
-
|
|
100
78
|
-- ─── FTS5: Full-text search on summaries ───────────────────
|
|
101
79
|
|
|
102
80
|
CREATE VIRTUAL TABLE IF NOT EXISTS summaries_fts USING fts5(
|
|
@@ -127,30 +105,3 @@ CREATE TRIGGER IF NOT EXISTS summaries_au AFTER UPDATE ON summaries BEGIN
|
|
|
127
105
|
INSERT INTO summaries_fts(rowid, request, investigated, learned, completed, next_steps, notes)
|
|
128
106
|
VALUES (new.id, new.request, new.investigated, new.learned, new.completed, new.next_steps, new.notes);
|
|
129
107
|
END;
|
|
130
|
-
|
|
131
|
-
-- ─── FTS5: Full-text search on tasks ──────────────────────
|
|
132
|
-
|
|
133
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS tasks_fts USING fts5(
|
|
134
|
-
title,
|
|
135
|
-
instructions,
|
|
136
|
-
context_snapshot,
|
|
137
|
-
content='tasks',
|
|
138
|
-
content_rowid='id'
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
CREATE TRIGGER IF NOT EXISTS tasks_ai AFTER INSERT ON tasks BEGIN
|
|
142
|
-
INSERT INTO tasks_fts(rowid, title, instructions, context_snapshot)
|
|
143
|
-
VALUES (new.id, new.title, new.instructions, new.context_snapshot);
|
|
144
|
-
END;
|
|
145
|
-
|
|
146
|
-
CREATE TRIGGER IF NOT EXISTS tasks_ad AFTER DELETE ON tasks BEGIN
|
|
147
|
-
INSERT INTO tasks_fts(tasks_fts, rowid, title, instructions, context_snapshot)
|
|
148
|
-
VALUES ('delete', old.id, old.title, old.instructions, old.context_snapshot);
|
|
149
|
-
END;
|
|
150
|
-
|
|
151
|
-
CREATE TRIGGER IF NOT EXISTS tasks_au AFTER UPDATE ON tasks BEGIN
|
|
152
|
-
INSERT INTO tasks_fts(tasks_fts, rowid, title, instructions, context_snapshot)
|
|
153
|
-
VALUES ('delete', old.id, old.title, old.instructions, old.context_snapshot);
|
|
154
|
-
INSERT INTO tasks_fts(rowid, title, instructions, context_snapshot)
|
|
155
|
-
VALUES (new.id, new.title, new.instructions, new.context_snapshot);
|
|
156
|
-
END;
|
package/hooks/post-tool-use.sh
CHANGED
|
@@ -83,7 +83,11 @@ esac
|
|
|
83
83
|
case "$tool_name" in
|
|
84
84
|
Write|Edit)
|
|
85
85
|
if [ -n "$fp" ]; then
|
|
86
|
+
# Reject path traversal: bash case `*` matches `/`, so
|
|
87
|
+
# patterns like projects/*/memory/*.md would match paths
|
|
88
|
+
# containing /../ segments. Block any path with `..` first.
|
|
86
89
|
case "$fp" in
|
|
90
|
+
*..*) ;; # path traversal — skip
|
|
87
91
|
"$HOME/.claude/projects"/*/memory/*.md)
|
|
88
92
|
mem_base=$(basename "$fp")
|
|
89
93
|
if [ "$mem_base" != "MEMORY.md" ] && [ -f "$fp" ]; then
|
package/hooks/session-start.sh
CHANGED
|
@@ -31,8 +31,10 @@ eagle_log "INFO" "SessionStart: session=$session_id project=$project source=$sou
|
|
|
31
31
|
eagle_upsert_session "$session_id" "$project" "$cwd" "$model" "$source_type"
|
|
32
32
|
|
|
33
33
|
# ─── Sweep stuck sessions (older than 4 hours) ─────────────
|
|
34
|
+
# Exclude the current session — it may be a resumed session older than 4h
|
|
34
35
|
eagle_db "UPDATE sessions SET status = 'abandoned'
|
|
35
36
|
WHERE status = 'active'
|
|
37
|
+
AND id != '$(eagle_sql_escape "$session_id")'
|
|
36
38
|
AND started_at < strftime('%Y-%m-%dT%H:%M:%fZ', 'now', '-4 hours');"
|
|
37
39
|
|
|
38
40
|
# ─── Build context injection ────────────────────────────────
|
|
@@ -80,57 +82,6 @@ Next steps: $next_steps"
|
|
|
80
82
|
"
|
|
81
83
|
fi
|
|
82
84
|
|
|
83
|
-
# Pending tasks from TaskAware loop
|
|
84
|
-
pending_tasks=$(eagle_get_pending_tasks "$project")
|
|
85
|
-
|
|
86
|
-
if [ -n "$pending_tasks" ]; then
|
|
87
|
-
context+="
|
|
88
|
-
=== EAGLE MEM — Tasks ===
|
|
89
|
-
Pending tasks for '$project':
|
|
90
|
-
"
|
|
91
|
-
first_pending=""
|
|
92
|
-
while IFS='|' read -r tid title instructions status _ordinal; do
|
|
93
|
-
[ -z "$tid" ] && continue
|
|
94
|
-
local_marker=""
|
|
95
|
-
if [ "$status" = "active" ]; then
|
|
96
|
-
local_marker=" [ACTIVE]"
|
|
97
|
-
elif [ -z "$first_pending" ]; then
|
|
98
|
-
local_marker=" [NEXT]"
|
|
99
|
-
first_pending="$tid"
|
|
100
|
-
fi
|
|
101
|
-
context+=" $tid. $title$local_marker"
|
|
102
|
-
[ -n "$instructions" ] && context+=" — $instructions"
|
|
103
|
-
context+="
|
|
104
|
-
"
|
|
105
|
-
done <<< "$pending_tasks"
|
|
106
|
-
|
|
107
|
-
# Load context snapshot for the active/next task
|
|
108
|
-
active_task=$(eagle_get_active_task "$project")
|
|
109
|
-
|
|
110
|
-
if [ -z "$active_task" ] && [ -n "$first_pending" ]; then
|
|
111
|
-
# Auto-activate the next pending task (atomic to prevent races
|
|
112
|
-
# when multiple sessions start simultaneously)
|
|
113
|
-
eagle_db "UPDATE tasks SET status = 'active', started_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
114
|
-
WHERE id = $first_pending
|
|
115
|
-
AND status = 'pending'
|
|
116
|
-
AND NOT EXISTS (SELECT 1 FROM tasks WHERE project = '$(eagle_sql_escape "$project")' AND status = 'active');"
|
|
117
|
-
active_task=$(eagle_get_active_task "$project")
|
|
118
|
-
fi
|
|
119
|
-
|
|
120
|
-
if [ -n "$active_task" ]; then
|
|
121
|
-
IFS='|' read -r atid atitle ainstructions asnapshot <<< "$active_task"
|
|
122
|
-
context+="
|
|
123
|
-
Current task (#$atid): $atitle
|
|
124
|
-
"
|
|
125
|
-
[ -n "$ainstructions" ] && context+="Instructions: $ainstructions
|
|
126
|
-
"
|
|
127
|
-
[ -n "$asnapshot" ] && context+="Context: $asnapshot
|
|
128
|
-
"
|
|
129
|
-
context+="When done, tell the user to run /compact so Eagle Mem can save progress and load the next task.
|
|
130
|
-
"
|
|
131
|
-
fi
|
|
132
|
-
fi
|
|
133
|
-
|
|
134
85
|
# ─── Mirrored Claude memories ──────────────────────────────
|
|
135
86
|
|
|
136
87
|
memories=$(eagle_list_claude_memories "$project" 5)
|
|
@@ -161,21 +112,28 @@ Recent plans for '$project':
|
|
|
161
112
|
done <<< "$plans"
|
|
162
113
|
fi
|
|
163
114
|
|
|
164
|
-
# ───
|
|
115
|
+
# ─── Claude Code tasks ───────────────────────────────────
|
|
165
116
|
|
|
166
|
-
synced_tasks=$(eagle_db "SELECT subject, status FROM claude_tasks
|
|
117
|
+
synced_tasks=$(eagle_db "SELECT subject, status, blocked_by FROM claude_tasks
|
|
167
118
|
WHERE project = '$(eagle_sql_escape "$project")'
|
|
168
119
|
AND status IN ('in_progress', 'pending')
|
|
169
|
-
AND updated_at >
|
|
170
|
-
ORDER BY
|
|
120
|
+
AND updated_at > strftime('%Y-%m-%dT%H:%M:%fZ', 'now', '-7 days')
|
|
121
|
+
ORDER BY
|
|
122
|
+
CASE status WHEN 'in_progress' THEN 0 ELSE 1 END,
|
|
123
|
+
updated_at DESC
|
|
124
|
+
LIMIT 10;")
|
|
171
125
|
if [ -n "$synced_tasks" ]; then
|
|
172
126
|
context+="
|
|
173
|
-
=== EAGLE MEM —
|
|
174
|
-
|
|
127
|
+
=== EAGLE MEM — Tasks ===
|
|
128
|
+
Tasks for '$project':
|
|
175
129
|
"
|
|
176
|
-
while IFS='|' read -r tsubject tstatus; do
|
|
130
|
+
while IFS='|' read -r tsubject tstatus tblocked; do
|
|
177
131
|
[ -z "$tsubject" ] && continue
|
|
178
|
-
|
|
132
|
+
local_marker=""
|
|
133
|
+
if [ "$tblocked" != "[]" ] && [ -n "$tblocked" ]; then
|
|
134
|
+
local_marker=" (blocked)"
|
|
135
|
+
fi
|
|
136
|
+
context+=" - [$tstatus] $tsubject$local_marker
|
|
179
137
|
"
|
|
180
138
|
done <<< "$synced_tasks"
|
|
181
139
|
fi
|
|
@@ -188,8 +146,7 @@ You have persistent memory powered by Eagle Mem. When you recall context from a
|
|
|
188
146
|
IMPORTANT: At the start of your VERY NEXT response (this fires on session start, /clear, AND context compaction — always show this block, even if you think you showed it before, because prior context may have been compressed away). Show the user what Eagle Mem loaded using this exact format:
|
|
189
147
|
|
|
190
148
|
\`\`\`
|
|
191
|
-
|
|
192
|
-
██▄ █▀█ █▄█ █▄▄ ██▄ █ ▀ █ ██▄ █ ▀ █
|
|
149
|
+
$eagle_logo
|
|
193
150
|
|
|
194
151
|
Project: <project name>
|
|
195
152
|
Sessions: N recent | Memories: N | Tasks: N pending
|
package/hooks/stop.sh
CHANGED
|
@@ -141,12 +141,4 @@ if [ -n "$request" ] || [ -n "$completed" ] || [ -n "$learned" ]; then
|
|
|
141
141
|
eagle_log "INFO" "Stop: summary saved for session=$session_id"
|
|
142
142
|
fi
|
|
143
143
|
|
|
144
|
-
# Mark active task as done only when Claude explicitly provided a summary
|
|
145
|
-
if [ -n "$summary_block" ] && [ -n "$completed" ]; then
|
|
146
|
-
completed_task_id=$(eagle_complete_active_task "$project")
|
|
147
|
-
if [ -n "$completed_task_id" ]; then
|
|
148
|
-
eagle_log "INFO" "Stop: marked task #$completed_task_id as done"
|
|
149
|
-
fi
|
|
150
|
-
fi
|
|
151
|
-
|
|
152
144
|
exit 0
|
package/lib/common.sh
CHANGED
|
@@ -46,6 +46,8 @@ eagle_fts_sanitize() {
|
|
|
46
46
|
# Claude Code session IDs are UUIDs or hex strings — reject anything else.
|
|
47
47
|
eagle_validate_session_id() {
|
|
48
48
|
local sid="$1"
|
|
49
|
+
# Length cap: Claude Code IDs are UUIDs/hex (36-64 chars). Reject oversized input.
|
|
50
|
+
[ ${#sid} -gt 128 ] && return 1
|
|
49
51
|
[[ "$sid" =~ ^[A-Za-z0-9_-]+$ ]]
|
|
50
52
|
}
|
|
51
53
|
|
package/lib/db.sh
CHANGED
|
@@ -18,7 +18,7 @@ eagle_db() {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
eagle_db_pipe() {
|
|
21
|
-
{ echo "$EAGLE_DB_SETUP"; cat; } | sqlite3 "$EAGLE_MEM_DB" 2>>"$EAGLE_MEM_LOG"
|
|
21
|
+
{ echo "$EAGLE_DB_SETUP"; echo ".bail on"; cat; } | sqlite3 "$EAGLE_MEM_DB" 2>>"$EAGLE_MEM_LOG"
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
eagle_db_json() {
|
|
@@ -45,7 +45,8 @@ eagle_upsert_session() {
|
|
|
45
45
|
ON CONFLICT(id) DO UPDATE SET
|
|
46
46
|
cwd = COALESCE(excluded.cwd, sessions.cwd),
|
|
47
47
|
model = COALESCE(excluded.model, sessions.model),
|
|
48
|
-
source = COALESCE(excluded.source, sessions.source)
|
|
48
|
+
source = COALESCE(excluded.source, sessions.source),
|
|
49
|
+
status = 'active';"
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
eagle_end_session() {
|
|
@@ -78,7 +79,7 @@ eagle_insert_summary() {
|
|
|
78
79
|
local notes; notes=$(eagle_sql_escape "${10:-}")
|
|
79
80
|
|
|
80
81
|
eagle_db_pipe <<SQL
|
|
81
|
-
INSERT
|
|
82
|
+
INSERT INTO summaries (session_id, project, request, investigated, learned, completed, next_steps, files_read, files_modified, notes)
|
|
82
83
|
VALUES (
|
|
83
84
|
'$session_id',
|
|
84
85
|
'$project',
|
|
@@ -90,7 +91,17 @@ VALUES (
|
|
|
90
91
|
'$files_read',
|
|
91
92
|
'$files_modified',
|
|
92
93
|
'$notes'
|
|
93
|
-
)
|
|
94
|
+
)
|
|
95
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
96
|
+
project = excluded.project,
|
|
97
|
+
request = COALESCE(NULLIF(excluded.request, ''), summaries.request),
|
|
98
|
+
investigated = COALESCE(NULLIF(excluded.investigated, ''), summaries.investigated),
|
|
99
|
+
learned = COALESCE(NULLIF(excluded.learned, ''), summaries.learned),
|
|
100
|
+
completed = COALESCE(NULLIF(excluded.completed, ''), summaries.completed),
|
|
101
|
+
next_steps = COALESCE(NULLIF(excluded.next_steps, ''), summaries.next_steps),
|
|
102
|
+
files_read = COALESCE(NULLIF(excluded.files_read, '[]'), summaries.files_read),
|
|
103
|
+
files_modified = COALESCE(NULLIF(excluded.files_modified, '[]'), summaries.files_modified),
|
|
104
|
+
notes = COALESCE(NULLIF(excluded.notes, ''), summaries.notes);
|
|
94
105
|
SQL
|
|
95
106
|
}
|
|
96
107
|
|
|
@@ -107,7 +118,8 @@ eagle_get_recent_summaries() {
|
|
|
107
118
|
}
|
|
108
119
|
|
|
109
120
|
eagle_search_summaries() {
|
|
110
|
-
local query; query=$(
|
|
121
|
+
local query; query=$(eagle_fts_sanitize "$1")
|
|
122
|
+
query=$(eagle_sql_escape "$query")
|
|
111
123
|
local project="${2:-}"
|
|
112
124
|
local limit; limit=$(eagle_sql_int "${3:-10}")
|
|
113
125
|
|
|
@@ -126,25 +138,6 @@ eagle_search_summaries() {
|
|
|
126
138
|
LIMIT $limit;"
|
|
127
139
|
}
|
|
128
140
|
|
|
129
|
-
eagle_get_pending_tasks() {
|
|
130
|
-
local project; project=$(eagle_sql_escape "$1")
|
|
131
|
-
|
|
132
|
-
eagle_db "SELECT id, title, instructions, status, ordinal
|
|
133
|
-
FROM tasks
|
|
134
|
-
WHERE project = '$project' AND status IN ('pending', 'active')
|
|
135
|
-
ORDER BY ordinal ASC, id ASC;"
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
eagle_get_next_task() {
|
|
139
|
-
local project; project=$(eagle_sql_escape "$1")
|
|
140
|
-
|
|
141
|
-
eagle_db "SELECT id, title, instructions, context_snapshot
|
|
142
|
-
FROM tasks
|
|
143
|
-
WHERE project = '$project' AND status = 'pending'
|
|
144
|
-
ORDER BY ordinal ASC, id ASC
|
|
145
|
-
LIMIT 1;"
|
|
146
|
-
}
|
|
147
|
-
|
|
148
141
|
eagle_observation_exists() {
|
|
149
142
|
local session_id; session_id=$(eagle_sql_escape "$1")
|
|
150
143
|
local tool_name; tool_name=$(eagle_sql_escape "$2")
|
|
@@ -174,33 +167,9 @@ eagle_get_overview() {
|
|
|
174
167
|
eagle_db "SELECT content FROM overviews WHERE project = '$project';"
|
|
175
168
|
}
|
|
176
169
|
|
|
177
|
-
eagle_activate_task() {
|
|
178
|
-
local task_id; task_id=$(eagle_sql_int "$1")
|
|
179
|
-
eagle_db "UPDATE tasks SET status = 'active', started_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = $task_id;"
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
eagle_complete_active_task() {
|
|
183
|
-
local project; project=$(eagle_sql_escape "$1")
|
|
184
|
-
local active_id
|
|
185
|
-
active_id=$(eagle_db "SELECT id FROM tasks WHERE project = '$project' AND status = 'active' LIMIT 1;")
|
|
186
|
-
if [ -n "$active_id" ]; then
|
|
187
|
-
local safe_id; safe_id=$(eagle_sql_int "$active_id")
|
|
188
|
-
eagle_db "UPDATE tasks SET status = 'done', completed_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = $safe_id;"
|
|
189
|
-
echo "$active_id"
|
|
190
|
-
fi
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
eagle_get_active_task() {
|
|
194
|
-
local project; project=$(eagle_sql_escape "$1")
|
|
195
|
-
eagle_db "SELECT id, title, instructions, context_snapshot
|
|
196
|
-
FROM tasks
|
|
197
|
-
WHERE project = '$project' AND status = 'active'
|
|
198
|
-
ORDER BY ordinal ASC, id ASC
|
|
199
|
-
LIMIT 1;"
|
|
200
|
-
}
|
|
201
|
-
|
|
202
170
|
eagle_search_code_chunks() {
|
|
203
|
-
local query; query=$(
|
|
171
|
+
local query; query=$(eagle_fts_sanitize "$1")
|
|
172
|
+
query=$(eagle_sql_escape "$query")
|
|
204
173
|
local project; project=$(eagle_sql_escape "$2")
|
|
205
174
|
local limit; limit=$(eagle_sql_int "${3:-5}")
|
|
206
175
|
|
package/package.json
CHANGED
package/scripts/help.sh
CHANGED
|
@@ -8,7 +8,7 @@ PACKAGE_DIR="$(cd "$SCRIPTS_DIR/.." && pwd)"
|
|
|
8
8
|
|
|
9
9
|
. "$SCRIPTS_DIR/style.sh"
|
|
10
10
|
|
|
11
|
-
version=$(
|
|
11
|
+
version=$(jq -r .version "$PACKAGE_DIR/package.json" 2>/dev/null || echo "unknown")
|
|
12
12
|
|
|
13
13
|
eagle_banner
|
|
14
14
|
|
|
@@ -20,7 +20,7 @@ echo -e " eagle-mem ${CYAN}<command>${RESET}"
|
|
|
20
20
|
echo ""
|
|
21
21
|
echo -e " ${BOLD}Commands:${RESET}"
|
|
22
22
|
echo -e " ${CYAN}search${RESET} Search past sessions, files, and observations"
|
|
23
|
-
echo -e " ${CYAN}tasks${RESET}
|
|
23
|
+
echo -e " ${CYAN}tasks${RESET} View mirrored Claude Code tasks"
|
|
24
24
|
echo -e " ${CYAN}overview${RESET} View or set project overview"
|
|
25
25
|
echo -e " ${CYAN}memories${RESET} Browse and search Claude Code memories, plans, and tasks"
|
|
26
26
|
echo -e " ${CYAN}scan${RESET} Analyze a project and generate an overview"
|
|
@@ -35,8 +35,8 @@ echo ""
|
|
|
35
35
|
echo -e " ${BOLD}Examples:${RESET}"
|
|
36
36
|
echo -e " ${DIM}\$${RESET} eagle-mem search \"auth bug\" ${DIM}# Search memory${RESET}"
|
|
37
37
|
echo -e " ${DIM}\$${RESET} eagle-mem search --timeline ${DIM}# Recent sessions${RESET}"
|
|
38
|
-
echo -e " ${DIM}\$${RESET} eagle-mem tasks ${DIM}#
|
|
39
|
-
echo -e " ${DIM}\$${RESET} eagle-mem tasks
|
|
38
|
+
echo -e " ${DIM}\$${RESET} eagle-mem tasks ${DIM}# View pending tasks${RESET}"
|
|
39
|
+
echo -e " ${DIM}\$${RESET} eagle-mem tasks search \"X\" ${DIM}# Search task history${RESET}"
|
|
40
40
|
echo -e " ${DIM}\$${RESET} eagle-mem overview ${DIM}# View overview${RESET}"
|
|
41
41
|
echo -e " ${DIM}\$${RESET} eagle-mem scan . ${DIM}# Scan current project${RESET}"
|
|
42
42
|
echo -e " ${DIM}\$${RESET} eagle-mem index . ${DIM}# Index source files${RESET}"
|
|
@@ -53,11 +53,11 @@ echo -e " ${DOT} Injects relevant memory at session start"
|
|
|
53
53
|
echo -e " ${DOT} Searches past sessions when you ask related questions"
|
|
54
54
|
echo -e " ${DOT} Tracks file operations across sessions"
|
|
55
55
|
echo -e " ${DOT} Mirrors Claude Code memories, plans, and tasks into FTS5-searchable storage"
|
|
56
|
-
echo -e " ${DOT}
|
|
56
|
+
echo -e " ${DOT} Uses Claude Code's native task system (TaskCreate/TaskUpdate) for multi-step work"
|
|
57
57
|
echo ""
|
|
58
58
|
echo -e " ${BOLD}Skills${RESET} ${DIM}(available inside Claude Code):${RESET}"
|
|
59
59
|
echo -e " ${CYAN}/eagle-mem-search${RESET} Search past sessions and observations"
|
|
60
|
-
echo -e " ${CYAN}/eagle-mem-tasks${RESET} Break work into
|
|
60
|
+
echo -e " ${CYAN}/eagle-mem-tasks${RESET} Break work into Claude Code tasks with dependencies"
|
|
61
61
|
echo -e " ${CYAN}/eagle-mem-overview${RESET} Generate a persistent project summary"
|
|
62
62
|
echo -e " ${CYAN}/eagle-mem-index${RESET} Index source files for code search"
|
|
63
63
|
echo -e " ${CYAN}/eagle-mem-scan${RESET} Scan and analyze a project"
|
package/scripts/index.sh
CHANGED
package/scripts/prune.sh
CHANGED
|
@@ -15,6 +15,33 @@ LIB_DIR="$SCRIPTS_DIR/../lib"
|
|
|
15
15
|
|
|
16
16
|
eagle_ensure_db
|
|
17
17
|
|
|
18
|
+
# ─── Help ────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
show_help() {
|
|
21
|
+
echo -e " ${BOLD}eagle-mem prune${RESET} — Clean up old data"
|
|
22
|
+
echo ""
|
|
23
|
+
echo -e " ${BOLD}Usage:${RESET}"
|
|
24
|
+
echo -e " eagle-mem prune ${DIM}# prune observations > 90 days${RESET}"
|
|
25
|
+
echo -e " eagle-mem prune ${CYAN}--days 30${RESET} ${DIM}# prune observations > 30 days${RESET}"
|
|
26
|
+
echo -e " eagle-mem prune ${CYAN}--dry-run${RESET} ${DIM}# show what would be pruned${RESET}"
|
|
27
|
+
echo ""
|
|
28
|
+
echo -e " ${BOLD}What gets pruned:${RESET}"
|
|
29
|
+
echo -e " ${DOT} Observations older than --days (default: 90)"
|
|
30
|
+
echo -e " ${DOT} Code chunks for files that no longer exist"
|
|
31
|
+
echo ""
|
|
32
|
+
echo -e " ${BOLD}What is preserved:${RESET}"
|
|
33
|
+
echo -e " ${DOT} All sessions and summaries (your session history)"
|
|
34
|
+
echo -e " ${DOT} All tasks"
|
|
35
|
+
echo -e " ${DOT} Project overviews"
|
|
36
|
+
echo ""
|
|
37
|
+
echo -e " ${BOLD}Options:${RESET}"
|
|
38
|
+
echo -e " ${CYAN}-d, --days${RESET} <N> Age threshold (default: 90)"
|
|
39
|
+
echo -e " ${CYAN}-p, --project${RESET} <name> Only prune for this project"
|
|
40
|
+
echo -e " ${CYAN}-n, --dry-run${RESET} Show counts without deleting"
|
|
41
|
+
echo ""
|
|
42
|
+
exit 0
|
|
43
|
+
}
|
|
44
|
+
|
|
18
45
|
# ─── Parse arguments ──────────────────────────────────────
|
|
19
46
|
|
|
20
47
|
days=90
|
|
@@ -26,30 +53,7 @@ while [ $# -gt 0 ]; do
|
|
|
26
53
|
--days|-d) days="$2"; shift 2 ;;
|
|
27
54
|
--project|-p) project="$2"; shift 2 ;;
|
|
28
55
|
--dry-run|-n) dry_run=true; shift ;;
|
|
29
|
-
--help|-h)
|
|
30
|
-
echo -e " ${BOLD}eagle-mem prune${RESET} — Clean up old data"
|
|
31
|
-
echo ""
|
|
32
|
-
echo -e " ${BOLD}Usage:${RESET}"
|
|
33
|
-
echo -e " eagle-mem prune ${DIM}# prune observations > 90 days${RESET}"
|
|
34
|
-
echo -e " eagle-mem prune ${CYAN}--days 30${RESET} ${DIM}# prune observations > 30 days${RESET}"
|
|
35
|
-
echo -e " eagle-mem prune ${CYAN}--dry-run${RESET} ${DIM}# show what would be pruned${RESET}"
|
|
36
|
-
echo ""
|
|
37
|
-
echo -e " ${BOLD}What gets pruned:${RESET}"
|
|
38
|
-
echo -e " ${DOT} Observations older than --days (default: 90)"
|
|
39
|
-
echo -e " ${DOT} Code chunks for files that no longer exist"
|
|
40
|
-
echo ""
|
|
41
|
-
echo -e " ${BOLD}What is preserved:${RESET}"
|
|
42
|
-
echo -e " ${DOT} All sessions and summaries (your session history)"
|
|
43
|
-
echo -e " ${DOT} All tasks"
|
|
44
|
-
echo -e " ${DOT} Project overviews"
|
|
45
|
-
echo ""
|
|
46
|
-
echo -e " ${BOLD}Options:${RESET}"
|
|
47
|
-
echo -e " ${CYAN}-d, --days${RESET} <N> Age threshold (default: 90)"
|
|
48
|
-
echo -e " ${CYAN}-p, --project${RESET} <name> Only prune for this project"
|
|
49
|
-
echo -e " ${CYAN}-n, --dry-run${RESET} Show counts without deleting"
|
|
50
|
-
echo ""
|
|
51
|
-
exit 0
|
|
52
|
-
;;
|
|
56
|
+
--help|-h) show_help ;;
|
|
53
57
|
*)
|
|
54
58
|
eagle_err "Unknown option: $1"
|
|
55
59
|
exit 1
|
|
@@ -109,7 +113,6 @@ if [ -n "$projects" ]; then
|
|
|
109
113
|
|
|
110
114
|
if [ -n "$proj_cwd" ] && [ -d "$proj_cwd" ]; then
|
|
111
115
|
if [ "$dry_run" = true ]; then
|
|
112
|
-
orphan_count=$(eagle_db "SELECT COUNT(DISTINCT file_path) FROM code_chunks WHERE project = '$(eagle_sql_escape "$proj")';")
|
|
113
116
|
# Count files that no longer exist
|
|
114
117
|
orphans=0
|
|
115
118
|
paths=$(eagle_db "SELECT DISTINCT file_path FROM code_chunks WHERE project = '$(eagle_sql_escape "$proj")';")
|
package/scripts/search.sh
CHANGED
|
@@ -14,6 +14,27 @@ LIB_DIR="$SCRIPTS_DIR/../lib"
|
|
|
14
14
|
|
|
15
15
|
eagle_ensure_db
|
|
16
16
|
|
|
17
|
+
# ─── Help ────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
show_help() {
|
|
20
|
+
echo -e " ${BOLD}eagle-mem search${RESET} — Search persistent memory"
|
|
21
|
+
echo ""
|
|
22
|
+
echo -e " ${BOLD}Usage:${RESET}"
|
|
23
|
+
echo -e " eagle-mem search ${CYAN}<query>${RESET} ${DIM}# keyword search${RESET}"
|
|
24
|
+
echo -e " eagle-mem search ${CYAN}--timeline${RESET} ${DIM}# recent sessions${RESET}"
|
|
25
|
+
echo -e " eagle-mem search ${CYAN}--session <id>${RESET} ${DIM}# session details${RESET}"
|
|
26
|
+
echo -e " eagle-mem search ${CYAN}--files${RESET} ${DIM}# frequently modified files${RESET}"
|
|
27
|
+
echo -e " eagle-mem search ${CYAN}--stats${RESET} ${DIM}# project statistics${RESET}"
|
|
28
|
+
echo ""
|
|
29
|
+
echo -e " ${BOLD}Options:${RESET}"
|
|
30
|
+
echo -e " ${CYAN}-p, --project${RESET} <name> Project name (default: current dir)"
|
|
31
|
+
echo -e " ${CYAN}-n, --limit${RESET} <N> Max results (default: 10)"
|
|
32
|
+
echo -e " ${CYAN}-a, --all${RESET} Search across all projects"
|
|
33
|
+
echo -e " ${CYAN}-j, --json${RESET} Output as JSON"
|
|
34
|
+
echo ""
|
|
35
|
+
exit 0
|
|
36
|
+
}
|
|
37
|
+
|
|
17
38
|
# ─── Parse arguments ──────────────────────────────────────
|
|
18
39
|
|
|
19
40
|
mode="keyword"
|
|
@@ -34,24 +55,7 @@ while [ $# -gt 0 ]; do
|
|
|
34
55
|
--limit|-n) limit="$2"; shift 2 ;;
|
|
35
56
|
--all|-a) cross_project=true; shift ;;
|
|
36
57
|
--json|-j) json_output=true; shift ;;
|
|
37
|
-
--help|-h)
|
|
38
|
-
echo -e " ${BOLD}eagle-mem search${RESET} — Search persistent memory"
|
|
39
|
-
echo ""
|
|
40
|
-
echo -e " ${BOLD}Usage:${RESET}"
|
|
41
|
-
echo -e " eagle-mem search ${CYAN}<query>${RESET} ${DIM}# keyword search${RESET}"
|
|
42
|
-
echo -e " eagle-mem search ${CYAN}--timeline${RESET} ${DIM}# recent sessions${RESET}"
|
|
43
|
-
echo -e " eagle-mem search ${CYAN}--session <id>${RESET} ${DIM}# session details${RESET}"
|
|
44
|
-
echo -e " eagle-mem search ${CYAN}--files${RESET} ${DIM}# frequently modified files${RESET}"
|
|
45
|
-
echo -e " eagle-mem search ${CYAN}--stats${RESET} ${DIM}# project statistics${RESET}"
|
|
46
|
-
echo ""
|
|
47
|
-
echo -e " ${BOLD}Options:${RESET}"
|
|
48
|
-
echo -e " ${CYAN}-p, --project${RESET} <name> Project name (default: current dir)"
|
|
49
|
-
echo -e " ${CYAN}-n, --limit${RESET} <N> Max results (default: 10)"
|
|
50
|
-
echo -e " ${CYAN}-a, --all${RESET} Search across all projects"
|
|
51
|
-
echo -e " ${CYAN}-j, --json${RESET} Output as JSON"
|
|
52
|
-
echo ""
|
|
53
|
-
exit 0
|
|
54
|
-
;;
|
|
58
|
+
--help|-h) show_help ;;
|
|
55
59
|
-*)
|
|
56
60
|
eagle_err "Unknown option: $1"
|
|
57
61
|
exit 1
|
|
@@ -249,7 +253,7 @@ search_stats() {
|
|
|
249
253
|
sessions=$(eagle_db "SELECT COUNT(*) FROM sessions WHERE project = '$p';")
|
|
250
254
|
summaries=$(eagle_db "SELECT COUNT(*) FROM summaries WHERE project = '$p';")
|
|
251
255
|
observations=$(eagle_db "SELECT COUNT(*) FROM observations o JOIN sessions s ON s.id = o.session_id WHERE s.project = '$p';")
|
|
252
|
-
tasks=$(eagle_db "SELECT COUNT(*) FROM
|
|
256
|
+
tasks=$(eagle_db "SELECT COUNT(*) FROM claude_tasks WHERE project = '$p';")
|
|
253
257
|
local chunks
|
|
254
258
|
chunks=$(eagle_db "SELECT COUNT(*) FROM code_chunks WHERE project = '$p';")
|
|
255
259
|
|
package/scripts/tasks.sh
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# ═══════════════════════════════════════════════════════════
|
|
3
|
-
# Eagle Mem — Tasks
|
|
4
|
-
#
|
|
3
|
+
# Eagle Mem — Tasks (read-only viewer of Claude Code tasks)
|
|
4
|
+
# Claude Code manages task state via TaskCreate/TaskUpdate;
|
|
5
|
+
# Eagle Mem mirrors it and displays it here.
|
|
5
6
|
# ═══════════════════════════════════════════════════════════
|
|
6
7
|
set -euo pipefail
|
|
7
8
|
|
|
@@ -22,28 +23,26 @@ shift 2>/dev/null || true
|
|
|
22
23
|
project=""
|
|
23
24
|
json_output=false
|
|
24
25
|
|
|
25
|
-
# Extract global options from remaining args
|
|
26
|
-
args=()
|
|
27
|
-
|
|
28
26
|
show_help() {
|
|
29
|
-
echo -e " ${BOLD}eagle-mem tasks${RESET} —
|
|
27
|
+
echo -e " ${BOLD}eagle-mem tasks${RESET} — View mirrored Claude Code tasks"
|
|
30
28
|
echo ""
|
|
31
29
|
echo -e " ${BOLD}Usage:${RESET}"
|
|
32
|
-
echo -e " eagle-mem tasks
|
|
33
|
-
echo -e " eagle-mem tasks ${CYAN}list${RESET}
|
|
34
|
-
echo -e " eagle-mem tasks ${CYAN}
|
|
35
|
-
echo -e " eagle-mem tasks ${CYAN}
|
|
36
|
-
echo -e " eagle-mem tasks ${CYAN}block${RESET} <id> ${DIM}# mark task blocked${RESET}"
|
|
37
|
-
echo -e " eagle-mem tasks ${CYAN}context${RESET} <id> <snapshot> ${DIM}# set task context${RESET}"
|
|
38
|
-
echo -e " eagle-mem tasks ${CYAN}clear${RESET} ${DIM}# remove all done tasks${RESET}"
|
|
30
|
+
echo -e " eagle-mem tasks ${DIM}# list pending/in-progress tasks${RESET}"
|
|
31
|
+
echo -e " eagle-mem tasks ${CYAN}list${RESET} ${DIM}# list all tasks${RESET}"
|
|
32
|
+
echo -e " eagle-mem tasks ${CYAN}completed${RESET} ${DIM}# list completed tasks${RESET}"
|
|
33
|
+
echo -e " eagle-mem tasks ${CYAN}search${RESET} <query> ${DIM}# search tasks by keyword${RESET}"
|
|
39
34
|
echo ""
|
|
40
35
|
echo -e " ${BOLD}Options:${RESET}"
|
|
41
36
|
echo -e " ${CYAN}-p, --project${RESET} <name> Project name (default: current dir)"
|
|
42
37
|
echo -e " ${CYAN}-j, --json${RESET} Output as JSON"
|
|
43
38
|
echo ""
|
|
39
|
+
echo -e " ${DIM}Tasks are managed by Claude Code (TaskCreate/TaskUpdate).${RESET}"
|
|
40
|
+
echo -e " ${DIM}Eagle Mem automatically mirrors them for cross-session recall.${RESET}"
|
|
41
|
+
echo ""
|
|
44
42
|
exit 0
|
|
45
43
|
}
|
|
46
44
|
|
|
45
|
+
args=()
|
|
47
46
|
while [ $# -gt 0 ]; do
|
|
48
47
|
case "$1" in
|
|
49
48
|
--project|-p) project="$2"; shift 2 ;;
|
|
@@ -63,24 +62,28 @@ tasks_list() {
|
|
|
63
62
|
|
|
64
63
|
local where_status=""
|
|
65
64
|
case "$filter" in
|
|
66
|
-
pending) where_status="AND status IN ('pending', '
|
|
67
|
-
|
|
68
|
-
all)
|
|
65
|
+
pending) where_status="AND status IN ('pending', 'in_progress')" ;;
|
|
66
|
+
completed) where_status="AND status = 'completed'" ;;
|
|
67
|
+
all) where_status="" ;;
|
|
69
68
|
esac
|
|
70
69
|
|
|
71
70
|
if [ "$json_output" = true ]; then
|
|
72
|
-
eagle_db_json "SELECT
|
|
73
|
-
FROM
|
|
71
|
+
eagle_db_json "SELECT source_task_id, subject, description, status, blocks, blocked_by, updated_at
|
|
72
|
+
FROM claude_tasks
|
|
74
73
|
WHERE project = '$project_sql' $where_status
|
|
75
|
-
ORDER BY
|
|
74
|
+
ORDER BY updated_at DESC
|
|
75
|
+
LIMIT 20;"
|
|
76
76
|
return
|
|
77
77
|
fi
|
|
78
78
|
|
|
79
79
|
local results
|
|
80
|
-
results=$(eagle_db "SELECT
|
|
81
|
-
FROM
|
|
80
|
+
results=$(eagle_db "SELECT source_task_id, subject, status, blocked_by, description
|
|
81
|
+
FROM claude_tasks
|
|
82
82
|
WHERE project = '$project_sql' $where_status
|
|
83
|
-
ORDER BY
|
|
83
|
+
ORDER BY
|
|
84
|
+
CASE status WHEN 'in_progress' THEN 0 WHEN 'pending' THEN 1 ELSE 2 END,
|
|
85
|
+
updated_at DESC
|
|
86
|
+
LIMIT 20;")
|
|
84
87
|
|
|
85
88
|
if [ -z "$results" ]; then
|
|
86
89
|
eagle_dim "No tasks for project '$project'"
|
|
@@ -92,170 +95,74 @@ tasks_list() {
|
|
|
92
95
|
echo -e " ${DIM}─────────────────────────────────────${RESET}"
|
|
93
96
|
echo ""
|
|
94
97
|
|
|
95
|
-
while IFS='|' read -r tid
|
|
98
|
+
while IFS='|' read -r tid subject status blocked_by desc; do
|
|
96
99
|
[ -z "$tid" ] && continue
|
|
97
100
|
local icon marker
|
|
98
101
|
case "$status" in
|
|
99
|
-
|
|
100
|
-
pending)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
*)
|
|
102
|
+
in_progress) icon="${CYAN}>${RESET}"; marker=" ${CYAN}[in_progress]${RESET}" ;;
|
|
103
|
+
pending) icon="${DIM}o${RESET}"; marker="" ;;
|
|
104
|
+
completed) icon="${GREEN}+${RESET}"; marker=" ${DIM}[completed]${RESET}" ;;
|
|
105
|
+
deleted) icon="${RED}x${RESET}"; marker=" ${RED}[deleted]${RESET}" ;;
|
|
106
|
+
*) icon="$DOT"; marker="" ;;
|
|
104
107
|
esac
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
if [ "$blocked_by" != "[]" ] && [ -n "$blocked_by" ]; then
|
|
109
|
+
marker+=" ${DIM}(blocked)${RESET}"
|
|
110
|
+
fi
|
|
111
|
+
echo -e " ${icon} ${BOLD}$tid${RESET} $subject$marker"
|
|
112
|
+
[ -n "$desc" ] && echo -e " ${DIM}$(echo "$desc" | cut -c1-80)${RESET}"
|
|
107
113
|
done <<< "$results"
|
|
108
114
|
echo ""
|
|
109
115
|
}
|
|
110
116
|
|
|
111
|
-
# ───
|
|
112
|
-
|
|
113
|
-
tasks_add() {
|
|
114
|
-
local title="${args[0]:-}"
|
|
115
|
-
local instructions="${args[1]:-}"
|
|
116
|
-
|
|
117
|
-
if [ -z "$title" ]; then
|
|
118
|
-
eagle_err "Usage: eagle-mem tasks add <title> [instructions]"
|
|
119
|
-
exit 1
|
|
120
|
-
fi
|
|
121
|
-
|
|
122
|
-
local title_sql; title_sql=$(eagle_sql_escape "$title")
|
|
123
|
-
local instr_sql; instr_sql=$(eagle_sql_escape "$instructions")
|
|
124
|
-
|
|
125
|
-
local max_ord
|
|
126
|
-
max_ord=$(eagle_db "SELECT COALESCE(MAX(ordinal), 0) FROM tasks WHERE project = '$project_sql';")
|
|
127
|
-
local next_ord=$((max_ord + 1))
|
|
128
|
-
|
|
129
|
-
local new_id
|
|
130
|
-
new_id=$(eagle_db "INSERT INTO tasks (project, title, instructions, ordinal)
|
|
131
|
-
VALUES ('$project_sql', '$title_sql', '$instr_sql', $next_ord);
|
|
132
|
-
SELECT last_insert_rowid();")
|
|
133
|
-
|
|
134
|
-
if [ "$json_output" = true ]; then
|
|
135
|
-
jq -nc --arg id "$new_id" --arg title "$title" --argjson ord "$next_ord" \
|
|
136
|
-
'{id: ($id | tonumber), title: $title, ordinal: $ord}'
|
|
137
|
-
return
|
|
138
|
-
fi
|
|
139
|
-
|
|
140
|
-
eagle_ok "Task #$new_id added: $title"
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
# ─── Done ─────────────────────────────────────────────────
|
|
144
|
-
|
|
145
|
-
tasks_done() {
|
|
146
|
-
local task_id="${args[0]:-}"
|
|
117
|
+
# ─── Search tasks ─────────────────────────────────────────
|
|
147
118
|
|
|
148
|
-
|
|
149
|
-
|
|
119
|
+
tasks_search() {
|
|
120
|
+
local query="${args[0]:-}"
|
|
121
|
+
if [ -z "$query" ]; then
|
|
122
|
+
eagle_err "Usage: eagle-mem tasks search <query>"
|
|
150
123
|
exit 1
|
|
151
124
|
fi
|
|
152
125
|
|
|
153
|
-
if ! [[ "$task_id" =~ ^[0-9]+$ ]]; then
|
|
154
|
-
eagle_err "Task ID must be a number, got: $task_id"
|
|
155
|
-
exit 1
|
|
156
|
-
fi
|
|
157
|
-
|
|
158
|
-
local changed
|
|
159
|
-
changed=$(eagle_db "UPDATE tasks SET status = 'done', completed_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
160
|
-
WHERE id = $task_id AND project = '$project_sql';
|
|
161
|
-
SELECT changes();")
|
|
162
|
-
|
|
163
|
-
if [ "${changed:-0}" -eq 0 ]; then
|
|
164
|
-
eagle_err "Task #$task_id not found in project '$project'"
|
|
165
|
-
exit 1
|
|
166
|
-
fi
|
|
167
|
-
|
|
168
|
-
local title
|
|
169
|
-
title=$(eagle_db "SELECT title FROM tasks WHERE id = $task_id AND project = '$project_sql';")
|
|
170
|
-
|
|
171
126
|
if [ "$json_output" = true ]; then
|
|
172
|
-
|
|
127
|
+
local query_sql; query_sql=$(eagle_fts_sanitize "$query")
|
|
128
|
+
query_sql=$(eagle_sql_escape "$query_sql")
|
|
129
|
+
eagle_db_json "SELECT t.source_task_id, t.subject, t.status, t.description, t.updated_at
|
|
130
|
+
FROM claude_tasks t
|
|
131
|
+
JOIN claude_tasks_fts f ON f.rowid = t.id
|
|
132
|
+
WHERE claude_tasks_fts MATCH '$query_sql'
|
|
133
|
+
AND t.project = '$project_sql'
|
|
134
|
+
ORDER BY rank
|
|
135
|
+
LIMIT 10;"
|
|
173
136
|
return
|
|
174
137
|
fi
|
|
175
138
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
exit 1
|
|
187
|
-
fi
|
|
188
|
-
|
|
189
|
-
if ! [[ "$task_id" =~ ^[0-9]+$ ]]; then
|
|
190
|
-
eagle_err "Task ID must be a number, got: $task_id"
|
|
191
|
-
exit 1
|
|
192
|
-
fi
|
|
193
|
-
|
|
194
|
-
local changed
|
|
195
|
-
changed=$(eagle_db "UPDATE tasks SET status = 'blocked' WHERE id = $task_id AND project = '$project_sql';
|
|
196
|
-
SELECT changes();")
|
|
197
|
-
|
|
198
|
-
if [ "${changed:-0}" -eq 0 ]; then
|
|
199
|
-
eagle_err "Task #$task_id not found in project '$project'"
|
|
200
|
-
exit 1
|
|
201
|
-
fi
|
|
202
|
-
|
|
203
|
-
if [ "$json_output" = true ]; then
|
|
204
|
-
jq -nc --arg id "$task_id" '{id: ($id | tonumber), status: "blocked"}'
|
|
205
|
-
return
|
|
206
|
-
fi
|
|
207
|
-
|
|
208
|
-
eagle_ok "Task #$task_id blocked"
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
# ─── Context snapshot ─────────────────────────────────────
|
|
212
|
-
|
|
213
|
-
tasks_context() {
|
|
214
|
-
local task_id="${args[0]:-}"
|
|
215
|
-
local snapshot="${args[1]:-}"
|
|
216
|
-
|
|
217
|
-
if [ -z "$task_id" ] || [ -z "$snapshot" ]; then
|
|
218
|
-
eagle_err "Usage: eagle-mem tasks context <id> <snapshot>"
|
|
219
|
-
exit 1
|
|
220
|
-
fi
|
|
221
|
-
|
|
222
|
-
if ! [[ "$task_id" =~ ^[0-9]+$ ]]; then
|
|
223
|
-
eagle_err "Task ID must be a number, got: $task_id"
|
|
224
|
-
exit 1
|
|
225
|
-
fi
|
|
226
|
-
|
|
227
|
-
local snap_sql; snap_sql=$(eagle_sql_escape "$snapshot")
|
|
228
|
-
local changed
|
|
229
|
-
changed=$(eagle_db "UPDATE tasks SET context_snapshot = '$snap_sql' WHERE id = $task_id AND project = '$project_sql';
|
|
230
|
-
SELECT changes();")
|
|
231
|
-
|
|
232
|
-
if [ "${changed:-0}" -eq 0 ]; then
|
|
233
|
-
eagle_err "Task #$task_id not found in project '$project'"
|
|
234
|
-
exit 1
|
|
235
|
-
fi
|
|
139
|
+
local results
|
|
140
|
+
local query_sql; query_sql=$(eagle_fts_sanitize "$query")
|
|
141
|
+
query_sql=$(eagle_sql_escape "$query_sql")
|
|
142
|
+
results=$(eagle_db "SELECT t.source_task_id, t.subject, t.status, t.description
|
|
143
|
+
FROM claude_tasks t
|
|
144
|
+
JOIN claude_tasks_fts f ON f.rowid = t.id
|
|
145
|
+
WHERE claude_tasks_fts MATCH '$query_sql'
|
|
146
|
+
AND t.project = '$project_sql'
|
|
147
|
+
ORDER BY rank
|
|
148
|
+
LIMIT 10;")
|
|
236
149
|
|
|
237
|
-
if [ "$
|
|
238
|
-
|
|
150
|
+
if [ -z "$results" ]; then
|
|
151
|
+
eagle_dim "No tasks matching '$query'"
|
|
239
152
|
return
|
|
240
153
|
fi
|
|
241
154
|
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
tasks_clear() {
|
|
248
|
-
local count
|
|
249
|
-
count=$(eagle_db "SELECT COUNT(*) FROM tasks WHERE project = '$project_sql' AND status = 'done';")
|
|
250
|
-
|
|
251
|
-
eagle_db "DELETE FROM tasks WHERE project = '$project_sql' AND status = 'done';"
|
|
252
|
-
|
|
253
|
-
if [ "$json_output" = true ]; then
|
|
254
|
-
jq -nc --argjson cleared "${count:-0}" '{cleared: $cleared}'
|
|
255
|
-
return
|
|
256
|
-
fi
|
|
155
|
+
echo ""
|
|
156
|
+
echo -e " ${BOLD}Task search:${RESET} $query"
|
|
157
|
+
echo -e " ${DIM}─────────────────────────────────────${RESET}"
|
|
158
|
+
echo ""
|
|
257
159
|
|
|
258
|
-
|
|
160
|
+
while IFS='|' read -r tid subject status desc; do
|
|
161
|
+
[ -z "$tid" ] && continue
|
|
162
|
+
echo -e " ${BOLD}$tid${RESET} [$status] $subject"
|
|
163
|
+
[ -n "$desc" ] && echo -e " ${DIM}$(echo "$desc" | cut -c1-80)${RESET}"
|
|
164
|
+
done <<< "$results"
|
|
165
|
+
echo ""
|
|
259
166
|
}
|
|
260
167
|
|
|
261
168
|
# ─── Dispatch ─────────────────────────────────────────────
|
|
@@ -263,12 +170,9 @@ tasks_clear() {
|
|
|
263
170
|
case "$action" in
|
|
264
171
|
list) tasks_list "all" ;;
|
|
265
172
|
pending) tasks_list "pending" ;;
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
context) tasks_context ;;
|
|
270
|
-
clear) tasks_clear ;;
|
|
271
|
-
--help|-h) show_help ;;
|
|
173
|
+
completed) tasks_list "completed" ;;
|
|
174
|
+
search) tasks_search ;;
|
|
175
|
+
--help|-h) show_help ;;
|
|
272
176
|
*)
|
|
273
177
|
eagle_err "Unknown action: $action"
|
|
274
178
|
eagle_dim " Run 'eagle-mem tasks --help' for options"
|
package/scripts/update.sh
CHANGED
|
@@ -117,5 +117,5 @@ fi
|
|
|
117
117
|
|
|
118
118
|
# ─── Summary ───────────────────────────────────────────────
|
|
119
119
|
|
|
120
|
-
version=$(
|
|
120
|
+
version=$(jq -r .version "$PACKAGE_DIR/package.json" 2>/dev/null || echo "unknown")
|
|
121
121
|
eagle_footer "Eagle Mem updated to v${version}."
|
|
@@ -1,100 +1,92 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: eagle-mem-tasks
|
|
3
3
|
description: >
|
|
4
|
-
TaskAware Compact Loop — break complex work into
|
|
5
|
-
between each. Use when: 'eagle tasks', 'break this into tasks', 'create task plan',
|
|
6
|
-
'task loop', 'compact loop', 'eagle mem tasks'. Uses
|
|
4
|
+
TaskAware Compact Loop — break complex work into Claude Code tasks with dependencies,
|
|
5
|
+
compact between each. Use when: 'eagle tasks', 'break this into tasks', 'create task plan',
|
|
6
|
+
'task loop', 'compact loop', 'eagle mem tasks'. Uses Claude Code's native TaskCreate/TaskUpdate.
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Eagle Mem — TaskAware Compact Loop
|
|
10
10
|
|
|
11
|
-
Break complex work into subtasks
|
|
11
|
+
Break complex work into subtasks using Claude Code's native task system. Execute one task at a time, compact between each, and let Eagle Mem re-inject task state for the next task.
|
|
12
12
|
|
|
13
13
|
## How it works
|
|
14
14
|
|
|
15
15
|
1. **Plan**: Break the user's request into ordered subtasks
|
|
16
|
-
2. **
|
|
17
|
-
3. **Execute**: Work on the current task (
|
|
18
|
-
4. **
|
|
19
|
-
5. **
|
|
20
|
-
6. **
|
|
16
|
+
2. **Create**: Add each subtask via `TaskCreate` with dependencies via `addBlockedBy`
|
|
17
|
+
3. **Execute**: Work on the current task (mark `in_progress` with `TaskUpdate`)
|
|
18
|
+
4. **Complete**: Mark done with `TaskUpdate(completed)`, emit `<eagle-summary>`
|
|
19
|
+
5. **Compact**: Tell the user to run `/compact`
|
|
20
|
+
6. **Resume**: After compact, SessionStart re-injects memory + loads task state from Eagle Mem's mirror
|
|
21
|
+
7. **Repeat**: Until all tasks are done
|
|
21
22
|
|
|
22
|
-
##
|
|
23
|
+
## Creating tasks
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
Use Claude Code's `TaskCreate` tool. Each task gets `pending` status automatically.
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
eagle-mem tasks
|
|
28
|
-
eagle-mem tasks list
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
### Add tasks
|
|
32
|
-
|
|
33
|
-
When the user invokes `/eagle-mem-tasks` or asks to break work into tasks:
|
|
34
|
-
|
|
35
|
-
1. Analyze the request and break it into 3-8 focused subtasks
|
|
36
|
-
2. Each task should be completable in one context window
|
|
37
|
-
3. Add tasks using the CLI:
|
|
27
|
+
For tasks that depend on earlier work, use `addBlockedBy` in `TaskUpdate`:
|
|
38
28
|
|
|
39
|
-
```bash
|
|
40
|
-
eagle-mem tasks add "Set up project structure" "Install deps, create folders, init config"
|
|
41
|
-
eagle-mem tasks add "Implement auth middleware" "JWT validation, role checks, error responses"
|
|
42
|
-
eagle-mem tasks add "Build CRUD endpoints" "Users and posts REST API with validation"
|
|
43
29
|
```
|
|
30
|
+
TaskCreate({ subject: "Set up project structure", description: "Install deps, create folders, init config" })
|
|
31
|
+
TaskCreate({ subject: "Implement auth middleware", description: "JWT validation, role checks, error responses" })
|
|
32
|
+
TaskCreate({ subject: "Build CRUD endpoints", description: "Users and posts REST API with validation" })
|
|
44
33
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
### Complete a task
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
eagle-mem tasks done <id>
|
|
34
|
+
// Then set dependencies:
|
|
35
|
+
TaskUpdate({ taskId: "3", addBlockedBy: ["1", "2"] })
|
|
52
36
|
```
|
|
53
37
|
|
|
54
|
-
|
|
55
|
-
1. Emit your `<eagle-summary>` block
|
|
56
|
-
2. Tell the user: **"Task #N complete. Run `/compact` to save progress and load the next task."**
|
|
38
|
+
## Working on a task
|
|
57
39
|
|
|
58
|
-
|
|
40
|
+
1. Call `TaskUpdate({ taskId: "N", status: "in_progress" })` before starting work
|
|
41
|
+
2. Do the work
|
|
42
|
+
3. Call `TaskUpdate({ taskId: "N", status: "completed" })` when done
|
|
43
|
+
4. Emit your `<eagle-summary>` block
|
|
44
|
+
5. Tell the user: **"Task complete. Run `/compact` to save progress and load the next task."**
|
|
59
45
|
|
|
60
|
-
|
|
61
|
-
eagle-mem tasks block <id>
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
### Set context snapshot
|
|
46
|
+
## Viewing tasks
|
|
65
47
|
|
|
66
|
-
|
|
48
|
+
Use `TaskList` to see all tasks, or the CLI for cross-session history:
|
|
67
49
|
|
|
68
50
|
```bash
|
|
69
|
-
eagle-mem tasks
|
|
51
|
+
eagle-mem tasks # pending/in-progress (from mirror)
|
|
52
|
+
eagle-mem tasks list # all tasks
|
|
53
|
+
eagle-mem tasks search <q> # FTS5 search across task history
|
|
70
54
|
```
|
|
71
55
|
|
|
72
|
-
|
|
56
|
+
## Cross-session context
|
|
73
57
|
|
|
74
|
-
|
|
75
|
-
eagle-mem tasks clear
|
|
76
|
-
```
|
|
58
|
+
When a task depends on decisions made in earlier tasks, put that context in the task's `description` field — it persists across compactions via Eagle Mem's mirror.
|
|
77
59
|
|
|
78
|
-
|
|
60
|
+
For example, if task 1 decides to use JWT with RS256:
|
|
79
61
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
| `-j, --json` | Output as JSON |
|
|
62
|
+
```
|
|
63
|
+
TaskUpdate({ taskId: "2", description: "Implement auth middleware. Decision from task 1: using JWT with RS256, sessions stored in Redis." })
|
|
64
|
+
```
|
|
84
65
|
|
|
85
66
|
## Task design guidelines
|
|
86
67
|
|
|
87
|
-
- Each task should be **self-contained** — completable
|
|
88
|
-
- Include
|
|
89
|
-
- Use
|
|
90
|
-
- Order tasks so
|
|
68
|
+
- Each task should be **self-contained** — completable in one context window
|
|
69
|
+
- Include enough detail in `description` that a fresh context window can pick it up
|
|
70
|
+
- Use `addBlockedBy` for tasks that depend on earlier tasks
|
|
71
|
+
- Order tasks so foundational work comes first (schema before API, API before UI)
|
|
72
|
+
- Keep tasks focused: 3-8 tasks per plan
|
|
91
73
|
|
|
92
74
|
## The compact cycle
|
|
93
75
|
|
|
94
76
|
```
|
|
95
|
-
User request
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
77
|
+
User request -> Plan tasks (TaskCreate) -> Execute task 1 (TaskUpdate: in_progress)
|
|
78
|
+
-> Complete task 1 (TaskUpdate: completed) -> /compact
|
|
79
|
+
-> Eagle Mem saves summary -> Context cleared -> Task state re-injected
|
|
80
|
+
-> Execute task 2 -> /compact
|
|
81
|
+
-> ... repeat until all tasks done ...
|
|
82
|
+
-> Final summary: "All N tasks complete."
|
|
100
83
|
```
|
|
84
|
+
|
|
85
|
+
## Status reference
|
|
86
|
+
|
|
87
|
+
| Status | Meaning |
|
|
88
|
+
|---|---|
|
|
89
|
+
| `pending` | Not started yet |
|
|
90
|
+
| `in_progress` | Currently being worked on |
|
|
91
|
+
| `completed` | Done |
|
|
92
|
+
| `deleted` | Removed |
|