eagle-mem 3.0.2 → 3.1.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 +1 -0
- package/db/016_eagle_meta.sql +8 -0
- package/hooks/post-tool-use.sh +1 -0
- package/hooks/pre-tool-use.sh +1 -0
- package/hooks/session-end.sh +23 -1
- package/hooks/session-start.sh +12 -0
- package/hooks/stop.sh +61 -9
- package/lib/common.sh +12 -0
- package/lib/db-sessions.sh +31 -0
- package/lib/db-summaries.sh +11 -0
- package/lib/provider.sh +2 -1
- package/package.json +1 -1
- package/scripts/curate.sh +1 -0
- package/scripts/health.sh +218 -0
- package/scripts/help.sh +1 -0
package/bin/eagle-mem
CHANGED
|
@@ -30,6 +30,7 @@ case "$command" in
|
|
|
30
30
|
config) bash "$SCRIPTS_DIR/config.sh" "$@" ;;
|
|
31
31
|
curate) bash "$SCRIPTS_DIR/curate.sh" "$@" ;;
|
|
32
32
|
feature) bash "$SCRIPTS_DIR/feature.sh" "$@" ;;
|
|
33
|
+
health) bash "$SCRIPTS_DIR/health.sh" "$@" ;;
|
|
33
34
|
help|--help|-h)
|
|
34
35
|
bash "$SCRIPTS_DIR/help.sh" ;;
|
|
35
36
|
version|--version|-v|-V)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
-- Eagle meta key-value store for system state (curator timestamps, etc.)
|
|
2
|
+
CREATE TABLE IF NOT EXISTS eagle_meta (
|
|
3
|
+
key TEXT NOT NULL,
|
|
4
|
+
project TEXT,
|
|
5
|
+
value TEXT NOT NULL,
|
|
6
|
+
updated_at TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
7
|
+
UNIQUE(key, project)
|
|
8
|
+
);
|
package/hooks/post-tool-use.sh
CHANGED
|
@@ -31,6 +31,7 @@ esac
|
|
|
31
31
|
[ ! -f "$EAGLE_MEM_DB" ] && exit 0
|
|
32
32
|
|
|
33
33
|
project=$(eagle_project_from_cwd "$cwd")
|
|
34
|
+
[ -z "$project" ] && exit 0
|
|
34
35
|
|
|
35
36
|
# Ensure session row exists before inserting observations (FK constraint).
|
|
36
37
|
# PostToolUse can race SessionStart — the session row might not exist yet.
|
package/hooks/pre-tool-use.sh
CHANGED
|
@@ -27,6 +27,7 @@ cmd=$(echo "$input" | jq -r '.tool_input.command // empty')
|
|
|
27
27
|
session_id=$(echo "$input" | jq -r '.session_id // empty')
|
|
28
28
|
cwd=$(echo "$input" | jq -r '.cwd // empty')
|
|
29
29
|
project=$(eagle_project_from_cwd "$cwd")
|
|
30
|
+
[ -z "$project" ] && exit 0
|
|
30
31
|
|
|
31
32
|
context=""
|
|
32
33
|
|
package/hooks/session-end.sh
CHANGED
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
# ═══════════════════════════════════════════════════════════
|
|
3
3
|
# Eagle Mem — SessionEnd hook
|
|
4
4
|
# Fires when the Claude Code session ends
|
|
5
|
-
# Marks the session as completed
|
|
5
|
+
# Marks the session as completed + triggers auto-curate
|
|
6
6
|
# ═══════════════════════════════════════════════════════════
|
|
7
7
|
set +e
|
|
8
8
|
|
|
9
9
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
10
10
|
LIB_DIR="$SCRIPT_DIR/../lib"
|
|
11
|
+
SCRIPTS_DIR="$SCRIPT_DIR/../scripts"
|
|
11
12
|
|
|
12
13
|
. "$LIB_DIR/common.sh"
|
|
13
14
|
. "$LIB_DIR/db.sh"
|
|
15
|
+
. "$LIB_DIR/provider.sh"
|
|
14
16
|
|
|
15
17
|
input=$(eagle_read_stdin)
|
|
16
18
|
[ -z "$input" ] && exit 0
|
|
@@ -21,6 +23,7 @@ session_id=$(echo "$input" | jq -r '.session_id // empty')
|
|
|
21
23
|
|
|
22
24
|
cwd=$(echo "$input" | jq -r '.cwd // empty')
|
|
23
25
|
project=$(eagle_project_from_cwd "$cwd")
|
|
26
|
+
[ -z "$project" ] && exit 0
|
|
24
27
|
|
|
25
28
|
# Final sweep: re-capture all task files to catch status changes
|
|
26
29
|
# Claude Code may update task status without triggering PostToolUse
|
|
@@ -41,4 +44,23 @@ eagle_log "INFO" "SessionEnd: session=$session_id marked completed"
|
|
|
41
44
|
# Prune observations older than 90 days (keeps DB size bounded)
|
|
42
45
|
eagle_prune_observations 90 "$project"
|
|
43
46
|
|
|
47
|
+
# ─── Auto-curate trigger ─────────────────────────────────
|
|
48
|
+
curator_schedule=$(eagle_config_get "curator" "schedule" "manual")
|
|
49
|
+
if [ "$curator_schedule" = "auto" ]; then
|
|
50
|
+
provider=$(eagle_config_get "provider" "type" "none")
|
|
51
|
+
if [ "$provider" != "none" ]; then
|
|
52
|
+
min_sessions=$(eagle_config_get "curator" "min_sessions" "5")
|
|
53
|
+
min_sessions=$(eagle_sql_int "$min_sessions")
|
|
54
|
+
|
|
55
|
+
last_curated=$(eagle_meta_get "last_curated_at" "$project")
|
|
56
|
+
since="${last_curated:-1970-01-01T00:00:00Z}"
|
|
57
|
+
|
|
58
|
+
sessions_since=$(eagle_count_sessions_since "$project" "$since")
|
|
59
|
+
if [ "${sessions_since:-0}" -ge "$min_sessions" ]; then
|
|
60
|
+
eagle_log "INFO" "SessionEnd: auto-curate triggered (${sessions_since} sessions since last curate)"
|
|
61
|
+
nohup bash "$SCRIPTS_DIR/curate.sh" -p "$project" >> "$EAGLE_MEM_LOG" 2>&1 &
|
|
62
|
+
fi
|
|
63
|
+
fi
|
|
64
|
+
fi
|
|
65
|
+
|
|
44
66
|
exit 0
|
package/hooks/session-start.sh
CHANGED
|
@@ -26,6 +26,9 @@ model=$(echo "$input" | jq -r '.model // empty')
|
|
|
26
26
|
|
|
27
27
|
project=$(eagle_project_from_cwd "$cwd")
|
|
28
28
|
|
|
29
|
+
# Skip ephemeral directories (tmp, Downloads, etc.) — no tracking
|
|
30
|
+
[ -z "$project" ] && exit 0
|
|
31
|
+
|
|
29
32
|
eagle_log "INFO" "SessionStart: session=$session_id project=$project source=$source_type"
|
|
30
33
|
|
|
31
34
|
eagle_upsert_session "$session_id" "$project" "$cwd" "$model" "$source_type"
|
|
@@ -134,6 +137,15 @@ if [ -n "$update_notice" ]; then
|
|
|
134
137
|
"
|
|
135
138
|
fi
|
|
136
139
|
|
|
140
|
+
# Nudge if last session lacked enrichment
|
|
141
|
+
last_enriched=$(eagle_last_session_enriched "$project")
|
|
142
|
+
if [ "${last_enriched:-1}" = "0" ] && [ "$stat_with_summaries" -gt 0 ]; then
|
|
143
|
+
context+="=== EAGLE MEM — Enrichment Reminder ===
|
|
144
|
+
The previous session's summary did NOT include decisions, gotchas, or key_files. These fields power Eagle Mem's self-learning (feature discovery, anti-regression, command intelligence). Please emit an <eagle-summary> block at the end of this session with these fields populated.
|
|
145
|
+
|
|
146
|
+
"
|
|
147
|
+
fi
|
|
148
|
+
|
|
137
149
|
# Project overview
|
|
138
150
|
overview=$(eagle_get_overview "$project")
|
|
139
151
|
if [ -n "$overview" ]; then
|
package/hooks/stop.sh
CHANGED
|
@@ -12,6 +12,7 @@ LIB_DIR="$SCRIPT_DIR/../lib"
|
|
|
12
12
|
|
|
13
13
|
. "$LIB_DIR/common.sh"
|
|
14
14
|
. "$LIB_DIR/db.sh"
|
|
15
|
+
. "$LIB_DIR/provider.sh"
|
|
15
16
|
|
|
16
17
|
eagle_ensure_db
|
|
17
18
|
|
|
@@ -29,6 +30,7 @@ agent_type=$(echo "$input" | jq -r '.agent_type // empty')
|
|
|
29
30
|
[ -n "$agent_type" ] && [ "$agent_type" != "main" ] && exit 0
|
|
30
31
|
|
|
31
32
|
project=$(eagle_project_from_cwd "$cwd")
|
|
33
|
+
[ -z "$project" ] && exit 0
|
|
32
34
|
|
|
33
35
|
eagle_log "INFO" "Stop: session=$session_id project=$project transcript=$transcript_path"
|
|
34
36
|
|
|
@@ -111,21 +113,25 @@ if [ -n "$summary_block" ]; then
|
|
|
111
113
|
eagle_log "INFO" "Stop: parsed eagle-summary block"
|
|
112
114
|
fi
|
|
113
115
|
|
|
114
|
-
# ───
|
|
116
|
+
# ─── Guard: skip fallback work if summary already exists ──
|
|
117
|
+
# Stop fires every assistant turn. Without this, the heuristic and LLM
|
|
118
|
+
# enrichment blocks fire on turn 2+ — wasting tokens and producing
|
|
119
|
+
# empty inserts that get rejected.
|
|
115
120
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
# Stop fires every turn -- without this guard, each turn creates a duplicate row.
|
|
121
|
+
existing_count=0
|
|
122
|
+
if [ -z "$summary_block" ] && [ -n "$transcript_path" ] && [ -f "$transcript_path" ]; then
|
|
119
123
|
existing_count=$(eagle_count_session_summaries "$session_id")
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
if [ -z "$summary_block" ] && [ "${existing_count:-0}" -eq 0 ]; then
|
|
127
|
+
|
|
128
|
+
# ─── Heuristic fallback: extract from tool calls ───────────
|
|
129
|
+
|
|
130
|
+
if [ -z "$request" ] && [ -n "$transcript_path" ] && [ -f "$transcript_path" ]; then
|
|
123
131
|
eagle_log "INFO" "Stop: no eagle-summary found, using heuristic fallback"
|
|
124
132
|
|
|
125
|
-
# Extract first user prompt as "request"
|
|
126
133
|
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)
|
|
127
134
|
|
|
128
|
-
# Extract files from Read/Write/Edit tool calls
|
|
129
135
|
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)
|
|
130
136
|
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)
|
|
131
137
|
|
|
@@ -138,6 +144,52 @@ if [ -z "$request" ] && [ -n "$transcript_path" ] && [ -f "$transcript_path" ];
|
|
|
138
144
|
|
|
139
145
|
completed="(auto-captured from tool usage)"
|
|
140
146
|
fi
|
|
147
|
+
|
|
148
|
+
# ─── LLM enrichment: extract decisions/gotchas/key_files ──
|
|
149
|
+
|
|
150
|
+
if [ -z "$decisions" ] && [ -z "$gotchas" ] && [ -z "$key_files" ]; then
|
|
151
|
+
provider=$(eagle_config_get "provider" "type" "none" 2>/dev/null)
|
|
152
|
+
if [ "$provider" != "none" ] && [ -n "$text_content" ]; then
|
|
153
|
+
excerpt=$(echo "$text_content" | tail -c 2000)
|
|
154
|
+
|
|
155
|
+
enrich_prompt="Extract from this Claude Code session excerpt:
|
|
156
|
+
1. DECISIONS: architectural or design choices made (with WHY). One per line.
|
|
157
|
+
2. GOTCHAS: non-obvious pitfalls, bugs found, things that surprised. One per line.
|
|
158
|
+
3. KEY_FILES: important files that were central to the work. One per line.
|
|
159
|
+
|
|
160
|
+
SESSION EXCERPT:
|
|
161
|
+
$excerpt
|
|
162
|
+
|
|
163
|
+
Output EXACTLY this format (omit sections with nothing to report):
|
|
164
|
+
DECISIONS:
|
|
165
|
+
- <decision> — why: <reason>
|
|
166
|
+
GOTCHAS:
|
|
167
|
+
- <gotcha>
|
|
168
|
+
KEY_FILES:
|
|
169
|
+
- <filepath>"
|
|
170
|
+
|
|
171
|
+
enrich_result=$(eagle_llm_call "$enrich_prompt" "Extract structured facts from development sessions. Be concise. Only include items with clear evidence." 512 2>/dev/null) || true
|
|
172
|
+
|
|
173
|
+
if [ -n "$enrich_result" ]; then
|
|
174
|
+
extract_section() {
|
|
175
|
+
local result="$1" header="$2"
|
|
176
|
+
echo "$result" | awk -v h="$header:" '
|
|
177
|
+
$0 == h || $0 ~ "^"h { found=1; next }
|
|
178
|
+
found && /^[A-Z_]+:/ { exit }
|
|
179
|
+
found && /^- / { sub(/^- /, ""); lines[++n] = $0 }
|
|
180
|
+
END { for (i=1; i<=n; i++) { printf "%s", lines[i]; if (i<n) printf "; " } }
|
|
181
|
+
'
|
|
182
|
+
}
|
|
183
|
+
decisions=$(extract_section "$enrich_result" "DECISIONS")
|
|
184
|
+
gotchas=$(extract_section "$enrich_result" "GOTCHAS")
|
|
185
|
+
key_files=$(extract_section "$enrich_result" "KEY_FILES")
|
|
186
|
+
[ -n "$decisions" ] || [ -n "$gotchas" ] || [ -n "$key_files" ] && eagle_log "INFO" "Stop: LLM enrichment extracted for session=$session_id"
|
|
187
|
+
fi
|
|
188
|
+
fi
|
|
189
|
+
fi
|
|
190
|
+
|
|
191
|
+
elif [ -z "$summary_block" ] && [ "${existing_count:-0}" -gt 0 ]; then
|
|
192
|
+
eagle_log "INFO" "Stop: skipping fallback — summary already exists for session=$session_id (count=$existing_count)"
|
|
141
193
|
fi
|
|
142
194
|
|
|
143
195
|
# ─── Redact secrets from all text fields before storage ────
|
package/lib/common.sh
CHANGED
|
@@ -25,6 +25,18 @@ eagle_log() {
|
|
|
25
25
|
|
|
26
26
|
eagle_project_from_cwd() {
|
|
27
27
|
local cwd="${1:-$(pwd)}"
|
|
28
|
+
local resolved="$cwd"
|
|
29
|
+
|
|
30
|
+
# Resolve /private/tmp → /tmp on macOS
|
|
31
|
+
case "$resolved" in /private/tmp*) resolved="/tmp${resolved#/private/tmp}" ;; esac
|
|
32
|
+
|
|
33
|
+
# Skip ephemeral directories — return empty so hooks early-exit
|
|
34
|
+
case "$resolved" in
|
|
35
|
+
/tmp|/tmp/*|/var/tmp|/var/tmp/*) echo ""; return ;;
|
|
36
|
+
"$HOME/Downloads"|"$HOME/Downloads/"*) echo ""; return ;;
|
|
37
|
+
"$HOME/Desktop"|"$HOME/Desktop/"*) echo ""; return ;;
|
|
38
|
+
esac
|
|
39
|
+
|
|
28
40
|
local git_root
|
|
29
41
|
git_root=$(git -C "$cwd" rev-parse --show-toplevel 2>/dev/null)
|
|
30
42
|
if [ -n "$git_root" ]; then
|
package/lib/db-sessions.sh
CHANGED
|
@@ -66,3 +66,34 @@ eagle_count_session_summaries() {
|
|
|
66
66
|
local sid; sid=$(eagle_sql_escape "$1")
|
|
67
67
|
eagle_db "SELECT COUNT(*) FROM summaries WHERE session_id = '$sid';"
|
|
68
68
|
}
|
|
69
|
+
|
|
70
|
+
eagle_meta_get() {
|
|
71
|
+
local key; key=$(eagle_sql_escape "$1")
|
|
72
|
+
local project="${2:-}"
|
|
73
|
+
if [ -n "$project" ]; then
|
|
74
|
+
local p_esc; p_esc=$(eagle_sql_escape "$project")
|
|
75
|
+
eagle_db "SELECT value FROM eagle_meta WHERE key = '$key' AND project = '$p_esc' LIMIT 1;"
|
|
76
|
+
else
|
|
77
|
+
eagle_db "SELECT value FROM eagle_meta WHERE key = '$key' AND project IS NULL LIMIT 1;"
|
|
78
|
+
fi
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
eagle_meta_set() {
|
|
82
|
+
local key; key=$(eagle_sql_escape "$1")
|
|
83
|
+
local value; value=$(eagle_sql_escape "$2")
|
|
84
|
+
local project="${3:-}"
|
|
85
|
+
if [ -n "$project" ]; then
|
|
86
|
+
local p_esc; p_esc=$(eagle_sql_escape "$project")
|
|
87
|
+
eagle_db "INSERT INTO eagle_meta (key, project, value) VALUES ('$key', '$p_esc', '$value')
|
|
88
|
+
ON CONFLICT(key, project) DO UPDATE SET value = excluded.value, updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now');"
|
|
89
|
+
else
|
|
90
|
+
eagle_db "INSERT INTO eagle_meta (key, project, value) VALUES ('$key', NULL, '$value')
|
|
91
|
+
ON CONFLICT(key, project) DO UPDATE SET value = excluded.value, updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now');"
|
|
92
|
+
fi
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
eagle_count_sessions_since() {
|
|
96
|
+
local project; project=$(eagle_sql_escape "$1")
|
|
97
|
+
local since; since=$(eagle_sql_escape "$2")
|
|
98
|
+
eagle_db "SELECT COUNT(*) FROM sessions WHERE project = '$project' AND started_at > '$since';"
|
|
99
|
+
}
|
package/lib/db-summaries.sh
CHANGED
|
@@ -130,6 +130,17 @@ eagle_search_decisions_for_file() {
|
|
|
130
130
|
LIMIT 1;"
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
eagle_last_session_enriched() {
|
|
134
|
+
local project; project=$(eagle_sql_escape "$1")
|
|
135
|
+
eagle_db "SELECT CASE
|
|
136
|
+
WHEN (decisions IS NOT NULL AND decisions != '')
|
|
137
|
+
OR (gotchas IS NOT NULL AND gotchas != '')
|
|
138
|
+
OR (key_files IS NOT NULL AND key_files != '')
|
|
139
|
+
THEN 1 ELSE 0 END
|
|
140
|
+
FROM summaries WHERE project = '$project'
|
|
141
|
+
ORDER BY created_at DESC LIMIT 1;"
|
|
142
|
+
}
|
|
143
|
+
|
|
133
144
|
eagle_search_stale_memories() {
|
|
134
145
|
local project; project=$(eagle_sql_escape "$1")
|
|
135
146
|
local fts_query; fts_query=$(eagle_sql_escape "$2")
|
package/lib/provider.sh
CHANGED
|
@@ -151,8 +151,9 @@ model = "claude-haiku-4-5-20251001"
|
|
|
151
151
|
model = "gpt-4o-mini"
|
|
152
152
|
|
|
153
153
|
[curator]
|
|
154
|
-
#
|
|
154
|
+
# "auto" = triggers at session end after min_sessions; "manual" = CLI only
|
|
155
155
|
schedule = "manual"
|
|
156
|
+
min_sessions = 5
|
|
156
157
|
|
|
157
158
|
[redaction]
|
|
158
159
|
# Additional secret patterns (regex) beyond built-in defaults
|
package/package.json
CHANGED
package/scripts/curate.sh
CHANGED
|
@@ -361,5 +361,6 @@ echo ""
|
|
|
361
361
|
if [ "$DRY_RUN" -eq 1 ]; then
|
|
362
362
|
eagle_footer "Dry run complete. Run without --dry-run to apply changes."
|
|
363
363
|
else
|
|
364
|
+
eagle_meta_set "last_curated_at" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$project"
|
|
364
365
|
eagle_footer "Curation complete for '$project'."
|
|
365
366
|
fi
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ═══════════════════════════════════════════════════════════
|
|
3
|
+
# Eagle Mem — Health Check
|
|
4
|
+
# Diagnoses how well the self-learning pipeline is working
|
|
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
|
+
. "$SCRIPT_DIR/style.sh"
|
|
13
|
+
. "$LIB_DIR/db.sh"
|
|
14
|
+
. "$LIB_DIR/provider.sh"
|
|
15
|
+
|
|
16
|
+
eagle_header "Health Check"
|
|
17
|
+
|
|
18
|
+
project=""
|
|
19
|
+
JSON_OUT=0
|
|
20
|
+
|
|
21
|
+
while [ $# -gt 0 ]; do
|
|
22
|
+
case "$1" in
|
|
23
|
+
-p|--project) project="$2"; shift 2 ;;
|
|
24
|
+
-j|--json) JSON_OUT=1; shift ;;
|
|
25
|
+
*) shift ;;
|
|
26
|
+
esac
|
|
27
|
+
done
|
|
28
|
+
|
|
29
|
+
if [ -z "$project" ]; then
|
|
30
|
+
project=$(eagle_project_from_cwd "$(pwd)")
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
if [ -z "$project" ]; then
|
|
34
|
+
eagle_err "Cannot determine project (ephemeral directory). Use -p <project>."
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
p_esc=$(eagle_sql_escape "$project")
|
|
39
|
+
|
|
40
|
+
eagle_info "Project: ${BOLD}$project${RESET}"
|
|
41
|
+
echo ""
|
|
42
|
+
|
|
43
|
+
score=0
|
|
44
|
+
max_score=0
|
|
45
|
+
issues=()
|
|
46
|
+
|
|
47
|
+
# ─── 1. Summary enrichment rate ──────────────────────────
|
|
48
|
+
|
|
49
|
+
max_score=$((max_score + 30))
|
|
50
|
+
|
|
51
|
+
total_summaries=$(eagle_db "SELECT COUNT(*) FROM summaries WHERE project = '$p_esc';")
|
|
52
|
+
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 != '');")
|
|
53
|
+
|
|
54
|
+
if [ "${total_summaries:-0}" -eq 0 ]; then
|
|
55
|
+
enrich_pct=0
|
|
56
|
+
else
|
|
57
|
+
enrich_pct=$((enriched_summaries * 100 / total_summaries))
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
if [ "$enrich_pct" -ge 50 ]; then
|
|
61
|
+
eagle_ok "Enriched summaries: ${enriched_summaries}/${total_summaries} (${enrich_pct}%)"
|
|
62
|
+
score=$((score + 30))
|
|
63
|
+
elif [ "$enrich_pct" -ge 20 ]; then
|
|
64
|
+
eagle_warn "Enriched summaries: ${enriched_summaries}/${total_summaries} (${enrich_pct}%) — aim for 50%+"
|
|
65
|
+
score=$((score + 15))
|
|
66
|
+
issues+=("Low enrichment rate (${enrich_pct}%). Eagle-summary blocks aren't being emitted reliably.")
|
|
67
|
+
elif [ "${total_summaries:-0}" -gt 0 ]; then
|
|
68
|
+
eagle_fail "Enriched summaries: ${enriched_summaries}/${total_summaries} (${enrich_pct}%) — self-learning not working"
|
|
69
|
+
issues+=("Critical: ${enrich_pct}% enrichment. Decisions/gotchas/key_files are not being captured.")
|
|
70
|
+
else
|
|
71
|
+
eagle_dim " No summaries yet"
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
# ─── 2. Feature discovery ────────────────────────────────
|
|
75
|
+
|
|
76
|
+
max_score=$((max_score + 20))
|
|
77
|
+
|
|
78
|
+
feature_count=$(eagle_db "SELECT COUNT(*) FROM features WHERE project = '$p_esc' AND status = 'active';")
|
|
79
|
+
feature_file_count=$(eagle_db "SELECT COUNT(*) FROM feature_files ff JOIN features f ON ff.feature_id = f.id WHERE f.project = '$p_esc' AND f.status = 'active';")
|
|
80
|
+
|
|
81
|
+
if [ "${feature_count:-0}" -ge 3 ]; then
|
|
82
|
+
eagle_ok "Features tracked: ${feature_count} (${feature_file_count} files mapped)"
|
|
83
|
+
score=$((score + 20))
|
|
84
|
+
elif [ "${feature_count:-0}" -ge 1 ]; then
|
|
85
|
+
eagle_warn "Features tracked: ${feature_count} — curator needs more sessions"
|
|
86
|
+
score=$((score + 10))
|
|
87
|
+
issues+=("Only ${feature_count} features discovered. Run curator more often.")
|
|
88
|
+
else
|
|
89
|
+
eagle_fail "Features tracked: 0 — feature graph is empty"
|
|
90
|
+
issues+=("No features discovered. Run: eagle-mem curate")
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
# ─── 3. Command intelligence ─────────────────────────────
|
|
94
|
+
|
|
95
|
+
max_score=$((max_score + 15))
|
|
96
|
+
|
|
97
|
+
rule_count=$(eagle_db "SELECT COUNT(*) FROM command_rules WHERE (project = '$p_esc' OR project IS NULL) AND enabled = 1;")
|
|
98
|
+
obs_with_metrics=$(eagle_db "SELECT COUNT(*) FROM observations WHERE project = '$p_esc' AND tool_name = 'Bash' AND output_bytes IS NOT NULL AND output_bytes > 0;")
|
|
99
|
+
|
|
100
|
+
if [ "${rule_count:-0}" -ge 2 ]; then
|
|
101
|
+
eagle_ok "Command rules: ${rule_count} active (${obs_with_metrics} observations with metrics)"
|
|
102
|
+
score=$((score + 15))
|
|
103
|
+
elif [ "${rule_count:-0}" -ge 1 ]; then
|
|
104
|
+
eagle_warn "Command rules: ${rule_count} — learning in progress"
|
|
105
|
+
score=$((score + 8))
|
|
106
|
+
elif [ "${obs_with_metrics:-0}" -gt 20 ]; then
|
|
107
|
+
eagle_fail "Command rules: 0 (but ${obs_with_metrics} observations available — run curator)"
|
|
108
|
+
issues+=("Command metrics collected but no rules generated yet.")
|
|
109
|
+
else
|
|
110
|
+
eagle_dim " Command metrics: ${obs_with_metrics} observations (need more data)"
|
|
111
|
+
score=$((score + 5))
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
# ─── 4. Provider configured ──────────────────────────────
|
|
115
|
+
|
|
116
|
+
max_score=$((max_score + 15))
|
|
117
|
+
|
|
118
|
+
provider=$(eagle_config_get "provider" "type" "none")
|
|
119
|
+
if [ "$provider" != "none" ]; then
|
|
120
|
+
model=$(eagle_config_get "$provider" "model" "default")
|
|
121
|
+
eagle_ok "LLM provider: ${provider} (${model})"
|
|
122
|
+
score=$((score + 15))
|
|
123
|
+
else
|
|
124
|
+
eagle_fail "No LLM provider — curator and enrichment extraction disabled"
|
|
125
|
+
issues+=("Configure a provider: eagle-mem config init")
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
# ─── 5. Project data quality ─────────────────────────────
|
|
129
|
+
|
|
130
|
+
max_score=$((max_score + 10))
|
|
131
|
+
|
|
132
|
+
tmp_sessions=$(eagle_db "SELECT COUNT(*) FROM sessions WHERE project IN ('tmp', 'private', '');")
|
|
133
|
+
total_sessions=$(eagle_db "SELECT COUNT(*) FROM sessions;")
|
|
134
|
+
|
|
135
|
+
if [ "${total_sessions:-0}" -eq 0 ]; then
|
|
136
|
+
noise_pct=0
|
|
137
|
+
else
|
|
138
|
+
noise_pct=$((tmp_sessions * 100 / total_sessions))
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
if [ "$noise_pct" -le 5 ]; then
|
|
142
|
+
eagle_ok "Data quality: ${tmp_sessions} ephemeral sessions (${noise_pct}% noise)"
|
|
143
|
+
score=$((score + 10))
|
|
144
|
+
elif [ "$noise_pct" -le 20 ]; then
|
|
145
|
+
eagle_warn "Data quality: ${tmp_sessions} ephemeral sessions (${noise_pct}% noise)"
|
|
146
|
+
score=$((score + 5))
|
|
147
|
+
issues+=("${noise_pct}% of sessions from ephemeral dirs. Skiplist should prevent new ones.")
|
|
148
|
+
else
|
|
149
|
+
eagle_fail "Data quality: ${noise_pct}% noise — ${tmp_sessions}/${total_sessions} sessions from ephemeral dirs"
|
|
150
|
+
issues+=("Heavy ephemeral pollution. Update Eagle Mem to get skiplist protection.")
|
|
151
|
+
fi
|
|
152
|
+
|
|
153
|
+
# ─── 6. Curator activity ─────────────────────────────────
|
|
154
|
+
|
|
155
|
+
max_score=$((max_score + 10))
|
|
156
|
+
|
|
157
|
+
curator_schedule=$(eagle_config_get "curator" "schedule" "manual")
|
|
158
|
+
last_curated=$(eagle_db "SELECT value FROM eagle_meta WHERE key = 'last_curated_at' AND (project = '$p_esc' OR project IS NULL) ORDER BY CASE WHEN project IS NOT NULL THEN 0 ELSE 1 END LIMIT 1;" 2>/dev/null || echo "")
|
|
159
|
+
|
|
160
|
+
if [ -n "$last_curated" ]; then
|
|
161
|
+
eagle_ok "Curator: last run ${last_curated} (schedule: ${curator_schedule})"
|
|
162
|
+
score=$((score + 10))
|
|
163
|
+
elif [ "$curator_schedule" = "auto" ]; then
|
|
164
|
+
eagle_warn "Curator: auto-scheduled but hasn't run yet"
|
|
165
|
+
score=$((score + 5))
|
|
166
|
+
issues+=("Auto-curate is configured but hasn't run. It triggers at session end.")
|
|
167
|
+
else
|
|
168
|
+
eagle_fail "Curator: never run (schedule: ${curator_schedule})"
|
|
169
|
+
issues+=("Curator has never run. Try: eagle-mem curate --dry-run")
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
# ─── Score ────────────────────────────────────────────────
|
|
173
|
+
|
|
174
|
+
echo ""
|
|
175
|
+
echo -e " ${DIM}─────────────────────────────────────${RESET}"
|
|
176
|
+
|
|
177
|
+
pct=$((score * 100 / max_score))
|
|
178
|
+
if [ "$pct" -ge 80 ]; then
|
|
179
|
+
color="$GREEN"
|
|
180
|
+
grade="Healthy"
|
|
181
|
+
elif [ "$pct" -ge 50 ]; then
|
|
182
|
+
color="$YELLOW"
|
|
183
|
+
grade="Needs attention"
|
|
184
|
+
else
|
|
185
|
+
color="$RED"
|
|
186
|
+
grade="Unhealthy"
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
echo -e " ${BOLD}Score: ${color}${score}/${max_score} (${pct}%)${RESET} ${color}${grade}${RESET}"
|
|
190
|
+
|
|
191
|
+
if [ ${#issues[@]} -gt 0 ]; then
|
|
192
|
+
echo ""
|
|
193
|
+
echo -e " ${BOLD}Issues:${RESET}"
|
|
194
|
+
for issue in "${issues[@]}"; do
|
|
195
|
+
echo -e " ${YELLOW}!${RESET} $issue"
|
|
196
|
+
done
|
|
197
|
+
fi
|
|
198
|
+
|
|
199
|
+
eagle_footer "Health check complete."
|
|
200
|
+
|
|
201
|
+
if [ "$JSON_OUT" -eq 1 ]; then
|
|
202
|
+
jq -nc \
|
|
203
|
+
--arg project "$project" \
|
|
204
|
+
--argjson score "$score" \
|
|
205
|
+
--argjson max_score "$max_score" \
|
|
206
|
+
--argjson pct "$pct" \
|
|
207
|
+
--arg grade "$grade" \
|
|
208
|
+
--argjson total_summaries "${total_summaries:-0}" \
|
|
209
|
+
--argjson enriched_summaries "${enriched_summaries:-0}" \
|
|
210
|
+
--argjson features "${feature_count:-0}" \
|
|
211
|
+
--argjson command_rules "${rule_count:-0}" \
|
|
212
|
+
--arg provider "$provider" \
|
|
213
|
+
--argjson noise_pct "$noise_pct" \
|
|
214
|
+
'{project:$project, score:$score, max:$max_score, pct:$pct, grade:$grade,
|
|
215
|
+
enrichment:{total:$total_summaries, enriched:$enriched_summaries},
|
|
216
|
+
features:$features, command_rules:$command_rules,
|
|
217
|
+
provider:$provider, noise_pct:$noise_pct}'
|
|
218
|
+
fi
|
package/scripts/help.sh
CHANGED
|
@@ -27,6 +27,7 @@ echo -e " ${CYAN}prune${RESET} Remove old observations and orphaned chu
|
|
|
27
27
|
echo -e " ${CYAN}config${RESET} View or change LLM provider settings"
|
|
28
28
|
echo -e " ${CYAN}curate${RESET} Run the self-learning curator (LLM-powered analysis)"
|
|
29
29
|
echo -e " ${CYAN}feature${RESET} Manage feature graph (list/show/verify/add)"
|
|
30
|
+
echo -e " ${CYAN}health${RESET} Diagnose self-learning pipeline health"
|
|
30
31
|
echo -e " ${CYAN}install${RESET} First-time setup: hooks, database, skills"
|
|
31
32
|
echo -e " ${CYAN}update${RESET} Re-deploy hooks and run migrations"
|
|
32
33
|
echo -e " ${CYAN}uninstall${RESET} Remove hooks and optionally delete data"
|