eagle-mem 1.0.0

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,161 @@
1
+ #!/usr/bin/env bash
2
+ # ═══════════════════════════════════════════════════════════
3
+ # Eagle Mem — SessionStart hook
4
+ # Fires on: startup, resume, clear, compact
5
+ # Injects project memory + pending tasks into Claude's context
6
+ # ═══════════════════════════════════════════════════════════
7
+ set +e
8
+
9
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
10
+ LIB_DIR="$SCRIPT_DIR/../lib"
11
+
12
+ . "$LIB_DIR/common.sh"
13
+ . "$LIB_DIR/db.sh"
14
+
15
+ eagle_ensure_db
16
+
17
+ input=$(eagle_read_stdin)
18
+ [ -z "$input" ] && exit 0
19
+
20
+ session_id=$(echo "$input" | jq -r '.session_id // empty')
21
+ cwd=$(echo "$input" | jq -r '.cwd // empty')
22
+ source_type=$(echo "$input" | jq -r '.source // empty')
23
+ model=$(echo "$input" | jq -r '.model // empty')
24
+
25
+ [ -z "$session_id" ] && exit 0
26
+
27
+ project=$(eagle_project_from_cwd "$cwd")
28
+ project_sql=$(eagle_sql_escape "$project")
29
+
30
+ eagle_log "INFO" "SessionStart: session=$session_id project=$project source=$source_type"
31
+
32
+ eagle_upsert_session "$session_id" "$project" "$cwd" "$model" "$source_type"
33
+
34
+ # ─── Build context injection ────────────────────────────────
35
+
36
+ context=""
37
+
38
+ # Project overview (if one exists)
39
+ overview=$(eagle_get_overview "$project")
40
+ if [ -n "$overview" ]; then
41
+ context+="=== EAGLE MEM — Project Overview ===
42
+ $overview
43
+
44
+ "
45
+ fi
46
+
47
+ # Recent summaries for this project (last 5 sessions)
48
+ recent=$(eagle_db "
49
+ SELECT s.request, s.completed, s.learned, s.next_steps, s.created_at
50
+ FROM summaries s
51
+ WHERE s.project = '$project_sql'
52
+ ORDER BY s.created_at DESC
53
+ LIMIT 5;
54
+ ")
55
+
56
+ if [ -n "$recent" ]; then
57
+ context+="=== EAGLE MEM ===
58
+ Recent sessions for project '$project':
59
+ "
60
+ while IFS='|' read -r request completed learned next_steps created_at; do
61
+ [ -z "$request" ] && [ -z "$completed" ] && continue
62
+ context+="
63
+ --- $created_at ---"
64
+ [ -n "$request" ] && context+="
65
+ Request: $request"
66
+ [ -n "$completed" ] && context+="
67
+ Completed: $completed"
68
+ [ -n "$learned" ] && context+="
69
+ Learned: $learned"
70
+ [ -n "$next_steps" ] && context+="
71
+ Next steps: $next_steps"
72
+ done <<< "$recent"
73
+ context+="
74
+ "
75
+ fi
76
+
77
+ # Pending tasks from TaskAware loop
78
+ pending_tasks=$(eagle_db "
79
+ SELECT id, title, instructions, status
80
+ FROM tasks
81
+ WHERE project = '$project_sql' AND status IN ('pending', 'active')
82
+ ORDER BY ordinal ASC, id ASC
83
+ LIMIT 10;
84
+ ")
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; 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_db "
109
+ SELECT id, title, instructions, context_snapshot
110
+ FROM tasks
111
+ WHERE project = '$project_sql' AND status = 'active'
112
+ ORDER BY ordinal ASC, id ASC
113
+ LIMIT 1;
114
+ ")
115
+
116
+ if [ -z "$active_task" ] && [ -n "$first_pending" ]; then
117
+ # Auto-activate the next pending task
118
+ eagle_db "UPDATE tasks SET status = 'active', started_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = $first_pending;"
119
+ active_task=$(eagle_db "
120
+ SELECT id, title, instructions, context_snapshot
121
+ FROM tasks
122
+ WHERE id = $first_pending;
123
+ ")
124
+ fi
125
+
126
+ if [ -n "$active_task" ]; then
127
+ IFS='|' read -r atid atitle ainstructions asnapshot <<< "$active_task"
128
+ context+="
129
+ Current task (#$atid): $atitle
130
+ "
131
+ [ -n "$ainstructions" ] && context+="Instructions: $ainstructions
132
+ "
133
+ [ -n "$asnapshot" ] && context+="Context: $asnapshot
134
+ "
135
+ context+="When done, tell the user to run /compact so Eagle Mem can save progress and load the next task.
136
+ "
137
+ fi
138
+ fi
139
+
140
+ # Emit the eagle-summary instruction
141
+ context+="
142
+ === EAGLE MEM INSTRUCTIONS ===
143
+ Before your final response in this session, emit a summary block:
144
+ <eagle-summary>
145
+ request: What the user asked for
146
+ investigated: Key files/areas explored
147
+ learned: Non-obvious discoveries
148
+ completed: What was accomplished
149
+ next_steps: What should happen next
150
+ files_read: [list of files read]
151
+ files_modified: [list of files modified]
152
+ </eagle-summary>
153
+ This helps Eagle Mem track what happened for future sessions.
154
+ "
155
+
156
+ # Output context (plain text stdout = additionalContext for SessionStart)
157
+ if [ -n "$context" ]; then
158
+ echo "$context"
159
+ fi
160
+
161
+ exit 0
package/hooks/stop.sh ADDED
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env bash
2
+ # ═══════════════════════════════════════════════════════════
3
+ # Eagle Mem — Stop hook
4
+ # Fires when Claude's turn ends
5
+ # Parses <eagle-summary> from transcript, saves to DB
6
+ # Falls back to heuristic extraction if no summary block
7
+ # ═══════════════════════════════════════════════════════════
8
+ set +e
9
+
10
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
11
+ LIB_DIR="$SCRIPT_DIR/../lib"
12
+
13
+ . "$LIB_DIR/common.sh"
14
+ . "$LIB_DIR/db.sh"
15
+
16
+ eagle_ensure_db
17
+
18
+ input=$(eagle_read_stdin)
19
+ [ -z "$input" ] && exit 0
20
+
21
+ session_id=$(echo "$input" | jq -r '.session_id // empty')
22
+ cwd=$(echo "$input" | jq -r '.cwd // empty')
23
+ transcript_path=$(echo "$input" | jq -r '.transcript_path // empty')
24
+
25
+ [ -z "$session_id" ] && exit 0
26
+
27
+ # Skip subagent contexts
28
+ agent_type=$(echo "$input" | jq -r '.agent_type // empty')
29
+ [ -n "$agent_type" ] && [ "$agent_type" != "main" ] && exit 0
30
+
31
+ project=$(eagle_project_from_cwd "$cwd")
32
+ project_sql=$(eagle_sql_escape "$project")
33
+
34
+ eagle_log "INFO" "Stop: session=$session_id project=$project transcript=$transcript_path"
35
+
36
+ # Ensure session exists (may not if SessionStart didn't fire)
37
+ eagle_upsert_session "$session_id" "$project" "$cwd" "" ""
38
+
39
+ # ─── Try to parse <eagle-summary> from transcript ──────────
40
+
41
+ summary_block=""
42
+ if [ -n "$transcript_path" ] && [ -f "$transcript_path" ]; then
43
+ # Extract all text from the last assistant message using jq on JSONL
44
+ # Real transcript format: top-level .type == "assistant", content in .message.content[]
45
+ text_content=$(jq -rs '
46
+ [.[] | select(.type == "assistant")] | last |
47
+ if . then
48
+ [.message.content[]? | select(.type == "text") | .text] | join("\n")
49
+ else "" end
50
+ ' "$transcript_path" 2>/dev/null)
51
+
52
+ # Strip <private>...</private> blocks before processing
53
+ text_content=$(echo "$text_content" | sed '/<private>/,/<\/private>/d')
54
+
55
+ # Parse <eagle-summary> block
56
+ if [ -n "$text_content" ] && echo "$text_content" | grep -q '<eagle-summary>' 2>/dev/null; then
57
+ summary_block=$(echo "$text_content" | sed -n '/<eagle-summary>/,/<\/eagle-summary>/p' | sed '1d;$d')
58
+ fi
59
+ fi
60
+
61
+ # ─── Extract fields from summary block ─────────────────────
62
+
63
+ parse_field() {
64
+ local block="$1"
65
+ local field="$2"
66
+ echo "$block" | awk -v f="$field" '
67
+ BEGIN { IGNORECASE=1; found=0 }
68
+ $0 ~ "^"f":" {
69
+ sub("^"f":[[:space:]]*", ""); found=1; val=$0; next
70
+ }
71
+ found && /^(request|investigated|learned|completed|next_steps|files_read|files_modified|notes):/ { exit }
72
+ found { val = val " " $0 }
73
+ END { if (found) print val }
74
+ '
75
+ }
76
+
77
+ request=""
78
+ investigated=""
79
+ learned=""
80
+ completed=""
81
+ next_steps=""
82
+ files_read="[]"
83
+ files_modified="[]"
84
+ notes=""
85
+
86
+ if [ -n "$summary_block" ]; then
87
+ request=$(parse_field "$summary_block" "request")
88
+ investigated=$(parse_field "$summary_block" "investigated")
89
+ learned=$(parse_field "$summary_block" "learned")
90
+ completed=$(parse_field "$summary_block" "completed")
91
+ next_steps=$(parse_field "$summary_block" "next_steps")
92
+
93
+ raw_fr=$(parse_field "$summary_block" "files_read")
94
+ raw_fm=$(parse_field "$summary_block" "files_modified")
95
+
96
+ # Convert bracket-list to JSON array (handles special chars safely)
97
+ to_json_array() {
98
+ local raw="$1"
99
+ raw=$(echo "$raw" | sed 's/^\[//;s/\]$//')
100
+ echo "$raw" | tr ',' '\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | grep -v '^$' | jq -Rsc 'split("\n") | map(select(. != ""))'
101
+ }
102
+
103
+ [ -n "$raw_fr" ] && files_read=$(to_json_array "$raw_fr")
104
+ [ -n "$raw_fm" ] && files_modified=$(to_json_array "$raw_fm")
105
+
106
+ eagle_log "INFO" "Stop: parsed eagle-summary block"
107
+ fi
108
+
109
+ # ─── Heuristic fallback: extract from tool calls ───────────
110
+
111
+ if [ -z "$request" ] && [ -n "$transcript_path" ] && [ -f "$transcript_path" ]; then
112
+ eagle_log "INFO" "Stop: no eagle-summary found, using heuristic fallback"
113
+
114
+ # Extract first user prompt as "request"
115
+ request=$(jq -r 'select(.type == "user") | .message.content | if type == "string" then . elif type == "array" then [.[] | select(.type == "text") | .text] | join(" ") else "" end' "$transcript_path" 2>/dev/null | head -1 | cut -c1-500)
116
+
117
+ # Extract files from Read/Write/Edit tool calls
118
+ heuristic_reads=$(jq -r 'select(.type == "assistant") | .message.content[]? | select(.type == "tool_use") | select(.name == "Read") | .input.file_path // empty' "$transcript_path" 2>/dev/null | sort -u | head -20)
119
+ heuristic_writes=$(jq -r 'select(.type == "assistant") | .message.content[]? | select(.type == "tool_use") | select(.name == "Write" or .name == "Edit") | .input.file_path // empty' "$transcript_path" 2>/dev/null | sort -u | head -20)
120
+
121
+ if [ -n "$heuristic_reads" ]; then
122
+ files_read=$(echo "$heuristic_reads" | jq -Rsc 'split("\n") | map(select(. != ""))')
123
+ fi
124
+ if [ -n "$heuristic_writes" ]; then
125
+ files_modified=$(echo "$heuristic_writes" | jq -Rsc 'split("\n") | map(select(. != ""))')
126
+ fi
127
+
128
+ completed="(auto-captured from tool usage)"
129
+ fi
130
+
131
+ # ─── Write to database ─────────────────────────────────────
132
+
133
+ if [ -n "$request" ] || [ -n "$completed" ] || [ -n "$learned" ]; then
134
+ eagle_insert_summary "$session_id" "$project" "$request" "$investigated" "$learned" "$completed" "$next_steps" "$files_read" "$files_modified" "$notes"
135
+ eagle_log "INFO" "Stop: summary saved for session=$session_id"
136
+ fi
137
+
138
+ # Mark active task as done if eagle-summary mentions completion
139
+ if [ -n "$completed" ]; then
140
+ active_task_id=$(eagle_db "SELECT id FROM tasks WHERE project = '$project_sql' AND status = 'active' LIMIT 1;")
141
+ if [ -n "$active_task_id" ]; then
142
+ eagle_db "UPDATE tasks SET status = 'done', completed_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = $active_task_id;"
143
+ eagle_log "INFO" "Stop: marked task #$active_task_id as done"
144
+ fi
145
+ fi
146
+
147
+ exit 0
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env bash
2
+ # ═══════════════════════════════════════════════════════════
3
+ # Eagle Mem — UserPromptSubmit hook
4
+ # Fires when the user submits a prompt
5
+ # Searches memory for relevant context and injects it
6
+ # ═══════════════════════════════════════════════════════════
7
+ set +e
8
+
9
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
10
+ LIB_DIR="$SCRIPT_DIR/../lib"
11
+
12
+ . "$LIB_DIR/common.sh"
13
+ . "$LIB_DIR/db.sh"
14
+
15
+ [ ! -f "$EAGLE_MEM_DB" ] && exit 0
16
+
17
+ input=$(eagle_read_stdin)
18
+ [ -z "$input" ] && exit 0
19
+
20
+ session_id=$(echo "$input" | jq -r '.session_id // empty')
21
+ cwd=$(echo "$input" | jq -r '.cwd // empty')
22
+ user_prompt=$(echo "$input" | jq -r '.prompt // empty')
23
+
24
+ [ -z "$user_prompt" ] && exit 0
25
+
26
+ project=$(eagle_project_from_cwd "$cwd")
27
+ project_sql=$(eagle_sql_escape "$project")
28
+
29
+ # Skip short prompts — not enough signal for meaningful search
30
+ word_count=$(echo "$user_prompt" | wc -w | tr -d ' ')
31
+ [ "$word_count" -lt 3 ] && exit 0
32
+
33
+ # Build FTS5 query from significant words (drop stop words, take first 6)
34
+ fts_query=$(echo "$user_prompt" | tr -cs '[:alnum:]' ' ' | tr '[:upper:]' '[:lower:]' | \
35
+ awk '{
36
+ split("the a an is are was were be been being have has had do does did will would shall should may might can could of in to for on with at by from", sw, " ")
37
+ for (i in sw) stop[sw[i]]=1
38
+ n=0
39
+ for (i=1; i<=NF && n<6; i++) {
40
+ if (length($i) > 2 && !($i in stop)) {
41
+ printf "%s%s", (n>0?" OR ":""), $i; n++
42
+ }
43
+ }
44
+ }')
45
+
46
+ [ -z "$fts_query" ] && exit 0
47
+
48
+ fts_query_escaped=$(eagle_sql_escape "$fts_query")
49
+
50
+ # Search for relevant past summaries (cross-session)
51
+ results=$(eagle_db "SELECT s.request, s.learned, s.completed, s.created_at
52
+ FROM summaries s
53
+ JOIN summaries_fts f ON f.rowid = s.id
54
+ WHERE summaries_fts MATCH '$fts_query_escaped'
55
+ AND s.project = '$project_sql'
56
+ ORDER BY rank
57
+ LIMIT 3;")
58
+
59
+ context=""
60
+
61
+ if [ -n "$results" ]; then
62
+ context+="=== EAGLE MEM — Relevant Memory ===
63
+ "
64
+ while IFS='|' read -r req learned completed created_at; do
65
+ [ -z "$req" ] && [ -z "$completed" ] && continue
66
+ context+="[$created_at] "
67
+ [ -n "$req" ] && context+="$req"
68
+ [ -n "$completed" ] && context+=" → $completed"
69
+ [ -n "$learned" ] && context+=" (Learned: $learned)"
70
+ context+="
71
+ "
72
+ done <<< "$results"
73
+ fi
74
+
75
+ # Search indexed code chunks (if any exist for this project)
76
+ has_chunks=$(eagle_db "SELECT COUNT(*) FROM code_chunks WHERE project = '$project_sql' LIMIT 1;")
77
+ if [ "${has_chunks:-0}" -gt 0 ]; then
78
+ code_results=$(eagle_db "SELECT c.file_path, c.start_line, c.end_line, c.language
79
+ FROM code_chunks c
80
+ JOIN code_chunks_fts f ON f.rowid = c.id
81
+ WHERE code_chunks_fts MATCH '$fts_query_escaped'
82
+ AND c.project = '$project_sql'
83
+ ORDER BY rank
84
+ LIMIT 5;")
85
+
86
+ if [ -n "$code_results" ]; then
87
+ context+="=== EAGLE MEM — Relevant Code ===
88
+ "
89
+ while IFS='|' read -r fpath sline eline lang; do
90
+ [ -z "$fpath" ] && continue
91
+ context+="$fpath:${sline}-${eline}"
92
+ [ -n "$lang" ] && context+=" ($lang)"
93
+ context+="
94
+ "
95
+ done <<< "$code_results"
96
+ fi
97
+ fi
98
+
99
+ [ -z "$context" ] && exit 0
100
+
101
+ echo "$context"
102
+ exit 0
package/lib/common.sh ADDED
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env bash
2
+ # ═══════════════════════════════════════════════════════════
3
+ # Eagle Mem — Shared constants and helpers
4
+ # Source this file: . "$(dirname "$0")/../lib/common.sh"
5
+ # ═══════════════════════════════════════════════════════════
6
+
7
+ EAGLE_MEM_DIR="${EAGLE_MEM_DIR:-$HOME/.eagle-mem}"
8
+ EAGLE_MEM_DB="$EAGLE_MEM_DIR/memory.db"
9
+ EAGLE_MEM_LOG="$EAGLE_MEM_DIR/eagle-mem.log"
10
+
11
+ eagle_log() {
12
+ local level="$1"
13
+ shift
14
+ echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] [$level] $*" >> "$EAGLE_MEM_LOG" 2>/dev/null || true
15
+ }
16
+
17
+ eagle_project_from_cwd() {
18
+ local cwd="${1:-$(pwd)}"
19
+ basename "$cwd"
20
+ }
21
+
22
+ eagle_sql_escape() {
23
+ printf '%s' "$1" | sed "s/'/''/g"
24
+ }
25
+
26
+ eagle_read_stdin() {
27
+ local input=""
28
+ if [ ! -t 0 ]; then
29
+ input=$(cat)
30
+ fi
31
+ echo "$input"
32
+ }
33
+
34
+ # Collect project files into a destination file.
35
+ # Uses git ls-files when available, falls back to find with common exclusions.
36
+ # Usage: eagle_collect_files <target_dir> <output_file>
37
+ eagle_collect_files() {
38
+ local target_dir="$1"
39
+ local output_file="$2"
40
+
41
+ if git -C "$target_dir" rev-parse --is-inside-work-tree &>/dev/null; then
42
+ git -C "$target_dir" ls-files --cached --others --exclude-standard > "$output_file"
43
+ else
44
+ (cd "$target_dir" && find . -type f \
45
+ -not -path '*/node_modules/*' \
46
+ -not -path '*/.git/*' \
47
+ -not -path '*/dist/*' \
48
+ -not -path '*/build/*' \
49
+ -not -path '*/.next/*' \
50
+ -not -path '*/target/*' \
51
+ -not -path '*/vendor/*' \
52
+ -not -path '*/__pycache__/*' \
53
+ -not -path '*/.venv/*' \
54
+ -not -path '*/venv/*' \
55
+ -not -path '*/.egg-info/*' \
56
+ -not -name '*.pyc' \
57
+ -not -name '*.lock' \
58
+ -not -name 'package-lock.json' \
59
+ -not -name 'yarn.lock' \
60
+ -not -name 'pnpm-lock.yaml' \
61
+ | sed 's|^\./||') > "$output_file"
62
+ fi
63
+ }
package/lib/db.sh ADDED
@@ -0,0 +1,186 @@
1
+ #!/usr/bin/env bash
2
+ # ═══════════════════════════════════════════════════════════
3
+ # Eagle Mem — Database helpers
4
+ # Source after common.sh: . "$(dirname "$0")/../lib/db.sh"
5
+ # ═══════════════════════════════════════════════════════════
6
+
7
+ EAGLE_DB_SETUP=".headers off
8
+ .output /dev/null
9
+ PRAGMA journal_mode=WAL;
10
+ PRAGMA synchronous=NORMAL;
11
+ PRAGMA busy_timeout=5000;
12
+ PRAGMA foreign_keys=ON;
13
+ PRAGMA trusted_schema=ON;
14
+ .output stdout"
15
+
16
+ eagle_db() {
17
+ { echo "$EAGLE_DB_SETUP"; echo "$*"; } | sqlite3 "$EAGLE_MEM_DB" 2>/dev/null
18
+ }
19
+
20
+ eagle_db_pipe() {
21
+ { echo "$EAGLE_DB_SETUP"; cat; } | sqlite3 "$EAGLE_MEM_DB" 2>/dev/null
22
+ }
23
+
24
+ eagle_db_json() {
25
+ { echo "$EAGLE_DB_SETUP"; echo ".mode json"; echo "$*"; } | sqlite3 "$EAGLE_MEM_DB" 2>/dev/null
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="$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)
44
+ VALUES ('$session_id', '$project', '$cwd', '$model', '$source')
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
+ }
50
+
51
+ eagle_end_session() {
52
+ local session_id="$1"
53
+ eagle_db "UPDATE sessions SET status = 'completed', ended_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = '$session_id';"
54
+ }
55
+
56
+ eagle_insert_observation() {
57
+ local session_id="$1"
58
+ local project; project=$(eagle_sql_escape "$2")
59
+ local tool_name; tool_name=$(eagle_sql_escape "$3")
60
+ local tool_input_summary; tool_input_summary=$(eagle_sql_escape "$4")
61
+ local files_read="$5"
62
+ local files_modified="$6"
63
+
64
+ eagle_db "INSERT INTO observations (session_id, project, tool_name, tool_input_summary, files_read, files_modified)
65
+ VALUES ('$session_id', '$project', '$tool_name', '$tool_input_summary', '$files_read', '$files_modified');"
66
+ }
67
+
68
+ eagle_insert_summary() {
69
+ local session_id; session_id=$(eagle_sql_escape "$1")
70
+ local project; project=$(eagle_sql_escape "$2")
71
+ local request; request=$(eagle_sql_escape "$3")
72
+ local investigated; investigated=$(eagle_sql_escape "$4")
73
+ local learned; learned=$(eagle_sql_escape "$5")
74
+ local completed; completed=$(eagle_sql_escape "$6")
75
+ local next_steps; next_steps=$(eagle_sql_escape "$7")
76
+ local files_read; files_read=$(eagle_sql_escape "$8")
77
+ local files_modified; files_modified=$(eagle_sql_escape "$9")
78
+ local notes; notes=$(eagle_sql_escape "${10:-}")
79
+
80
+ eagle_db_pipe <<SQL
81
+ INSERT INTO summaries (session_id, project, request, investigated, learned, completed, next_steps, files_read, files_modified, notes)
82
+ VALUES (
83
+ '$session_id',
84
+ '$project',
85
+ '$request',
86
+ '$investigated',
87
+ '$learned',
88
+ '$completed',
89
+ '$next_steps',
90
+ '$files_read',
91
+ '$files_modified',
92
+ '$notes'
93
+ );
94
+ SQL
95
+ }
96
+
97
+ eagle_get_recent_summaries() {
98
+ local project; project=$(eagle_sql_escape "$1")
99
+ local limit="${2:-5}"
100
+
101
+ eagle_db "SELECT s.request, s.completed, s.learned, s.next_steps, s.created_at
102
+ FROM summaries s
103
+ WHERE s.project = '$project'
104
+ ORDER BY s.created_at DESC
105
+ LIMIT $limit;"
106
+ }
107
+
108
+ eagle_search_summaries() {
109
+ local query; query=$(eagle_sql_escape "$1")
110
+ local project="${2:-}"
111
+ local limit="${3:-10}"
112
+
113
+ local where_clause=""
114
+ if [ -n "$project" ]; then
115
+ project=$(eagle_sql_escape "$project")
116
+ where_clause="AND s.project = '$project'"
117
+ fi
118
+
119
+ eagle_db "SELECT s.request, s.completed, s.learned, s.next_steps, s.created_at, s.project
120
+ FROM summaries s
121
+ JOIN summaries_fts f ON f.rowid = s.id
122
+ WHERE summaries_fts MATCH '$query'
123
+ $where_clause
124
+ ORDER BY rank
125
+ LIMIT $limit;"
126
+ }
127
+
128
+ eagle_get_pending_tasks() {
129
+ local project; project=$(eagle_sql_escape "$1")
130
+
131
+ eagle_db "SELECT id, title, instructions, status, ordinal
132
+ FROM tasks
133
+ WHERE project = '$project' AND status IN ('pending', 'active')
134
+ ORDER BY ordinal ASC, id ASC;"
135
+ }
136
+
137
+ eagle_get_next_task() {
138
+ local project; project=$(eagle_sql_escape "$1")
139
+
140
+ eagle_db "SELECT id, title, instructions, context_snapshot
141
+ FROM tasks
142
+ WHERE project = '$project' AND status = 'pending'
143
+ ORDER BY ordinal ASC, id ASC
144
+ LIMIT 1;"
145
+ }
146
+
147
+ eagle_get_active_files() {
148
+ local project; project=$(eagle_sql_escape "$1")
149
+ local limit="${2:-20}"
150
+
151
+ eagle_db "SELECT json_each.value
152
+ FROM observations, json_each(observations.files_modified)
153
+ WHERE observations.project = '$project'
154
+ GROUP BY json_each.value
155
+ ORDER BY MAX(observations.created_at) DESC
156
+ LIMIT $limit;"
157
+ }
158
+
159
+ eagle_observation_exists() {
160
+ local session_id="$1"
161
+ local tool_name; tool_name=$(eagle_sql_escape "$2")
162
+ local tool_summary; tool_summary=$(eagle_sql_escape "$3")
163
+
164
+ eagle_db "SELECT COUNT(*) FROM observations
165
+ WHERE session_id = '$session_id'
166
+ AND tool_name = '$tool_name'
167
+ AND tool_input_summary = '$tool_summary'
168
+ AND created_at > strftime('%Y-%m-%dT%H:%M:%fZ', 'now', '-5 seconds');"
169
+ }
170
+
171
+ eagle_upsert_overview() {
172
+ local project; project=$(eagle_sql_escape "$1")
173
+ local content; content=$(eagle_sql_escape "$2")
174
+
175
+ eagle_db "INSERT INTO overviews (project, content, updated_at)
176
+ VALUES ('$project', '$content', strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
177
+ ON CONFLICT(project) DO UPDATE SET
178
+ content = excluded.content,
179
+ updated_at = excluded.updated_at;"
180
+ }
181
+
182
+ eagle_get_overview() {
183
+ local project; project=$(eagle_sql_escape "$1")
184
+
185
+ eagle_db "SELECT content FROM overviews WHERE project = '$project';"
186
+ }