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.
- package/db/015_integrity_fixes.sql +156 -0
- package/db/migrate.sh +3 -0
- package/hooks/post-tool-use.sh +18 -147
- package/hooks/pre-tool-use.sh +4 -23
- package/hooks/session-end.sh +4 -1
- package/hooks/session-start.sh +23 -29
- package/hooks/stop.sh +6 -3
- package/lib/common.sh +34 -8
- package/lib/db-backfill.sh +96 -0
- package/lib/db-core.sh +65 -0
- package/lib/db-features.sh +160 -0
- package/lib/db-mirrors.sh +264 -0
- package/lib/db-observations.sh +77 -0
- package/lib/db-sessions.sh +68 -0
- package/lib/db-summaries.sh +142 -0
- package/lib/db.sh +14 -706
- package/lib/hooks-posttool.sh +138 -0
- package/lib/provider.sh +22 -6
- package/package.json +1 -1
- package/scripts/curate.sh +16 -0
- package/scripts/install.sh +5 -2
- package/scripts/memories.sh +3 -3
- package/scripts/uninstall.sh +1 -1
- package/scripts/update.sh +5 -1
package/lib/db.sh
CHANGED
|
@@ -1,709 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# ═══════════════════════════════════════════════════════════
|
|
3
|
-
# Eagle Mem — Database helpers
|
|
4
|
-
#
|
|
3
|
+
# Eagle Mem — Database helpers (backward-compat shim)
|
|
4
|
+
# Sources all domain files. Callers: . "$LIB_DIR/db.sh"
|
|
5
5
|
# ═══════════════════════════════════════════════════════════
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
eagle_db_pipe() {
|
|
21
|
-
{ echo "$EAGLE_DB_SETUP"; echo ".bail on"; cat; } | sqlite3 "$EAGLE_MEM_DB" 2>>"$EAGLE_MEM_LOG"
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
eagle_db_json() {
|
|
25
|
-
{ echo "$EAGLE_DB_SETUP"; echo ".mode json"; echo "$*"; } | sqlite3 "$EAGLE_MEM_DB" 2>>"$EAGLE_MEM_LOG"
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
eagle_ensure_db() {
|
|
29
|
-
if [ ! -f "$EAGLE_MEM_DB" ]; then
|
|
30
|
-
local script_dir
|
|
31
|
-
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../db" && pwd)"
|
|
32
|
-
"$script_dir/migrate.sh"
|
|
33
|
-
fi
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
eagle_upsert_session() {
|
|
37
|
-
local session_id; session_id=$(eagle_sql_escape "$1")
|
|
38
|
-
local project; project=$(eagle_sql_escape "$2")
|
|
39
|
-
local cwd; cwd=$(eagle_sql_escape "${3:-}")
|
|
40
|
-
local model; model=$(eagle_sql_escape "${4:-}")
|
|
41
|
-
local source; source=$(eagle_sql_escape "${5:-}")
|
|
42
|
-
|
|
43
|
-
eagle_db "INSERT INTO sessions (id, project, cwd, model, source, last_activity_at)
|
|
44
|
-
VALUES ('$session_id', '$project', '$cwd', '$model', '$source', strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
|
45
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
46
|
-
cwd = COALESCE(excluded.cwd, sessions.cwd),
|
|
47
|
-
model = COALESCE(excluded.model, sessions.model),
|
|
48
|
-
source = COALESCE(excluded.source, sessions.source),
|
|
49
|
-
status = 'active',
|
|
50
|
-
last_activity_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now');"
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
eagle_end_session() {
|
|
54
|
-
local session_id; session_id=$(eagle_sql_escape "$1")
|
|
55
|
-
eagle_db "UPDATE sessions SET status = 'completed', ended_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = '$session_id';"
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
eagle_insert_observation() {
|
|
59
|
-
local session_id; session_id=$(eagle_sql_escape "$1")
|
|
60
|
-
local project; project=$(eagle_sql_escape "$2")
|
|
61
|
-
local tool_name; tool_name=$(eagle_sql_escape "$3")
|
|
62
|
-
local tool_input_summary; tool_input_summary=$(eagle_sql_escape "$4")
|
|
63
|
-
local files_read; files_read=$(eagle_sql_escape "$5")
|
|
64
|
-
local files_modified; files_modified=$(eagle_sql_escape "$6")
|
|
65
|
-
local output_bytes="${7:-}"
|
|
66
|
-
local output_lines="${8:-}"
|
|
67
|
-
local command_category; command_category=$(eagle_sql_escape "${9:-}")
|
|
68
|
-
|
|
69
|
-
local extra_cols=""
|
|
70
|
-
local extra_vals=""
|
|
71
|
-
if [ -n "$output_bytes" ]; then
|
|
72
|
-
extra_cols=", output_bytes, output_lines, command_category"
|
|
73
|
-
extra_vals=", $(eagle_sql_int "$output_bytes"), $(eagle_sql_int "$output_lines"), '$command_category'"
|
|
74
|
-
fi
|
|
75
|
-
|
|
76
|
-
eagle_db "INSERT INTO observations (session_id, project, tool_name, tool_input_summary, files_read, files_modified${extra_cols})
|
|
77
|
-
SELECT '$session_id', '$project', '$tool_name', '$tool_input_summary', '$files_read', '$files_modified'${extra_vals}
|
|
78
|
-
WHERE NOT EXISTS (
|
|
79
|
-
SELECT 1 FROM observations
|
|
80
|
-
WHERE session_id = '$session_id'
|
|
81
|
-
AND tool_name = '$tool_name'
|
|
82
|
-
AND tool_input_summary = '$tool_input_summary'
|
|
83
|
-
AND created_at > strftime('%Y-%m-%dT%H:%M:%fZ', 'now', '-5 seconds')
|
|
84
|
-
);"
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
eagle_insert_summary() {
|
|
88
|
-
local session_id; session_id=$(eagle_sql_escape "$1")
|
|
89
|
-
local project; project=$(eagle_sql_escape "$2")
|
|
90
|
-
local request; request=$(eagle_sql_escape "$3")
|
|
91
|
-
local investigated; investigated=$(eagle_sql_escape "$4")
|
|
92
|
-
local learned; learned=$(eagle_sql_escape "$5")
|
|
93
|
-
local completed; completed=$(eagle_sql_escape "$6")
|
|
94
|
-
local next_steps; next_steps=$(eagle_sql_escape "$7")
|
|
95
|
-
local files_read; files_read=$(eagle_sql_escape "$8")
|
|
96
|
-
local files_modified; files_modified=$(eagle_sql_escape "$9")
|
|
97
|
-
local notes; notes=$(eagle_sql_escape "${10:-}")
|
|
98
|
-
local decisions; decisions=$(eagle_sql_escape "${11:-}")
|
|
99
|
-
local gotchas; gotchas=$(eagle_sql_escape "${12:-}")
|
|
100
|
-
local key_files; key_files=$(eagle_sql_escape "${13:-}")
|
|
101
|
-
|
|
102
|
-
eagle_db_pipe <<SQL
|
|
103
|
-
INSERT INTO summaries (session_id, project, request, investigated, learned, completed, next_steps, files_read, files_modified, notes, decisions, gotchas, key_files)
|
|
104
|
-
VALUES (
|
|
105
|
-
'$session_id',
|
|
106
|
-
'$project',
|
|
107
|
-
'$request',
|
|
108
|
-
'$investigated',
|
|
109
|
-
'$learned',
|
|
110
|
-
'$completed',
|
|
111
|
-
'$next_steps',
|
|
112
|
-
'$files_read',
|
|
113
|
-
'$files_modified',
|
|
114
|
-
'$notes',
|
|
115
|
-
'$decisions',
|
|
116
|
-
'$gotchas',
|
|
117
|
-
'$key_files'
|
|
118
|
-
)
|
|
119
|
-
ON CONFLICT(session_id) DO UPDATE SET
|
|
120
|
-
project = excluded.project,
|
|
121
|
-
request = COALESCE(NULLIF(excluded.request, ''), summaries.request),
|
|
122
|
-
investigated = COALESCE(NULLIF(excluded.investigated, ''), summaries.investigated),
|
|
123
|
-
learned = COALESCE(NULLIF(excluded.learned, ''), summaries.learned),
|
|
124
|
-
completed = COALESCE(NULLIF(excluded.completed, ''), summaries.completed),
|
|
125
|
-
next_steps = COALESCE(NULLIF(excluded.next_steps, ''), summaries.next_steps),
|
|
126
|
-
files_read = COALESCE(NULLIF(excluded.files_read, '[]'), summaries.files_read),
|
|
127
|
-
files_modified = COALESCE(NULLIF(excluded.files_modified, '[]'), summaries.files_modified),
|
|
128
|
-
notes = COALESCE(NULLIF(excluded.notes, ''), summaries.notes),
|
|
129
|
-
decisions = COALESCE(NULLIF(excluded.decisions, ''), summaries.decisions),
|
|
130
|
-
gotchas = COALESCE(NULLIF(excluded.gotchas, ''), summaries.gotchas),
|
|
131
|
-
key_files = COALESCE(NULLIF(excluded.key_files, ''), summaries.key_files);
|
|
132
|
-
SQL
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
eagle_get_recent_summaries() {
|
|
136
|
-
local project; project=$(eagle_sql_escape "$1")
|
|
137
|
-
local limit; limit=$(eagle_sql_int "${2:-5}")
|
|
138
|
-
|
|
139
|
-
eagle_db "SELECT s.request, s.completed, s.learned, s.next_steps, s.created_at, s.decisions, s.gotchas, s.key_files
|
|
140
|
-
FROM summaries s
|
|
141
|
-
WHERE s.project = '$project'
|
|
142
|
-
AND s.request NOT LIKE '%<local-command-caveat>%'
|
|
143
|
-
ORDER BY s.created_at DESC
|
|
144
|
-
LIMIT $limit;"
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
eagle_search_summaries() {
|
|
148
|
-
local query; query=$(eagle_fts_sanitize "$1")
|
|
149
|
-
query=$(eagle_sql_escape "$query")
|
|
150
|
-
local project="${2:-}"
|
|
151
|
-
local limit; limit=$(eagle_sql_int "${3:-10}")
|
|
152
|
-
|
|
153
|
-
local where_clause=""
|
|
154
|
-
if [ -n "$project" ]; then
|
|
155
|
-
project=$(eagle_sql_escape "$project")
|
|
156
|
-
where_clause="AND s.project = '$project'"
|
|
157
|
-
fi
|
|
158
|
-
|
|
159
|
-
eagle_db "SELECT s.request, s.completed, s.learned, s.next_steps, s.created_at, s.project, s.decisions, s.gotchas, s.key_files
|
|
160
|
-
FROM summaries s
|
|
161
|
-
JOIN summaries_fts f ON f.rowid = s.id
|
|
162
|
-
WHERE summaries_fts MATCH '$query'
|
|
163
|
-
$where_clause
|
|
164
|
-
ORDER BY rank
|
|
165
|
-
LIMIT $limit;"
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
eagle_upsert_overview() {
|
|
169
|
-
local project; project=$(eagle_sql_escape "$1")
|
|
170
|
-
local raw_content="$2"
|
|
171
|
-
local ov_source; ov_source=$(eagle_sql_escape "${3:-manual}")
|
|
172
|
-
|
|
173
|
-
if [ ${#raw_content} -gt 16384 ]; then
|
|
174
|
-
raw_content="${raw_content:0:16384}"
|
|
175
|
-
eagle_log "WARN" "Overview for '$1' truncated to 16 KB (was ${#2} bytes)"
|
|
176
|
-
fi
|
|
177
|
-
|
|
178
|
-
local content; content=$(eagle_sql_escape "$raw_content")
|
|
179
|
-
|
|
180
|
-
eagle_db "INSERT INTO overviews (project, content, source, updated_at)
|
|
181
|
-
VALUES ('$project', '$content', '$ov_source', strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
|
182
|
-
ON CONFLICT(project) DO UPDATE SET
|
|
183
|
-
content = excluded.content,
|
|
184
|
-
source = excluded.source,
|
|
185
|
-
updated_at = excluded.updated_at;"
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
eagle_get_overview_source() {
|
|
189
|
-
local project; project=$(eagle_sql_escape "$1")
|
|
190
|
-
eagle_db "SELECT source FROM overviews WHERE project = '$project';"
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
eagle_get_overview() {
|
|
194
|
-
local project; project=$(eagle_sql_escape "$1")
|
|
195
|
-
|
|
196
|
-
eagle_db "SELECT content FROM overviews WHERE project = '$project';"
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
eagle_search_code_chunks() {
|
|
200
|
-
local query; query=$(eagle_fts_sanitize "$1")
|
|
201
|
-
query=$(eagle_sql_escape "$query")
|
|
202
|
-
local project; project=$(eagle_sql_escape "$2")
|
|
203
|
-
local limit; limit=$(eagle_sql_int "${3:-5}")
|
|
204
|
-
|
|
205
|
-
eagle_db "SELECT c.file_path, c.start_line, c.end_line, c.language
|
|
206
|
-
FROM code_chunks c
|
|
207
|
-
JOIN code_chunks_fts f ON f.rowid = c.id
|
|
208
|
-
WHERE code_chunks_fts MATCH '$query'
|
|
209
|
-
AND c.project = '$project'
|
|
210
|
-
ORDER BY rank
|
|
211
|
-
LIMIT $limit;"
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
eagle_count_code_chunks() {
|
|
215
|
-
local project; project=$(eagle_sql_escape "$1")
|
|
216
|
-
eagle_db "SELECT COUNT(*) FROM code_chunks WHERE project = '$project' LIMIT 1;"
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
eagle_prune_observations() {
|
|
220
|
-
local days; days=$(eagle_sql_int "${1:-90}")
|
|
221
|
-
local project_filter=""
|
|
222
|
-
if [ -n "${2:-}" ]; then
|
|
223
|
-
local proj; proj=$(eagle_sql_escape "$2")
|
|
224
|
-
project_filter="AND session_id IN (SELECT id FROM sessions WHERE project = '$proj')"
|
|
225
|
-
fi
|
|
226
|
-
eagle_db "DELETE FROM observations WHERE created_at < strftime('%Y-%m-%dT%H:%M:%fZ', 'now', '-$days days') $project_filter;"
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
eagle_capture_claude_memory() {
|
|
230
|
-
local file_path="$1"
|
|
231
|
-
local session_id="${2:-}"
|
|
232
|
-
local project="${3:-}"
|
|
233
|
-
|
|
234
|
-
[ ! -f "$file_path" ] && return 0
|
|
235
|
-
|
|
236
|
-
local chash
|
|
237
|
-
chash=$(shasum -a 256 "$file_path" | awk '{print $1}')
|
|
238
|
-
|
|
239
|
-
local fm body
|
|
240
|
-
fm=$(awk '/^---$/{c++; next} c==1' "$file_path")
|
|
241
|
-
body=$(awk '/^---$/{c++; next} c>=2' "$file_path")
|
|
242
|
-
|
|
243
|
-
_fm_field() { printf '%s\n' "$fm" | awk -F': *' -v k="$1" '$1==k{sub(/^[^:]+: */,""); gsub(/^"|"$/,""); print; exit}'; }
|
|
244
|
-
|
|
245
|
-
local mname mdesc mtype morigin
|
|
246
|
-
mname=$(_fm_field "name")
|
|
247
|
-
mdesc=$(_fm_field "description")
|
|
248
|
-
mtype=$(_fm_field "type")
|
|
249
|
-
morigin=$(_fm_field "originSessionId")
|
|
250
|
-
[ -z "$morigin" ] && morigin="$session_id"
|
|
251
|
-
|
|
252
|
-
local fp_sql proj_sql name_sql desc_sql type_sql content_sql hash_sql origin_sql
|
|
253
|
-
fp_sql=$(eagle_sql_escape "$file_path")
|
|
254
|
-
proj_sql=$(eagle_sql_escape "$project")
|
|
255
|
-
name_sql=$(eagle_sql_escape "$mname")
|
|
256
|
-
desc_sql=$(eagle_sql_escape "$mdesc")
|
|
257
|
-
type_sql=$(eagle_sql_escape "$mtype")
|
|
258
|
-
content_sql=$(eagle_sql_escape "$body")
|
|
259
|
-
hash_sql=$(eagle_sql_escape "$chash")
|
|
260
|
-
origin_sql=$(eagle_sql_escape "$morigin")
|
|
261
|
-
|
|
262
|
-
eagle_db_pipe <<SQL
|
|
263
|
-
INSERT INTO claude_memories (project, file_path, memory_name, description, memory_type, content, content_hash, origin_session_id)
|
|
264
|
-
VALUES ('$proj_sql', '$fp_sql', '$name_sql', '$desc_sql', '$type_sql', '$content_sql', '$hash_sql', '$origin_sql')
|
|
265
|
-
ON CONFLICT(file_path) DO UPDATE SET
|
|
266
|
-
memory_name = excluded.memory_name,
|
|
267
|
-
description = excluded.description,
|
|
268
|
-
memory_type = excluded.memory_type,
|
|
269
|
-
content = excluded.content,
|
|
270
|
-
content_hash = excluded.content_hash,
|
|
271
|
-
origin_session_id = excluded.origin_session_id,
|
|
272
|
-
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
273
|
-
WHERE claude_memories.content_hash != excluded.content_hash;
|
|
274
|
-
SQL
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
eagle_search_claude_memories() {
|
|
278
|
-
local query; query=$(eagle_fts_sanitize "$1")
|
|
279
|
-
query=$(eagle_sql_escape "$query")
|
|
280
|
-
local project="${2:-}"
|
|
281
|
-
local limit; limit=$(eagle_sql_int "${3:-10}")
|
|
282
|
-
|
|
283
|
-
local where_clause=""
|
|
284
|
-
if [ -n "$project" ]; then
|
|
285
|
-
project=$(eagle_sql_escape "$project")
|
|
286
|
-
where_clause="AND m.project = '$project'"
|
|
287
|
-
fi
|
|
288
|
-
|
|
289
|
-
eagle_db "SELECT m.memory_name, m.memory_type, m.description,
|
|
290
|
-
replace(substr(m.content, 1, 200), char(10), ' '),
|
|
291
|
-
m.file_path, m.updated_at
|
|
292
|
-
FROM claude_memories m
|
|
293
|
-
JOIN claude_memories_fts f ON f.rowid = m.id
|
|
294
|
-
WHERE claude_memories_fts MATCH '$query'
|
|
295
|
-
$where_clause
|
|
296
|
-
ORDER BY rank
|
|
297
|
-
LIMIT $limit;"
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
eagle_list_claude_memories() {
|
|
301
|
-
local project="${1:-}"
|
|
302
|
-
local limit; limit=$(eagle_sql_int "${2:-20}")
|
|
303
|
-
|
|
304
|
-
local where_clause=""
|
|
305
|
-
if [ -n "$project" ]; then
|
|
306
|
-
project=$(eagle_sql_escape "$project")
|
|
307
|
-
where_clause="WHERE project = '$project'"
|
|
308
|
-
fi
|
|
309
|
-
|
|
310
|
-
eagle_db "SELECT memory_name, memory_type, description, file_path, updated_at
|
|
311
|
-
FROM claude_memories
|
|
312
|
-
$where_clause
|
|
313
|
-
ORDER BY updated_at DESC
|
|
314
|
-
LIMIT $limit;"
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
eagle_capture_claude_plan() {
|
|
318
|
-
local file_path="$1"
|
|
319
|
-
local session_id="${2:-}"
|
|
320
|
-
local project="${3:-}"
|
|
321
|
-
|
|
322
|
-
[ ! -f "$file_path" ] && return 0
|
|
323
|
-
|
|
324
|
-
local chash
|
|
325
|
-
chash=$(shasum -a 256 "$file_path" | awk '{print $1}')
|
|
326
|
-
|
|
327
|
-
local title content
|
|
328
|
-
title=$(awk '/^# /{print; exit}' "$file_path" | sed 's/^# //')
|
|
329
|
-
content=$(cat "$file_path")
|
|
330
|
-
|
|
331
|
-
local fp_sql proj_sql title_sql content_sql hash_sql origin_sql
|
|
332
|
-
fp_sql=$(eagle_sql_escape "$file_path")
|
|
333
|
-
proj_sql=$(eagle_sql_escape "$project")
|
|
334
|
-
title_sql=$(eagle_sql_escape "$title")
|
|
335
|
-
content_sql=$(eagle_sql_escape "$content")
|
|
336
|
-
hash_sql=$(eagle_sql_escape "$chash")
|
|
337
|
-
origin_sql=$(eagle_sql_escape "$session_id")
|
|
338
|
-
|
|
339
|
-
eagle_db_pipe <<SQL
|
|
340
|
-
INSERT INTO claude_plans (project, file_path, title, content, content_hash, origin_session_id)
|
|
341
|
-
VALUES ('$proj_sql', '$fp_sql', '$title_sql', '$content_sql', '$hash_sql', '$origin_sql')
|
|
342
|
-
ON CONFLICT(file_path) DO UPDATE SET
|
|
343
|
-
title = excluded.title,
|
|
344
|
-
content = excluded.content,
|
|
345
|
-
content_hash = excluded.content_hash,
|
|
346
|
-
origin_session_id = COALESCE(NULLIF(excluded.origin_session_id, ''), claude_plans.origin_session_id),
|
|
347
|
-
project = CASE WHEN excluded.project != '' THEN excluded.project ELSE claude_plans.project END,
|
|
348
|
-
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
349
|
-
WHERE claude_plans.content_hash != excluded.content_hash;
|
|
350
|
-
SQL
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
eagle_search_claude_plans() {
|
|
354
|
-
local query; query=$(eagle_fts_sanitize "$1")
|
|
355
|
-
query=$(eagle_sql_escape "$query")
|
|
356
|
-
local project="${2:-}"
|
|
357
|
-
local limit; limit=$(eagle_sql_int "${3:-10}")
|
|
358
|
-
|
|
359
|
-
local where_clause=""
|
|
360
|
-
if [ -n "$project" ]; then
|
|
361
|
-
project=$(eagle_sql_escape "$project")
|
|
362
|
-
where_clause="AND p.project = '$project'"
|
|
363
|
-
fi
|
|
364
|
-
|
|
365
|
-
eagle_db "SELECT p.title, p.project,
|
|
366
|
-
replace(substr(p.content, 1, 200), char(10), ' '),
|
|
367
|
-
p.file_path, p.updated_at
|
|
368
|
-
FROM claude_plans p
|
|
369
|
-
JOIN claude_plans_fts f ON f.rowid = p.id
|
|
370
|
-
WHERE claude_plans_fts MATCH '$query'
|
|
371
|
-
$where_clause
|
|
372
|
-
ORDER BY rank
|
|
373
|
-
LIMIT $limit;"
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
eagle_list_claude_plans() {
|
|
377
|
-
local project="${1:-}"
|
|
378
|
-
local limit; limit=$(eagle_sql_int "${2:-20}")
|
|
379
|
-
|
|
380
|
-
local where_clause=""
|
|
381
|
-
if [ -n "$project" ]; then
|
|
382
|
-
project=$(eagle_sql_escape "$project")
|
|
383
|
-
where_clause="WHERE project = '$project'"
|
|
384
|
-
fi
|
|
385
|
-
|
|
386
|
-
eagle_db "SELECT title, project, file_path, updated_at
|
|
387
|
-
FROM claude_plans
|
|
388
|
-
$where_clause
|
|
389
|
-
ORDER BY updated_at DESC
|
|
390
|
-
LIMIT $limit;"
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
eagle_capture_claude_task() {
|
|
394
|
-
local file_path="$1"
|
|
395
|
-
local session_id="${2:-}"
|
|
396
|
-
local project="${3:-}"
|
|
397
|
-
|
|
398
|
-
[ ! -f "$file_path" ] && return 0
|
|
399
|
-
|
|
400
|
-
local chash
|
|
401
|
-
chash=$(shasum -a 256 "$file_path" | awk '{print $1}')
|
|
402
|
-
|
|
403
|
-
local task_json
|
|
404
|
-
task_json=$(cat "$file_path")
|
|
405
|
-
|
|
406
|
-
local task_id subject desc active_form status blocks blocked_by
|
|
407
|
-
task_id=$(printf '%s' "$task_json" | jq -r '.id // empty')
|
|
408
|
-
subject=$(printf '%s' "$task_json" | jq -r '.subject // empty')
|
|
409
|
-
desc=$(printf '%s' "$task_json" | jq -r '.description // empty')
|
|
410
|
-
active_form=$(printf '%s' "$task_json" | jq -r '.activeForm // empty')
|
|
411
|
-
status=$(printf '%s' "$task_json" | jq -r '.status // "pending"')
|
|
412
|
-
blocks=$(printf '%s' "$task_json" | jq -c '.blocks // []')
|
|
413
|
-
blocked_by=$(printf '%s' "$task_json" | jq -c '.blockedBy // []')
|
|
414
|
-
|
|
415
|
-
[ -z "$task_id" ] && return 0
|
|
416
|
-
|
|
417
|
-
local fp_sql proj_sql sid_sql tid_sql subj_sql desc_sql af_sql status_sql blocks_sql bb_sql hash_sql
|
|
418
|
-
fp_sql=$(eagle_sql_escape "$file_path")
|
|
419
|
-
proj_sql=$(eagle_sql_escape "$project")
|
|
420
|
-
sid_sql=$(eagle_sql_escape "$session_id")
|
|
421
|
-
tid_sql=$(eagle_sql_escape "$task_id")
|
|
422
|
-
subj_sql=$(eagle_sql_escape "$subject")
|
|
423
|
-
desc_sql=$(eagle_sql_escape "$desc")
|
|
424
|
-
af_sql=$(eagle_sql_escape "$active_form")
|
|
425
|
-
status_sql=$(eagle_sql_escape "$status")
|
|
426
|
-
blocks_sql=$(eagle_sql_escape "$blocks")
|
|
427
|
-
bb_sql=$(eagle_sql_escape "$blocked_by")
|
|
428
|
-
hash_sql=$(eagle_sql_escape "$chash")
|
|
429
|
-
|
|
430
|
-
eagle_db_pipe <<SQL
|
|
431
|
-
INSERT INTO claude_tasks (project, source_session_id, source_task_id, file_path, subject, description, active_form, status, blocks, blocked_by, content_hash)
|
|
432
|
-
VALUES ('$proj_sql', '$sid_sql', '$tid_sql', '$fp_sql', '$subj_sql', '$desc_sql', '$af_sql', '$status_sql', '$blocks_sql', '$bb_sql', '$hash_sql')
|
|
433
|
-
ON CONFLICT(file_path) DO UPDATE SET
|
|
434
|
-
subject = excluded.subject,
|
|
435
|
-
description = excluded.description,
|
|
436
|
-
active_form = excluded.active_form,
|
|
437
|
-
status = excluded.status,
|
|
438
|
-
blocks = excluded.blocks,
|
|
439
|
-
blocked_by = excluded.blocked_by,
|
|
440
|
-
content_hash = excluded.content_hash,
|
|
441
|
-
project = CASE WHEN excluded.project != '' THEN excluded.project ELSE claude_tasks.project END,
|
|
442
|
-
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
443
|
-
WHERE claude_tasks.content_hash != excluded.content_hash;
|
|
444
|
-
SQL
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
eagle_list_claude_tasks() {
|
|
448
|
-
local project="${1:-}"
|
|
449
|
-
local limit; limit=$(eagle_sql_int "${2:-20}")
|
|
450
|
-
|
|
451
|
-
local where_clause=""
|
|
452
|
-
if [ -n "$project" ]; then
|
|
453
|
-
project=$(eagle_sql_escape "$project")
|
|
454
|
-
where_clause="WHERE project = '$project'"
|
|
455
|
-
fi
|
|
456
|
-
|
|
457
|
-
eagle_db "SELECT subject, status, source_session_id, source_task_id, updated_at
|
|
458
|
-
FROM claude_tasks
|
|
459
|
-
$where_clause
|
|
460
|
-
ORDER BY updated_at DESC
|
|
461
|
-
LIMIT $limit;"
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
eagle_search_claude_tasks() {
|
|
465
|
-
local query; query=$(eagle_fts_sanitize "$1")
|
|
466
|
-
query=$(eagle_sql_escape "$query")
|
|
467
|
-
local project="${2:-}"
|
|
468
|
-
local limit; limit=$(eagle_sql_int "${3:-10}")
|
|
469
|
-
|
|
470
|
-
local where_clause=""
|
|
471
|
-
if [ -n "$project" ]; then
|
|
472
|
-
project=$(eagle_sql_escape "$project")
|
|
473
|
-
where_clause="AND t.project = '$project'"
|
|
474
|
-
fi
|
|
475
|
-
|
|
476
|
-
eagle_db "SELECT t.subject, t.status,
|
|
477
|
-
replace(substr(t.description, 1, 200), char(10), ' '),
|
|
478
|
-
t.source_session_id, t.source_task_id, t.updated_at
|
|
479
|
-
FROM claude_tasks t
|
|
480
|
-
JOIN claude_tasks_fts f ON f.rowid = t.id
|
|
481
|
-
WHERE claude_tasks_fts MATCH '$query'
|
|
482
|
-
$where_clause
|
|
483
|
-
ORDER BY rank
|
|
484
|
-
LIMIT $limit;"
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
eagle_build_session_project_map() {
|
|
488
|
-
local claude_projects_dir="$HOME/.claude/projects"
|
|
489
|
-
[ ! -d "$claude_projects_dir" ] && return 0
|
|
490
|
-
|
|
491
|
-
for proj_dir in "$claude_projects_dir"/*/; do
|
|
492
|
-
[ ! -d "$proj_dir" ] && continue
|
|
493
|
-
|
|
494
|
-
local project=""
|
|
495
|
-
local sample_jsonl
|
|
496
|
-
sample_jsonl=$(ls "$proj_dir"*.jsonl 2>/dev/null | head -1)
|
|
497
|
-
if [ -n "$sample_jsonl" ] && [ -f "$sample_jsonl" ]; then
|
|
498
|
-
local cwd
|
|
499
|
-
cwd=$(head -10 "$sample_jsonl" | jq -r 'select(.cwd != null) | .cwd' 2>/dev/null | head -1)
|
|
500
|
-
if [ -n "$cwd" ]; then
|
|
501
|
-
project=$(eagle_project_from_cwd "$cwd")
|
|
502
|
-
fi
|
|
503
|
-
fi
|
|
504
|
-
[ -z "$project" ] && continue
|
|
505
|
-
|
|
506
|
-
for jsonl in "$proj_dir"*.jsonl; do
|
|
507
|
-
[ ! -f "$jsonl" ] && continue
|
|
508
|
-
local sid
|
|
509
|
-
sid=$(basename "$jsonl" .jsonl)
|
|
510
|
-
echo "$sid|$project"
|
|
511
|
-
done
|
|
512
|
-
done
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
eagle_backfill_projects() {
|
|
516
|
-
local updated=0
|
|
517
|
-
local map
|
|
518
|
-
map=$(eagle_build_session_project_map)
|
|
519
|
-
[ -z "$map" ] && echo "0" && return 0
|
|
520
|
-
|
|
521
|
-
while IFS='|' read -r sid project; do
|
|
522
|
-
[ -z "$sid" ] || [ -z "$project" ] && continue
|
|
523
|
-
local sid_sql proj_sql
|
|
524
|
-
sid_sql=$(eagle_sql_escape "$sid")
|
|
525
|
-
proj_sql=$(eagle_sql_escape "$project")
|
|
526
|
-
|
|
527
|
-
# All six tables updated atomically per session to prevent
|
|
528
|
-
# partial backfill if the process is interrupted.
|
|
529
|
-
# Note: total_changes() includes FTS trigger changes, so the
|
|
530
|
-
# reported count may be higher than actual rows updated.
|
|
531
|
-
# This is cosmetic — the count is only used for status messages.
|
|
532
|
-
local ch
|
|
533
|
-
ch=$(eagle_db_pipe <<SQL
|
|
534
|
-
BEGIN;
|
|
535
|
-
UPDATE sessions SET project = '$proj_sql' WHERE id = '$sid_sql' AND (project = '' OR project != '$proj_sql');
|
|
536
|
-
UPDATE claude_tasks SET project = '$proj_sql' WHERE source_session_id = '$sid_sql' AND (project = '' OR project != '$proj_sql');
|
|
537
|
-
UPDATE claude_memories SET project = '$proj_sql' WHERE origin_session_id = '$sid_sql' AND (project = '' OR project != '$proj_sql');
|
|
538
|
-
UPDATE claude_plans SET project = '$proj_sql' WHERE origin_session_id = '$sid_sql' AND (project = '' OR project != '$proj_sql');
|
|
539
|
-
UPDATE summaries SET project = '$proj_sql' WHERE session_id = '$sid_sql' AND (project = '' OR project != '$proj_sql');
|
|
540
|
-
UPDATE observations SET project = '$proj_sql' WHERE session_id = '$sid_sql' AND (project = '' OR project != '$proj_sql');
|
|
541
|
-
SELECT total_changes();
|
|
542
|
-
COMMIT;
|
|
543
|
-
SQL
|
|
544
|
-
)
|
|
545
|
-
[ "${ch:-0}" -gt 0 ] && updated=$((updated + ch))
|
|
546
|
-
done <<< "$map"
|
|
547
|
-
|
|
548
|
-
echo "$updated"
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
eagle_prune_orphan_chunks() {
|
|
552
|
-
local project; project=$(eagle_sql_escape "$1")
|
|
553
|
-
local target_dir="$2"
|
|
554
|
-
|
|
555
|
-
# Get all indexed file paths for this project
|
|
556
|
-
local paths
|
|
557
|
-
paths=$(eagle_db "SELECT DISTINCT file_path FROM code_chunks WHERE project = '$project';")
|
|
558
|
-
|
|
559
|
-
local removed=0
|
|
560
|
-
local txn_sql="BEGIN;"
|
|
561
|
-
while IFS= read -r fpath; do
|
|
562
|
-
[ -z "$fpath" ] && continue
|
|
563
|
-
if [ ! -f "$target_dir/$fpath" ]; then
|
|
564
|
-
local fpath_sql; fpath_sql=$(eagle_sql_escape "$fpath")
|
|
565
|
-
txn_sql+="
|
|
566
|
-
DELETE FROM code_chunks WHERE project = '$project' AND file_path = '$fpath_sql';"
|
|
567
|
-
removed=$((removed + 1))
|
|
568
|
-
fi
|
|
569
|
-
done <<< "$paths"
|
|
570
|
-
txn_sql+="
|
|
571
|
-
COMMIT;"
|
|
572
|
-
|
|
573
|
-
if [ "$removed" -gt 0 ]; then
|
|
574
|
-
eagle_db_pipe <<< "$txn_sql"
|
|
575
|
-
fi
|
|
576
|
-
echo "$removed"
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
# ─── Feature graph helpers ─────��───────────────────────────
|
|
580
|
-
|
|
581
|
-
eagle_upsert_feature() {
|
|
582
|
-
local project; project=$(eagle_sql_escape "$1")
|
|
583
|
-
local name; name=$(eagle_sql_escape "$2")
|
|
584
|
-
local description; description=$(eagle_sql_escape "${3:-}")
|
|
585
|
-
|
|
586
|
-
eagle_db "INSERT INTO features (project, name, description)
|
|
587
|
-
VALUES ('$project', '$name', '$description')
|
|
588
|
-
ON CONFLICT(project, name) DO UPDATE SET
|
|
589
|
-
description = COALESCE(NULLIF('$description', ''), features.description),
|
|
590
|
-
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now');"
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
eagle_add_feature_dependency() {
|
|
594
|
-
local feature_id; feature_id=$(eagle_sql_int "$1")
|
|
595
|
-
local kind; kind=$(eagle_sql_escape "$2")
|
|
596
|
-
local target; target=$(eagle_sql_escape "$3")
|
|
597
|
-
local name; name=$(eagle_sql_escape "$4")
|
|
598
|
-
local notes; notes=$(eagle_sql_escape "${5:-}")
|
|
599
|
-
|
|
600
|
-
eagle_db "INSERT OR IGNORE INTO feature_dependencies (feature_id, kind, target, name, notes)
|
|
601
|
-
VALUES ($feature_id, '$kind', '$target', '$name', '$notes');"
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
eagle_add_feature_file() {
|
|
605
|
-
local feature_id; feature_id=$(eagle_sql_int "$1")
|
|
606
|
-
local file_path; file_path=$(eagle_sql_escape "$2")
|
|
607
|
-
local role; role=$(eagle_sql_escape "${3:-}")
|
|
608
|
-
|
|
609
|
-
eagle_db "INSERT OR IGNORE INTO feature_files (feature_id, file_path, role)
|
|
610
|
-
VALUES ($feature_id, '$file_path', '$role');"
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
eagle_add_feature_smoke_test() {
|
|
614
|
-
local feature_id; feature_id=$(eagle_sql_int "$1")
|
|
615
|
-
local command; command=$(eagle_sql_escape "$2")
|
|
616
|
-
local description; description=$(eagle_sql_escape "${3:-}")
|
|
617
|
-
|
|
618
|
-
eagle_db "INSERT INTO feature_smoke_tests (feature_id, command, description)
|
|
619
|
-
VALUES ($feature_id, '$command', '$description');"
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
eagle_verify_feature() {
|
|
623
|
-
local project; project=$(eagle_sql_escape "$1")
|
|
624
|
-
local name; name=$(eagle_sql_escape "$2")
|
|
625
|
-
local notes; notes=$(eagle_sql_escape "${3:-}")
|
|
626
|
-
|
|
627
|
-
eagle_db "UPDATE features SET
|
|
628
|
-
last_verified_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
|
|
629
|
-
last_verified_notes = '$notes',
|
|
630
|
-
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
631
|
-
WHERE project = '$project' AND name = '$name';"
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
eagle_get_feature_id() {
|
|
635
|
-
local project; project=$(eagle_sql_escape "$1")
|
|
636
|
-
local name; name=$(eagle_sql_escape "$2")
|
|
637
|
-
eagle_db "SELECT id FROM features WHERE project = '$project' AND name = '$name';"
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
eagle_list_features() {
|
|
641
|
-
local project; project=$(eagle_sql_escape "$1")
|
|
642
|
-
local limit; limit=$(eagle_sql_int "${2:-20}")
|
|
643
|
-
|
|
644
|
-
eagle_db "SELECT f.name, f.description, f.status, f.last_verified_at,
|
|
645
|
-
(SELECT COUNT(*) FROM feature_dependencies WHERE feature_id = f.id) as dep_count,
|
|
646
|
-
(SELECT COUNT(*) FROM feature_files WHERE feature_id = f.id) as file_count,
|
|
647
|
-
(SELECT COUNT(*) FROM feature_smoke_tests WHERE feature_id = f.id) as test_count
|
|
648
|
-
FROM features f
|
|
649
|
-
WHERE f.project = '$project' AND f.status = 'active'
|
|
650
|
-
ORDER BY f.updated_at DESC
|
|
651
|
-
LIMIT $limit;"
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
eagle_show_feature() {
|
|
655
|
-
local project; project=$(eagle_sql_escape "$1")
|
|
656
|
-
local name; name=$(eagle_sql_escape "$2")
|
|
657
|
-
|
|
658
|
-
local feature_id
|
|
659
|
-
feature_id=$(eagle_get_feature_id "$1" "$2")
|
|
660
|
-
[ -z "$feature_id" ] && return 1
|
|
661
|
-
|
|
662
|
-
echo "=== Feature: $2 ==="
|
|
663
|
-
eagle_db "SELECT name, description, status, last_verified_at, last_verified_notes
|
|
664
|
-
FROM features WHERE id = $feature_id;"
|
|
665
|
-
|
|
666
|
-
local deps
|
|
667
|
-
deps=$(eagle_db "SELECT kind, target, name, notes FROM feature_dependencies WHERE feature_id = $feature_id;")
|
|
668
|
-
if [ -n "$deps" ]; then
|
|
669
|
-
echo "--- Dependencies ---"
|
|
670
|
-
echo "$deps"
|
|
671
|
-
fi
|
|
672
|
-
|
|
673
|
-
local files
|
|
674
|
-
files=$(eagle_db "SELECT file_path, role FROM feature_files WHERE feature_id = $feature_id;")
|
|
675
|
-
if [ -n "$files" ]; then
|
|
676
|
-
echo "--- Files ---"
|
|
677
|
-
echo "$files"
|
|
678
|
-
fi
|
|
679
|
-
|
|
680
|
-
local tests
|
|
681
|
-
tests=$(eagle_db "SELECT command, description FROM feature_smoke_tests WHERE feature_id = $feature_id;")
|
|
682
|
-
if [ -n "$tests" ]; then
|
|
683
|
-
echo "--- Smoke Tests ---"
|
|
684
|
-
echo "$tests"
|
|
685
|
-
fi
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
eagle_find_features_for_file() {
|
|
689
|
-
local project; project=$(eagle_sql_escape "$1")
|
|
690
|
-
local file_path="$2"
|
|
691
|
-
local fname; fname=$(basename "$file_path")
|
|
692
|
-
local fname_esc; fname_esc=$(eagle_sql_escape "$fname")
|
|
693
|
-
|
|
694
|
-
eagle_db "SELECT f.name, f.description, f.last_verified_at,
|
|
695
|
-
ff.role,
|
|
696
|
-
(SELECT GROUP_CONCAT(fd.target || ':' || fd.name, ', ')
|
|
697
|
-
FROM feature_dependencies fd WHERE fd.feature_id = f.id) as deps,
|
|
698
|
-
(SELECT GROUP_CONCAT(ff2.file_path, ', ')
|
|
699
|
-
FROM feature_files ff2 WHERE ff2.feature_id = f.id AND ff2.file_path != ff.file_path) as other_files,
|
|
700
|
-
(SELECT GROUP_CONCAT(fst.command, ', ')
|
|
701
|
-
FROM feature_smoke_tests fst WHERE fst.feature_id = f.id) as smoke_tests
|
|
702
|
-
FROM features f
|
|
703
|
-
JOIN feature_files ff ON ff.feature_id = f.id
|
|
704
|
-
WHERE f.project = '$project'
|
|
705
|
-
AND f.status = 'active'
|
|
706
|
-
AND (ff.file_path LIKE '%$fname_esc' OR ff.file_path LIKE '%$fname_esc%')
|
|
707
|
-
ORDER BY f.updated_at DESC
|
|
708
|
-
LIMIT 3;"
|
|
709
|
-
}
|
|
6
|
+
[ -n "${_EAGLE_DB_LOADED:-}" ] && return 0
|
|
7
|
+
_EAGLE_DB_LOADED=1
|
|
8
|
+
|
|
9
|
+
_eagle_db_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
|
+
|
|
11
|
+
. "$_eagle_db_dir/db-core.sh"
|
|
12
|
+
. "$_eagle_db_dir/db-sessions.sh"
|
|
13
|
+
. "$_eagle_db_dir/db-observations.sh"
|
|
14
|
+
. "$_eagle_db_dir/db-summaries.sh"
|
|
15
|
+
. "$_eagle_db_dir/db-mirrors.sh"
|
|
16
|
+
. "$_eagle_db_dir/db-features.sh"
|
|
17
|
+
. "$_eagle_db_dir/db-backfill.sh"
|