eagle-mem 4.0.3 → 4.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -3
- package/db/021_file_hints.sql +15 -0
- package/hooks/post-tool-use.sh +5 -0
- package/hooks/pre-tool-use.sh +40 -2
- package/hooks/session-start.sh +31 -0
- package/hooks/stop.sh +9 -10
- package/lib/db-core.sh +0 -1
- package/lib/db-hints.sh +67 -0
- package/lib/db.sh +1 -0
- package/package.json +1 -1
- package/scripts/curate.sh +163 -1
- package/scripts/install.sh +8 -0
- package/scripts/update.sh +2 -0
package/README.md
CHANGED
|
@@ -75,8 +75,8 @@ Six hooks fire automatically at different points in Claude Code's lifecycle:
|
|
|
75
75
|
|
|
76
76
|
| Hook | Fires when | What it does |
|
|
77
77
|
|------|-----------|--------------|
|
|
78
|
-
| **SessionStart** | startup, resume, clear, compact | Injects overview, summaries, memories, tasks. Auto-provisions new projects (scan, index). |
|
|
79
|
-
| **PreToolUse** | before Bash and
|
|
78
|
+
| **SessionStart** | startup, resume, clear, compact | Injects overview, summaries, memories, tasks, core files, working set. Auto-provisions new projects (scan, index). |
|
|
79
|
+
| **PreToolUse** | before Bash, Read, Edit, and Write calls | Rewrites noisy commands (learned rules), detects redundant reads, nudges co-edit partners |
|
|
80
80
|
| **UserPromptSubmit** | user sends a message | FTS5 search for relevant past context |
|
|
81
81
|
| **PostToolUse** | after tool calls | Records file touches, mirrors memory/plan/task writes, tracks modifications |
|
|
82
82
|
| **Stop** | Claude's turn ends | Extracts `<eagle-summary>`, strips `<private>` tags |
|
|
@@ -89,7 +89,7 @@ These run automatically via SessionStart — no commands needed:
|
|
|
89
89
|
- **Auto-scan** — new project with no overview triggers a codebase scan
|
|
90
90
|
- **Auto-index** — new or stale project triggers FTS5 source indexing
|
|
91
91
|
- **Auto-prune** — observations over 10K rows trigger cleanup
|
|
92
|
-
- **Auto-curate** — the self-learning curator analyzes observation data and generates
|
|
92
|
+
- **Auto-curate** — the self-learning curator analyzes observation data and generates command rules, co-edit patterns, and hot file detection (partially requires LLM provider)
|
|
93
93
|
|
|
94
94
|
### Token savings
|
|
95
95
|
|
|
@@ -99,6 +99,10 @@ Eagle Mem actively reduces token consumption:
|
|
|
99
99
|
- **Command rewriting** — PreToolUse rewrites noisy Bash commands to pipe through `head -N` via `updatedInput`. Rules are learned by the curator from real usage, not hardcoded.
|
|
100
100
|
- **Read-after-modify detection** — detects when you read a file that was just edited or written, nudges that the diff is already in context
|
|
101
101
|
- **Read dedup tracking** — files read 3+ times in a session get a soft nudge
|
|
102
|
+
- **Co-edit nudges** — learned from observation data: when you edit file X, PreToolUse reminds you that you usually also touch file Y
|
|
103
|
+
- **Hot file awareness** — curator identifies files read in 50%+ of sessions; SessionStart flags them as "likely in context" to reduce re-reads
|
|
104
|
+
- **Working set recovery** — on compact, SessionStart injects the files you were actively editing so you resume without re-reading everything
|
|
105
|
+
- **Stuck loop detection** — if the same file is edited 5+ times in one session, PreToolUse nudges to reconsider the approach
|
|
102
106
|
|
|
103
107
|
### Data
|
|
104
108
|
|
|
@@ -112,6 +116,7 @@ Single SQLite database at `~/.eagle-mem/memory.db` (WAL mode, FTS5 full-text sea
|
|
|
112
116
|
| overviews | One overview per project (auto-scan or manual) |
|
|
113
117
|
| code_chunks | FTS5-indexed source file chunks |
|
|
114
118
|
| command_rules | Curator-learned command output rules |
|
|
119
|
+
| file_hints | Curator-learned file access patterns (co-edit pairs) |
|
|
115
120
|
| claude_memories | Mirror of Claude Code auto-memories |
|
|
116
121
|
| claude_plans | Mirror of Claude Code plans |
|
|
117
122
|
| claude_tasks | Mirror of Claude Code tasks |
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
-- Migration 021: File hints — learned file access patterns from curator
|
|
2
|
+
-- Stores co-edit patterns, hot files, and other learned behaviors.
|
|
3
|
+
|
|
4
|
+
CREATE TABLE IF NOT EXISTS file_hints (
|
|
5
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6
|
+
project TEXT NOT NULL,
|
|
7
|
+
hint_type TEXT NOT NULL CHECK (hint_type IN ('co_edit', 'hot_file', 'read_threshold', 'session_profile')),
|
|
8
|
+
file_path TEXT NOT NULL DEFAULT '',
|
|
9
|
+
hint_value TEXT NOT NULL,
|
|
10
|
+
created_at TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
11
|
+
updated_at TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
12
|
+
UNIQUE(project, hint_type, file_path)
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
CREATE INDEX IF NOT EXISTS idx_file_hints_lookup ON file_hints(project, hint_type, file_path);
|
package/hooks/post-tool-use.sh
CHANGED
|
@@ -113,6 +113,11 @@ if [ -n "$fp" ] && [ -n "$session_id" ] && eagle_validate_session_id "$session_i
|
|
|
113
113
|
_mod_tmp=$(mktemp "${mod_file}.XXXXXX" 2>/dev/null) || _mod_tmp="${mod_file}.$$"
|
|
114
114
|
tail -3 "$mod_file" > "$_mod_tmp" && mv "$_mod_tmp" "$mod_file" || rm -f "$_mod_tmp"
|
|
115
115
|
fi
|
|
116
|
+
|
|
117
|
+
# Full edit history for stuck loop detection (not truncated)
|
|
118
|
+
edit_dir="$EAGLE_MEM_DIR/edit-tracker"
|
|
119
|
+
mkdir -p "$edit_dir" 2>/dev/null
|
|
120
|
+
echo "$fp" >> "$edit_dir/${session_id}"
|
|
116
121
|
;;
|
|
117
122
|
esac
|
|
118
123
|
fi
|
package/hooks/pre-tool-use.sh
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# ═══════════════════════════════════════════════════════════
|
|
3
3
|
# Eagle Mem — PreToolUse hook
|
|
4
|
-
# Fires before Bash
|
|
4
|
+
# Fires before Bash, Read, Edit, Write tool calls
|
|
5
5
|
# 1. Surfaces feature verification checklists before git push
|
|
6
6
|
# 2. Truncates noisy commands via updatedInput (curator-learned rules)
|
|
7
7
|
# 3. Detects Read-after-Edit/Write (content already in context)
|
|
8
8
|
# 4. Nudges on repeated file reads (dedup tracker)
|
|
9
|
+
# 5. Co-edit nudge on Edit/Write (curator-learned pairs)
|
|
10
|
+
# 6. Stuck loop detection (repeated edits to same file)
|
|
9
11
|
# ═══════════════════════════════════════════════════════════
|
|
10
12
|
set +e
|
|
11
13
|
|
|
@@ -21,7 +23,7 @@ input=$(eagle_read_stdin)
|
|
|
21
23
|
tool_name=$(echo "$input" | jq -r '.tool_name // empty')
|
|
22
24
|
|
|
23
25
|
case "$tool_name" in
|
|
24
|
-
Bash|Read) ;;
|
|
26
|
+
Bash|Read|Edit|Write) ;;
|
|
25
27
|
*) exit 0 ;;
|
|
26
28
|
esac
|
|
27
29
|
|
|
@@ -116,6 +118,42 @@ ${context}"
|
|
|
116
118
|
fi
|
|
117
119
|
;;
|
|
118
120
|
|
|
121
|
+
Edit|Write)
|
|
122
|
+
fp=$(echo "$input" | jq -r '.tool_input.file_path // empty')
|
|
123
|
+
if [ -n "$fp" ]; then
|
|
124
|
+
# ─── Stuck loop detection ─────────────────────────
|
|
125
|
+
if [ -n "$session_id" ] && eagle_validate_session_id "$session_id"; then
|
|
126
|
+
edit_tracker="$EAGLE_MEM_DIR/edit-tracker/${session_id}"
|
|
127
|
+
if [ -f "$edit_tracker" ]; then
|
|
128
|
+
edit_count=$(grep -cFx -- "$fp" "$edit_tracker" 2>/dev/null)
|
|
129
|
+
edit_count=${edit_count:-0}
|
|
130
|
+
if [ "$edit_count" -ge 8 ]; then
|
|
131
|
+
context+="Eagle Mem: '$(basename "$fp")' has been edited ${edit_count} times this session. You may be stuck — consider stepping back to rethink your approach before making more changes. "
|
|
132
|
+
elif [ "$edit_count" -ge 5 ]; then
|
|
133
|
+
context+="Eagle Mem: '$(basename "$fp")' has been edited ${edit_count} times this session. If the changes aren't converging, consider a different approach. "
|
|
134
|
+
fi
|
|
135
|
+
fi
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
# ─── Co-edit nudge (skip SQLite call if no co-edit hints exist) ──
|
|
139
|
+
_proj_hash=$(printf '%s' "$project" | shasum | cut -c1-8)
|
|
140
|
+
co_edit_marker="$EAGLE_MEM_DIR/.co-edit-active.${_proj_hash}"
|
|
141
|
+
co_edits=""
|
|
142
|
+
if [ -f "$co_edit_marker" ]; then
|
|
143
|
+
co_edits=$(eagle_get_co_edits "$project" "$fp")
|
|
144
|
+
fi
|
|
145
|
+
if [ -n "$co_edits" ]; then
|
|
146
|
+
partners=""
|
|
147
|
+
IFS=',' read -ra co_arr <<< "$co_edits"
|
|
148
|
+
for co_file in "${co_arr[@]}"; do
|
|
149
|
+
[ -n "$co_file" ] && partners+="$(basename "$co_file"), "
|
|
150
|
+
done
|
|
151
|
+
partners=${partners%, }
|
|
152
|
+
context+="Eagle Mem: When you change '$(basename "$fp")' you usually also touch: $partners"
|
|
153
|
+
fi
|
|
154
|
+
fi
|
|
155
|
+
;;
|
|
156
|
+
|
|
119
157
|
Read)
|
|
120
158
|
fp=$(echo "$input" | jq -r '.tool_input.file_path // empty')
|
|
121
159
|
if [ -n "$fp" ] && [ -n "$session_id" ] && eagle_validate_session_id "$session_id"; then
|
package/hooks/session-start.sh
CHANGED
|
@@ -45,6 +45,7 @@ eagle_sessionstart_auto_curate "$project" "$SCRIPTS_DIR"
|
|
|
45
45
|
|
|
46
46
|
find "$EAGLE_MEM_DIR/read-tracker" -type f -mtime +1 -delete 2>/dev/null &
|
|
47
47
|
find "$EAGLE_MEM_DIR/mod-tracker" -type f -mtime +1 -delete 2>/dev/null &
|
|
48
|
+
find "$EAGLE_MEM_DIR/edit-tracker" -type f -mtime +1 -delete 2>/dev/null &
|
|
48
49
|
|
|
49
50
|
# ─── Version check (non-blocking) ────────────────────────
|
|
50
51
|
|
|
@@ -264,6 +265,36 @@ if [ -n "$synced_tasks" ]; then
|
|
|
264
265
|
done <<< "$synced_tasks"
|
|
265
266
|
fi
|
|
266
267
|
|
|
268
|
+
# ─── Core files (hot file hints from curator) ───────────
|
|
269
|
+
|
|
270
|
+
hot_files=$(eagle_get_hot_files "$project")
|
|
271
|
+
if [ -n "$hot_files" ]; then
|
|
272
|
+
context+="
|
|
273
|
+
=== Core Files (frequently read — re-read sparingly if unchanged) ===
|
|
274
|
+
"
|
|
275
|
+
IFS=',' read -ra hf_arr <<< "$hot_files"
|
|
276
|
+
for hf in "${hf_arr[@]}"; do
|
|
277
|
+
[ -n "$hf" ] && context+=" - $(basename "$hf")
|
|
278
|
+
"
|
|
279
|
+
done
|
|
280
|
+
fi
|
|
281
|
+
|
|
282
|
+
# ─── Working set (on compact — what you were editing) ────
|
|
283
|
+
|
|
284
|
+
if [ "$source_type" = "compact" ] || [ "$source_type" = "clear" ]; then
|
|
285
|
+
working_set=$(eagle_get_working_set "$session_id")
|
|
286
|
+
if [ -n "$working_set" ]; then
|
|
287
|
+
context+="
|
|
288
|
+
=== Working Set (files you were modifying before compact) ===
|
|
289
|
+
"
|
|
290
|
+
while IFS='|' read -r ws_path ws_edits; do
|
|
291
|
+
[ -z "$ws_path" ] && continue
|
|
292
|
+
context+=" - $(basename "$ws_path") (${ws_edits} edits)
|
|
293
|
+
"
|
|
294
|
+
done <<< "$working_set"
|
|
295
|
+
fi
|
|
296
|
+
fi
|
|
297
|
+
|
|
267
298
|
# ─── Instructions (compressed) ───────────────────────────
|
|
268
299
|
|
|
269
300
|
if [ "$source_type" = "compact" ] || [ "$source_type" = "clear" ]; then
|
package/hooks/stop.sh
CHANGED
|
@@ -38,15 +38,6 @@ eagle_log "INFO" "Stop: session=$session_id project=$project transcript=$transcr
|
|
|
38
38
|
# Ensure session exists (may not if SessionStart didn't fire)
|
|
39
39
|
eagle_upsert_session "$session_id" "$project" "$cwd" "" ""
|
|
40
40
|
|
|
41
|
-
# ─── Guard: skip if summary already exists for this session ──
|
|
42
|
-
# Stop fires every assistant turn. Only process once per session.
|
|
43
|
-
|
|
44
|
-
existing_count=$(eagle_count_session_summaries "$session_id")
|
|
45
|
-
if [ "${existing_count:-0}" -gt 0 ]; then
|
|
46
|
-
eagle_log "INFO" "Stop: summary already exists for session=$session_id — skipping"
|
|
47
|
-
exit 0
|
|
48
|
-
fi
|
|
49
|
-
|
|
50
41
|
[ -z "$transcript_path" ] || [ ! -f "$transcript_path" ] && exit 0
|
|
51
42
|
|
|
52
43
|
# ─── Primary: heuristic extraction from transcript ───────────
|
|
@@ -67,7 +58,7 @@ fi
|
|
|
67
58
|
|
|
68
59
|
investigated=""
|
|
69
60
|
learned=""
|
|
70
|
-
completed="
|
|
61
|
+
completed=""
|
|
71
62
|
next_steps=""
|
|
72
63
|
notes=""
|
|
73
64
|
decisions=""
|
|
@@ -134,8 +125,16 @@ if [ -n "$summary_block" ]; then
|
|
|
134
125
|
fi
|
|
135
126
|
|
|
136
127
|
# ─── LLM enrichment: fill in decisions/gotchas/key_files ─────
|
|
128
|
+
# Check both local vars (from eagle-summary block) AND existing DB enrichment.
|
|
129
|
+
# Skip LLM call if either source already has enrichment data.
|
|
137
130
|
|
|
131
|
+
existing_enrichment=""
|
|
138
132
|
if [ -z "$decisions" ] && [ -z "$gotchas" ] && [ -z "$key_files" ]; then
|
|
133
|
+
s_esc=$(eagle_sql_escape "$session_id")
|
|
134
|
+
existing_enrichment=$(eagle_db "SELECT decisions||gotchas||key_files FROM summaries WHERE session_id='$s_esc';")
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
if [ -z "$decisions" ] && [ -z "$gotchas" ] && [ -z "$key_files" ] && [ -z "$existing_enrichment" ]; then
|
|
139
138
|
provider=$(eagle_config_get "provider" "type" "none" 2>/dev/null)
|
|
140
139
|
if [ "$provider" != "none" ] && [ -n "$text_content" ]; then
|
|
141
140
|
excerpt=$(echo "$text_content" | tail -c 2000)
|
package/lib/db-core.sh
CHANGED
package/lib/db-hints.sh
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ═══════════════════════════════════════════════════════════
|
|
3
|
+
# Eagle Mem — File hint helpers (learned patterns from curator)
|
|
4
|
+
# ═══════════════════════════════════════════════════════════
|
|
5
|
+
[ -n "${_EAGLE_DB_HINTS_LOADED:-}" ] && return 0
|
|
6
|
+
_EAGLE_DB_HINTS_LOADED=1
|
|
7
|
+
|
|
8
|
+
eagle_upsert_file_hint() {
|
|
9
|
+
local project; project=$(eagle_sql_escape "$1")
|
|
10
|
+
local hint_type; hint_type=$(eagle_sql_escape "$2")
|
|
11
|
+
local file_path; file_path=$(eagle_sql_escape "$3")
|
|
12
|
+
local hint_value; hint_value=$(eagle_sql_escape "$4")
|
|
13
|
+
|
|
14
|
+
eagle_db "INSERT INTO file_hints (project, hint_type, file_path, hint_value)
|
|
15
|
+
VALUES ('$project', '$hint_type', '$file_path', '$hint_value')
|
|
16
|
+
ON CONFLICT(project, hint_type, file_path) DO UPDATE SET
|
|
17
|
+
hint_value = excluded.hint_value,
|
|
18
|
+
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now');"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
eagle_get_co_edits() {
|
|
22
|
+
local project; project=$(eagle_sql_escape "$1")
|
|
23
|
+
local file_path; file_path=$(eagle_sql_escape "$2")
|
|
24
|
+
|
|
25
|
+
eagle_db "SELECT hint_value FROM file_hints
|
|
26
|
+
WHERE project = '$project'
|
|
27
|
+
AND hint_type = 'co_edit'
|
|
28
|
+
AND file_path = '$file_path'
|
|
29
|
+
LIMIT 1;"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
eagle_get_hot_files() {
|
|
33
|
+
local project; project=$(eagle_sql_escape "$1")
|
|
34
|
+
|
|
35
|
+
eagle_db "SELECT hint_value FROM file_hints
|
|
36
|
+
WHERE project = '$project'
|
|
37
|
+
AND hint_type = 'hot_file'
|
|
38
|
+
AND file_path = ''
|
|
39
|
+
LIMIT 1;"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
eagle_get_working_set() {
|
|
43
|
+
local session_id; session_id=$(eagle_sql_escape "$1")
|
|
44
|
+
|
|
45
|
+
eagle_db "SELECT
|
|
46
|
+
CASE tool_name
|
|
47
|
+
WHEN 'Edit' THEN SUBSTR(tool_input_summary, 6)
|
|
48
|
+
WHEN 'Write' THEN SUBSTR(tool_input_summary, 7)
|
|
49
|
+
END as file_path,
|
|
50
|
+
COUNT(*) as edits
|
|
51
|
+
FROM observations
|
|
52
|
+
WHERE session_id = '$session_id'
|
|
53
|
+
AND tool_name IN ('Edit', 'Write')
|
|
54
|
+
AND tool_input_summary IS NOT NULL
|
|
55
|
+
GROUP BY file_path
|
|
56
|
+
ORDER BY MAX(created_at) DESC
|
|
57
|
+
LIMIT 10;"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
eagle_delete_file_hints() {
|
|
61
|
+
local project; project=$(eagle_sql_escape "$1")
|
|
62
|
+
local hint_type; hint_type=$(eagle_sql_escape "$2")
|
|
63
|
+
|
|
64
|
+
eagle_db "DELETE FROM file_hints
|
|
65
|
+
WHERE project = '$project'
|
|
66
|
+
AND hint_type = '$hint_type';"
|
|
67
|
+
}
|
package/lib/db.sh
CHANGED
package/package.json
CHANGED
package/scripts/curate.sh
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
# 2. Superseded decision detection
|
|
7
7
|
# 3. Command compression rules
|
|
8
8
|
# 4. Feature auto-discovery
|
|
9
|
+
# 5. Co-edit pattern detection
|
|
10
|
+
# 6. Hot file detection
|
|
9
11
|
# ═══════════════════════════════════════════════════════════
|
|
10
12
|
set -euo pipefail
|
|
11
13
|
|
|
@@ -348,7 +350,167 @@ else
|
|
|
348
350
|
eagle_dim " Not enough session data for feature discovery"
|
|
349
351
|
fi
|
|
350
352
|
|
|
351
|
-
# ─── 5.
|
|
353
|
+
# ─── 5. Co-edit pattern detection (pure SQL) ─────────────
|
|
354
|
+
|
|
355
|
+
eagle_info "Analyzing co-edit patterns..."
|
|
356
|
+
|
|
357
|
+
# Need 3+ sessions with edits — with fewer, every pair trivially co-occurs
|
|
358
|
+
edit_sessions=$(eagle_db "SELECT COUNT(DISTINCT session_id) FROM observations
|
|
359
|
+
WHERE project = '$p_esc' AND tool_name IN ('Edit', 'Write');")
|
|
360
|
+
|
|
361
|
+
co_edit_data=""
|
|
362
|
+
if [ "${edit_sessions:-0}" -ge 3 ]; then
|
|
363
|
+
co_edit_data=$(eagle_db "WITH edits AS (
|
|
364
|
+
SELECT session_id,
|
|
365
|
+
CASE tool_name
|
|
366
|
+
WHEN 'Edit' THEN SUBSTR(tool_input_summary, 6)
|
|
367
|
+
WHEN 'Write' THEN SUBSTR(tool_input_summary, 7)
|
|
368
|
+
END as file_path
|
|
369
|
+
FROM observations
|
|
370
|
+
WHERE project = '$p_esc'
|
|
371
|
+
AND tool_name IN ('Edit', 'Write')
|
|
372
|
+
AND tool_input_summary IS NOT NULL
|
|
373
|
+
),
|
|
374
|
+
file_stats AS (
|
|
375
|
+
SELECT file_path, COUNT(DISTINCT session_id) as edit_sessions
|
|
376
|
+
FROM edits GROUP BY file_path
|
|
377
|
+
),
|
|
378
|
+
-- Filter files edited in >85% of all edit sessions (like .env — changes with everything)
|
|
379
|
+
noisy_files AS (
|
|
380
|
+
SELECT file_path FROM file_stats
|
|
381
|
+
WHERE CAST(edit_sessions AS REAL) / $edit_sessions > 0.85
|
|
382
|
+
)
|
|
383
|
+
SELECT e1.file_path as f1, e2.file_path as f2,
|
|
384
|
+
COUNT(DISTINCT e1.session_id) as co_sessions
|
|
385
|
+
FROM edits e1
|
|
386
|
+
JOIN edits e2 ON e1.session_id = e2.session_id AND e1.file_path < e2.file_path
|
|
387
|
+
WHERE e1.file_path NOT IN (SELECT file_path FROM noisy_files)
|
|
388
|
+
AND e2.file_path NOT IN (SELECT file_path FROM noisy_files)
|
|
389
|
+
GROUP BY f1, f2
|
|
390
|
+
HAVING co_sessions >= 2
|
|
391
|
+
ORDER BY co_sessions DESC
|
|
392
|
+
LIMIT 30;")
|
|
393
|
+
fi
|
|
394
|
+
|
|
395
|
+
if [ -n "$co_edit_data" ]; then
|
|
396
|
+
if [ "$DRY_RUN" -eq 1 ]; then
|
|
397
|
+
eagle_info " Co-edit pairs found:"
|
|
398
|
+
fi
|
|
399
|
+
|
|
400
|
+
co_edit_count=0
|
|
401
|
+
declare -A co_map
|
|
402
|
+
|
|
403
|
+
while IFS='|' read -r f1 f2 co_sessions; do
|
|
404
|
+
[ -z "$f1" ] || [ -z "$f2" ] && continue
|
|
405
|
+
|
|
406
|
+
# Build comma-separated partner list per file (both directions)
|
|
407
|
+
if [ -n "${co_map[$f1]+x}" ]; then
|
|
408
|
+
co_map[$f1]="${co_map[$f1]},$f2"
|
|
409
|
+
else
|
|
410
|
+
co_map[$f1]="$f2"
|
|
411
|
+
fi
|
|
412
|
+
if [ -n "${co_map[$f2]+x}" ]; then
|
|
413
|
+
co_map[$f2]="${co_map[$f2]},$f1"
|
|
414
|
+
else
|
|
415
|
+
co_map[$f2]="$f1"
|
|
416
|
+
fi
|
|
417
|
+
co_edit_count=$((co_edit_count + 1))
|
|
418
|
+
done <<< "$co_edit_data"
|
|
419
|
+
|
|
420
|
+
if [ "$DRY_RUN" -eq 1 ]; then
|
|
421
|
+
for f in "${!co_map[@]}"; do
|
|
422
|
+
eagle_info " $(basename "$f") → ${co_map[$f]}"
|
|
423
|
+
done
|
|
424
|
+
else
|
|
425
|
+
{
|
|
426
|
+
echo "BEGIN;"
|
|
427
|
+
echo "DELETE FROM file_hints WHERE project = '$(eagle_sql_escape "$project")' AND hint_type = 'co_edit';"
|
|
428
|
+
for f in "${!co_map[@]}"; do
|
|
429
|
+
local_f=$(eagle_sql_escape "$f")
|
|
430
|
+
local_v=$(eagle_sql_escape "${co_map[$f]}")
|
|
431
|
+
echo "INSERT INTO file_hints (project, hint_type, file_path, hint_value) VALUES ('$(eagle_sql_escape "$project")', 'co_edit', '$local_f', '$local_v') ON CONFLICT(project, hint_type, file_path) DO UPDATE SET hint_value = excluded.hint_value, updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now');"
|
|
432
|
+
done
|
|
433
|
+
echo "COMMIT;"
|
|
434
|
+
} | eagle_db_pipe
|
|
435
|
+
_proj_hash=$(printf '%s' "$project" | shasum | cut -c1-8)
|
|
436
|
+
touch "$EAGLE_MEM_DIR/.co-edit-active.${_proj_hash}"
|
|
437
|
+
eagle_log "INFO" "Curator: stored ${#co_map[@]} co-edit hints from $co_edit_count pairs"
|
|
438
|
+
fi
|
|
439
|
+
eagle_ok "$co_edit_count co-edit pairs found (${#co_map[@]} files)"
|
|
440
|
+
else
|
|
441
|
+
if [ "$DRY_RUN" -eq 0 ]; then
|
|
442
|
+
eagle_delete_file_hints "$project" "co_edit"
|
|
443
|
+
_proj_hash=$(printf '%s' "$project" | shasum | cut -c1-8)
|
|
444
|
+
rm -f "$EAGLE_MEM_DIR/.co-edit-active.${_proj_hash}"
|
|
445
|
+
fi
|
|
446
|
+
eagle_dim " Not enough edit data for co-edit detection (need 3+ sessions, have ${edit_sessions:-0})"
|
|
447
|
+
fi
|
|
448
|
+
|
|
449
|
+
# ─── 6. Hot file detection (pure SQL) ────────────────────
|
|
450
|
+
|
|
451
|
+
eagle_info "Detecting hot files..."
|
|
452
|
+
|
|
453
|
+
total_sessions=$(eagle_db "SELECT COUNT(DISTINCT session_id) FROM observations
|
|
454
|
+
WHERE project = '$p_esc' AND tool_name = 'Read';")
|
|
455
|
+
|
|
456
|
+
hot_file_data=""
|
|
457
|
+
if [ "${total_sessions:-0}" -ge 3 ]; then
|
|
458
|
+
hot_file_data=$(eagle_db "WITH read_stats AS (
|
|
459
|
+
SELECT
|
|
460
|
+
SUBSTR(tool_input_summary, 6) as file_path,
|
|
461
|
+
COUNT(*) as total_reads,
|
|
462
|
+
COUNT(DISTINCT session_id) as sessions_read
|
|
463
|
+
FROM observations
|
|
464
|
+
WHERE project = '$p_esc'
|
|
465
|
+
AND tool_name = 'Read'
|
|
466
|
+
AND tool_input_summary IS NOT NULL
|
|
467
|
+
GROUP BY file_path
|
|
468
|
+
)
|
|
469
|
+
SELECT file_path, total_reads, sessions_read,
|
|
470
|
+
CAST(total_reads * 1.0 / sessions_read AS INTEGER) as reads_per_session
|
|
471
|
+
FROM read_stats
|
|
472
|
+
WHERE (CAST(sessions_read AS REAL) / $total_sessions > 0.5
|
|
473
|
+
OR total_reads * 1.0 / sessions_read >= 10)
|
|
474
|
+
AND total_reads >= 5
|
|
475
|
+
ORDER BY reads_per_session DESC
|
|
476
|
+
LIMIT 15;")
|
|
477
|
+
fi
|
|
478
|
+
|
|
479
|
+
if [ -n "$hot_file_data" ]; then
|
|
480
|
+
hot_files=""
|
|
481
|
+
hot_count=0
|
|
482
|
+
|
|
483
|
+
while IFS='|' read -r hf_path hf_reads hf_sessions hf_rps; do
|
|
484
|
+
[ -z "$hf_path" ] && continue
|
|
485
|
+
if [ -n "$hot_files" ]; then
|
|
486
|
+
hot_files+=","
|
|
487
|
+
fi
|
|
488
|
+
hot_files+="$hf_path"
|
|
489
|
+
hot_count=$((hot_count + 1))
|
|
490
|
+
|
|
491
|
+
if [ "$DRY_RUN" -eq 1 ]; then
|
|
492
|
+
eagle_info " $(basename "$hf_path") — ${hf_rps} reads/session, ${hf_sessions}/${total_sessions} sessions"
|
|
493
|
+
fi
|
|
494
|
+
done <<< "$hot_file_data"
|
|
495
|
+
|
|
496
|
+
if [ "$DRY_RUN" -eq 0 ] && [ -n "$hot_files" ]; then
|
|
497
|
+
{
|
|
498
|
+
echo "BEGIN;"
|
|
499
|
+
echo "DELETE FROM file_hints WHERE project = '$(eagle_sql_escape "$project")' AND hint_type = 'hot_file';"
|
|
500
|
+
echo "INSERT INTO file_hints (project, hint_type, file_path, hint_value) VALUES ('$(eagle_sql_escape "$project")', 'hot_file', '', '$(eagle_sql_escape "$hot_files")') ON CONFLICT(project, hint_type, file_path) DO UPDATE SET hint_value = excluded.hint_value, updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now');"
|
|
501
|
+
echo "COMMIT;"
|
|
502
|
+
} | eagle_db_pipe
|
|
503
|
+
eagle_log "INFO" "Curator: stored $hot_count hot files"
|
|
504
|
+
fi
|
|
505
|
+
eagle_ok "$hot_count hot files detected"
|
|
506
|
+
else
|
|
507
|
+
if [ "$DRY_RUN" -eq 0 ]; then
|
|
508
|
+
eagle_delete_file_hints "$project" "hot_file"
|
|
509
|
+
fi
|
|
510
|
+
eagle_dim " Not enough session data for hot file detection (need 3+ sessions, have ${total_sessions:-0})"
|
|
511
|
+
fi
|
|
512
|
+
|
|
513
|
+
# ─── 7. Session compression (--full only) ─────────────────
|
|
352
514
|
|
|
353
515
|
if [ "$FULL" -eq 1 ]; then
|
|
354
516
|
eagle_info "Compressing old sessions..."
|
package/scripts/install.sh
CHANGED
|
@@ -192,6 +192,14 @@ eagle_patch_hook "$SETTINGS" "PreToolUse" "Read" \
|
|
|
192
192
|
"$EAGLE_MEM_DIR/hooks/pre-tool-use.sh" \
|
|
193
193
|
"PreToolUse hook (Read)"
|
|
194
194
|
|
|
195
|
+
eagle_patch_hook "$SETTINGS" "PreToolUse" "Edit" \
|
|
196
|
+
"$EAGLE_MEM_DIR/hooks/pre-tool-use.sh" \
|
|
197
|
+
"PreToolUse hook (Edit)"
|
|
198
|
+
|
|
199
|
+
eagle_patch_hook "$SETTINGS" "PreToolUse" "Write" \
|
|
200
|
+
"$EAGLE_MEM_DIR/hooks/pre-tool-use.sh" \
|
|
201
|
+
"PreToolUse hook (Write)"
|
|
202
|
+
|
|
195
203
|
# ─── Install skills ────────────────────────────────────────
|
|
196
204
|
|
|
197
205
|
if [ -d "$PACKAGE_DIR/skills" ]; then
|
package/scripts/update.sh
CHANGED
|
@@ -77,6 +77,8 @@ if [ -f "$SETTINGS" ] && command -v jq &>/dev/null; then
|
|
|
77
77
|
eagle_patch_hook "$SETTINGS" "UserPromptSubmit" "" "$EAGLE_MEM_DIR/hooks/user-prompt-submit.sh"
|
|
78
78
|
eagle_patch_hook "$SETTINGS" "PreToolUse" "Bash" "$EAGLE_MEM_DIR/hooks/pre-tool-use.sh"
|
|
79
79
|
eagle_patch_hook "$SETTINGS" "PreToolUse" "Read" "$EAGLE_MEM_DIR/hooks/pre-tool-use.sh"
|
|
80
|
+
eagle_patch_hook "$SETTINGS" "PreToolUse" "Edit" "$EAGLE_MEM_DIR/hooks/pre-tool-use.sh"
|
|
81
|
+
eagle_patch_hook "$SETTINGS" "PreToolUse" "Write" "$EAGLE_MEM_DIR/hooks/pre-tool-use.sh"
|
|
80
82
|
|
|
81
83
|
eagle_ok "Hooks registered"
|
|
82
84
|
fi
|