eagle-mem 4.9.3 → 4.9.5
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 +11 -3
- package/db/034_safe_feature_rekey_trigger.sql +17 -0
- package/db/migrate.sh +8 -7
- package/hooks/stop.sh +30 -1
- package/lib/codex-hooks.sh +1 -1
- package/lib/common.sh +414 -12
- package/lib/db-core.sh +12 -3
- package/lib/db-sessions.sh +127 -2
- package/lib/updater.sh +4 -2
- package/package.json +1 -1
- package/scripts/enrich-summary.sh +98 -0
- package/scripts/install.sh +38 -28
- package/scripts/statusline-em.sh +8 -4
- package/scripts/update.sh +11 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
Eagle Mem turns AI coding sessions into compounding project knowledge. It gives Claude Code and Codex the same local memory, labels which agent created each memory, blocks risky release commands until affected features are verified, and lets broad work split into durable worker lanes.
|
|
13
13
|
|
|
14
|
-
**v4.
|
|
14
|
+
**v4.9.5 hardens hooks and SQLite:** Stop hooks save immediately and queue LLM enrichment in the background instead of launching nested agents during turn shutdown. SQLite calls also resolve through one FTS5-capable binary selector, so Android SDK or other PATH shims do not accidentally break Eagle Mem.
|
|
15
15
|
|
|
16
16
|
**Website:** [Product](https://eagleisbatman.github.io/eagle-mem/) |
|
|
17
17
|
[Architecture](https://eagleisbatman.github.io/eagle-mem/architecture.html) |
|
|
@@ -73,7 +73,7 @@ For Codex, the installer enables `codex_hooks` in `~/.codex/config.toml`, regist
|
|
|
73
73
|
|
|
74
74
|
### Prerequisites
|
|
75
75
|
|
|
76
|
-
- `sqlite3` with FTS5 support (ships with macOS;
|
|
76
|
+
- `sqlite3` with FTS5 support (ships with macOS; Eagle Mem prefers known system/Homebrew SQLite binaries before PATH shims)
|
|
77
77
|
- `jq` (the installer offers to install if missing)
|
|
78
78
|
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code), Codex, or both installed
|
|
79
79
|
|
|
@@ -87,7 +87,7 @@ Hooks fire automatically at different points in the agent lifecycle:
|
|
|
87
87
|
| **PreToolUse** | before Bash/shell, Read, Edit, Write, apply_patch | Surfaces guardrails and decisions before edits. Blocks release-boundary commands while feature verification is pending. Rewrites noisy commands through RTK when available. Detects redundant reads, nudges co-edit partners, detects stuck loops. |
|
|
88
88
|
| **UserPromptSubmit** | user sends a message | FTS5 search across past sessions and indexed code for relevant context |
|
|
89
89
|
| **PostToolUse** | after tool calls | Records file touches, mirrors memory/plan/task writes, surfaces decision history and feature impacts on reads, stale memory warnings on edits |
|
|
90
|
-
| **Stop** | agent turn ends |
|
|
90
|
+
| **Stop** | agent turn ends | Saves fast heuristic summaries and extracts `<eagle-summary>` blocks when present. LLM enrichment runs later in the background so the agent lifecycle is not blocked. |
|
|
91
91
|
| **SessionEnd** | session closes | Re-syncs tasks, marks session completed |
|
|
92
92
|
|
|
93
93
|
Codex shell hooks are registered for `Bash`, `exec_command`, `shell_command`, and `unified_exec` tool names so release-boundary protection works across current Codex shell paths.
|
|
@@ -152,6 +152,14 @@ Eagle Mem prevents Claude from repeating past mistakes:
|
|
|
152
152
|
| `eagle-mem scan` | Scan codebase and generate overview |
|
|
153
153
|
| `eagle-mem index` | Index source files for FTS5 code search |
|
|
154
154
|
|
|
155
|
+
### v4.9.5 Patch
|
|
156
|
+
|
|
157
|
+
Stop hooks now use a fast path: they save heuristic summaries immediately, extract explicit summary blocks when present, and queue LLM enrichment in the background so Codex/Claude lifecycle hooks do not time out. SQLite access now goes through a shared FTS5-capable binary resolver used by migrations, DB helpers, updater backups, install checks, and the statusline, avoiding Android SDK or other PATH shims that shadow working SQLite builds.
|
|
158
|
+
|
|
159
|
+
### v4.9.4 Patch
|
|
160
|
+
|
|
161
|
+
Project-key hardening for agents that move between folders: hooks now keep a per-session project identity instead of recalculating from every new cwd, and statuslines prefer the stored session project before falling back to folder paths. Install/update also repairs older embedded Eagle Mem statusline blocks so nested-repo projects stop showing `Memories: 0` when the session belongs to the parent workspace.
|
|
162
|
+
|
|
155
163
|
### v4.9.3 Patch
|
|
156
164
|
|
|
157
165
|
Follow-up hardening for the v4.9.2 project-key repair: Claude transcript workspace detection now reads complete early JSONL records instead of a fixed byte slice, so large SessionStart hook context cannot hide the first `cwd`. Metadata-only memory/plan/task repairs also avoid touching FTS-indexed columns, preventing SQLite FTS update triggers from firing during safe project/source rekeys.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
-- Migration 034: Make feature FTS updates safe for project-key repairs.
|
|
2
|
+
--
|
|
3
|
+
-- Project-key repairs update features.project, but the older trigger fired on
|
|
4
|
+
-- every UPDATE and tried to rewrite the FTS5 row even when searchable text did
|
|
5
|
+
-- not change. Restrict it to the indexed columns so metadata-only rekeys are
|
|
6
|
+
-- safe.
|
|
7
|
+
|
|
8
|
+
DROP TRIGGER IF EXISTS features_au;
|
|
9
|
+
|
|
10
|
+
CREATE TRIGGER features_au
|
|
11
|
+
AFTER UPDATE OF name, description ON features
|
|
12
|
+
BEGIN
|
|
13
|
+
INSERT INTO features_fts(features_fts, rowid, name, description)
|
|
14
|
+
VALUES ('delete', old.id, old.name, old.description);
|
|
15
|
+
INSERT INTO features_fts(rowid, name, description)
|
|
16
|
+
VALUES (new.id, new.name, new.description);
|
|
17
|
+
END;
|
package/db/migrate.sh
CHANGED
|
@@ -13,11 +13,12 @@ COMMON_SH="$SCRIPT_DIR/../lib/common.sh"
|
|
|
13
13
|
if [ -f "$COMMON_SH" ]; then
|
|
14
14
|
. "$COMMON_SH"
|
|
15
15
|
eagle_require_sqlite_fts5 || exit 1
|
|
16
|
+
SQLITE_BIN=$(eagle_sqlite_path)
|
|
16
17
|
else
|
|
17
|
-
|
|
18
|
-
if [ -z "$
|
|
18
|
+
SQLITE_BIN=$(command -v sqlite3 2>/dev/null || true)
|
|
19
|
+
if [ -z "$SQLITE_BIN" ] || ! "$SQLITE_BIN" :memory: "CREATE VIRTUAL TABLE eagle_mem_fts5_probe USING fts5(value);" >/dev/null 2>&1; then
|
|
19
20
|
echo "Eagle Mem requires SQLite FTS5, but the active sqlite3 does not support it." >&2
|
|
20
|
-
[ -n "$
|
|
21
|
+
[ -n "$SQLITE_BIN" ] && echo "Detected sqlite3: $SQLITE_BIN" >&2
|
|
21
22
|
echo "Fix PATH so an FTS5-capable sqlite3 is first, then re-run the command." >&2
|
|
22
23
|
exit 1
|
|
23
24
|
fi
|
|
@@ -33,7 +34,7 @@ run_migration() {
|
|
|
33
34
|
local file="$2"
|
|
34
35
|
|
|
35
36
|
local already_applied
|
|
36
|
-
already_applied=$(
|
|
37
|
+
already_applied=$("$SQLITE_BIN" "$DB" "SELECT COUNT(*) FROM _migrations WHERE name = '$name';" 2>/dev/null || echo "0")
|
|
37
38
|
|
|
38
39
|
if [ "$already_applied" = "0" ]; then
|
|
39
40
|
# Strip PRAGMAs from migration body (they can't run inside transactions
|
|
@@ -43,20 +44,20 @@ run_migration() {
|
|
|
43
44
|
|
|
44
45
|
# Set connection PRAGMAs, then run migration body + tracking insert atomically
|
|
45
46
|
# .bail on ensures sqlite3 stops on the first error instead of continuing
|
|
46
|
-
{ echo ".bail on"; echo "PRAGMA trusted_schema=ON;"; echo "PRAGMA foreign_keys=ON;"; echo "PRAGMA busy_timeout=5000;"; echo "BEGIN;"; echo "$body"; echo "INSERT INTO _migrations (name) VALUES ('$name');"; echo "COMMIT;"; } |
|
|
47
|
+
{ echo ".bail on"; echo "PRAGMA trusted_schema=ON;"; echo "PRAGMA foreign_keys=ON;"; echo "PRAGMA busy_timeout=5000;"; echo "BEGIN;"; echo "$body"; echo "INSERT INTO _migrations (name) VALUES ('$name');"; echo "COMMIT;"; } | "$SQLITE_BIN" "$DB"
|
|
47
48
|
echo " applied: $name"
|
|
48
49
|
fi
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
# Ensure _migrations table exists (bootstrap)
|
|
52
|
-
|
|
53
|
+
"$SQLITE_BIN" "$DB" "CREATE TABLE IF NOT EXISTS _migrations (
|
|
53
54
|
id INTEGER PRIMARY KEY,
|
|
54
55
|
name TEXT NOT NULL UNIQUE,
|
|
55
56
|
applied_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
|
56
57
|
);"
|
|
57
58
|
|
|
58
59
|
# Set PRAGMAs (these must be set on every connection)
|
|
59
|
-
|
|
60
|
+
"$SQLITE_BIN" "$DB" "PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL; PRAGMA busy_timeout = 5000; PRAGMA foreign_keys = ON;"
|
|
60
61
|
|
|
61
62
|
# ─── Migration 001: Initial schema (special name) ──────────
|
|
62
63
|
run_migration "001_initial_schema" "$SCRIPT_DIR/schema.sql"
|
package/hooks/stop.sh
CHANGED
|
@@ -223,6 +223,7 @@ if echo "$request" | grep -qE '<(local-command-caveat|system-reminder|command-na
|
|
|
223
223
|
fi
|
|
224
224
|
|
|
225
225
|
needs_enrichment=0
|
|
226
|
+
defer_enrichment=0
|
|
226
227
|
if [ "$has_rich_data" -eq 0 ]; then
|
|
227
228
|
needs_enrichment=1
|
|
228
229
|
elif [ "$context_pressure" -eq 1 ]; then
|
|
@@ -235,7 +236,13 @@ fi
|
|
|
235
236
|
|
|
236
237
|
if [ "$needs_enrichment" -eq 1 ]; then
|
|
237
238
|
provider=$(eagle_config_get "provider" "type" "none" 2>/dev/null)
|
|
238
|
-
|
|
239
|
+
# Stop hooks must stay fast. Expensive LLM enrichment belongs in curate or
|
|
240
|
+
# another background path; nested agent_cli calls can exceed Codex/Claude
|
|
241
|
+
# lifecycle timeouts and make the hook look broken to users.
|
|
242
|
+
if [ "${EAGLE_MEM_STOP_ENRICH:-0}" != "1" ]; then
|
|
243
|
+
defer_enrichment=1
|
|
244
|
+
eagle_log "INFO" "Stop: LLM enrichment skipped — fast hook path (provider=$provider)"
|
|
245
|
+
elif [ "$provider" != "none" ] && [ -n "$text_content" ]; then
|
|
239
246
|
excerpt=$(echo "$text_content" | tail -c 3000)
|
|
240
247
|
|
|
241
248
|
enrich_prompt="Extract facts from this AI coding session. Only include items with clear evidence in the session text. Do NOT invent or repeat example content.
|
|
@@ -401,4 +408,26 @@ if [ -n "$request" ] || [ -n "$completed" ] || [ -n "$learned" ]; then
|
|
|
401
408
|
fi
|
|
402
409
|
fi
|
|
403
410
|
|
|
411
|
+
if [ "$defer_enrichment" -eq 1 ] && [ "${EAGLE_MEM_STOP_BACKGROUND_ENRICH:-1}" = "1" ] && [ -n "$text_content" ]; then
|
|
412
|
+
mkdir -p "$EAGLE_MEM_DIR/tmp" 2>/dev/null || true
|
|
413
|
+
enrich_job=$(mktemp "$EAGLE_MEM_DIR/tmp/summary-enrich.XXXXXX.json" 2>/dev/null)
|
|
414
|
+
if [ -n "$enrich_job" ]; then
|
|
415
|
+
jq -cn \
|
|
416
|
+
--arg session_id "$session_id" \
|
|
417
|
+
--arg project "$project" \
|
|
418
|
+
--arg agent "$agent" \
|
|
419
|
+
--arg text "$text_content" \
|
|
420
|
+
'{session_id:$session_id, project:$project, agent:$agent, text:$text}' > "$enrich_job"
|
|
421
|
+
|
|
422
|
+
enrich_script="$SCRIPT_DIR/../scripts/enrich-summary.sh"
|
|
423
|
+
if [ -x "$enrich_script" ]; then
|
|
424
|
+
nohup env EAGLE_MEM_DISABLE_HOOKS=1 EAGLE_AGENT_SOURCE="$agent" EAGLE_AGENT_CWD="$cwd" bash "$enrich_script" "$enrich_job" >/dev/null 2>&1 &
|
|
425
|
+
eagle_log "INFO" "Stop: queued background summary enrichment for session=$session_id"
|
|
426
|
+
else
|
|
427
|
+
rm -f "$enrich_job" 2>/dev/null || true
|
|
428
|
+
eagle_log "WARN" "Stop: background enrichment script missing: $enrich_script"
|
|
429
|
+
fi
|
|
430
|
+
fi
|
|
431
|
+
fi
|
|
432
|
+
|
|
404
433
|
exit 0
|
package/lib/codex-hooks.sh
CHANGED
package/lib/common.sh
CHANGED
|
@@ -20,24 +20,63 @@ EAGLE_CODEX_SKILLS_DIR="${EAGLE_CODEX_SKILLS_DIR:-$EAGLE_CODEX_DIR/skills}"
|
|
|
20
20
|
EAGLE_CODEX_MEMORIES_DIR="${EAGLE_CODEX_MEMORIES_DIR:-$EAGLE_CODEX_DIR/memories}"
|
|
21
21
|
EAGLE_RAW_BASH_UNLOCK="${EAGLE_RAW_BASH_UNLOCK:-/tmp/eagle-mem-raw-bash-unlock}"
|
|
22
22
|
|
|
23
|
+
_eagle_sqlite_candidate_paths() {
|
|
24
|
+
[ -n "${EAGLE_SQLITE_BIN:-}" ] && printf '%s\n' "$EAGLE_SQLITE_BIN"
|
|
25
|
+
[ -f "$EAGLE_MEM_DIR/.sqlite-bin" ] && sed -n '1p' "$EAGLE_MEM_DIR/.sqlite-bin" 2>/dev/null
|
|
26
|
+
printf '%s\n' \
|
|
27
|
+
"/opt/homebrew/opt/sqlite/bin/sqlite3" \
|
|
28
|
+
"/usr/local/opt/sqlite/bin/sqlite3" \
|
|
29
|
+
"/usr/bin/sqlite3" \
|
|
30
|
+
"/opt/homebrew/bin/sqlite3" \
|
|
31
|
+
"/usr/local/bin/sqlite3"
|
|
32
|
+
command -v sqlite3 2>/dev/null || true
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
_eagle_sqlite_bin_supports_fts5() {
|
|
36
|
+
local bin="$1"
|
|
37
|
+
[ -n "$bin" ] && [ -x "$bin" ] || return 1
|
|
38
|
+
"$bin" :memory: "CREATE VIRTUAL TABLE eagle_mem_fts5_probe USING fts5(value);" >/dev/null 2>&1
|
|
39
|
+
}
|
|
40
|
+
|
|
23
41
|
eagle_sqlite_path() {
|
|
42
|
+
local seen="" candidate
|
|
43
|
+
while IFS= read -r candidate; do
|
|
44
|
+
[ -n "$candidate" ] || continue
|
|
45
|
+
case "|$seen|" in *"|$candidate|"*) continue ;; esac
|
|
46
|
+
seen="${seen}|${candidate}"
|
|
47
|
+
if _eagle_sqlite_bin_supports_fts5 "$candidate"; then
|
|
48
|
+
printf '%s\n' "$candidate"
|
|
49
|
+
return 0
|
|
50
|
+
fi
|
|
51
|
+
done <<EOF
|
|
52
|
+
$(_eagle_sqlite_candidate_paths)
|
|
53
|
+
EOF
|
|
54
|
+
|
|
24
55
|
command -v sqlite3 2>/dev/null || true
|
|
25
56
|
}
|
|
26
57
|
|
|
27
58
|
eagle_sqlite_version() {
|
|
28
|
-
|
|
59
|
+
local sqlite_bin
|
|
60
|
+
sqlite_bin=$(eagle_sqlite_path)
|
|
61
|
+
[ -n "$sqlite_bin" ] || return 0
|
|
62
|
+
"$sqlite_bin" --version 2>/dev/null | awk '{print $1}'
|
|
29
63
|
}
|
|
30
64
|
|
|
31
65
|
eagle_sqlite_supports_fts5() {
|
|
32
|
-
|
|
33
|
-
|
|
66
|
+
local sqlite_bin
|
|
67
|
+
sqlite_bin=$(eagle_sqlite_path)
|
|
68
|
+
_eagle_sqlite_bin_supports_fts5 "$sqlite_bin"
|
|
34
69
|
}
|
|
35
70
|
|
|
36
71
|
eagle_print_sqlite_fts5_error() {
|
|
37
72
|
local sqlite_path sqlite_version probe_error
|
|
38
73
|
sqlite_path=$(eagle_sqlite_path)
|
|
39
74
|
sqlite_version=$(eagle_sqlite_version)
|
|
40
|
-
|
|
75
|
+
if [ -n "$sqlite_path" ]; then
|
|
76
|
+
probe_error=$("$sqlite_path" :memory: "CREATE VIRTUAL TABLE eagle_mem_fts5_probe USING fts5(value);" 2>&1 >/dev/null || true)
|
|
77
|
+
else
|
|
78
|
+
probe_error=""
|
|
79
|
+
fi
|
|
41
80
|
|
|
42
81
|
printf '%s\n' "Eagle Mem requires SQLite FTS5, but the active sqlite3 does not support it." >&2
|
|
43
82
|
if [ -n "$sqlite_path" ]; then
|
|
@@ -47,8 +86,8 @@ eagle_print_sqlite_fts5_error() {
|
|
|
47
86
|
fi
|
|
48
87
|
[ -n "$sqlite_version" ] && printf '%s\n' "SQLite version: $sqlite_version" >&2
|
|
49
88
|
[ -n "$probe_error" ] && printf '%s\n' "SQLite error: $probe_error" >&2
|
|
50
|
-
printf '%s\n' "Fix:
|
|
51
|
-
printf '%s\n' "macOS:
|
|
89
|
+
printf '%s\n' "Fix: install an FTS5-capable sqlite3, or set EAGLE_SQLITE_BIN to its absolute path." >&2
|
|
90
|
+
printf '%s\n' "macOS: /usr/bin/sqlite3 usually has FTS5. Eagle Mem now prefers known system/Homebrew sqlite3 paths over Android SDK shims." >&2
|
|
52
91
|
printf '%s\n' "Homebrew: install sqlite and prepend its bin directory, for example: export PATH=\"/opt/homebrew/opt/sqlite/bin:\$PATH\"" >&2
|
|
53
92
|
printf '%s\n' "Linux: install a sqlite3 package compiled with ENABLE_FTS5." >&2
|
|
54
93
|
}
|
|
@@ -210,14 +249,231 @@ eagle_transcript_first_cwd() {
|
|
|
210
249
|
local transcript_path="${1:-}"
|
|
211
250
|
[ -f "$transcript_path" ] || return 1
|
|
212
251
|
|
|
252
|
+
local head_lines
|
|
253
|
+
head_lines=$(sed -n '1,200p' "$transcript_path" 2>/dev/null || true)
|
|
254
|
+
[ -n "$head_lines" ] || return 1
|
|
255
|
+
|
|
213
256
|
local cwd
|
|
214
|
-
cwd=$(
|
|
215
|
-
| jq -r '
|
|
257
|
+
cwd=$(printf '%s\n' "$head_lines" \
|
|
258
|
+
| jq -r '
|
|
259
|
+
[
|
|
260
|
+
(.payload? | objects | .workspace? | objects | .project_dir? | strings),
|
|
261
|
+
(.workspace? | objects | .project_dir? | strings),
|
|
262
|
+
(.payload? | objects | .workspace? | objects | .current_dir? | strings),
|
|
263
|
+
(.workspace? | objects | .current_dir? | strings),
|
|
264
|
+
(.cwd? | strings),
|
|
265
|
+
(.payload? | objects | .cwd? | strings),
|
|
266
|
+
(.payload? | objects | .current_dir? | strings)
|
|
267
|
+
]
|
|
268
|
+
| .[0] // empty
|
|
269
|
+
' 2>/dev/null \
|
|
216
270
|
| awk 'NF { print; exit }' || true)
|
|
271
|
+
|
|
217
272
|
[ -n "$cwd" ] || return 1
|
|
218
273
|
printf '%s\n' "$cwd"
|
|
219
274
|
}
|
|
220
275
|
|
|
276
|
+
eagle_session_project_marker_file() {
|
|
277
|
+
local session_id="${1:-}"
|
|
278
|
+
eagle_validate_session_id "$session_id" || return 1
|
|
279
|
+
mkdir -p "$EAGLE_MEM_DIR/session-projects" 2>/dev/null || return 1
|
|
280
|
+
printf '%s/session-projects/%s\n' "$EAGLE_MEM_DIR" "$session_id"
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
eagle_get_session_project_marker() {
|
|
284
|
+
local marker
|
|
285
|
+
marker=$(eagle_session_project_marker_file "$1") || return 1
|
|
286
|
+
[ -s "$marker" ] || return 1
|
|
287
|
+
awk 'NF { print; exit }' "$marker"
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
eagle_remember_session_project() {
|
|
291
|
+
local session_id="${1:-}"
|
|
292
|
+
local project="${2:-}"
|
|
293
|
+
local force="${3:-0}"
|
|
294
|
+
[ -n "$project" ] || return 1
|
|
295
|
+
|
|
296
|
+
local marker
|
|
297
|
+
marker=$(eagle_session_project_marker_file "$session_id") || return 1
|
|
298
|
+
if [ "$force" = "1" ] || [ ! -s "$marker" ]; then
|
|
299
|
+
printf '%s\n' "$project" > "$marker" 2>/dev/null || return 1
|
|
300
|
+
fi
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
eagle_get_session_project_light() {
|
|
304
|
+
local session_id="${1:-}"
|
|
305
|
+
eagle_validate_session_id "$session_id" || return 1
|
|
306
|
+
command -v sqlite3 >/dev/null 2>&1 || return 1
|
|
307
|
+
[ -f "$EAGLE_MEM_DB" ] || return 1
|
|
308
|
+
|
|
309
|
+
local sid_sql project
|
|
310
|
+
sid_sql=$(eagle_sql_escape "$session_id")
|
|
311
|
+
project=$(sqlite3 "$EAGLE_MEM_DB" "SELECT project FROM sessions WHERE id = '$sid_sql' AND project != '' LIMIT 1;" 2>/dev/null | awk 'NF { print; exit }')
|
|
312
|
+
[ -n "$project" ] || return 1
|
|
313
|
+
printf '%s\n' "$project"
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
eagle_project_has_table_row() {
|
|
317
|
+
local table="${1:-}"
|
|
318
|
+
local project="${2:-}"
|
|
319
|
+
[ -n "$table" ] && [ -n "$project" ] || return 1
|
|
320
|
+
command -v sqlite3 >/dev/null 2>&1 || return 1
|
|
321
|
+
[ -f "$EAGLE_MEM_DB" ] || return 1
|
|
322
|
+
|
|
323
|
+
local project_sql found
|
|
324
|
+
project_sql=$(eagle_sql_escape "$project")
|
|
325
|
+
found=$(sqlite3 "$EAGLE_MEM_DB" "SELECT 1 FROM $table WHERE project = '$project_sql' LIMIT 1;" 2>/dev/null | awk 'NF { print; exit }')
|
|
326
|
+
[ "$found" = "1" ]
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
eagle_project_from_existing_ancestor() {
|
|
330
|
+
local path="${1:-}"
|
|
331
|
+
[ -n "$path" ] || return 1
|
|
332
|
+
|
|
333
|
+
local current key
|
|
334
|
+
current=$(eagle_normalize_project_path "$path")
|
|
335
|
+
eagle_is_ephemeral_project_path "$current" && return 1
|
|
336
|
+
|
|
337
|
+
while [ -n "$current" ] && [ "$current" != "/" ]; do
|
|
338
|
+
key=$(eagle_project_key_from_target_dir "$current")
|
|
339
|
+
if [ -n "$key" ]; then
|
|
340
|
+
# Prefer ancestors with durable memory/summary content. Session-only
|
|
341
|
+
# rows can be created by the very folder drift this helper repairs.
|
|
342
|
+
if eagle_project_has_table_row "agent_memories" "$key" \
|
|
343
|
+
|| eagle_project_has_table_row "summaries" "$key"; then
|
|
344
|
+
printf '%s\n' "$key"
|
|
345
|
+
return 0
|
|
346
|
+
fi
|
|
347
|
+
fi
|
|
348
|
+
|
|
349
|
+
[ "$current" = "$HOME" ] && break
|
|
350
|
+
current=$(dirname "$current")
|
|
351
|
+
done
|
|
352
|
+
|
|
353
|
+
return 1
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
eagle_project_from_workspace_path() {
|
|
357
|
+
if [ -n "${EAGLE_MEM_PROJECT:-}" ]; then
|
|
358
|
+
printf '%s\n' "$EAGLE_MEM_PROJECT"
|
|
359
|
+
return 0
|
|
360
|
+
fi
|
|
361
|
+
|
|
362
|
+
local path="${1:-}"
|
|
363
|
+
[ -n "$path" ] || return 1
|
|
364
|
+
|
|
365
|
+
local resolved worktree_project git_root project
|
|
366
|
+
resolved=$(eagle_normalize_project_path "$path")
|
|
367
|
+
eagle_is_ephemeral_project_path "$resolved" && return 1
|
|
368
|
+
|
|
369
|
+
if worktree_project=$(eagle_project_key_for_worktree_path "$resolved"); then
|
|
370
|
+
printf '%s\n' "$worktree_project"
|
|
371
|
+
return 0
|
|
372
|
+
fi
|
|
373
|
+
|
|
374
|
+
git_root=$(git -C "$resolved" rev-parse --show-toplevel 2>/dev/null)
|
|
375
|
+
if [ -n "$git_root" ]; then
|
|
376
|
+
project=$(eagle_project_key_from_target_dir "$git_root")
|
|
377
|
+
[ -n "$project" ] && { printf '%s\n' "$project"; return 0; }
|
|
378
|
+
fi
|
|
379
|
+
|
|
380
|
+
if project=$(eagle_project_from_existing_ancestor "$path"); then
|
|
381
|
+
printf '%s\n' "$project"
|
|
382
|
+
return 0
|
|
383
|
+
fi
|
|
384
|
+
|
|
385
|
+
project=$(eagle_project_from_path_no_git "$path")
|
|
386
|
+
[ -n "$project" ] || return 1
|
|
387
|
+
printf '%s\n' "$project"
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
eagle_project_from_transcript_start() {
|
|
391
|
+
local transcript_path="${1:-}"
|
|
392
|
+
local cwd="${2:-}"
|
|
393
|
+
[ -f "$transcript_path" ] || return 1
|
|
394
|
+
|
|
395
|
+
local transcript_cwd project
|
|
396
|
+
if project=$(eagle_project_from_claude_transcript "$transcript_path" "$cwd"); then
|
|
397
|
+
printf '%s\n' "$project"
|
|
398
|
+
return 0
|
|
399
|
+
fi
|
|
400
|
+
|
|
401
|
+
transcript_cwd=$(eagle_transcript_first_cwd "$transcript_path") || return 1
|
|
402
|
+
if [ -n "$cwd" ] && ! eagle_path_is_same_or_child "$transcript_cwd" "$cwd"; then
|
|
403
|
+
return 1
|
|
404
|
+
fi
|
|
405
|
+
|
|
406
|
+
project=$(eagle_project_from_workspace_path "$transcript_cwd")
|
|
407
|
+
[ -n "$project" ] || return 1
|
|
408
|
+
printf '%s\n' "$project"
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
eagle_project_from_statusline_input() {
|
|
412
|
+
local input="${1:-}"
|
|
413
|
+
local project_dir="${2:-}"
|
|
414
|
+
local cwd="${3:-}"
|
|
415
|
+
local session_id="${4:-}"
|
|
416
|
+
local project workspace_project_dir transcript_path
|
|
417
|
+
|
|
418
|
+
if [ -n "${EAGLE_MEM_PROJECT:-}" ]; then
|
|
419
|
+
printf '%s\n' "$EAGLE_MEM_PROJECT"
|
|
420
|
+
return
|
|
421
|
+
fi
|
|
422
|
+
|
|
423
|
+
if [ -z "$session_id" ] && [ -n "$input" ]; then
|
|
424
|
+
session_id=$(printf '%s' "$input" | jq -r '.session_id // .session.id // empty' 2>/dev/null)
|
|
425
|
+
fi
|
|
426
|
+
|
|
427
|
+
if [ -n "$input" ]; then
|
|
428
|
+
workspace_project_dir=$(printf '%s' "$input" | jq -r '.workspace.project_dir // empty' 2>/dev/null)
|
|
429
|
+
transcript_path=$(printf '%s' "$input" | jq -r '.transcript_path // empty' 2>/dev/null)
|
|
430
|
+
[ -z "$cwd" ] && cwd=$(printf '%s' "$input" | jq -r '.workspace.current_dir // .cwd // empty' 2>/dev/null)
|
|
431
|
+
fi
|
|
432
|
+
|
|
433
|
+
# Explicit workspace project and transcript start are stronger than older
|
|
434
|
+
# cached session rows because they repair pre-fix sessions that were stored
|
|
435
|
+
# under a nested folder key.
|
|
436
|
+
if [ -n "$workspace_project_dir" ]; then
|
|
437
|
+
project=$(eagle_project_from_workspace_path "$workspace_project_dir")
|
|
438
|
+
[ -n "$project" ] && { printf '%s\n' "$project"; return; }
|
|
439
|
+
fi
|
|
440
|
+
|
|
441
|
+
if [ -n "$transcript_path" ]; then
|
|
442
|
+
if project=$(eagle_project_from_transcript_start "$transcript_path" "${cwd:-$project_dir}"); then
|
|
443
|
+
printf '%s\n' "$project"
|
|
444
|
+
return
|
|
445
|
+
fi
|
|
446
|
+
fi
|
|
447
|
+
|
|
448
|
+
if [ -n "$session_id" ]; then
|
|
449
|
+
if project=$(eagle_get_session_project_light "$session_id"); then
|
|
450
|
+
printf '%s\n' "$project"
|
|
451
|
+
return
|
|
452
|
+
fi
|
|
453
|
+
if project=$(eagle_get_session_project_marker "$session_id"); then
|
|
454
|
+
printf '%s\n' "$project"
|
|
455
|
+
return
|
|
456
|
+
fi
|
|
457
|
+
fi
|
|
458
|
+
|
|
459
|
+
if [ -z "$project_dir" ] && [ -n "$input" ]; then
|
|
460
|
+
project_dir=$(printf '%s' "$input" | jq -r '.workspace.current_dir // .cwd // empty' 2>/dev/null)
|
|
461
|
+
fi
|
|
462
|
+
if [ -n "$project_dir" ]; then
|
|
463
|
+
project=$(eagle_project_from_workspace_path "$project_dir")
|
|
464
|
+
[ -n "$project" ] && { printf '%s\n' "$project"; return; }
|
|
465
|
+
fi
|
|
466
|
+
|
|
467
|
+
if [ -z "$cwd" ] && [ -n "$input" ]; then
|
|
468
|
+
cwd=$(printf '%s' "$input" | jq -r '.workspace.current_dir // .cwd // empty' 2>/dev/null)
|
|
469
|
+
fi
|
|
470
|
+
if [ -n "$cwd" ]; then
|
|
471
|
+
project=$(eagle_project_from_workspace_path "$cwd")
|
|
472
|
+
[ -n "$project" ] && { printf '%s\n' "$project"; return; }
|
|
473
|
+
fi
|
|
474
|
+
eagle_project_from_cwd "$cwd"
|
|
475
|
+
}
|
|
476
|
+
|
|
221
477
|
eagle_project_from_claude_project_dir() {
|
|
222
478
|
local project_dir="${1:-}"
|
|
223
479
|
project_dir="${project_dir%/}"
|
|
@@ -228,7 +484,7 @@ eagle_project_from_claude_project_dir() {
|
|
|
228
484
|
[ -f "$jsonl" ] || continue
|
|
229
485
|
cwd=$(eagle_transcript_first_cwd "$jsonl")
|
|
230
486
|
[ -z "$cwd" ] && continue
|
|
231
|
-
project=$(
|
|
487
|
+
project=$(eagle_project_from_workspace_path "$cwd")
|
|
232
488
|
[ -n "$project" ] && { printf '%s\n' "$project"; return 0; }
|
|
233
489
|
done
|
|
234
490
|
|
|
@@ -252,7 +508,7 @@ eagle_project_from_claude_transcript() {
|
|
|
252
508
|
return 1
|
|
253
509
|
fi
|
|
254
510
|
|
|
255
|
-
project=$(
|
|
511
|
+
project=$(eagle_project_from_workspace_path "$transcript_cwd")
|
|
256
512
|
[ -n "$project" ] || return 1
|
|
257
513
|
printf '%s\n' "$project"
|
|
258
514
|
}
|
|
@@ -265,16 +521,54 @@ eagle_project_from_hook_input() {
|
|
|
265
521
|
return
|
|
266
522
|
fi
|
|
267
523
|
|
|
268
|
-
local cwd transcript_path project
|
|
524
|
+
local session_id cwd transcript_path workspace_project project
|
|
525
|
+
session_id=$(printf '%s' "$input" | jq -r '.session_id // empty' 2>/dev/null)
|
|
269
526
|
cwd=$(printf '%s' "$input" | jq -r '.cwd // empty' 2>/dev/null)
|
|
270
527
|
transcript_path=$(printf '%s' "$input" | jq -r '.transcript_path // empty' 2>/dev/null)
|
|
271
528
|
|
|
529
|
+
workspace_project=$(printf '%s' "$input" | jq -r '.workspace.project_dir // empty' 2>/dev/null)
|
|
530
|
+
if [ -n "$workspace_project" ]; then
|
|
531
|
+
project=$(eagle_project_from_workspace_path "$workspace_project")
|
|
532
|
+
if [ -n "$project" ]; then
|
|
533
|
+
[ -n "$session_id" ] && eagle_remember_session_project "$session_id" "$project" 1 >/dev/null 2>&1
|
|
534
|
+
printf '%s\n' "$project"
|
|
535
|
+
return
|
|
536
|
+
fi
|
|
537
|
+
fi
|
|
538
|
+
|
|
272
539
|
if project=$(eagle_project_from_claude_transcript "$transcript_path" "$cwd"); then
|
|
540
|
+
[ -n "$session_id" ] && eagle_remember_session_project "$session_id" "$project" 1 >/dev/null 2>&1
|
|
273
541
|
printf '%s\n' "$project"
|
|
274
542
|
return
|
|
275
543
|
fi
|
|
276
544
|
|
|
277
|
-
|
|
545
|
+
if [ -n "$transcript_path" ] && [ -f "$transcript_path" ]; then
|
|
546
|
+
local transcript_cwd
|
|
547
|
+
transcript_cwd=$(eagle_transcript_first_cwd "$transcript_path")
|
|
548
|
+
if [ -n "$transcript_cwd" ]; then
|
|
549
|
+
project=$(eagle_project_from_workspace_path "$transcript_cwd")
|
|
550
|
+
if [ -n "$project" ]; then
|
|
551
|
+
[ -n "$session_id" ] && eagle_remember_session_project "$session_id" "$project" 1 >/dev/null 2>&1
|
|
552
|
+
printf '%s\n' "$project"
|
|
553
|
+
return
|
|
554
|
+
fi
|
|
555
|
+
fi
|
|
556
|
+
fi
|
|
557
|
+
|
|
558
|
+
if [ -n "$session_id" ]; then
|
|
559
|
+
if project=$(eagle_get_session_project_light "$session_id"); then
|
|
560
|
+
printf '%s\n' "$project"
|
|
561
|
+
return
|
|
562
|
+
fi
|
|
563
|
+
if project=$(eagle_get_session_project_marker "$session_id"); then
|
|
564
|
+
printf '%s\n' "$project"
|
|
565
|
+
return
|
|
566
|
+
fi
|
|
567
|
+
fi
|
|
568
|
+
|
|
569
|
+
project=$(eagle_project_from_cwd "$cwd")
|
|
570
|
+
[ -n "$session_id" ] && [ -n "$project" ] && eagle_remember_session_project "$session_id" "$project" 0 >/dev/null 2>&1
|
|
571
|
+
printf '%s\n' "$project"
|
|
278
572
|
}
|
|
279
573
|
|
|
280
574
|
eagle_project_file_path() {
|
|
@@ -810,6 +1104,114 @@ eagle_collect_files() {
|
|
|
810
1104
|
fi
|
|
811
1105
|
}
|
|
812
1106
|
|
|
1107
|
+
eagle_statusline_script_from_command() {
|
|
1108
|
+
local cmd="${1:-}"
|
|
1109
|
+
[ -z "$cmd" ] && return 1
|
|
1110
|
+
|
|
1111
|
+
cmd="${cmd#sh }"
|
|
1112
|
+
cmd="${cmd#bash }"
|
|
1113
|
+
cmd="${cmd#/bin/sh }"
|
|
1114
|
+
cmd="${cmd#/bin/bash }"
|
|
1115
|
+
cmd="${cmd#zsh }"
|
|
1116
|
+
cmd="${cmd#/bin/zsh }"
|
|
1117
|
+
cmd="${cmd%\"}"
|
|
1118
|
+
cmd="${cmd#\"}"
|
|
1119
|
+
cmd="${cmd%\'}"
|
|
1120
|
+
cmd="${cmd#\'}"
|
|
1121
|
+
|
|
1122
|
+
case "$cmd" in
|
|
1123
|
+
"~/"*) cmd="$HOME/${cmd#\~/}" ;;
|
|
1124
|
+
"\$HOME/"*) cmd="$HOME/${cmd#\$HOME/}" ;;
|
|
1125
|
+
"\${HOME}/"*) cmd="$HOME/${cmd#\$\{HOME\}/}" ;;
|
|
1126
|
+
esac
|
|
1127
|
+
|
|
1128
|
+
if [ -L "$cmd" ]; then
|
|
1129
|
+
local link_target link_dir
|
|
1130
|
+
link_target=$(readlink "$cmd" 2>/dev/null || true)
|
|
1131
|
+
if [ -n "$link_target" ]; then
|
|
1132
|
+
case "$link_target" in
|
|
1133
|
+
/*) cmd="$link_target" ;;
|
|
1134
|
+
*)
|
|
1135
|
+
link_dir=$(cd "$(dirname "$cmd")" && pwd -P) || return 1
|
|
1136
|
+
cmd="$link_dir/$link_target"
|
|
1137
|
+
;;
|
|
1138
|
+
esac
|
|
1139
|
+
fi
|
|
1140
|
+
fi
|
|
1141
|
+
|
|
1142
|
+
[ -f "$cmd" ] || return 1
|
|
1143
|
+
printf '%s\n' "$cmd"
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
eagle_statusline_script_uses_input() {
|
|
1147
|
+
local sl_file="${1:-}"
|
|
1148
|
+
[ -f "$sl_file" ] || return 1
|
|
1149
|
+
grep -Eq 'eagle_project_from_statusline_input|eagle_mem_statusline.*(\$\{input:-\}|\$input)' "$sl_file"
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
eagle_patch_statusline_script() {
|
|
1153
|
+
local sl_file="${1:-}"
|
|
1154
|
+
[ -f "$sl_file" ] || return 1
|
|
1155
|
+
command -v perl >/dev/null 2>&1 || return 1
|
|
1156
|
+
|
|
1157
|
+
if [ -L "$sl_file" ]; then
|
|
1158
|
+
local link_target link_dir
|
|
1159
|
+
link_target=$(readlink "$sl_file" 2>/dev/null || true)
|
|
1160
|
+
if [ -n "$link_target" ]; then
|
|
1161
|
+
case "$link_target" in
|
|
1162
|
+
/*) sl_file="$link_target" ;;
|
|
1163
|
+
*)
|
|
1164
|
+
link_dir=$(cd "$(dirname "$sl_file")" && pwd -P) || return 1
|
|
1165
|
+
sl_file="$link_dir/$link_target"
|
|
1166
|
+
;;
|
|
1167
|
+
esac
|
|
1168
|
+
fi
|
|
1169
|
+
fi
|
|
1170
|
+
|
|
1171
|
+
local tmp backup mode
|
|
1172
|
+
tmp=$(mktemp) || return 1
|
|
1173
|
+
cp "$sl_file" "$tmp" || { rm -f "$tmp"; return 1; }
|
|
1174
|
+
|
|
1175
|
+
if ! grep -Eq 'eagle_mem_statusline|eagle_project_from_cwd|claude_memories|agent_memories|\.eagle-mem/scripts/statusline-em' "$tmp" 2>/dev/null; then
|
|
1176
|
+
rm -f "$tmp"
|
|
1177
|
+
return 1
|
|
1178
|
+
fi
|
|
1179
|
+
|
|
1180
|
+
perl -0pi -e '
|
|
1181
|
+
s/(project_dir=\$\(echo "\$input" \| jq -r \x27)\.workspace\.current_dir \/\/ \.cwd/$1.workspace.project_dir \/\/ .workspace.current_dir \/\/ .cwd/g;
|
|
1182
|
+
s/(project_dir=\$\(echo "\$input" \| jq -r ")\.workspace\.current_dir \/\/ \.cwd/$1.workspace.project_dir \/\/ .workspace.current_dir \/\/ .cwd/g;
|
|
1183
|
+
s/eagle_mem_statusline "\$project_dir" "\$session_id" "\$\{input\}"/eagle_mem_statusline "\$project_dir" "\$session_id" "\${input:-}"/g;
|
|
1184
|
+
s/eagle_mem_statusline "\$project_dir" "\$session_id"(?=[\)\n;])/eagle_mem_statusline "\$project_dir" "\$session_id" "\${input:-}"/g;
|
|
1185
|
+
s/eagle_mem_statusline "\$project_dir"(?=[\)\n;])/eagle_mem_statusline "\$project_dir" "\${session_id:-}" "\${input:-}"/g;
|
|
1186
|
+
s/eagle_project_from_cwd "\$cwd"/eagle_project_from_statusline_input "\${input:-}" "\${project_dir:-}" "\${cwd:-}" "\${session_id:-}"/g;
|
|
1187
|
+
s/eagle_project_from_cwd "\$project_dir"/eagle_project_from_statusline_input "\${input:-}" "\${project_dir:-}" "\${cwd:-}" "\${session_id:-}"/g;
|
|
1188
|
+
s/eagle_project_from_cwd "\${project_dir:-\$cwd}"/eagle_project_from_statusline_input "\${input:-}" "\${project_dir:-}" "\${cwd:-}" "\${session_id:-}"/g;
|
|
1189
|
+
s/eagle_project_from_cwd "\${project_dir:-\$(pwd)}"/eagle_project_from_statusline_input "\${input:-}" "\${project_dir:-}" "\${cwd:-}" "\${session_id:-}"/g;
|
|
1190
|
+
s/eagle_project_from_cwd "\${eagle_mem_project_dir:-\${project_dir:-\$cwd}}"/eagle_project_from_statusline_input "\${input:-}" "\${project_dir:-}" "\${cwd:-}" "\${session_id:-}"/g;
|
|
1191
|
+
s/eagle_project_from_cwd\("\$cwd"\)/eagle_project_from_statusline_input "\${input:-}" "\${project_dir:-}" "\${cwd:-}" "\${session_id:-}"/g;
|
|
1192
|
+
s/eagle_project_from_cwd\("\$project_dir"\)/eagle_project_from_statusline_input "\${input:-}" "\${project_dir:-}" "\${cwd:-}" "\${session_id:-}"/g;
|
|
1193
|
+
s/eagle_project_from_cwd\("\${project_dir:-\$cwd}"\)/eagle_project_from_statusline_input "\${input:-}" "\${project_dir:-}" "\${cwd:-}" "\${session_id:-}"/g;
|
|
1194
|
+
s/eagle_project_from_cwd\("\${project_dir:-\$(pwd)}"\)/eagle_project_from_statusline_input "\${input:-}" "\${project_dir:-}" "\${cwd:-}" "\${session_id:-}"/g;
|
|
1195
|
+
s/eagle_project_from_cwd\("\${eagle_mem_project_dir:-\${project_dir:-\$cwd}}"\)/eagle_project_from_statusline_input "\${input:-}" "\${project_dir:-}" "\${cwd:-}" "\${session_id:-}"/g;
|
|
1196
|
+
s/FROM claude_memories WHERE project/FROM agent_memories WHERE project/g;
|
|
1197
|
+
' "$tmp" || { rm -f "$tmp"; return 1; }
|
|
1198
|
+
|
|
1199
|
+
if cmp -s "$sl_file" "$tmp"; then
|
|
1200
|
+
rm -f "$tmp"
|
|
1201
|
+
return 1
|
|
1202
|
+
fi
|
|
1203
|
+
|
|
1204
|
+
backup="${sl_file}.eagle-mem.bak-$(date -u +%Y%m%dT%H%M%SZ)"
|
|
1205
|
+
cp "$sl_file" "$backup" 2>/dev/null || true
|
|
1206
|
+
mode=$(stat -f %Lp "$sl_file" 2>/dev/null || stat -c %a "$sl_file" 2>/dev/null || echo "")
|
|
1207
|
+
if ! mv "$tmp" "$sl_file"; then
|
|
1208
|
+
rm -f "$tmp"
|
|
1209
|
+
return 1
|
|
1210
|
+
fi
|
|
1211
|
+
[ -n "$mode" ] && chmod "$mode" "$sl_file" 2>/dev/null || chmod +x "$sl_file" 2>/dev/null || true
|
|
1212
|
+
return 0
|
|
1213
|
+
}
|
|
1214
|
+
|
|
813
1215
|
_eagle_claude_md_section() {
|
|
814
1216
|
cat << 'EAGLE_MD'
|
|
815
1217
|
|
package/lib/db-core.sh
CHANGED
|
@@ -15,10 +15,13 @@ PRAGMA trusted_schema=ON;
|
|
|
15
15
|
.output stdout"
|
|
16
16
|
|
|
17
17
|
eagle_db() {
|
|
18
|
+
local _eagle_sqlite_bin
|
|
19
|
+
_eagle_sqlite_bin=$(eagle_sqlite_path)
|
|
20
|
+
[ -n "$_eagle_sqlite_bin" ] || return 1
|
|
18
21
|
local _eagle_db_err
|
|
19
22
|
_eagle_db_err=$(mktemp 2>/dev/null || echo "/tmp/_eagle_db_err.$$")
|
|
20
23
|
local _eagle_db_out
|
|
21
|
-
_eagle_db_out=$({ echo "$EAGLE_DB_SETUP"; echo "$*"; } |
|
|
24
|
+
_eagle_db_out=$({ echo "$EAGLE_DB_SETUP"; echo "$*"; } | "$_eagle_sqlite_bin" "$EAGLE_MEM_DB" 2>"$_eagle_db_err")
|
|
22
25
|
local _eagle_db_rc=$?
|
|
23
26
|
if [ -s "$_eagle_db_err" ]; then
|
|
24
27
|
cat "$_eagle_db_err" >> "$EAGLE_MEM_LOG" 2>/dev/null
|
|
@@ -29,10 +32,13 @@ eagle_db() {
|
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
eagle_db_pipe() {
|
|
35
|
+
local _eagle_sqlite_bin
|
|
36
|
+
_eagle_sqlite_bin=$(eagle_sqlite_path)
|
|
37
|
+
[ -n "$_eagle_sqlite_bin" ] || return 1
|
|
32
38
|
local _eagle_db_err
|
|
33
39
|
_eagle_db_err=$(mktemp 2>/dev/null || echo "/tmp/_eagle_db_pipe_err.$$")
|
|
34
40
|
local _eagle_db_out
|
|
35
|
-
_eagle_db_out=$({ echo "$EAGLE_DB_SETUP"; echo ".bail on"; cat; } |
|
|
41
|
+
_eagle_db_out=$({ echo "$EAGLE_DB_SETUP"; echo ".bail on"; cat; } | "$_eagle_sqlite_bin" "$EAGLE_MEM_DB" 2>"$_eagle_db_err")
|
|
36
42
|
local _eagle_db_rc=$?
|
|
37
43
|
if [ -s "$_eagle_db_err" ]; then
|
|
38
44
|
cat "$_eagle_db_err" >> "$EAGLE_MEM_LOG" 2>/dev/null
|
|
@@ -43,10 +49,13 @@ eagle_db_pipe() {
|
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
eagle_db_json() {
|
|
52
|
+
local _eagle_sqlite_bin
|
|
53
|
+
_eagle_sqlite_bin=$(eagle_sqlite_path)
|
|
54
|
+
[ -n "$_eagle_sqlite_bin" ] || return 1
|
|
46
55
|
local _eagle_db_err
|
|
47
56
|
_eagle_db_err=$(mktemp 2>/dev/null || echo "/tmp/_eagle_db_json_err.$$")
|
|
48
57
|
local _eagle_db_out
|
|
49
|
-
_eagle_db_out=$({ echo "$EAGLE_DB_SETUP"; echo ".mode json"; echo "$*"; } |
|
|
58
|
+
_eagle_db_out=$({ echo "$EAGLE_DB_SETUP"; echo ".mode json"; echo "$*"; } | "$_eagle_sqlite_bin" "$EAGLE_MEM_DB" 2>"$_eagle_db_err")
|
|
50
59
|
local _eagle_db_rc=$?
|
|
51
60
|
if [ -s "$_eagle_db_err" ]; then
|
|
52
61
|
cat "$_eagle_db_err" >> "$EAGLE_MEM_LOG" 2>/dev/null
|
package/lib/db-sessions.sh
CHANGED
|
@@ -6,12 +6,16 @@
|
|
|
6
6
|
_EAGLE_DB_SESSIONS_LOADED=1
|
|
7
7
|
|
|
8
8
|
eagle_upsert_session() {
|
|
9
|
-
local
|
|
10
|
-
local
|
|
9
|
+
local session_id_raw="${1:-}"
|
|
10
|
+
local project_raw="${2:-}"
|
|
11
|
+
local session_id; session_id=$(eagle_sql_escape "$session_id_raw")
|
|
12
|
+
local project; project=$(eagle_sql_escape "$project_raw")
|
|
11
13
|
local cwd; cwd=$(eagle_sql_escape "${3:-}")
|
|
12
14
|
local model; model=$(eagle_sql_escape "${4:-}")
|
|
13
15
|
local source; source=$(eagle_sql_escape "${5:-}")
|
|
14
16
|
local agent; agent=$(eagle_sql_escape "${6:-$(eagle_agent_source)}")
|
|
17
|
+
local prior_project
|
|
18
|
+
prior_project=$(eagle_db "SELECT project FROM sessions WHERE id = '$session_id' LIMIT 1;" 2>/dev/null || true)
|
|
15
19
|
|
|
16
20
|
eagle_db "INSERT INTO sessions (id, project, cwd, model, source, agent, last_activity_at)
|
|
17
21
|
VALUES ('$session_id', '$project', '$cwd', '$model', '$source', '$agent', strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
|
@@ -22,6 +26,127 @@ eagle_upsert_session() {
|
|
|
22
26
|
agent = COALESCE(NULLIF(excluded.agent, ''), sessions.agent),
|
|
23
27
|
status = 'active',
|
|
24
28
|
last_activity_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now');"
|
|
29
|
+
|
|
30
|
+
local needs_project_repair=0
|
|
31
|
+
if [ -n "$project_raw" ]; then
|
|
32
|
+
if [ "$prior_project" != "$project_raw" ]; then
|
|
33
|
+
needs_project_repair=1
|
|
34
|
+
else
|
|
35
|
+
local stale_child
|
|
36
|
+
stale_child=$(eagle_db "SELECT 1 WHERE
|
|
37
|
+
EXISTS (SELECT 1 FROM summaries WHERE session_id = '$session_id' AND project != '$project')
|
|
38
|
+
OR EXISTS (SELECT 1 FROM observations WHERE session_id = '$session_id' AND project != '$project')
|
|
39
|
+
OR EXISTS (SELECT 1 FROM agent_tasks WHERE source_session_id = '$session_id' AND project != '$project')
|
|
40
|
+
OR EXISTS (SELECT 1 FROM agent_memories WHERE origin_session_id = '$session_id' AND project != '$project')
|
|
41
|
+
OR EXISTS (SELECT 1 FROM agent_plans WHERE origin_session_id = '$session_id' AND project != '$project')
|
|
42
|
+
OR EXISTS (SELECT 1 FROM pending_feature_verifications WHERE source_session_id = '$session_id' AND project != '$project')
|
|
43
|
+
OR EXISTS (
|
|
44
|
+
SELECT 1
|
|
45
|
+
FROM pending_feature_verifications p
|
|
46
|
+
JOIN features f ON f.id = p.feature_id
|
|
47
|
+
WHERE p.source_session_id = '$session_id'
|
|
48
|
+
AND f.project != '$project'
|
|
49
|
+
)
|
|
50
|
+
LIMIT 1;" 2>/dev/null || true)
|
|
51
|
+
[ "$stale_child" = "1" ] && needs_project_repair=1
|
|
52
|
+
fi
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
if [ "$needs_project_repair" = "1" ]; then
|
|
56
|
+
eagle_db_pipe <<SQL >/dev/null 2>&1
|
|
57
|
+
BEGIN;
|
|
58
|
+
UPDATE summaries SET project = '$project' WHERE session_id = '$session_id' AND project != '$project';
|
|
59
|
+
UPDATE observations SET project = '$project' WHERE session_id = '$session_id' AND project != '$project';
|
|
60
|
+
UPDATE agent_tasks SET project = '$project' WHERE source_session_id = '$session_id' AND project != '$project';
|
|
61
|
+
UPDATE agent_memories SET project = '$project' WHERE origin_session_id = '$session_id' AND project != '$project';
|
|
62
|
+
UPDATE agent_plans SET project = '$project' WHERE origin_session_id = '$session_id' AND project != '$project';
|
|
63
|
+
CREATE TEMP TABLE IF NOT EXISTS eagle_feature_repair_map (
|
|
64
|
+
old_feature_id INTEGER PRIMARY KEY,
|
|
65
|
+
new_feature_id INTEGER NOT NULL
|
|
66
|
+
);
|
|
67
|
+
DELETE FROM eagle_feature_repair_map;
|
|
68
|
+
INSERT OR IGNORE INTO features (project, name, description, status, last_verified_at, last_verified_notes)
|
|
69
|
+
SELECT '$project', f_old.name, f_old.description, f_old.status, f_old.last_verified_at, f_old.last_verified_notes
|
|
70
|
+
FROM pending_feature_verifications p
|
|
71
|
+
JOIN features f_old ON f_old.id = p.feature_id
|
|
72
|
+
WHERE p.source_session_id = '$session_id'
|
|
73
|
+
AND (p.project != '$project' OR f_old.project != '$project')
|
|
74
|
+
GROUP BY f_old.name;
|
|
75
|
+
INSERT OR REPLACE INTO eagle_feature_repair_map (old_feature_id, new_feature_id)
|
|
76
|
+
SELECT DISTINCT f_old.id, f_new.id
|
|
77
|
+
FROM pending_feature_verifications p
|
|
78
|
+
JOIN features f_old ON f_old.id = p.feature_id
|
|
79
|
+
JOIN features f_new ON f_new.project = '$project' AND f_new.name = f_old.name
|
|
80
|
+
WHERE p.source_session_id = '$session_id'
|
|
81
|
+
AND (p.project != '$project' OR f_old.project != '$project');
|
|
82
|
+
INSERT OR IGNORE INTO feature_files (feature_id, file_path, role)
|
|
83
|
+
SELECT f_new.id, ff.file_path, ff.role
|
|
84
|
+
FROM pending_feature_verifications p
|
|
85
|
+
JOIN features f_old ON f_old.id = p.feature_id
|
|
86
|
+
JOIN eagle_feature_repair_map m ON m.old_feature_id = f_old.id
|
|
87
|
+
JOIN features f_new ON f_new.id = m.new_feature_id
|
|
88
|
+
JOIN feature_files ff ON ff.feature_id = f_old.id
|
|
89
|
+
WHERE p.source_session_id = '$session_id'
|
|
90
|
+
AND (p.project != '$project' OR f_old.project != '$project');
|
|
91
|
+
INSERT OR IGNORE INTO feature_dependencies (feature_id, kind, target, name, notes)
|
|
92
|
+
SELECT f_new.id, fd.kind, fd.target, fd.name, fd.notes
|
|
93
|
+
FROM pending_feature_verifications p
|
|
94
|
+
JOIN features f_old ON f_old.id = p.feature_id
|
|
95
|
+
JOIN eagle_feature_repair_map m ON m.old_feature_id = f_old.id
|
|
96
|
+
JOIN features f_new ON f_new.id = m.new_feature_id
|
|
97
|
+
JOIN feature_dependencies fd ON fd.feature_id = f_old.id
|
|
98
|
+
WHERE p.source_session_id = '$session_id'
|
|
99
|
+
AND (p.project != '$project' OR f_old.project != '$project');
|
|
100
|
+
INSERT OR IGNORE INTO feature_smoke_tests (feature_id, command, description)
|
|
101
|
+
SELECT f_new.id, fst.command, fst.description
|
|
102
|
+
FROM pending_feature_verifications p
|
|
103
|
+
JOIN features f_old ON f_old.id = p.feature_id
|
|
104
|
+
JOIN eagle_feature_repair_map m ON m.old_feature_id = f_old.id
|
|
105
|
+
JOIN features f_new ON f_new.id = m.new_feature_id
|
|
106
|
+
JOIN feature_smoke_tests fst ON fst.feature_id = f_old.id
|
|
107
|
+
WHERE p.source_session_id = '$session_id'
|
|
108
|
+
AND (p.project != '$project' OR f_old.project != '$project');
|
|
109
|
+
DELETE FROM pending_feature_verifications
|
|
110
|
+
WHERE source_session_id = '$session_id'
|
|
111
|
+
AND status = 'pending'
|
|
112
|
+
AND id NOT IN (
|
|
113
|
+
SELECT MIN(p.id)
|
|
114
|
+
FROM pending_feature_verifications p
|
|
115
|
+
LEFT JOIN eagle_feature_repair_map m ON m.old_feature_id = p.feature_id
|
|
116
|
+
WHERE p.source_session_id = '$session_id'
|
|
117
|
+
AND p.status = 'pending'
|
|
118
|
+
GROUP BY
|
|
119
|
+
CASE WHEN m.new_feature_id IS NOT NULL THEN '$project' ELSE p.project END,
|
|
120
|
+
COALESCE(m.new_feature_id, p.feature_id),
|
|
121
|
+
p.file_path
|
|
122
|
+
)
|
|
123
|
+
AND EXISTS (
|
|
124
|
+
SELECT 1
|
|
125
|
+
FROM eagle_feature_repair_map m
|
|
126
|
+
WHERE m.old_feature_id = pending_feature_verifications.feature_id
|
|
127
|
+
);
|
|
128
|
+
UPDATE pending_feature_verifications
|
|
129
|
+
SET feature_id = COALESCE((SELECT new_feature_id FROM eagle_feature_repair_map WHERE old_feature_id = feature_id), feature_id)
|
|
130
|
+
WHERE source_session_id = '$session_id'
|
|
131
|
+
AND EXISTS (SELECT 1 FROM eagle_feature_repair_map WHERE old_feature_id = pending_feature_verifications.feature_id);
|
|
132
|
+
DELETE FROM pending_feature_verifications
|
|
133
|
+
WHERE source_session_id = '$session_id'
|
|
134
|
+
AND project != '$project'
|
|
135
|
+
AND status = 'pending'
|
|
136
|
+
AND EXISTS (
|
|
137
|
+
SELECT 1
|
|
138
|
+
FROM pending_feature_verifications p2
|
|
139
|
+
WHERE p2.project = '$project'
|
|
140
|
+
AND p2.feature_id = pending_feature_verifications.feature_id
|
|
141
|
+
AND p2.file_path = pending_feature_verifications.file_path
|
|
142
|
+
AND p2.status = 'pending'
|
|
143
|
+
AND p2.id != pending_feature_verifications.id
|
|
144
|
+
);
|
|
145
|
+
UPDATE pending_feature_verifications SET project = '$project' WHERE source_session_id = '$session_id' AND project != '$project';
|
|
146
|
+
UPDATE sessions SET project = '$project' WHERE id = '$session_id' AND project != '$project';
|
|
147
|
+
COMMIT;
|
|
148
|
+
SQL
|
|
149
|
+
fi
|
|
25
150
|
}
|
|
26
151
|
|
|
27
152
|
eagle_end_session() {
|
package/lib/updater.sh
CHANGED
|
@@ -186,8 +186,10 @@ eagle_update_backup_runtime() {
|
|
|
186
186
|
done
|
|
187
187
|
|
|
188
188
|
if [ -f "$EAGLE_MEM_DB" ]; then
|
|
189
|
-
|
|
190
|
-
|
|
189
|
+
local sqlite_bin
|
|
190
|
+
sqlite_bin=$(eagle_sqlite_path)
|
|
191
|
+
if [ -n "$sqlite_bin" ]; then
|
|
192
|
+
"$sqlite_bin" "$EAGLE_MEM_DB" ".backup '$backup_dir/memory.db'" >/dev/null 2>&1 || cp "$EAGLE_MEM_DB" "$backup_dir/memory.db" 2>/dev/null || true
|
|
191
193
|
else
|
|
192
194
|
cp "$EAGLE_MEM_DB" "$backup_dir/memory.db" 2>/dev/null || true
|
|
193
195
|
fi
|
package/package.json
CHANGED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Eagle Mem — background summary enrichment
|
|
3
|
+
# Runs outside lifecycle hook timeouts. Input is a JSON job file created by Stop.
|
|
4
|
+
set +e
|
|
5
|
+
[ "${EAGLE_MEM_DISABLE_BACKGROUND_ENRICH:-}" = "1" ] && exit 0
|
|
6
|
+
|
|
7
|
+
job_file="${1:-}"
|
|
8
|
+
[ -n "$job_file" ] && [ -f "$job_file" ] || exit 0
|
|
9
|
+
|
|
10
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
11
|
+
LIB_DIR="$SCRIPT_DIR/../lib"
|
|
12
|
+
|
|
13
|
+
. "$LIB_DIR/common.sh"
|
|
14
|
+
. "$LIB_DIR/db.sh"
|
|
15
|
+
. "$LIB_DIR/provider.sh"
|
|
16
|
+
|
|
17
|
+
eagle_ensure_db || exit 0
|
|
18
|
+
|
|
19
|
+
session_id=$(jq -r '.session_id // empty' "$job_file" 2>/dev/null)
|
|
20
|
+
project=$(jq -r '.project // empty' "$job_file" 2>/dev/null)
|
|
21
|
+
agent=$(jq -r '.agent // empty' "$job_file" 2>/dev/null)
|
|
22
|
+
text_content=$(jq -r '.text // empty' "$job_file" 2>/dev/null)
|
|
23
|
+
|
|
24
|
+
if [ -z "$session_id" ] || [ -z "$project" ] || [ -z "$text_content" ]; then
|
|
25
|
+
rm -f "$job_file" 2>/dev/null || true
|
|
26
|
+
exit 0
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
provider=$(eagle_config_get "provider" "type" "none" 2>/dev/null)
|
|
30
|
+
if [ "$provider" = "none" ]; then
|
|
31
|
+
eagle_log "INFO" "Summary enrichment skipped: no provider for session=$session_id"
|
|
32
|
+
rm -f "$job_file" 2>/dev/null || true
|
|
33
|
+
exit 0
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
excerpt=$(printf '%s' "$text_content" | tail -c 3000)
|
|
37
|
+
|
|
38
|
+
enrich_prompt="Extract facts from this AI coding session. Only include items with clear evidence in the session text. Do NOT invent or repeat example content.
|
|
39
|
+
|
|
40
|
+
Respond with EXACTLY these sections (omit sections with no evidence):
|
|
41
|
+
|
|
42
|
+
REQUEST:
|
|
43
|
+
One-line summary of what the user asked for. No system tags or XML.
|
|
44
|
+
|
|
45
|
+
COMPLETED:
|
|
46
|
+
What was actually accomplished. Be specific about changes made.
|
|
47
|
+
|
|
48
|
+
LEARNED:
|
|
49
|
+
Non-obvious discoveries or insights from the session.
|
|
50
|
+
|
|
51
|
+
DECISIONS:
|
|
52
|
+
Each as: <what was decided> — why: <reason>
|
|
53
|
+
|
|
54
|
+
GOTCHAS:
|
|
55
|
+
Each as: <surprising finding or pitfall>
|
|
56
|
+
|
|
57
|
+
KEY_FILES:
|
|
58
|
+
Each as: <filepath>
|
|
59
|
+
|
|
60
|
+
SESSION TEXT:
|
|
61
|
+
$excerpt"
|
|
62
|
+
|
|
63
|
+
enrich_system="You extract structured facts from Claude Code and Codex 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."
|
|
64
|
+
enrich_result=$(eagle_llm_call "$enrich_prompt" "$enrich_system" 768 2>/dev/null)
|
|
65
|
+
llm_rc=$?
|
|
66
|
+
|
|
67
|
+
if [ $llm_rc -ne 0 ] || [ -z "$enrich_result" ]; then
|
|
68
|
+
eagle_log "WARN" "Summary enrichment failed (rc=$llm_rc) for session=$session_id provider=$provider"
|
|
69
|
+
rm -f "$job_file" 2>/dev/null || true
|
|
70
|
+
exit 0
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
extract_section() {
|
|
74
|
+
local result="$1" header="$2"
|
|
75
|
+
printf '%s\n' "$result" | awk -v h="$header:" '
|
|
76
|
+
$0 == h || $0 ~ "^"h { found=1; next }
|
|
77
|
+
found && /^[A-Z_]+:/ { exit }
|
|
78
|
+
found && /^[[:space:]]*$/ { next }
|
|
79
|
+
found && /^- / { sub(/^- /, ""); lines[++n] = $0; next }
|
|
80
|
+
found { lines[++n] = $0 }
|
|
81
|
+
END { for (i=1; i<=n; i++) { printf "%s", lines[i]; if (i<n) printf "; " } }
|
|
82
|
+
'
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
request=$(extract_section "$enrich_result" "REQUEST" | eagle_redact)
|
|
86
|
+
completed=$(extract_section "$enrich_result" "COMPLETED" | eagle_redact)
|
|
87
|
+
learned=$(extract_section "$enrich_result" "LEARNED" | eagle_redact)
|
|
88
|
+
decisions=$(extract_section "$enrich_result" "DECISIONS" | eagle_redact)
|
|
89
|
+
gotchas=$(extract_section "$enrich_result" "GOTCHAS" | eagle_redact)
|
|
90
|
+
key_files=$(extract_section "$enrich_result" "KEY_FILES" | eagle_redact)
|
|
91
|
+
|
|
92
|
+
if [ -n "$request" ] || [ -n "$completed" ] || [ -n "$learned" ] || [ -n "$decisions" ] || [ -n "$gotchas" ] || [ -n "$key_files" ]; then
|
|
93
|
+
eagle_insert_summary "$session_id" "$project" "$request" "" "$learned" "$completed" "" "[]" "[]" "" "$decisions" "$gotchas" "$key_files" "$agent"
|
|
94
|
+
eagle_log "INFO" "Summary enrichment saved for session=$session_id provider=$provider"
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
rm -f "$job_file" 2>/dev/null || true
|
|
98
|
+
exit 0
|
package/scripts/install.sh
CHANGED
|
@@ -80,16 +80,18 @@ echo ""
|
|
|
80
80
|
prereqs_ok=true
|
|
81
81
|
|
|
82
82
|
# sqlite3
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
sqlite_path=$(eagle_sqlite_path)
|
|
84
|
+
if [ -n "$sqlite_path" ]; then
|
|
85
|
+
sqlite_version=$(eagle_sqlite_version)
|
|
86
|
+
eagle_ok "sqlite3 ${DIM}($sqlite_version at $sqlite_path)${RESET}"
|
|
86
87
|
else
|
|
87
88
|
eagle_fail "sqlite3 not found"
|
|
88
89
|
if eagle_confirm "Install sqlite3?"; then
|
|
89
90
|
install_package sqlite3
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
sqlite_path=$(eagle_sqlite_path)
|
|
92
|
+
if [ -n "$sqlite_path" ]; then
|
|
93
|
+
sqlite_version=$("$sqlite_path" --version 2>/dev/null | awk '{print $1}')
|
|
94
|
+
eagle_ok "sqlite3 installed ${DIM}($sqlite_version at $sqlite_path)${RESET}"
|
|
93
95
|
else
|
|
94
96
|
eagle_fail "sqlite3 installation failed"
|
|
95
97
|
prereqs_ok=false
|
|
@@ -100,8 +102,7 @@ else
|
|
|
100
102
|
fi
|
|
101
103
|
|
|
102
104
|
# FTS5 support
|
|
103
|
-
if
|
|
104
|
-
sqlite_path=$(eagle_sqlite_path)
|
|
105
|
+
if [ -n "$sqlite_path" ]; then
|
|
105
106
|
if eagle_sqlite_supports_fts5; then
|
|
106
107
|
eagle_ok "FTS5 support ${DIM}($sqlite_path)${RESET}"
|
|
107
108
|
else
|
|
@@ -109,9 +110,9 @@ if command -v sqlite3 &>/dev/null; then
|
|
|
109
110
|
eagle_dim "Detected sqlite3: $sqlite_path"
|
|
110
111
|
sqlite_version=$(eagle_sqlite_version)
|
|
111
112
|
[ -n "$sqlite_version" ] && eagle_dim "SQLite version: $sqlite_version"
|
|
112
|
-
eagle_dim "Run:
|
|
113
|
-
eagle_dim "
|
|
114
|
-
eagle_dim "macOS: /usr/bin/sqlite3 usually has FTS5;
|
|
113
|
+
eagle_dim "Run: eagle-mem health, or set EAGLE_SQLITE_BIN=/absolute/path/to/sqlite3."
|
|
114
|
+
eagle_dim "Eagle Mem prefers known system/Homebrew SQLite paths before PATH shims."
|
|
115
|
+
eagle_dim "macOS: /usr/bin/sqlite3 usually has FTS5; Android SDK sqlite3 shims are ignored when a better binary exists."
|
|
115
116
|
eagle_dim "Homebrew: brew install sqlite, then prepend /opt/homebrew/opt/sqlite/bin or /usr/local/opt/sqlite/bin."
|
|
116
117
|
eagle_dim "Linux: install a sqlite3 package compiled with ENABLE_FTS5."
|
|
117
118
|
prereqs_ok=false
|
|
@@ -299,6 +300,7 @@ fi
|
|
|
299
300
|
if [ "$claude_found" = true ]; then
|
|
300
301
|
EM_STATUSLINE="$EAGLE_MEM_DIR/scripts/statusline-em.sh"
|
|
301
302
|
existing_sl=$(jq -r '.statusLine.command // empty' "$SETTINGS" 2>/dev/null)
|
|
303
|
+
existing_sl_file=$(eagle_statusline_script_from_command "$existing_sl" 2>/dev/null || true)
|
|
302
304
|
|
|
303
305
|
if [ -z "$existing_sl" ]; then
|
|
304
306
|
# No statusline configured — set up a minimal one that shows Eagle Mem
|
|
@@ -309,29 +311,37 @@ input=$(cat)
|
|
|
309
311
|
project_dir=$(echo "$input" | jq -r '.workspace.project_dir // .workspace.current_dir // .cwd // ""' 2>/dev/null)
|
|
310
312
|
session_id=$(echo "$input" | jq -r '.session_id // .session.id // ""' 2>/dev/null)
|
|
311
313
|
source "$HOME/.eagle-mem/scripts/statusline-em.sh"
|
|
312
|
-
eagle_mem_statusline "$project_dir" "$session_id"
|
|
314
|
+
eagle_mem_statusline "$project_dir" "$session_id" "$input"
|
|
313
315
|
WRAPPER
|
|
314
316
|
chmod +x "$wrapper"
|
|
315
317
|
tmp=$(mktemp)
|
|
316
318
|
jq --arg cmd "sh $wrapper" '.statusLine = {"type": "command", "command": $cmd, "refreshInterval": 30}' "$SETTINGS" > "$tmp" && mv "$tmp" "$SETTINGS"
|
|
317
319
|
eagle_ok "Statusline ${DIM}(new — Eagle Mem indicator)${RESET}"
|
|
318
|
-
elif echo "$existing_sl" | grep -q "eagle-mem"; then
|
|
319
|
-
eagle_ok "Statusline ${DIM}(already has Eagle Mem)${RESET}"
|
|
320
320
|
else
|
|
321
|
-
# Existing statusline —
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
321
|
+
# Existing statusline — if it points at a shell script, inspect the
|
|
322
|
+
# target file. Custom HUD commands often do not include "eagle-mem" in
|
|
323
|
+
# the command string even when the script contains an embedded block.
|
|
324
|
+
sl_file="$existing_sl_file"
|
|
325
|
+
if [ -n "$sl_file" ] && [ -f "$sl_file" ]; then
|
|
326
|
+
if eagle_patch_statusline_script "$sl_file"; then
|
|
327
|
+
eagle_ok "Statusline ${DIM}(patched existing Eagle Mem block)${RESET}"
|
|
328
|
+
elif eagle_statusline_script_uses_input "$sl_file"; then
|
|
329
|
+
eagle_ok "Statusline ${DIM}(already has Eagle Mem)${RESET}"
|
|
330
|
+
else
|
|
331
|
+
eagle_dim " Statusline detected: $sl_file"
|
|
332
|
+
eagle_dim " To add Eagle Mem, add this snippet before your ASSEMBLE section:"
|
|
333
|
+
echo ""
|
|
334
|
+
eagle_dim " # ── Eagle Mem ──"
|
|
335
|
+
eagle_dim " em_section=\"\""
|
|
336
|
+
eagle_dim " if [ -f \"\$HOME/.eagle-mem/scripts/statusline-em.sh\" ]; then"
|
|
337
|
+
eagle_dim " source \"\$HOME/.eagle-mem/scripts/statusline-em.sh\""
|
|
338
|
+
eagle_dim " em_section=\$(eagle_mem_statusline \"\$project_dir\" \"\$session_id\" \"\$input\")"
|
|
339
|
+
eagle_dim " fi"
|
|
340
|
+
echo ""
|
|
341
|
+
eagle_ok "Statusline ${DIM}(manual patch needed — instructions above)${RESET}"
|
|
342
|
+
fi
|
|
343
|
+
elif echo "$existing_sl" | grep -q "eagle-mem"; then
|
|
344
|
+
eagle_ok "Statusline ${DIM}(already has Eagle Mem)${RESET}"
|
|
335
345
|
else
|
|
336
346
|
eagle_ok "Statusline ${DIM}(existing — cannot auto-patch; add Eagle Mem manually)${RESET}"
|
|
337
347
|
fi
|
package/scripts/statusline-em.sh
CHANGED
|
@@ -6,15 +6,19 @@
|
|
|
6
6
|
eagle_mem_statusline() {
|
|
7
7
|
local project_dir="${1:-}"
|
|
8
8
|
local session_id="${2:-}"
|
|
9
|
+
local statusline_input="${3:-}"
|
|
9
10
|
local em_db="$HOME/.eagle-mem/memory.db"
|
|
10
11
|
[ -f "$em_db" ] || return
|
|
11
12
|
|
|
12
13
|
local SCRIPT_DIR; SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
14
|
. "$SCRIPT_DIR/../lib/common.sh"
|
|
15
|
+
local sqlite_bin
|
|
16
|
+
sqlite_bin=$(eagle_sqlite_path)
|
|
17
|
+
[ -n "$sqlite_bin" ] || return
|
|
14
18
|
|
|
15
19
|
local proj
|
|
16
20
|
[ -z "$project_dir" ] && project_dir="$(pwd)"
|
|
17
|
-
proj=$(
|
|
21
|
+
proj=$(eagle_project_from_statusline_input "$statusline_input" "$project_dir" "$project_dir" "$session_id")
|
|
18
22
|
[ -z "$proj" ] && return
|
|
19
23
|
|
|
20
24
|
proj=$(eagle_sql_escape "$proj")
|
|
@@ -23,9 +27,9 @@ eagle_mem_statusline() {
|
|
|
23
27
|
version=$(tr -d '[:space:]' < "$HOME/.eagle-mem/.version" 2>/dev/null)
|
|
24
28
|
[ -z "$version" ] && version="?"
|
|
25
29
|
cnt=$(echo ".headers off
|
|
26
|
-
SELECT COUNT(*) FROM sessions WHERE project = '${proj}';" |
|
|
30
|
+
SELECT COUNT(*) FROM sessions WHERE project = '${proj}';" | "$sqlite_bin" "$em_db" 2>/dev/null | tr -d '[:space:]')
|
|
27
31
|
mem=$(echo ".headers off
|
|
28
|
-
SELECT COUNT(*) FROM agent_memories WHERE project = '${proj}';" |
|
|
32
|
+
SELECT COUNT(*) FROM agent_memories WHERE project = '${proj}';" | "$sqlite_bin" "$em_db" 2>/dev/null | tr -d '[:space:]')
|
|
29
33
|
if [ -n "$session_id" ] && [ -f "$HOME/.eagle-mem/.turn-counter.${session_id}" ]; then
|
|
30
34
|
turns=$(tr -d '[:space:]' < "$HOME/.eagle-mem/.turn-counter.${session_id}" 2>/dev/null)
|
|
31
35
|
else
|
|
@@ -57,5 +61,5 @@ if [ "${BASH_SOURCE[0]}" = "$0" ]; then
|
|
|
57
61
|
fi
|
|
58
62
|
project_dir=$(echo "$input" | jq -r '.workspace.project_dir // .workspace.current_dir // .cwd // empty' 2>/dev/null)
|
|
59
63
|
session_id=$(echo "$input" | jq -r '.session_id // .session.id // empty' 2>/dev/null)
|
|
60
|
-
eagle_mem_statusline "${project_dir:-$(pwd)}" "$session_id"
|
|
64
|
+
eagle_mem_statusline "${project_dir:-$(pwd)}" "$session_id" "$input"
|
|
61
65
|
fi
|
package/scripts/update.sh
CHANGED
|
@@ -153,12 +153,22 @@ input=$(cat)
|
|
|
153
153
|
project_dir=$(echo "$input" | jq -r '.workspace.project_dir // .workspace.current_dir // .cwd // ""' 2>/dev/null)
|
|
154
154
|
session_id=$(echo "$input" | jq -r '.session_id // .session.id // ""' 2>/dev/null)
|
|
155
155
|
source "$HOME/.eagle-mem/scripts/statusline-em.sh"
|
|
156
|
-
eagle_mem_statusline "$project_dir" "$session_id"
|
|
156
|
+
eagle_mem_statusline "$project_dir" "$session_id" "$input"
|
|
157
157
|
WRAPPER
|
|
158
158
|
chmod +x "$statusline_wrapper"
|
|
159
159
|
eagle_ok "Statusline wrapper updated"
|
|
160
160
|
fi
|
|
161
161
|
|
|
162
|
+
if [ "$claude_found" = true ] && [ -f "$SETTINGS" ] && command -v jq >/dev/null 2>&1; then
|
|
163
|
+
existing_sl=$(jq -r '.statusLine.command // empty' "$SETTINGS" 2>/dev/null)
|
|
164
|
+
existing_sl_file=$(eagle_statusline_script_from_command "$existing_sl" 2>/dev/null || true)
|
|
165
|
+
if eagle_patch_statusline_script "$existing_sl_file"; then
|
|
166
|
+
eagle_ok "Statusline custom Eagle Mem block patched"
|
|
167
|
+
elif [ -n "$existing_sl_file" ] && [ -f "$existing_sl_file" ] && grep -q "eagle-mem" "$existing_sl_file" && ! eagle_statusline_script_uses_input "$existing_sl_file"; then
|
|
168
|
+
eagle_warn "Statusline custom Eagle Mem block needs manual input-aware update"
|
|
169
|
+
fi
|
|
170
|
+
fi
|
|
171
|
+
|
|
162
172
|
# ─── Backfill project names ───────────────────────────────
|
|
163
173
|
|
|
164
174
|
backfilled=$(eagle_backfill_projects 2>/dev/null)
|