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
|
@@ -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
|
+
}
|