eagle-mem 4.2.0 → 4.4.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.
- package/bin/eagle-mem +9 -0
- package/db/022_cleanup_fossils.sql +12 -0
- package/db/023_guardrails.sql +18 -0
- package/hooks/post-tool-use.sh +17 -1
- package/hooks/pre-tool-use.sh +36 -0
- package/hooks/stop.sh +107 -28
- package/hooks/user-prompt-submit.sh +35 -33
- package/lib/db-core.sh +2 -1
- package/lib/db-guardrails.sh +107 -0
- package/lib/db-summaries.sh +15 -0
- package/lib/db.sh +1 -0
- package/lib/provider.sh +17 -8
- package/package.json +1 -1
- package/scripts/curate.sh +2 -1
- package/scripts/guard.sh +160 -0
- package/scripts/help.sh +7 -0
- package/scripts/install.sh +8 -0
- package/scripts/overview.sh +8 -4
- package/scripts/search.sh +21 -3
- package/scripts/tasks.sh +15 -6
- package/scripts/update.sh +2 -0
package/bin/eagle-mem
CHANGED
|
@@ -22,6 +22,15 @@ case "$command" in
|
|
|
22
22
|
search) bash "$SCRIPTS_DIR/search.sh" "$@" ;;
|
|
23
23
|
health) bash "$SCRIPTS_DIR/health.sh" "$@" ;;
|
|
24
24
|
config) bash "$SCRIPTS_DIR/config.sh" "$@" ;;
|
|
25
|
+
guard) bash "$SCRIPTS_DIR/guard.sh" "$@" ;;
|
|
26
|
+
overview) bash "$SCRIPTS_DIR/overview.sh" "$@" ;;
|
|
27
|
+
memories) bash "$SCRIPTS_DIR/memories.sh" "$@" ;;
|
|
28
|
+
tasks) bash "$SCRIPTS_DIR/tasks.sh" "$@" ;;
|
|
29
|
+
curate) bash "$SCRIPTS_DIR/curate.sh" "$@" ;;
|
|
30
|
+
feature) bash "$SCRIPTS_DIR/feature.sh" "$@" ;;
|
|
31
|
+
prune) bash "$SCRIPTS_DIR/prune.sh" "$@" ;;
|
|
32
|
+
scan) bash "$SCRIPTS_DIR/scan.sh" "$@" ;;
|
|
33
|
+
index) bash "$SCRIPTS_DIR/index.sh" "$@" ;;
|
|
25
34
|
help|--help|-h)
|
|
26
35
|
bash "$SCRIPTS_DIR/help.sh" ;;
|
|
27
36
|
version|--version|-v|-V)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
-- Clear fossil placeholder data that the COALESCE upsert preserves forever.
|
|
2
|
+
-- These rows block real data from being written on future Stop hook fires.
|
|
3
|
+
PRAGMA trusted_schema=ON;
|
|
4
|
+
|
|
5
|
+
UPDATE summaries SET completed = ''
|
|
6
|
+
WHERE completed LIKE '%(auto-captured%';
|
|
7
|
+
|
|
8
|
+
UPDATE summaries SET request = ''
|
|
9
|
+
WHERE request LIKE '%<local-command-caveat>%';
|
|
10
|
+
|
|
11
|
+
UPDATE summaries SET request = ''
|
|
12
|
+
WHERE request LIKE '%<system-reminder>%';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
-- ═══════════════════════════════════════════════════════════
|
|
2
|
+
-- Migration 023: Guardrails table
|
|
3
|
+
-- Persistent per-project rules surfaced at Edit/Write time
|
|
4
|
+
-- ═══════════════════════════════════════════════════════════
|
|
5
|
+
|
|
6
|
+
CREATE TABLE IF NOT EXISTS guardrails (
|
|
7
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
8
|
+
project TEXT NOT NULL,
|
|
9
|
+
file_pattern TEXT NOT NULL DEFAULT '',
|
|
10
|
+
rule TEXT NOT NULL,
|
|
11
|
+
source TEXT NOT NULL DEFAULT 'manual',
|
|
12
|
+
active INTEGER NOT NULL DEFAULT 1,
|
|
13
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
14
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
15
|
+
UNIQUE(project, source, file_pattern, rule)
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
CREATE INDEX IF NOT EXISTS idx_guardrails_project ON guardrails(project, active);
|
package/hooks/post-tool-use.sh
CHANGED
|
@@ -20,7 +20,23 @@ session_id=$(echo "$input" | jq -r '.session_id // empty')
|
|
|
20
20
|
cwd=$(echo "$input" | jq -r '.cwd // empty')
|
|
21
21
|
tool_name=$(echo "$input" | jq -r '.tool_name // empty')
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
hook_event=$(echo "$input" | jq -r '.hook_event_name // empty')
|
|
24
|
+
|
|
25
|
+
if [ -z "$session_id" ]; then exit 0; fi
|
|
26
|
+
|
|
27
|
+
# TaskCreated/TaskCompleted dedicated events — mirror tasks and exit
|
|
28
|
+
case "$hook_event" in
|
|
29
|
+
TaskCreated|TaskCompleted)
|
|
30
|
+
[ ! -f "$EAGLE_MEM_DB" ] && exit 0
|
|
31
|
+
project=$(eagle_project_from_cwd "$cwd")
|
|
32
|
+
[ -z "$project" ] && exit 0
|
|
33
|
+
eagle_upsert_session "$session_id" "$project" "$cwd" "" ""
|
|
34
|
+
eagle_posttool_mirror_tasks "TaskCreate" "$session_id" "$project" "$input"
|
|
35
|
+
exit 0
|
|
36
|
+
;;
|
|
37
|
+
esac
|
|
38
|
+
|
|
39
|
+
[ -z "$tool_name" ] && exit 0
|
|
24
40
|
|
|
25
41
|
# Only track relevant tools
|
|
26
42
|
case "$tool_name" in
|
package/hooks/pre-tool-use.sh
CHANGED
|
@@ -121,6 +121,42 @@ ${context}"
|
|
|
121
121
|
Edit|Write)
|
|
122
122
|
fp=$(echo "$input" | jq -r '.tool_input.file_path // empty')
|
|
123
123
|
if [ -n "$fp" ]; then
|
|
124
|
+
# ─── Guardrail + decision/gotcha surfacing ────────
|
|
125
|
+
fname=$(basename "$fp")
|
|
126
|
+
fname_stem="${fname%.*}"
|
|
127
|
+
case "$fp" in
|
|
128
|
+
"$HOME/.claude/"*) ;;
|
|
129
|
+
*)
|
|
130
|
+
# Guardrails use GLOB on full filename — no stem length minimum needed.
|
|
131
|
+
# FTS decision/gotcha lookups need a meaningful stem (>= 3 chars).
|
|
132
|
+
if [ ${#fname_stem} -ge 3 ]; then
|
|
133
|
+
fts_query=$(eagle_fts_sanitize "$fname_stem")
|
|
134
|
+
fts_query=${fts_query:-"$fname_stem"}
|
|
135
|
+
edit_ctx=$(eagle_get_edit_context "$project" "$fname" "$fts_query" 2>/dev/null)
|
|
136
|
+
else
|
|
137
|
+
# Short stem (e.g. db.sh) — only fetch guardrails, skip FTS queries
|
|
138
|
+
edit_ctx=$(eagle_get_guardrails_for_file "$project" "$fname" 2>/dev/null)
|
|
139
|
+
if [ -n "$edit_ctx" ]; then
|
|
140
|
+
# Prefix with GR: to match batched output format; strip empty lines
|
|
141
|
+
edit_ctx=$(echo "$edit_ctx" | grep -v '^$' | sed 's/^/GR:/')
|
|
142
|
+
fi
|
|
143
|
+
fi
|
|
144
|
+
if [ -n "$edit_ctx" ]; then
|
|
145
|
+
gr_block=""
|
|
146
|
+
while IFS= read -r ctx_line; do
|
|
147
|
+
case "$ctx_line" in
|
|
148
|
+
GR:*) gr_block+=" - ${ctx_line#GR:}"$'\n' ;;
|
|
149
|
+
DEC:*) context+="Eagle Mem decisions for '${fname}': ${ctx_line#DEC:} — Do not revert without asking. " ;;
|
|
150
|
+
GOT:*) context+="Eagle Mem gotchas for '${fname}': ${ctx_line#GOT:} " ;;
|
|
151
|
+
esac
|
|
152
|
+
done <<< "$edit_ctx"
|
|
153
|
+
if [ -n "$gr_block" ]; then
|
|
154
|
+
context+="Eagle Mem guardrails for '${fname}':"$'\n'"${gr_block}"
|
|
155
|
+
fi
|
|
156
|
+
fi
|
|
157
|
+
;;
|
|
158
|
+
esac
|
|
159
|
+
|
|
124
160
|
# ─── Stuck loop detection ─────────────────────────
|
|
125
161
|
if [ -n "$session_id" ] && eagle_validate_session_id "$session_id"; then
|
|
126
162
|
edit_tracker="$EAGLE_MEM_DIR/edit-tracker/${session_id}"
|
package/hooks/stop.sh
CHANGED
|
@@ -42,7 +42,13 @@ eagle_upsert_session "$session_id" "$project" "$cwd" "" ""
|
|
|
42
42
|
|
|
43
43
|
# ─── Primary: heuristic extraction from transcript ───────────
|
|
44
44
|
|
|
45
|
-
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
|
|
45
|
+
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 \
|
|
46
|
+
| grep -v '<local-command-caveat>' \
|
|
47
|
+
| grep -v '<system-reminder>' \
|
|
48
|
+
| grep -v '<command-name>' \
|
|
49
|
+
| grep -v '<command-message>' \
|
|
50
|
+
| grep -v '^\[{' \
|
|
51
|
+
| head -1 | cut -c1-500)
|
|
46
52
|
|
|
47
53
|
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)
|
|
48
54
|
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)
|
|
@@ -124,59 +130,132 @@ if [ -n "$summary_block" ]; then
|
|
|
124
130
|
eagle_log "INFO" "Stop: eagle-summary block merged over heuristic data"
|
|
125
131
|
fi
|
|
126
132
|
|
|
127
|
-
# ─── LLM enrichment:
|
|
128
|
-
#
|
|
129
|
-
#
|
|
130
|
-
# Exception: under context pressure, force re-enrichment for richest summary.
|
|
133
|
+
# ─── LLM enrichment: extract structured data when eagle-summary absent ──
|
|
134
|
+
# Runs when: (a) no eagle-summary block, OR (b) heuristic data is thin
|
|
135
|
+
# Skips when: eagle-summary already provided rich data, OR no text to analyze
|
|
131
136
|
|
|
132
137
|
context_pressure=0
|
|
133
138
|
if [ -f "$EAGLE_MEM_DIR/.context-pressure" ]; then
|
|
134
139
|
context_pressure=1
|
|
135
140
|
fi
|
|
136
141
|
|
|
137
|
-
|
|
138
|
-
if [ -
|
|
139
|
-
|
|
140
|
-
existing_enrichment=$(eagle_db "SELECT decisions||gotchas||key_files FROM summaries WHERE session_id='$s_esc';")
|
|
142
|
+
has_rich_data=0
|
|
143
|
+
if [ -n "$decisions" ] || [ -n "$gotchas" ] || [ -n "$key_files" ]; then
|
|
144
|
+
has_rich_data=1
|
|
141
145
|
fi
|
|
142
146
|
|
|
143
|
-
|
|
147
|
+
request_is_polluted=0
|
|
148
|
+
if echo "$request" | grep -qE '<(local-command-caveat|system-reminder|command-name)>' 2>/dev/null; then
|
|
149
|
+
request_is_polluted=1
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
needs_enrichment=0
|
|
153
|
+
if [ "$has_rich_data" -eq 0 ]; then
|
|
154
|
+
needs_enrichment=1
|
|
155
|
+
elif [ "$context_pressure" -eq 1 ]; then
|
|
156
|
+
needs_enrichment=1
|
|
157
|
+
elif [ -z "$completed" ] && [ -z "$learned" ]; then
|
|
158
|
+
needs_enrichment=1
|
|
159
|
+
elif [ "$request_is_polluted" -eq 1 ]; then
|
|
160
|
+
needs_enrichment=1
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
if [ "$needs_enrichment" -eq 1 ]; then
|
|
144
164
|
provider=$(eagle_config_get "provider" "type" "none" 2>/dev/null)
|
|
145
165
|
if [ "$provider" != "none" ] && [ -n "$text_content" ]; then
|
|
146
|
-
excerpt=$(echo "$text_content" | tail -c
|
|
166
|
+
excerpt=$(echo "$text_content" | tail -c 3000)
|
|
147
167
|
|
|
148
|
-
enrich_prompt="Extract from this Claude Code session
|
|
149
|
-
1. DECISIONS: architectural or design choices made (with WHY). One per line.
|
|
150
|
-
2. GOTCHAS: non-obvious pitfalls, bugs found, things that surprised. One per line.
|
|
151
|
-
3. KEY_FILES: important files that were central to the work. One per line.
|
|
168
|
+
enrich_prompt="Extract facts from this Claude Code session. Only include items with clear evidence in the session text. Do NOT invent or repeat example content.
|
|
152
169
|
|
|
153
|
-
|
|
154
|
-
|
|
170
|
+
Respond with EXACTLY these sections (omit sections with no evidence):
|
|
171
|
+
|
|
172
|
+
REQUEST:
|
|
173
|
+
One-line summary of what the user asked for. No system tags or XML.
|
|
174
|
+
|
|
175
|
+
COMPLETED:
|
|
176
|
+
What was actually accomplished. Be specific about changes made.
|
|
177
|
+
|
|
178
|
+
LEARNED:
|
|
179
|
+
Non-obvious discoveries or insights from the session.
|
|
155
180
|
|
|
156
|
-
Output EXACTLY this format (omit sections with nothing to report):
|
|
157
181
|
DECISIONS:
|
|
158
|
-
|
|
182
|
+
Each as: <what was decided> — why: <reason>
|
|
183
|
+
|
|
159
184
|
GOTCHAS:
|
|
160
|
-
|
|
185
|
+
Each as: <surprising finding or pitfall>
|
|
186
|
+
|
|
161
187
|
KEY_FILES:
|
|
162
|
-
|
|
188
|
+
Each as: <filepath>
|
|
189
|
+
|
|
190
|
+
SESSION TEXT:
|
|
191
|
+
$excerpt"
|
|
163
192
|
|
|
164
|
-
|
|
193
|
+
enrich_system="You extract structured facts from development sessions. Output format for decisions: '- Did X — why: Y'. Output format for gotchas: '- Gotcha description'. Be concise. Only include items with clear evidence in the session text. Never fabricate content."
|
|
194
|
+
enrich_result=$(eagle_llm_call "$enrich_prompt" "$enrich_system" 768 2>/dev/null)
|
|
195
|
+
llm_rc=$?
|
|
165
196
|
|
|
166
|
-
if [ -
|
|
197
|
+
if [ $llm_rc -ne 0 ] || [ -z "$enrich_result" ]; then
|
|
198
|
+
eagle_log "WARN" "Stop: LLM enrichment failed (rc=$llm_rc) for session=$session_id provider=$provider"
|
|
199
|
+
else
|
|
167
200
|
extract_section() {
|
|
168
201
|
local result="$1" header="$2"
|
|
169
202
|
echo "$result" | awk -v h="$header:" '
|
|
170
203
|
$0 == h || $0 ~ "^"h { found=1; next }
|
|
171
204
|
found && /^[A-Z_]+:/ { exit }
|
|
172
|
-
found &&
|
|
205
|
+
found && /^[[:space:]]*$/ { next }
|
|
206
|
+
found && /^- / { sub(/^- /, ""); lines[++n] = $0; next }
|
|
207
|
+
found { lines[++n] = $0 }
|
|
173
208
|
END { for (i=1; i<=n; i++) { printf "%s", lines[i]; if (i<n) printf "; " } }
|
|
174
209
|
'
|
|
175
210
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
211
|
+
_req=$(extract_section "$enrich_result" "REQUEST")
|
|
212
|
+
_comp=$(extract_section "$enrich_result" "COMPLETED")
|
|
213
|
+
_learn=$(extract_section "$enrich_result" "LEARNED")
|
|
214
|
+
_dec=$(extract_section "$enrich_result" "DECISIONS")
|
|
215
|
+
_got=$(extract_section "$enrich_result" "GOTCHAS")
|
|
216
|
+
_kf=$(extract_section "$enrich_result" "KEY_FILES")
|
|
217
|
+
|
|
218
|
+
[ -z "$request" ] || [ "$request_is_polluted" -eq 1 ] && [ -n "$_req" ] && request="$_req"
|
|
219
|
+
[ -z "$completed" ] && [ -n "$_comp" ] && completed="$_comp"
|
|
220
|
+
[ -z "$learned" ] && [ -n "$_learn" ] && learned="$_learn"
|
|
221
|
+
[ -z "$decisions" ] && [ -n "$_dec" ] && decisions="$_dec"
|
|
222
|
+
[ -z "$gotchas" ] && [ -n "$_got" ] && gotchas="$_got"
|
|
223
|
+
[ -z "$key_files" ] && [ -n "$_kf" ] && key_files="$_kf"
|
|
224
|
+
|
|
225
|
+
eagle_log "INFO" "Stop: LLM enrichment extracted for session=$session_id (req=${#_req} comp=${#_comp} dec=${#_dec})"
|
|
226
|
+
fi
|
|
227
|
+
else
|
|
228
|
+
eagle_log "INFO" "Stop: LLM enrichment skipped — provider=$provider text_len=${#text_content}"
|
|
229
|
+
fi
|
|
230
|
+
else
|
|
231
|
+
eagle_log "INFO" "Stop: LLM enrichment skipped — rich data already present"
|
|
232
|
+
fi
|
|
233
|
+
|
|
234
|
+
# ─── Test reminder for guardrailed files ─────────────────
|
|
235
|
+
|
|
236
|
+
if [ -n "$files_modified" ] && [ "$files_modified" != "[]" ]; then
|
|
237
|
+
# Short-circuit: skip per-file loop if project has no guardrails at all
|
|
238
|
+
has_gr=$(eagle_has_any_guardrails "$project" 2>/dev/null)
|
|
239
|
+
if [ -n "$has_gr" ]; then
|
|
240
|
+
guardrailed_files=""
|
|
241
|
+
while IFS= read -r mod_file; do
|
|
242
|
+
[ -z "$mod_file" ] && continue
|
|
243
|
+
mod_basename=$(basename "$mod_file")
|
|
244
|
+
gr_check=$(eagle_get_guardrails_for_file "$project" "$mod_basename" 2>/dev/null)
|
|
245
|
+
if [ -n "$gr_check" ]; then
|
|
246
|
+
guardrailed_files+="${mod_basename}, "
|
|
247
|
+
fi
|
|
248
|
+
done < <(echo "$files_modified" | jq -r '.[]?' 2>/dev/null)
|
|
249
|
+
|
|
250
|
+
if [ -n "$guardrailed_files" ]; then
|
|
251
|
+
guardrailed_files=${guardrailed_files%, }
|
|
252
|
+
test_reminder="Run affected tests for guardrailed files: ${guardrailed_files}"
|
|
253
|
+
if [ -n "$next_steps" ]; then
|
|
254
|
+
next_steps="${next_steps}; ${test_reminder}"
|
|
255
|
+
else
|
|
256
|
+
next_steps="$test_reminder"
|
|
257
|
+
fi
|
|
258
|
+
eagle_log "INFO" "Stop: added test reminder for guardrailed files: $guardrailed_files"
|
|
180
259
|
fi
|
|
181
260
|
fi
|
|
182
261
|
fi
|
|
@@ -25,9 +25,42 @@ user_prompt=$(echo "$input" | jq -r '.prompt // empty')
|
|
|
25
25
|
|
|
26
26
|
project=$(eagle_project_from_cwd "$cwd")
|
|
27
27
|
|
|
28
|
+
# ─── Context pressure detection (turn counter since last compact) ──
|
|
29
|
+
# Must run before any early exits so every prompt is counted
|
|
30
|
+
|
|
31
|
+
context=""
|
|
32
|
+
|
|
33
|
+
if [ -n "$session_id" ] && eagle_validate_session_id "$session_id"; then
|
|
34
|
+
counter_file="$EAGLE_MEM_DIR/.turn-counter.${session_id}"
|
|
35
|
+
turn_count=0
|
|
36
|
+
[ -f "$counter_file" ] && turn_count=$(cat "$counter_file" 2>/dev/null | tr -d '[:space:]')
|
|
37
|
+
turn_count=${turn_count:-0}
|
|
38
|
+
turn_count=$((turn_count + 1))
|
|
39
|
+
echo "$turn_count" > "$counter_file" 2>/dev/null
|
|
40
|
+
eagle_log "INFO" "UserPromptSubmit: turn=$turn_count session=$session_id"
|
|
41
|
+
|
|
42
|
+
if [ "$turn_count" -ge 30 ]; then
|
|
43
|
+
context+="
|
|
44
|
+
=== EAGLE MEM — Context Pressure: CRITICAL ($turn_count turns since compact) ===
|
|
45
|
+
IMMEDIATELY emit a detailed <eagle-summary> covering ALL work this session.
|
|
46
|
+
Tell the user to run /compact NOW to avoid losing context.
|
|
47
|
+
"
|
|
48
|
+
echo "$turn_count" > "$EAGLE_MEM_DIR/.context-pressure"
|
|
49
|
+
elif [ "$turn_count" -ge 20 ]; then
|
|
50
|
+
context+="
|
|
51
|
+
=== EAGLE MEM — Context Pressure: HIGH ($turn_count turns since compact) ===
|
|
52
|
+
Include a thorough <eagle-summary> in your next response — capture all decisions, gotchas, and learned context before compaction.
|
|
53
|
+
Suggest the user run /compact to free context for continued work.
|
|
54
|
+
"
|
|
55
|
+
echo "$turn_count" > "$EAGLE_MEM_DIR/.context-pressure"
|
|
56
|
+
else
|
|
57
|
+
rm -f "$EAGLE_MEM_DIR/.context-pressure" 2>/dev/null
|
|
58
|
+
fi
|
|
59
|
+
fi
|
|
60
|
+
|
|
28
61
|
# Skip short prompts — not enough signal for meaningful search
|
|
29
62
|
word_count=$(echo "$user_prompt" | wc -w | tr -d ' ')
|
|
30
|
-
[ "$word_count" -lt 3 ] && exit 0
|
|
63
|
+
[ "$word_count" -lt 3 ] && { [ -n "$context" ] && echo "$context"; exit 0; }
|
|
31
64
|
|
|
32
65
|
# Build FTS5 query from significant words (drop stop words, take first 6)
|
|
33
66
|
fts_query=$(echo "$user_prompt" | tr -cs '[:alnum:]' ' ' | tr '[:upper:]' '[:lower:]' | \
|
|
@@ -42,13 +75,11 @@ fts_query=$(echo "$user_prompt" | tr -cs '[:alnum:]' ' ' | tr '[:upper:]' '[:low
|
|
|
42
75
|
}
|
|
43
76
|
}')
|
|
44
77
|
|
|
45
|
-
[ -z "$fts_query" ] && exit 0
|
|
78
|
+
[ -z "$fts_query" ] && { [ -n "$context" ] && echo "$context"; exit 0; }
|
|
46
79
|
|
|
47
80
|
# Search for relevant past summaries (cross-session)
|
|
48
81
|
results=$(eagle_search_summaries "$fts_query" "$project" 3)
|
|
49
82
|
|
|
50
|
-
context=""
|
|
51
|
-
|
|
52
83
|
if [ -n "$results" ]; then
|
|
53
84
|
context+="=== EAGLE MEM — Relevant Memory ===
|
|
54
85
|
"
|
|
@@ -87,35 +118,6 @@ if [ "${has_chunks:-0}" -gt 0 ]; then
|
|
|
87
118
|
fi
|
|
88
119
|
fi
|
|
89
120
|
|
|
90
|
-
# ─── Context pressure detection (turn counter since last compact) ──
|
|
91
|
-
|
|
92
|
-
if [ -n "$session_id" ] && eagle_validate_session_id "$session_id"; then
|
|
93
|
-
counter_file="$EAGLE_MEM_DIR/.turn-counter.${session_id}"
|
|
94
|
-
turn_count=0
|
|
95
|
-
[ -f "$counter_file" ] && turn_count=$(cat "$counter_file" 2>/dev/null | tr -d '[:space:]')
|
|
96
|
-
turn_count=${turn_count:-0}
|
|
97
|
-
turn_count=$((turn_count + 1))
|
|
98
|
-
echo "$turn_count" > "$counter_file" 2>/dev/null
|
|
99
|
-
|
|
100
|
-
if [ "$turn_count" -ge 30 ]; then
|
|
101
|
-
context+="
|
|
102
|
-
=== EAGLE MEM — Context Pressure: CRITICAL ($turn_count turns since compact) ===
|
|
103
|
-
IMMEDIATELY emit a detailed <eagle-summary> covering ALL work this session.
|
|
104
|
-
Tell the user to run /compact NOW to avoid losing context.
|
|
105
|
-
"
|
|
106
|
-
echo "$turn_count" > "$EAGLE_MEM_DIR/.context-pressure"
|
|
107
|
-
elif [ "$turn_count" -ge 20 ]; then
|
|
108
|
-
context+="
|
|
109
|
-
=== EAGLE MEM — Context Pressure: HIGH ($turn_count turns since compact) ===
|
|
110
|
-
Include a thorough <eagle-summary> in your next response — capture all decisions, gotchas, and learned context before compaction.
|
|
111
|
-
Suggest the user run /compact to free context for continued work.
|
|
112
|
-
"
|
|
113
|
-
echo "$turn_count" > "$EAGLE_MEM_DIR/.context-pressure"
|
|
114
|
-
else
|
|
115
|
-
rm -f "$EAGLE_MEM_DIR/.context-pressure" 2>/dev/null
|
|
116
|
-
fi
|
|
117
|
-
fi
|
|
118
|
-
|
|
119
121
|
[ -z "$context" ] && exit 0
|
|
120
122
|
|
|
121
123
|
context+="
|
package/lib/db-core.sh
CHANGED
|
@@ -7,8 +7,9 @@ _EAGLE_DB_CORE_LOADED=1
|
|
|
7
7
|
|
|
8
8
|
EAGLE_DB_SETUP=".headers off
|
|
9
9
|
.output /dev/null
|
|
10
|
+
PRAGMA busy_timeout=10000;
|
|
11
|
+
PRAGMA journal_mode=WAL;
|
|
10
12
|
PRAGMA synchronous=NORMAL;
|
|
11
|
-
PRAGMA busy_timeout=5000;
|
|
12
13
|
PRAGMA foreign_keys=ON;
|
|
13
14
|
PRAGMA trusted_schema=ON;
|
|
14
15
|
.output stdout"
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ═══════════════════════════════════════════════════════════
|
|
3
|
+
# Eagle Mem — Guardrails helpers
|
|
4
|
+
# Persistent per-project rules surfaced at Edit/Write time
|
|
5
|
+
# ═══════════════════════════════════════════════════════════
|
|
6
|
+
[ -n "${_EAGLE_DB_GUARDRAILS_LOADED:-}" ] && return 0
|
|
7
|
+
_EAGLE_DB_GUARDRAILS_LOADED=1
|
|
8
|
+
|
|
9
|
+
eagle_add_guardrail() {
|
|
10
|
+
local raw_rule="$2"
|
|
11
|
+
# Cap rule length to 2048 chars to prevent unbounded storage
|
|
12
|
+
if [ ${#raw_rule} -gt 2048 ]; then
|
|
13
|
+
raw_rule="${raw_rule:0:2048}"
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
local project; project=$(eagle_sql_escape "$1")
|
|
17
|
+
local rule; rule=$(eagle_sql_escape "$raw_rule")
|
|
18
|
+
local file_pattern="${3:-}"
|
|
19
|
+
local source; source=$(eagle_sql_escape "${4:-manual}")
|
|
20
|
+
|
|
21
|
+
file_pattern=$(eagle_sql_escape "$file_pattern")
|
|
22
|
+
eagle_db "INSERT INTO guardrails (project, file_pattern, rule, source)
|
|
23
|
+
VALUES ('$project', '$file_pattern', '$rule', '$source')
|
|
24
|
+
ON CONFLICT(project, source, file_pattern, rule) DO UPDATE SET
|
|
25
|
+
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now');"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
eagle_get_guardrails_for_file() {
|
|
29
|
+
local project; project=$(eagle_sql_escape "$1")
|
|
30
|
+
local filename; filename=$(eagle_sql_escape "$2")
|
|
31
|
+
|
|
32
|
+
eagle_db "SELECT rule FROM guardrails
|
|
33
|
+
WHERE project = '$project'
|
|
34
|
+
AND active = 1
|
|
35
|
+
AND (
|
|
36
|
+
file_pattern = ''
|
|
37
|
+
OR '$filename' GLOB file_pattern
|
|
38
|
+
OR file_pattern = '$filename'
|
|
39
|
+
)
|
|
40
|
+
ORDER BY file_pattern = '', created_at DESC
|
|
41
|
+
LIMIT 3;"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
eagle_get_edit_context() {
|
|
45
|
+
local project; project=$(eagle_sql_escape "$1")
|
|
46
|
+
local filename; filename=$(eagle_sql_escape "$2")
|
|
47
|
+
local fts_query; fts_query=$(eagle_sql_escape "$3")
|
|
48
|
+
|
|
49
|
+
# Batched query: guardrails + decisions + gotchas in one sqlite3 call.
|
|
50
|
+
# Results tagged with TYPE: prefix for caller to parse.
|
|
51
|
+
eagle_db_pipe <<SQL
|
|
52
|
+
SELECT 'GR:' || rule FROM guardrails
|
|
53
|
+
WHERE project = '$project'
|
|
54
|
+
AND active = 1
|
|
55
|
+
AND (
|
|
56
|
+
file_pattern = ''
|
|
57
|
+
OR '$filename' GLOB file_pattern
|
|
58
|
+
OR file_pattern = '$filename'
|
|
59
|
+
)
|
|
60
|
+
ORDER BY file_pattern = '', created_at DESC
|
|
61
|
+
LIMIT 3;
|
|
62
|
+
SELECT 'DEC:' || s.decisions
|
|
63
|
+
FROM summaries s
|
|
64
|
+
JOIN summaries_fts f ON f.rowid = s.id
|
|
65
|
+
WHERE summaries_fts MATCH '$fts_query'
|
|
66
|
+
AND s.project = '$project'
|
|
67
|
+
AND s.decisions IS NOT NULL AND s.decisions != ''
|
|
68
|
+
ORDER BY s.created_at DESC LIMIT 1;
|
|
69
|
+
SELECT 'GOT:' || s.gotchas
|
|
70
|
+
FROM summaries s
|
|
71
|
+
JOIN summaries_fts f ON f.rowid = s.id
|
|
72
|
+
WHERE summaries_fts MATCH '$fts_query'
|
|
73
|
+
AND s.project = '$project'
|
|
74
|
+
AND s.gotchas IS NOT NULL AND s.gotchas != ''
|
|
75
|
+
ORDER BY s.created_at DESC LIMIT 2;
|
|
76
|
+
SQL
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
eagle_list_guardrails() {
|
|
80
|
+
local project; project=$(eagle_sql_escape "$1")
|
|
81
|
+
|
|
82
|
+
eagle_db "SELECT id, file_pattern, rule, source, active, created_at
|
|
83
|
+
FROM guardrails
|
|
84
|
+
WHERE project = '$project'
|
|
85
|
+
ORDER BY active DESC, created_at DESC;"
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
eagle_remove_guardrail() {
|
|
89
|
+
local id; id=$(eagle_sql_int "$1")
|
|
90
|
+
|
|
91
|
+
eagle_db "DELETE FROM guardrails WHERE id = $id;"
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
eagle_has_any_guardrails() {
|
|
95
|
+
local project; project=$(eagle_sql_escape "$1")
|
|
96
|
+
eagle_db "SELECT 1 FROM guardrails
|
|
97
|
+
WHERE project = '$project' AND active = 1
|
|
98
|
+
LIMIT 1;"
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
eagle_deactivate_guardrail() {
|
|
102
|
+
local id; id=$(eagle_sql_int "$1")
|
|
103
|
+
|
|
104
|
+
eagle_db "UPDATE guardrails SET active = 0,
|
|
105
|
+
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
106
|
+
WHERE id = $id;"
|
|
107
|
+
}
|
package/lib/db-summaries.sh
CHANGED
|
@@ -81,6 +81,7 @@ eagle_search_summaries() {
|
|
|
81
81
|
FROM summaries s
|
|
82
82
|
JOIN summaries_fts f ON f.rowid = s.id
|
|
83
83
|
WHERE summaries_fts MATCH '$query'
|
|
84
|
+
AND s.request NOT LIKE '%<local-command-caveat>%'
|
|
84
85
|
$where_clause
|
|
85
86
|
ORDER BY rank
|
|
86
87
|
LIMIT $limit;"
|
|
@@ -141,6 +142,20 @@ eagle_last_session_enriched() {
|
|
|
141
142
|
ORDER BY created_at DESC LIMIT 1;"
|
|
142
143
|
}
|
|
143
144
|
|
|
145
|
+
eagle_search_gotchas_for_file() {
|
|
146
|
+
local project; project=$(eagle_sql_escape "$1")
|
|
147
|
+
local fts_query; fts_query=$(eagle_sql_escape "$2")
|
|
148
|
+
eagle_db "SELECT s.gotchas
|
|
149
|
+
FROM summaries s
|
|
150
|
+
JOIN summaries_fts f ON f.rowid = s.id
|
|
151
|
+
WHERE summaries_fts MATCH '$fts_query'
|
|
152
|
+
AND s.project = '$project'
|
|
153
|
+
AND s.gotchas IS NOT NULL
|
|
154
|
+
AND s.gotchas != ''
|
|
155
|
+
ORDER BY s.created_at DESC
|
|
156
|
+
LIMIT 2;"
|
|
157
|
+
}
|
|
158
|
+
|
|
144
159
|
eagle_search_stale_memories() {
|
|
145
160
|
local project; project=$(eagle_sql_escape "$1")
|
|
146
161
|
local fts_query; fts_query=$(eagle_sql_escape "$2")
|
package/lib/db.sh
CHANGED
package/lib/provider.sh
CHANGED
|
@@ -65,13 +65,22 @@ eagle_config_set() {
|
|
|
65
65
|
safe_value=$(printf '%s' "$value" | sed 's/[|&/\]/\\&/g')
|
|
66
66
|
|
|
67
67
|
if grep -q "^\[${section}\]" "$EAGLE_CONFIG_FILE" 2>/dev/null; then
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
" "
|
|
74
|
-
|
|
68
|
+
local tmp_cfg="${EAGLE_CONFIG_FILE}.tmp.$$"
|
|
69
|
+
awk -v sect="[${section}]" -v k="$key" -v v="$safe_value" '
|
|
70
|
+
BEGIN { in_sect=0; replaced=0 }
|
|
71
|
+
/^\[/ {
|
|
72
|
+
if (in_sect && !replaced) {
|
|
73
|
+
print k" = \""v"\""
|
|
74
|
+
replaced=1
|
|
75
|
+
}
|
|
76
|
+
in_sect=($0 == sect)
|
|
77
|
+
}
|
|
78
|
+
in_sect && !replaced && $0 ~ "^[[:space:]]*"k"[[:space:]]*=" {
|
|
79
|
+
print k" = \""v"\""; replaced=1; next
|
|
80
|
+
}
|
|
81
|
+
{ print }
|
|
82
|
+
END { if (in_sect && !replaced) print k" = \""v"\"" }
|
|
83
|
+
' "$EAGLE_CONFIG_FILE" > "$tmp_cfg" && mv "$tmp_cfg" "$EAGLE_CONFIG_FILE"
|
|
75
84
|
else
|
|
76
85
|
# printf is safe — no sed interpolation needed for append
|
|
77
86
|
printf '\n[%s]\n%s = "%s"\n' "$section" "$key" "$value" >> "$EAGLE_CONFIG_FILE"
|
|
@@ -95,7 +104,7 @@ eagle_ollama_best_model() {
|
|
|
95
104
|
models=$(eagle_ollama_models "$1")
|
|
96
105
|
[ -z "$models" ] && return 1
|
|
97
106
|
|
|
98
|
-
local preferred="
|
|
107
|
+
local preferred="gemma4 gemma3 gemma2 mistral llama3 phi3 deepseek-coder"
|
|
99
108
|
for pref in $preferred; do
|
|
100
109
|
if echo "$models" | grep -qi "$pref"; then
|
|
101
110
|
echo "$models" | grep -i "$pref" | head -1
|
package/package.json
CHANGED
package/scripts/curate.sh
CHANGED
|
@@ -89,7 +89,8 @@ If none qualify, output: NONE"
|
|
|
89
89
|
if [ "$DRY_RUN" -eq 1 ]; then
|
|
90
90
|
eagle_info " Would promote: $gotcha_text"
|
|
91
91
|
else
|
|
92
|
-
|
|
92
|
+
eagle_add_guardrail "$project" "$gotcha_text" "" "promoted"
|
|
93
|
+
eagle_log "INFO" "Curator: promoted gotcha to guardrail: $gotcha_text"
|
|
93
94
|
fi
|
|
94
95
|
promoted=$((promoted + 1))
|
|
95
96
|
;;
|
package/scripts/guard.sh
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ═══════════════════════════════════════════════════════════
|
|
3
|
+
# Eagle Mem — Guardrails management
|
|
4
|
+
# eagle-mem guard [add|list|remove]
|
|
5
|
+
# ═══════════════════════════════════════════════════════════
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
9
|
+
LIB_DIR="$SCRIPT_DIR/../lib"
|
|
10
|
+
|
|
11
|
+
. "$LIB_DIR/common.sh"
|
|
12
|
+
. "$LIB_DIR/db.sh"
|
|
13
|
+
. "$SCRIPT_DIR/style.sh"
|
|
14
|
+
|
|
15
|
+
eagle_ensure_db
|
|
16
|
+
|
|
17
|
+
project=$(eagle_project_from_cwd "$(pwd)")
|
|
18
|
+
if [ -z "$project" ]; then
|
|
19
|
+
eagle_err "Not in a recognized project directory"
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
eagle_header "Guardrails"
|
|
24
|
+
|
|
25
|
+
subcommand="${1:-list}"
|
|
26
|
+
shift 2>/dev/null || true
|
|
27
|
+
|
|
28
|
+
case "$subcommand" in
|
|
29
|
+
add)
|
|
30
|
+
rule="${1:-}"
|
|
31
|
+
if [ -z "$rule" ]; then
|
|
32
|
+
eagle_err "Usage: eagle-mem guard add \"rule text\" [--file pattern]"
|
|
33
|
+
eagle_info "Examples:"
|
|
34
|
+
eagle_info " eagle-mem guard add \"PRAGMA busy_timeout must precede synchronous\" --file \"db-core.sh\""
|
|
35
|
+
eagle_info " eagle-mem guard add \"Never manually copy files to ~/.eagle-mem\""
|
|
36
|
+
eagle_info " eagle-mem guard add \"Always validate session IDs\" --file \"*.sh\""
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
shift
|
|
40
|
+
|
|
41
|
+
file_pattern=""
|
|
42
|
+
while [ $# -gt 0 ]; do
|
|
43
|
+
case "$1" in
|
|
44
|
+
--file|-f)
|
|
45
|
+
if [ $# -lt 2 ] || [ -z "${2:-}" ]; then
|
|
46
|
+
eagle_err "--file requires a pattern argument"
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
file_pattern="$2"
|
|
50
|
+
shift 2
|
|
51
|
+
;;
|
|
52
|
+
*)
|
|
53
|
+
shift
|
|
54
|
+
;;
|
|
55
|
+
esac
|
|
56
|
+
done
|
|
57
|
+
|
|
58
|
+
eagle_add_guardrail "$project" "$rule" "$file_pattern" "manual"
|
|
59
|
+
eagle_ok "Guardrail added for project: $project"
|
|
60
|
+
if [ -n "$file_pattern" ]; then
|
|
61
|
+
eagle_info "File pattern: $file_pattern"
|
|
62
|
+
else
|
|
63
|
+
eagle_info "Scope: project-wide"
|
|
64
|
+
fi
|
|
65
|
+
eagle_dim "Rule: $rule"
|
|
66
|
+
;;
|
|
67
|
+
|
|
68
|
+
list|ls)
|
|
69
|
+
results=$(eagle_list_guardrails "$project")
|
|
70
|
+
if [ -z "$results" ]; then
|
|
71
|
+
eagle_info "No guardrails for project: $project"
|
|
72
|
+
eagle_dim "Add one: eagle-mem guard add \"rule\" [--file pattern]"
|
|
73
|
+
exit 0
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
echo -e " ${BOLD}ID File Pattern Rule Source Active${RESET}"
|
|
77
|
+
echo -e " ${DIM}──── ──────────────────── ──────────────────────────────────────── ──────── ──────${RESET}"
|
|
78
|
+
|
|
79
|
+
while IFS='|' read -r id pat rule source active _created; do
|
|
80
|
+
[ -z "$id" ] && continue
|
|
81
|
+
pat="${pat:-(all)}"
|
|
82
|
+
rule_display="${rule:0:40}"
|
|
83
|
+
[ ${#rule} -gt 40 ] && rule_display="${rule_display}..."
|
|
84
|
+
if [ "$active" = "1" ]; then
|
|
85
|
+
active_display="${GREEN}yes${RESET}"
|
|
86
|
+
else
|
|
87
|
+
active_display="${DIM}no${RESET}"
|
|
88
|
+
fi
|
|
89
|
+
printf " %-4s %-20s %-40s %-8s %b\n" "$id" "$pat" "$rule_display" "$source" "$active_display"
|
|
90
|
+
done <<< "$results"
|
|
91
|
+
echo ""
|
|
92
|
+
;;
|
|
93
|
+
|
|
94
|
+
remove|rm|delete)
|
|
95
|
+
id="${1:-}"
|
|
96
|
+
if [ -z "$id" ]; then
|
|
97
|
+
eagle_err "Usage: eagle-mem guard remove <id>"
|
|
98
|
+
exit 1
|
|
99
|
+
fi
|
|
100
|
+
case "$id" in
|
|
101
|
+
*[!0-9]*)
|
|
102
|
+
eagle_err "Invalid ID: '$id' (must be numeric)"
|
|
103
|
+
exit 1
|
|
104
|
+
;;
|
|
105
|
+
esac
|
|
106
|
+
eagle_remove_guardrail "$id"
|
|
107
|
+
eagle_ok "Guardrail #$id removed"
|
|
108
|
+
;;
|
|
109
|
+
|
|
110
|
+
sync)
|
|
111
|
+
eagle_info "Syncing rules from CLAUDE.md files..."
|
|
112
|
+
|
|
113
|
+
# Collect rules first, then delete+insert in a single transaction
|
|
114
|
+
# to prevent data loss if interrupted mid-sync
|
|
115
|
+
sync_sql=""
|
|
116
|
+
synced=0
|
|
117
|
+
p_esc=$(eagle_sql_escape "$project")
|
|
118
|
+
|
|
119
|
+
for claude_md in "$HOME/.claude/CLAUDE.md" ".claude/CLAUDE.md" "CLAUDE.md"; do
|
|
120
|
+
[ ! -f "$claude_md" ] && continue
|
|
121
|
+
eagle_dim " Reading: $claude_md"
|
|
122
|
+
|
|
123
|
+
# Extract lines that look like imperative rules
|
|
124
|
+
while IFS= read -r line; do
|
|
125
|
+
# Strip markdown formatting
|
|
126
|
+
clean=$(printf '%s\n' "$line" | sed 's/^[[:space:]]*[-*>]*[[:space:]]*//' | sed 's/\*\*//g' | sed 's/`//g')
|
|
127
|
+
[ -z "$clean" ] && continue
|
|
128
|
+
# Skip headings, short lines, and non-rule content
|
|
129
|
+
[ ${#clean} -lt 15 ] && continue
|
|
130
|
+
case "$clean" in \#*) continue ;; esac
|
|
131
|
+
|
|
132
|
+
# Cap rule length
|
|
133
|
+
[ ${#clean} -gt 2048 ] && clean="${clean:0:2048}"
|
|
134
|
+
rule_esc=$(eagle_sql_escape "$clean")
|
|
135
|
+
sync_sql+="INSERT OR IGNORE INTO guardrails (project, file_pattern, rule, source) VALUES ('$p_esc', '', '$rule_esc', 'claude-md');"$'\n'
|
|
136
|
+
synced=$((synced + 1))
|
|
137
|
+
done < <(grep -iE '^[[:space:]]*[-*>]*[[:space:]]*(never |always |do not |don'\''t |must |rule:|important:)' "$claude_md" 2>/dev/null)
|
|
138
|
+
done
|
|
139
|
+
|
|
140
|
+
# Atomic: always delete stale + insert new in one transaction
|
|
141
|
+
# Even when synced=0, DELETE must run to clear removed rules
|
|
142
|
+
eagle_db_pipe <<SQL
|
|
143
|
+
BEGIN;
|
|
144
|
+
DELETE FROM guardrails WHERE project = '$p_esc' AND source = 'claude-md';
|
|
145
|
+
$sync_sql
|
|
146
|
+
COMMIT;
|
|
147
|
+
SQL
|
|
148
|
+
if [ "$synced" -gt 0 ]; then
|
|
149
|
+
eagle_ok "$synced rules synced from CLAUDE.md"
|
|
150
|
+
else
|
|
151
|
+
eagle_info "No imperative rules found in CLAUDE.md files (stale rules cleared)"
|
|
152
|
+
fi
|
|
153
|
+
;;
|
|
154
|
+
|
|
155
|
+
*)
|
|
156
|
+
eagle_err "Unknown guard command: $subcommand"
|
|
157
|
+
eagle_info "Usage: eagle-mem guard [add|list|remove|sync]"
|
|
158
|
+
exit 1
|
|
159
|
+
;;
|
|
160
|
+
esac
|
package/scripts/help.sh
CHANGED
|
@@ -22,6 +22,13 @@ echo -e " ${CYAN}uninstall${RESET} Remove hooks and optionally delete data"
|
|
|
22
22
|
echo -e " ${CYAN}search${RESET} Search past sessions, memories, and code"
|
|
23
23
|
echo -e " ${CYAN}health${RESET} Diagnose pipeline health and background automation"
|
|
24
24
|
echo -e " ${CYAN}config${RESET} View or change LLM provider settings"
|
|
25
|
+
echo -e " ${CYAN}guard${RESET} Manage regression guardrails for files"
|
|
26
|
+
echo -e " ${CYAN}overview${RESET} Build or view project overview"
|
|
27
|
+
echo -e " ${CYAN}memories${RESET} View/sync Claude Code memories"
|
|
28
|
+
echo -e " ${CYAN}tasks${RESET} View mirrored tasks"
|
|
29
|
+
echo -e " ${CYAN}curate${RESET} Run curator (co-edits, hot files, guardrails)"
|
|
30
|
+
echo -e " ${CYAN}feature${RESET} Track and verify features"
|
|
31
|
+
echo -e " ${CYAN}prune${RESET} Clean old sessions and stale data"
|
|
25
32
|
echo ""
|
|
26
33
|
echo -e " ${BOLD}Search modes:${RESET}"
|
|
27
34
|
echo -e " ${DIM}\$${RESET} eagle-mem search \"auth bug\" ${DIM}# keyword search${RESET}"
|
package/scripts/install.sh
CHANGED
|
@@ -176,6 +176,14 @@ eagle_patch_hook "$SETTINGS" "PostToolUse" "Read|Write|Edit|Bash|TaskCreate|Task
|
|
|
176
176
|
"$EAGLE_MEM_DIR/hooks/post-tool-use.sh" \
|
|
177
177
|
"PostToolUse hook"
|
|
178
178
|
|
|
179
|
+
eagle_patch_hook "$SETTINGS" "TaskCreated" "" \
|
|
180
|
+
"$EAGLE_MEM_DIR/hooks/post-tool-use.sh" \
|
|
181
|
+
"TaskCreated hook"
|
|
182
|
+
|
|
183
|
+
eagle_patch_hook "$SETTINGS" "TaskCompleted" "" \
|
|
184
|
+
"$EAGLE_MEM_DIR/hooks/post-tool-use.sh" \
|
|
185
|
+
"TaskCompleted hook"
|
|
186
|
+
|
|
179
187
|
eagle_patch_hook "$SETTINGS" "SessionEnd" "" \
|
|
180
188
|
"$EAGLE_MEM_DIR/hooks/session-end.sh" \
|
|
181
189
|
"SessionEnd hook"
|
package/scripts/overview.sh
CHANGED
|
@@ -16,8 +16,12 @@ eagle_ensure_db
|
|
|
16
16
|
|
|
17
17
|
# ─── Parse arguments ──────────────────────────────────────
|
|
18
18
|
|
|
19
|
-
action="
|
|
20
|
-
|
|
19
|
+
action="show"
|
|
20
|
+
case "${1:-}" in
|
|
21
|
+
-*) ;; # flags parsed below
|
|
22
|
+
"") ;;
|
|
23
|
+
*) action="$1"; shift ;;
|
|
24
|
+
esac
|
|
21
25
|
|
|
22
26
|
project=""
|
|
23
27
|
json_output=false
|
|
@@ -36,7 +40,7 @@ show_help() {
|
|
|
36
40
|
echo -e " ${CYAN}-p, --project${RESET} <name> Project name (default: current dir)"
|
|
37
41
|
echo -e " ${CYAN}-j, --json${RESET} Output as JSON"
|
|
38
42
|
echo ""
|
|
39
|
-
echo -e " ${BOLD}Tip:${RESET}
|
|
43
|
+
echo -e " ${BOLD}Tip:${RESET} Overviews are auto-generated during ${CYAN}eagle-mem install${RESET} and background scans."
|
|
40
44
|
echo ""
|
|
41
45
|
exit 0
|
|
42
46
|
}
|
|
@@ -66,7 +70,7 @@ overview_show() {
|
|
|
66
70
|
|
|
67
71
|
if [ -z "$content" ]; then
|
|
68
72
|
eagle_dim "No overview for project '$project'"
|
|
69
|
-
eagle_dim "
|
|
73
|
+
eagle_dim "Use 'eagle-mem overview set <text>' to create one, or run install/update to auto-generate"
|
|
70
74
|
return
|
|
71
75
|
fi
|
|
72
76
|
|
package/scripts/search.sh
CHANGED
|
@@ -78,7 +78,13 @@ limit=$(eagle_sql_int "$limit")
|
|
|
78
78
|
# ─── Keyword search ──────────────────────────────────────
|
|
79
79
|
|
|
80
80
|
search_keyword() {
|
|
81
|
-
local
|
|
81
|
+
local sanitized_q
|
|
82
|
+
sanitized_q=$(eagle_fts_sanitize "$query")
|
|
83
|
+
if [ -z "$sanitized_q" ]; then
|
|
84
|
+
eagle_err "Search query contains no valid search terms"
|
|
85
|
+
exit 1
|
|
86
|
+
fi
|
|
87
|
+
local q; q=$(eagle_sql_escape "$sanitized_q")
|
|
82
88
|
local p; p=$(eagle_sql_escape "$project")
|
|
83
89
|
|
|
84
90
|
local where_project=""
|
|
@@ -328,7 +334,13 @@ search_memories() {
|
|
|
328
334
|
fi
|
|
329
335
|
|
|
330
336
|
if [ -n "$query" ]; then
|
|
331
|
-
local
|
|
337
|
+
local sanitized_mq
|
|
338
|
+
sanitized_mq=$(eagle_fts_sanitize "$query")
|
|
339
|
+
if [ -z "$sanitized_mq" ]; then
|
|
340
|
+
eagle_err "Search query contains no valid search terms"
|
|
341
|
+
exit 1
|
|
342
|
+
fi
|
|
343
|
+
local q; q=$(eagle_sql_escape "$sanitized_mq")
|
|
332
344
|
local where_match="WHERE claude_memories_fts MATCH '$q'"
|
|
333
345
|
if [ "$cross_project" = false ]; then
|
|
334
346
|
where_match="$where_match AND m.project = '$p'"
|
|
@@ -409,7 +421,13 @@ search_tasks() {
|
|
|
409
421
|
fi
|
|
410
422
|
|
|
411
423
|
if [ -n "$query" ]; then
|
|
412
|
-
local
|
|
424
|
+
local sanitized_tq
|
|
425
|
+
sanitized_tq=$(eagle_fts_sanitize "$query")
|
|
426
|
+
if [ -z "$sanitized_tq" ]; then
|
|
427
|
+
eagle_err "Search query contains no valid search terms"
|
|
428
|
+
exit 1
|
|
429
|
+
fi
|
|
430
|
+
local q; q=$(eagle_sql_escape "$sanitized_tq")
|
|
413
431
|
|
|
414
432
|
if [ "$json_output" = true ]; then
|
|
415
433
|
eagle_db_json "SELECT t.subject, t.status, t.project, t.updated_at
|
package/scripts/tasks.sh
CHANGED
|
@@ -17,8 +17,12 @@ eagle_ensure_db
|
|
|
17
17
|
|
|
18
18
|
# ─── Parse arguments ──────────────────────────────────────
|
|
19
19
|
|
|
20
|
-
action="
|
|
21
|
-
|
|
20
|
+
action="pending"
|
|
21
|
+
case "${1:-}" in
|
|
22
|
+
-*) ;; # flags parsed below
|
|
23
|
+
"") ;;
|
|
24
|
+
*) action="$1"; shift ;;
|
|
25
|
+
esac
|
|
22
26
|
|
|
23
27
|
project=""
|
|
24
28
|
json_output=false
|
|
@@ -123,9 +127,15 @@ tasks_search() {
|
|
|
123
127
|
exit 1
|
|
124
128
|
fi
|
|
125
129
|
|
|
130
|
+
local sanitized_query
|
|
131
|
+
sanitized_query=$(eagle_fts_sanitize "$query")
|
|
132
|
+
if [ -z "$sanitized_query" ]; then
|
|
133
|
+
eagle_err "Search query contains no valid search terms"
|
|
134
|
+
exit 1
|
|
135
|
+
fi
|
|
136
|
+
|
|
126
137
|
if [ "$json_output" = true ]; then
|
|
127
|
-
local query_sql; query_sql=$(
|
|
128
|
-
query_sql=$(eagle_sql_escape "$query_sql")
|
|
138
|
+
local query_sql; query_sql=$(eagle_sql_escape "$sanitized_query")
|
|
129
139
|
eagle_db_json "SELECT t.source_task_id, t.subject, t.status, t.description, t.updated_at
|
|
130
140
|
FROM claude_tasks t
|
|
131
141
|
JOIN claude_tasks_fts f ON f.rowid = t.id
|
|
@@ -137,8 +147,7 @@ tasks_search() {
|
|
|
137
147
|
fi
|
|
138
148
|
|
|
139
149
|
local results
|
|
140
|
-
local query_sql; query_sql=$(
|
|
141
|
-
query_sql=$(eagle_sql_escape "$query_sql")
|
|
150
|
+
local query_sql; query_sql=$(eagle_sql_escape "$sanitized_query")
|
|
142
151
|
results=$(eagle_db "SELECT t.source_task_id, t.subject, t.status, t.description
|
|
143
152
|
FROM claude_tasks t
|
|
144
153
|
JOIN claude_tasks_fts f ON f.rowid = t.id
|
package/scripts/update.sh
CHANGED
|
@@ -73,6 +73,8 @@ if [ -f "$SETTINGS" ] && command -v jq &>/dev/null; then
|
|
|
73
73
|
eagle_patch_hook "$SETTINGS" "SessionStart" "" "$EAGLE_MEM_DIR/hooks/session-start.sh"
|
|
74
74
|
eagle_patch_hook "$SETTINGS" "Stop" "" "$EAGLE_MEM_DIR/hooks/stop.sh"
|
|
75
75
|
eagle_patch_hook "$SETTINGS" "PostToolUse" "Read|Write|Edit|Bash|TaskCreate|TaskUpdate" "$EAGLE_MEM_DIR/hooks/post-tool-use.sh"
|
|
76
|
+
eagle_patch_hook "$SETTINGS" "TaskCreated" "" "$EAGLE_MEM_DIR/hooks/post-tool-use.sh"
|
|
77
|
+
eagle_patch_hook "$SETTINGS" "TaskCompleted" "" "$EAGLE_MEM_DIR/hooks/post-tool-use.sh"
|
|
76
78
|
eagle_patch_hook "$SETTINGS" "SessionEnd" "" "$EAGLE_MEM_DIR/hooks/session-end.sh"
|
|
77
79
|
eagle_patch_hook "$SETTINGS" "UserPromptSubmit" "" "$EAGLE_MEM_DIR/hooks/user-prompt-submit.sh"
|
|
78
80
|
eagle_patch_hook "$SETTINGS" "PreToolUse" "Bash" "$EAGLE_MEM_DIR/hooks/pre-tool-use.sh"
|