eagle-mem 4.2.1 → 4.6.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,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/health.sh CHANGED
@@ -54,8 +54,8 @@ max_score=$((max_score + 25))
54
54
 
55
55
  total_sessions=$(eagle_db "SELECT COUNT(*) FROM sessions WHERE project = '$p_esc';")
56
56
  total_summaries=$(eagle_db "SELECT COUNT(*) FROM summaries WHERE project = '$p_esc';")
57
- heuristic_summaries=$(eagle_db "SELECT COUNT(*) FROM summaries WHERE project = '$p_esc' AND completed = '(auto-captured)';")
58
- enriched_summaries=$(eagle_db "SELECT COUNT(*) FROM summaries WHERE project = '$p_esc' AND (decisions IS NOT NULL AND decisions != '' OR gotchas IS NOT NULL AND gotchas != '' OR key_files IS NOT NULL AND key_files != '');")
57
+ heuristic_summaries=$(eagle_db "SELECT COUNT(*) FROM summaries WHERE project = '$p_esc' AND (decisions IS NULL OR decisions = '') AND (gotchas IS NULL OR gotchas = '');")
58
+ enriched_summaries=$(eagle_db "SELECT COUNT(*) FROM summaries WHERE project = '$p_esc' AND (decisions IS NOT NULL AND decisions != '' OR gotchas IS NOT NULL AND gotchas != '');")
59
59
 
60
60
  if [ "${total_sessions:-0}" -eq 0 ]; then
61
61
  capture_pct=0
@@ -92,7 +92,7 @@ fi
92
92
 
93
93
  if [ "${total_summaries:-0}" -gt 0 ]; then
94
94
  if [ "$enrich_pct" -ge 50 ]; then
95
- eagle_ok "Enriched: ${enriched_summaries}/${total_summaries} (${enrich_pct}%) have decisions/gotchas/key_files"
95
+ eagle_ok "Enriched: ${enriched_summaries}/${total_summaries} (${enrich_pct}%) have decisions/gotchas"
96
96
  score=$((score + 25))
97
97
  elif [ "$enrich_pct" -ge 20 ]; then
98
98
  eagle_warn "Enriched: ${enriched_summaries}/${total_summaries} (${enrich_pct}%) — LLM extraction may need tuning"
@@ -101,9 +101,9 @@ if [ "${total_summaries:-0}" -gt 0 ]; then
101
101
  elif [ "${enriched_summaries:-0}" -gt 0 ]; then
102
102
  eagle_fail "Enriched: ${enriched_summaries}/${total_summaries} (${enrich_pct}%)"
103
103
  score=$((score + 5))
104
- issues+=("${enrich_pct}% enrichment. Decisions/gotchas/key_files mostly missing.")
104
+ issues+=("${enrich_pct}% enrichment. Decisions/gotchas mostly missing.")
105
105
  else
106
- eagle_fail "Enriched: 0/${total_summaries} — no summaries have decisions/gotchas/key_files"
106
+ eagle_fail "Enriched: 0/${total_summaries} — no summaries have decisions/gotchas"
107
107
  issues+=("Zero enrichment. Check provider config: eagle-mem config")
108
108
  fi
109
109
  else
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}"
@@ -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"
@@ -16,8 +16,12 @@ eagle_ensure_db
16
16
 
17
17
  # ─── Parse arguments ──────────────────────────────────────
18
18
 
19
- action="${1:-list}"
20
- shift 2>/dev/null || true
19
+ action="list"
20
+ case "${1:-}" in
21
+ -*) ;; # flags parsed below
22
+ "") ;;
23
+ *) action="$1"; shift ;;
24
+ esac
21
25
 
22
26
  project=""
23
27
  limit=20
@@ -16,8 +16,12 @@ eagle_ensure_db
16
16
 
17
17
  # ─── Parse arguments ──────────────────────────────────────
18
18
 
19
- action="${1:-show}"
20
- shift 2>/dev/null || true
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} Use ${CYAN}eagle-mem scan${RESET} to auto-generate an overview from code."
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 "Run 'eagle-mem scan' or 'eagle-mem overview set <text>' to create one"
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 q; q=$(eagle_sql_escape "$(eagle_fts_sanitize "$query")")
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 q; q=$(eagle_sql_escape "$(eagle_fts_sanitize "$query")")
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 q; q=$(eagle_sql_escape "$(eagle_fts_sanitize "$query")")
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
@@ -8,12 +8,14 @@ eagle_mem_statusline() {
8
8
  local em_db="$HOME/.eagle-mem/memory.db"
9
9
  [ -f "$em_db" ] || return
10
10
 
11
+ local SCRIPT_DIR; SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
+ . "$SCRIPT_DIR/../lib/common.sh"
13
+
11
14
  local proj
12
- proj=$(basename "$project_dir")
15
+ proj=$(eagle_project_from_cwd "$project_dir")
13
16
  [ -z "$proj" ] && return
14
17
 
15
- # Escape single quotes for safe SQL interpolation
16
- proj=$(printf '%s' "$proj" | sed "s/'/''/g")
18
+ proj=$(eagle_sql_escape "$proj")
17
19
 
18
20
  local cnt mem
19
21
  cnt=$(echo ".headers off
package/scripts/tasks.sh CHANGED
@@ -17,8 +17,12 @@ eagle_ensure_db
17
17
 
18
18
  # ─── Parse arguments ──────────────────────────────────────
19
19
 
20
- action="${1:-pending}"
21
- shift 2>/dev/null || true
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=$(eagle_fts_sanitize "$query")
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=$(eagle_fts_sanitize "$query")
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"