eagle-mem 3.0.0 → 3.0.2

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.
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env bash
2
+ # ═══════════════════════════════════════════════════════════
3
+ # Eagle Mem — Backfill + orphan cleanup helpers
4
+ # ═══════════════════════════════════════════════════════════
5
+ [ -n "${_EAGLE_DB_BACKFILL_LOADED:-}" ] && return 0
6
+ _EAGLE_DB_BACKFILL_LOADED=1
7
+
8
+ eagle_build_session_project_map() {
9
+ local claude_projects_dir="$EAGLE_CLAUDE_PROJECTS_DIR"
10
+ [ ! -d "$claude_projects_dir" ] && return 0
11
+
12
+ for proj_dir in "$claude_projects_dir"/*/; do
13
+ [ ! -d "$proj_dir" ] && continue
14
+
15
+ local project=""
16
+ local sample_jsonl
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
25
+ [ -z "$project" ] && continue
26
+
27
+ for jsonl in "$proj_dir"*.jsonl; do
28
+ [ ! -f "$jsonl" ] && continue
29
+ local sid
30
+ sid=$(basename "$jsonl" .jsonl)
31
+ echo "$sid|$project"
32
+ done
33
+ done
34
+ }
35
+
36
+ eagle_backfill_projects() {
37
+ local updated=0
38
+ local map
39
+ map=$(eagle_build_session_project_map)
40
+ [ -z "$map" ] && echo "0" && return 0
41
+
42
+ while IFS='|' read -r sid project; do
43
+ [ -z "$sid" ] || [ -z "$project" ] && continue
44
+ local sid_sql proj_sql
45
+ sid_sql=$(eagle_sql_escape "$sid")
46
+ proj_sql=$(eagle_sql_escape "$project")
47
+
48
+ # All six tables updated atomically per session to prevent
49
+ # partial backfill if the process is interrupted.
50
+ # Note: total_changes() includes FTS trigger changes, so the
51
+ # reported count may be higher than actual rows updated.
52
+ local ch
53
+ ch=$(eagle_db_pipe <<SQL
54
+ BEGIN;
55
+ UPDATE sessions SET project = '$proj_sql' WHERE id = '$sid_sql' AND (project = '' OR project != '$proj_sql');
56
+ UPDATE claude_tasks SET project = '$proj_sql' WHERE source_session_id = '$sid_sql' AND (project = '' OR project != '$proj_sql');
57
+ UPDATE claude_memories SET project = '$proj_sql' WHERE origin_session_id = '$sid_sql' AND (project = '' OR project != '$proj_sql');
58
+ UPDATE claude_plans SET project = '$proj_sql' WHERE origin_session_id = '$sid_sql' AND (project = '' OR project != '$proj_sql');
59
+ UPDATE summaries SET project = '$proj_sql' WHERE session_id = '$sid_sql' AND (project = '' OR project != '$proj_sql');
60
+ UPDATE observations SET project = '$proj_sql' WHERE session_id = '$sid_sql' AND (project = '' OR project != '$proj_sql');
61
+ SELECT total_changes();
62
+ COMMIT;
63
+ SQL
64
+ )
65
+ [ "${ch:-0}" -gt 0 ] && updated=$((updated + ch))
66
+ done <<< "$map"
67
+
68
+ echo "$updated"
69
+ }
70
+
71
+ eagle_prune_orphan_chunks() {
72
+ local project; project=$(eagle_sql_escape "$1")
73
+ local target_dir="$2"
74
+
75
+ local paths
76
+ paths=$(eagle_db "SELECT DISTINCT file_path FROM code_chunks WHERE project = '$project';")
77
+
78
+ local removed=0
79
+ local txn_sql="BEGIN;"
80
+ while IFS= read -r fpath; do
81
+ [ -z "$fpath" ] && continue
82
+ if [ ! -f "$target_dir/$fpath" ]; then
83
+ local fpath_sql; fpath_sql=$(eagle_sql_escape "$fpath")
84
+ txn_sql+="
85
+ DELETE FROM code_chunks WHERE project = '$project' AND file_path = '$fpath_sql';"
86
+ removed=$((removed + 1))
87
+ fi
88
+ done <<< "$paths"
89
+ txn_sql+="
90
+ COMMIT;"
91
+
92
+ if [ "$removed" -gt 0 ]; then
93
+ eagle_db_pipe <<< "$txn_sql"
94
+ fi
95
+ echo "$removed"
96
+ }
package/lib/db-core.sh ADDED
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env bash
2
+ # ═══════════════════════════════════════════════════════════
3
+ # Eagle Mem — Database primitives
4
+ # ═══════════════════════════════════════════════════════════
5
+ [ -n "${_EAGLE_DB_CORE_LOADED:-}" ] && return 0
6
+ _EAGLE_DB_CORE_LOADED=1
7
+
8
+ EAGLE_DB_SETUP=".headers off
9
+ .output /dev/null
10
+ PRAGMA journal_mode=WAL;
11
+ PRAGMA synchronous=NORMAL;
12
+ PRAGMA busy_timeout=5000;
13
+ PRAGMA foreign_keys=ON;
14
+ PRAGMA trusted_schema=ON;
15
+ .output stdout"
16
+
17
+ eagle_db() {
18
+ local _eagle_db_err
19
+ _eagle_db_err=$(mktemp 2>/dev/null || echo "/tmp/_eagle_db_err.$$")
20
+ local _eagle_db_out
21
+ _eagle_db_out=$({ echo "$EAGLE_DB_SETUP"; echo "$*"; } | sqlite3 "$EAGLE_MEM_DB" 2>"$_eagle_db_err")
22
+ local _eagle_db_rc=$?
23
+ if [ -s "$_eagle_db_err" ]; then
24
+ cat "$_eagle_db_err" >> "$EAGLE_MEM_LOG" 2>/dev/null
25
+ fi
26
+ rm -f "$_eagle_db_err" 2>/dev/null
27
+ [ -n "$_eagle_db_out" ] && printf '%s\n' "$_eagle_db_out"
28
+ return $_eagle_db_rc
29
+ }
30
+
31
+ eagle_db_pipe() {
32
+ local _eagle_db_err
33
+ _eagle_db_err=$(mktemp 2>/dev/null || echo "/tmp/_eagle_db_pipe_err.$$")
34
+ local _eagle_db_out
35
+ _eagle_db_out=$({ echo "$EAGLE_DB_SETUP"; echo ".bail on"; cat; } | sqlite3 "$EAGLE_MEM_DB" 2>"$_eagle_db_err")
36
+ local _eagle_db_rc=$?
37
+ if [ -s "$_eagle_db_err" ]; then
38
+ cat "$_eagle_db_err" >> "$EAGLE_MEM_LOG" 2>/dev/null
39
+ fi
40
+ rm -f "$_eagle_db_err" 2>/dev/null
41
+ [ -n "$_eagle_db_out" ] && printf '%s\n' "$_eagle_db_out"
42
+ return $_eagle_db_rc
43
+ }
44
+
45
+ eagle_db_json() {
46
+ local _eagle_db_err
47
+ _eagle_db_err=$(mktemp 2>/dev/null || echo "/tmp/_eagle_db_json_err.$$")
48
+ local _eagle_db_out
49
+ _eagle_db_out=$({ echo "$EAGLE_DB_SETUP"; echo ".mode json"; echo "$*"; } | sqlite3 "$EAGLE_MEM_DB" 2>"$_eagle_db_err")
50
+ local _eagle_db_rc=$?
51
+ if [ -s "$_eagle_db_err" ]; then
52
+ cat "$_eagle_db_err" >> "$EAGLE_MEM_LOG" 2>/dev/null
53
+ fi
54
+ rm -f "$_eagle_db_err" 2>/dev/null
55
+ [ -n "$_eagle_db_out" ] && printf '%s\n' "$_eagle_db_out"
56
+ return $_eagle_db_rc
57
+ }
58
+
59
+ eagle_ensure_db() {
60
+ if [ ! -f "$EAGLE_MEM_DB" ]; then
61
+ local script_dir
62
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../db" && pwd)"
63
+ "$script_dir/migrate.sh"
64
+ fi
65
+ }
@@ -0,0 +1,160 @@
1
+ #!/usr/bin/env bash
2
+ # ═══════════════════════════════════════════════════════════
3
+ # Eagle Mem — Feature graph helpers
4
+ # ═══════════════════════════════════════════════════════════
5
+ [ -n "${_EAGLE_DB_FEATURES_LOADED:-}" ] && return 0
6
+ _EAGLE_DB_FEATURES_LOADED=1
7
+
8
+ eagle_upsert_feature() {
9
+ local project; project=$(eagle_sql_escape "$1")
10
+ local name; name=$(eagle_sql_escape "$2")
11
+ local description; description=$(eagle_sql_escape "${3:-}")
12
+
13
+ eagle_db "INSERT INTO features (project, name, description)
14
+ VALUES ('$project', '$name', '$description')
15
+ ON CONFLICT(project, name) DO UPDATE SET
16
+ description = COALESCE(NULLIF('$description', ''), features.description),
17
+ updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now');"
18
+ }
19
+
20
+ eagle_add_feature_dependency() {
21
+ local feature_id; feature_id=$(eagle_sql_int "$1")
22
+ local kind; kind=$(eagle_sql_escape "$2")
23
+ local target; target=$(eagle_sql_escape "$3")
24
+ local name; name=$(eagle_sql_escape "$4")
25
+ local notes; notes=$(eagle_sql_escape "${5:-}")
26
+
27
+ eagle_db "INSERT OR IGNORE INTO feature_dependencies (feature_id, kind, target, name, notes)
28
+ VALUES ($feature_id, '$kind', '$target', '$name', '$notes');"
29
+ }
30
+
31
+ eagle_add_feature_file() {
32
+ local feature_id; feature_id=$(eagle_sql_int "$1")
33
+ local file_path; file_path=$(eagle_sql_escape "$2")
34
+ local role; role=$(eagle_sql_escape "${3:-}")
35
+
36
+ eagle_db "INSERT OR IGNORE INTO feature_files (feature_id, file_path, role)
37
+ VALUES ($feature_id, '$file_path', '$role');"
38
+ }
39
+
40
+ eagle_add_feature_smoke_test() {
41
+ local feature_id; feature_id=$(eagle_sql_int "$1")
42
+ local command; command=$(eagle_sql_escape "$2")
43
+ local description; description=$(eagle_sql_escape "${3:-}")
44
+
45
+ eagle_db "INSERT OR IGNORE INTO feature_smoke_tests (feature_id, command, description)
46
+ VALUES ($feature_id, '$command', '$description');"
47
+ }
48
+
49
+ eagle_verify_feature() {
50
+ local project; project=$(eagle_sql_escape "$1")
51
+ local name; name=$(eagle_sql_escape "$2")
52
+ local notes; notes=$(eagle_sql_escape "${3:-}")
53
+
54
+ eagle_db "UPDATE features SET
55
+ last_verified_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
56
+ last_verified_notes = '$notes',
57
+ updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
58
+ WHERE project = '$project' AND name = '$name';"
59
+ }
60
+
61
+ eagle_get_feature_id() {
62
+ local project; project=$(eagle_sql_escape "$1")
63
+ local name; name=$(eagle_sql_escape "$2")
64
+ eagle_db "SELECT id FROM features WHERE project = '$project' AND name = '$name';"
65
+ }
66
+
67
+ eagle_list_features() {
68
+ local project; project=$(eagle_sql_escape "$1")
69
+ local limit; limit=$(eagle_sql_int "${2:-20}")
70
+
71
+ eagle_db "SELECT f.name, f.description, f.status, f.last_verified_at,
72
+ (SELECT COUNT(*) FROM feature_dependencies WHERE feature_id = f.id) as dep_count,
73
+ (SELECT COUNT(*) FROM feature_files WHERE feature_id = f.id) as file_count,
74
+ (SELECT COUNT(*) FROM feature_smoke_tests WHERE feature_id = f.id) as test_count
75
+ FROM features f
76
+ WHERE f.project = '$project' AND f.status = 'active'
77
+ ORDER BY f.updated_at DESC
78
+ LIMIT $limit;"
79
+ }
80
+
81
+ eagle_show_feature() {
82
+ local project; project=$(eagle_sql_escape "$1")
83
+ local name; name=$(eagle_sql_escape "$2")
84
+
85
+ local feature_id
86
+ feature_id=$(eagle_get_feature_id "$1" "$2")
87
+ [ -z "$feature_id" ] && return 1
88
+
89
+ echo "=== Feature: $2 ==="
90
+ eagle_db "SELECT name, description, status, last_verified_at, last_verified_notes
91
+ FROM features WHERE id = $feature_id;"
92
+
93
+ local deps
94
+ deps=$(eagle_db "SELECT kind, target, name, notes FROM feature_dependencies WHERE feature_id = $feature_id;")
95
+ if [ -n "$deps" ]; then
96
+ echo "--- Dependencies ---"
97
+ echo "$deps"
98
+ fi
99
+
100
+ local files
101
+ files=$(eagle_db "SELECT file_path, role FROM feature_files WHERE feature_id = $feature_id;")
102
+ if [ -n "$files" ]; then
103
+ echo "--- Files ---"
104
+ echo "$files"
105
+ fi
106
+
107
+ local tests
108
+ tests=$(eagle_db "SELECT command, description FROM feature_smoke_tests WHERE feature_id = $feature_id;")
109
+ if [ -n "$tests" ]; then
110
+ echo "--- Smoke Tests ---"
111
+ echo "$tests"
112
+ fi
113
+ }
114
+
115
+ eagle_count_active_features() {
116
+ local project; project=$(eagle_sql_escape "$1")
117
+ eagle_db "SELECT COUNT(*) FROM features WHERE project = '$project' AND status = 'active';"
118
+ }
119
+
120
+ eagle_find_feature_for_push() {
121
+ local project; project=$(eagle_sql_escape "$1")
122
+ local fname; fname=$(eagle_sql_escape "$2")
123
+ local fname_like; fname_like=$(eagle_like_escape "$fname")
124
+
125
+ eagle_db "SELECT DISTINCT f.name,
126
+ (SELECT GROUP_CONCAT(fst.command, '; ')
127
+ FROM feature_smoke_tests fst WHERE fst.feature_id = f.id) as smoke,
128
+ (SELECT GROUP_CONCAT(fd.target || ':' || fd.name, ', ')
129
+ FROM feature_dependencies fd WHERE fd.feature_id = f.id) as deps,
130
+ f.last_verified_at
131
+ FROM features f
132
+ JOIN feature_files ff ON ff.feature_id = f.id
133
+ WHERE f.project = '$project'
134
+ AND f.status = 'active'
135
+ AND (ff.file_path LIKE '%$fname_like' ESCAPE '\\' OR ff.file_path LIKE '%$fname_like%' ESCAPE '\\');"
136
+ }
137
+
138
+ eagle_find_features_for_file() {
139
+ local project; project=$(eagle_sql_escape "$1")
140
+ local file_path="$2"
141
+ local fname; fname=$(basename "$file_path")
142
+ local fname_esc; fname_esc=$(eagle_sql_escape "$fname")
143
+ local fname_like; fname_like=$(eagle_like_escape "$fname_esc")
144
+
145
+ eagle_db "SELECT f.name, f.description, f.last_verified_at,
146
+ ff.role,
147
+ (SELECT GROUP_CONCAT(fd.target || ':' || fd.name, ', ')
148
+ FROM feature_dependencies fd WHERE fd.feature_id = f.id) as deps,
149
+ (SELECT GROUP_CONCAT(ff2.file_path, ', ')
150
+ FROM feature_files ff2 WHERE ff2.feature_id = f.id AND ff2.file_path != ff.file_path) as other_files,
151
+ (SELECT GROUP_CONCAT(fst.command, ', ')
152
+ FROM feature_smoke_tests fst WHERE fst.feature_id = f.id) as smoke_tests
153
+ FROM features f
154
+ JOIN feature_files ff ON ff.feature_id = f.id
155
+ WHERE f.project = '$project'
156
+ AND f.status = 'active'
157
+ AND (ff.file_path LIKE '%$fname_like' ESCAPE '\\' OR ff.file_path LIKE '%$fname_like%' ESCAPE '\\')
158
+ ORDER BY f.updated_at DESC
159
+ LIMIT 3;"
160
+ }
@@ -0,0 +1,264 @@
1
+ #!/usr/bin/env bash
2
+ # ═══════════════════════════════════════════════════════════
3
+ # Eagle Mem — Claude Code memory/plan/task mirror helpers
4
+ # ═══════════════════════════════════════════════════════════
5
+ [ -n "${_EAGLE_DB_MIRRORS_LOADED:-}" ] && return 0
6
+ _EAGLE_DB_MIRRORS_LOADED=1
7
+
8
+ eagle_capture_claude_memory() {
9
+ local file_path="$1"
10
+ local session_id="${2:-}"
11
+ local project="${3:-}"
12
+
13
+ [ ! -f "$file_path" ] && return 0
14
+
15
+ local chash
16
+ chash=$(shasum -a 256 "$file_path" | awk '{print $1}')
17
+
18
+ local fm body
19
+ fm=$(awk '/^---$/{c++; next} c==1' "$file_path")
20
+ body=$(awk '/^---$/{c++; next} c>=2' "$file_path")
21
+
22
+ _fm_field() { printf '%s\n' "$fm" | awk -F': *' -v k="$1" '$1==k{sub(/^[^:]+: */,""); gsub(/^"|"$/,""); print; exit}'; }
23
+
24
+ local mname mdesc mtype morigin
25
+ mname=$(_fm_field "name")
26
+ mdesc=$(_fm_field "description")
27
+ mtype=$(_fm_field "type")
28
+ morigin=$(_fm_field "originSessionId")
29
+ [ -z "$morigin" ] && morigin="$session_id"
30
+
31
+ local fp_sql proj_sql name_sql desc_sql type_sql content_sql hash_sql origin_sql
32
+ fp_sql=$(eagle_sql_escape "$file_path")
33
+ proj_sql=$(eagle_sql_escape "$project")
34
+ name_sql=$(eagle_sql_escape "$mname")
35
+ desc_sql=$(eagle_sql_escape "$mdesc")
36
+ type_sql=$(eagle_sql_escape "$mtype")
37
+ content_sql=$(eagle_sql_escape "$body")
38
+ hash_sql=$(eagle_sql_escape "$chash")
39
+ origin_sql=$(eagle_sql_escape "$morigin")
40
+
41
+ eagle_db_pipe <<SQL
42
+ INSERT INTO claude_memories (project, file_path, memory_name, description, memory_type, content, content_hash, origin_session_id)
43
+ VALUES ('$proj_sql', '$fp_sql', '$name_sql', '$desc_sql', '$type_sql', '$content_sql', '$hash_sql', '$origin_sql')
44
+ ON CONFLICT(file_path) DO UPDATE SET
45
+ memory_name = excluded.memory_name,
46
+ description = excluded.description,
47
+ memory_type = excluded.memory_type,
48
+ content = excluded.content,
49
+ content_hash = excluded.content_hash,
50
+ origin_session_id = excluded.origin_session_id,
51
+ updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
52
+ WHERE claude_memories.content_hash != excluded.content_hash;
53
+ SQL
54
+ }
55
+
56
+ eagle_search_claude_memories() {
57
+ local query; query=$(eagle_fts_sanitize "$1")
58
+ query=$(eagle_sql_escape "$query")
59
+ local project="${2:-}"
60
+ local limit; limit=$(eagle_sql_int "${3:-10}")
61
+
62
+ local where_clause=""
63
+ if [ -n "$project" ]; then
64
+ project=$(eagle_sql_escape "$project")
65
+ where_clause="AND m.project = '$project'"
66
+ fi
67
+
68
+ eagle_db "SELECT m.memory_name, m.memory_type, m.description,
69
+ replace(substr(m.content, 1, 200), char(10), ' '),
70
+ m.file_path, m.updated_at
71
+ FROM claude_memories m
72
+ JOIN claude_memories_fts f ON f.rowid = m.id
73
+ WHERE claude_memories_fts MATCH '$query'
74
+ $where_clause
75
+ ORDER BY rank
76
+ LIMIT $limit;"
77
+ }
78
+
79
+ eagle_list_claude_memories() {
80
+ local project="${1:-}"
81
+ local limit; limit=$(eagle_sql_int "${2:-20}")
82
+
83
+ local where_clause=""
84
+ if [ -n "$project" ]; then
85
+ project=$(eagle_sql_escape "$project")
86
+ where_clause="WHERE project = '$project'"
87
+ fi
88
+
89
+ eagle_db "SELECT memory_name, memory_type, description, file_path, updated_at
90
+ FROM claude_memories
91
+ $where_clause
92
+ ORDER BY updated_at DESC
93
+ LIMIT $limit;"
94
+ }
95
+
96
+ eagle_capture_claude_plan() {
97
+ local file_path="$1"
98
+ local session_id="${2:-}"
99
+ local project="${3:-}"
100
+
101
+ [ ! -f "$file_path" ] && return 0
102
+
103
+ local chash
104
+ chash=$(shasum -a 256 "$file_path" | awk '{print $1}')
105
+
106
+ local title content
107
+ title=$(awk '/^# /{print; exit}' "$file_path" | sed 's/^# //')
108
+ content=$(cat "$file_path")
109
+
110
+ local fp_sql proj_sql title_sql content_sql hash_sql origin_sql
111
+ fp_sql=$(eagle_sql_escape "$file_path")
112
+ proj_sql=$(eagle_sql_escape "$project")
113
+ title_sql=$(eagle_sql_escape "$title")
114
+ content_sql=$(eagle_sql_escape "$content")
115
+ hash_sql=$(eagle_sql_escape "$chash")
116
+ origin_sql=$(eagle_sql_escape "$session_id")
117
+
118
+ eagle_db_pipe <<SQL
119
+ INSERT INTO claude_plans (project, file_path, title, content, content_hash, origin_session_id)
120
+ VALUES ('$proj_sql', '$fp_sql', '$title_sql', '$content_sql', '$hash_sql', '$origin_sql')
121
+ ON CONFLICT(file_path) DO UPDATE SET
122
+ title = excluded.title,
123
+ content = excluded.content,
124
+ content_hash = excluded.content_hash,
125
+ origin_session_id = COALESCE(NULLIF(excluded.origin_session_id, ''), claude_plans.origin_session_id),
126
+ project = CASE WHEN excluded.project != '' THEN excluded.project ELSE claude_plans.project END,
127
+ updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
128
+ WHERE claude_plans.content_hash != excluded.content_hash;
129
+ SQL
130
+ }
131
+
132
+ eagle_search_claude_plans() {
133
+ local query; query=$(eagle_fts_sanitize "$1")
134
+ query=$(eagle_sql_escape "$query")
135
+ local project="${2:-}"
136
+ local limit; limit=$(eagle_sql_int "${3:-10}")
137
+
138
+ local where_clause=""
139
+ if [ -n "$project" ]; then
140
+ project=$(eagle_sql_escape "$project")
141
+ where_clause="AND p.project = '$project'"
142
+ fi
143
+
144
+ eagle_db "SELECT p.title, p.project,
145
+ replace(substr(p.content, 1, 200), char(10), ' '),
146
+ p.file_path, p.updated_at
147
+ FROM claude_plans p
148
+ JOIN claude_plans_fts f ON f.rowid = p.id
149
+ WHERE claude_plans_fts MATCH '$query'
150
+ $where_clause
151
+ ORDER BY rank
152
+ LIMIT $limit;"
153
+ }
154
+
155
+ eagle_list_claude_plans() {
156
+ local project="${1:-}"
157
+ local limit; limit=$(eagle_sql_int "${2:-20}")
158
+
159
+ local where_clause=""
160
+ if [ -n "$project" ]; then
161
+ project=$(eagle_sql_escape "$project")
162
+ where_clause="WHERE project = '$project'"
163
+ fi
164
+
165
+ eagle_db "SELECT title, project, file_path, updated_at
166
+ FROM claude_plans
167
+ $where_clause
168
+ ORDER BY updated_at DESC
169
+ LIMIT $limit;"
170
+ }
171
+
172
+ eagle_capture_claude_task() {
173
+ local file_path="$1"
174
+ local session_id="${2:-}"
175
+ local project="${3:-}"
176
+
177
+ [ ! -f "$file_path" ] && return 0
178
+
179
+ local chash
180
+ chash=$(shasum -a 256 "$file_path" | awk '{print $1}')
181
+
182
+ local task_json
183
+ task_json=$(cat "$file_path")
184
+
185
+ local task_id subject desc active_form status blocks blocked_by
186
+ task_id=$(printf '%s' "$task_json" | jq -r '.id // empty')
187
+ subject=$(printf '%s' "$task_json" | jq -r '.subject // empty')
188
+ desc=$(printf '%s' "$task_json" | jq -r '.description // empty')
189
+ active_form=$(printf '%s' "$task_json" | jq -r '.activeForm // empty')
190
+ status=$(printf '%s' "$task_json" | jq -r '.status // "pending"')
191
+ blocks=$(printf '%s' "$task_json" | jq -c '.blocks // []')
192
+ blocked_by=$(printf '%s' "$task_json" | jq -c '.blockedBy // []')
193
+
194
+ [ -z "$task_id" ] && return 0
195
+
196
+ local fp_sql proj_sql sid_sql tid_sql subj_sql desc_sql af_sql status_sql blocks_sql bb_sql hash_sql
197
+ fp_sql=$(eagle_sql_escape "$file_path")
198
+ proj_sql=$(eagle_sql_escape "$project")
199
+ sid_sql=$(eagle_sql_escape "$session_id")
200
+ tid_sql=$(eagle_sql_escape "$task_id")
201
+ subj_sql=$(eagle_sql_escape "$subject")
202
+ desc_sql=$(eagle_sql_escape "$desc")
203
+ af_sql=$(eagle_sql_escape "$active_form")
204
+ status_sql=$(eagle_sql_escape "$status")
205
+ blocks_sql=$(eagle_sql_escape "$blocks")
206
+ bb_sql=$(eagle_sql_escape "$blocked_by")
207
+ hash_sql=$(eagle_sql_escape "$chash")
208
+
209
+ eagle_db_pipe <<SQL
210
+ INSERT INTO claude_tasks (project, source_session_id, source_task_id, file_path, subject, description, active_form, status, blocks, blocked_by, content_hash)
211
+ VALUES ('$proj_sql', '$sid_sql', '$tid_sql', '$fp_sql', '$subj_sql', '$desc_sql', '$af_sql', '$status_sql', '$blocks_sql', '$bb_sql', '$hash_sql')
212
+ ON CONFLICT(file_path) DO UPDATE SET
213
+ subject = excluded.subject,
214
+ description = excluded.description,
215
+ active_form = excluded.active_form,
216
+ status = excluded.status,
217
+ blocks = excluded.blocks,
218
+ blocked_by = excluded.blocked_by,
219
+ content_hash = excluded.content_hash,
220
+ project = CASE WHEN excluded.project != '' THEN excluded.project ELSE claude_tasks.project END,
221
+ updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
222
+ WHERE claude_tasks.content_hash != excluded.content_hash;
223
+ SQL
224
+ }
225
+
226
+ eagle_list_claude_tasks() {
227
+ local project="${1:-}"
228
+ local limit; limit=$(eagle_sql_int "${2:-20}")
229
+
230
+ local where_clause=""
231
+ if [ -n "$project" ]; then
232
+ project=$(eagle_sql_escape "$project")
233
+ where_clause="WHERE project = '$project'"
234
+ fi
235
+
236
+ eagle_db "SELECT subject, status, source_session_id, source_task_id, updated_at
237
+ FROM claude_tasks
238
+ $where_clause
239
+ ORDER BY updated_at DESC
240
+ LIMIT $limit;"
241
+ }
242
+
243
+ eagle_search_claude_tasks() {
244
+ local query; query=$(eagle_fts_sanitize "$1")
245
+ query=$(eagle_sql_escape "$query")
246
+ local project="${2:-}"
247
+ local limit; limit=$(eagle_sql_int "${3:-10}")
248
+
249
+ local where_clause=""
250
+ if [ -n "$project" ]; then
251
+ project=$(eagle_sql_escape "$project")
252
+ where_clause="AND t.project = '$project'"
253
+ fi
254
+
255
+ eagle_db "SELECT t.subject, t.status,
256
+ replace(substr(t.description, 1, 200), char(10), ' '),
257
+ t.source_session_id, t.source_task_id, t.updated_at
258
+ FROM claude_tasks t
259
+ JOIN claude_tasks_fts f ON f.rowid = t.id
260
+ WHERE claude_tasks_fts MATCH '$query'
261
+ $where_clause
262
+ ORDER BY rank
263
+ LIMIT $limit;"
264
+ }
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env bash
2
+ # ═══════════════════════════════════════════════════════════
3
+ # Eagle Mem — Observation + code chunk helpers
4
+ # ═══════════════════════════════════════════════════════════
5
+ [ -n "${_EAGLE_DB_OBSERVATIONS_LOADED:-}" ] && return 0
6
+ _EAGLE_DB_OBSERVATIONS_LOADED=1
7
+
8
+ eagle_insert_observation() {
9
+ local session_id; session_id=$(eagle_sql_escape "$1")
10
+ local project; project=$(eagle_sql_escape "$2")
11
+ local tool_name; tool_name=$(eagle_sql_escape "$3")
12
+ local tool_input_summary; tool_input_summary=$(eagle_sql_escape "$4")
13
+ local files_read; files_read=$(eagle_sql_escape "$5")
14
+ local files_modified; files_modified=$(eagle_sql_escape "$6")
15
+ local output_bytes="${7:-}"
16
+ local output_lines="${8:-}"
17
+ local command_category; command_category=$(eagle_sql_escape "${9:-}")
18
+
19
+ local extra_cols=""
20
+ local extra_vals=""
21
+ if [ -n "$output_bytes" ]; then
22
+ extra_cols=", output_bytes, output_lines, command_category"
23
+ extra_vals=", $(eagle_sql_int "$output_bytes"), $(eagle_sql_int "$output_lines"), '$command_category'"
24
+ fi
25
+
26
+ eagle_db "INSERT INTO observations (session_id, project, tool_name, tool_input_summary, files_read, files_modified${extra_cols})
27
+ SELECT '$session_id', '$project', '$tool_name', '$tool_input_summary', '$files_read', '$files_modified'${extra_vals}
28
+ WHERE NOT EXISTS (
29
+ SELECT 1 FROM observations
30
+ WHERE session_id = '$session_id'
31
+ AND tool_name = '$tool_name'
32
+ AND tool_input_summary = '$tool_input_summary'
33
+ AND created_at > strftime('%Y-%m-%dT%H:%M:%fZ', 'now', '-5 seconds')
34
+ );"
35
+ }
36
+
37
+ eagle_prune_observations() {
38
+ local days; days=$(eagle_sql_int "${1:-90}")
39
+ local project_filter=""
40
+ if [ -n "${2:-}" ]; then
41
+ local proj; proj=$(eagle_sql_escape "$2")
42
+ project_filter="AND session_id IN (SELECT id FROM sessions WHERE project = '$proj')"
43
+ fi
44
+ eagle_db "DELETE FROM observations WHERE created_at < strftime('%Y-%m-%dT%H:%M:%fZ', 'now', '-$days days') $project_filter;"
45
+ }
46
+
47
+ eagle_get_command_rule() {
48
+ local project; project=$(eagle_sql_escape "$1")
49
+ local cmd; cmd=$(eagle_sql_escape "$2")
50
+ eagle_db "SELECT strategy, max_lines, reason
51
+ FROM command_rules
52
+ WHERE enabled = 1
53
+ AND (project = '$project' OR project IS NULL)
54
+ AND ('$cmd' LIKE pattern OR '$cmd' = pattern)
55
+ ORDER BY CASE WHEN project IS NOT NULL THEN 0 ELSE 1 END
56
+ LIMIT 1;"
57
+ }
58
+
59
+ eagle_search_code_chunks() {
60
+ local query; query=$(eagle_fts_sanitize "$1")
61
+ query=$(eagle_sql_escape "$query")
62
+ local project; project=$(eagle_sql_escape "$2")
63
+ local limit; limit=$(eagle_sql_int "${3:-5}")
64
+
65
+ eagle_db "SELECT c.file_path, c.start_line, c.end_line, c.language
66
+ FROM code_chunks c
67
+ JOIN code_chunks_fts f ON f.rowid = c.id
68
+ WHERE code_chunks_fts MATCH '$query'
69
+ AND c.project = '$project'
70
+ ORDER BY rank
71
+ LIMIT $limit;"
72
+ }
73
+
74
+ eagle_count_code_chunks() {
75
+ local project; project=$(eagle_sql_escape "$1")
76
+ eagle_db "SELECT COUNT(*) FROM code_chunks WHERE project = '$project' LIMIT 1;"
77
+ }