eagle-mem 4.9.5 → 4.9.7

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/lib/common.sh CHANGED
@@ -303,12 +303,14 @@ eagle_remember_session_project() {
303
303
  eagle_get_session_project_light() {
304
304
  local session_id="${1:-}"
305
305
  eagle_validate_session_id "$session_id" || return 1
306
- command -v sqlite3 >/dev/null 2>&1 || return 1
306
+ local sqlite_bin
307
+ sqlite_bin=$(eagle_sqlite_path)
308
+ [ -n "$sqlite_bin" ] || return 1
307
309
  [ -f "$EAGLE_MEM_DB" ] || return 1
308
310
 
309
311
  local sid_sql project
310
312
  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 }')
313
+ project=$("$sqlite_bin" "$EAGLE_MEM_DB" "SELECT project FROM sessions WHERE id = '$sid_sql' AND project != '' LIMIT 1;" 2>/dev/null | awk 'NF { print; exit }')
312
314
  [ -n "$project" ] || return 1
313
315
  printf '%s\n' "$project"
314
316
  }
@@ -317,12 +319,14 @@ eagle_project_has_table_row() {
317
319
  local table="${1:-}"
318
320
  local project="${2:-}"
319
321
  [ -n "$table" ] && [ -n "$project" ] || return 1
320
- command -v sqlite3 >/dev/null 2>&1 || return 1
322
+ local sqlite_bin
323
+ sqlite_bin=$(eagle_sqlite_path)
324
+ [ -n "$sqlite_bin" ] || return 1
321
325
  [ -f "$EAGLE_MEM_DB" ] || return 1
322
326
 
323
327
  local project_sql found
324
328
  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 }')
329
+ found=$("$sqlite_bin" "$EAGLE_MEM_DB" "SELECT 1 FROM $table WHERE project = '$project_sql' LIMIT 1;" 2>/dev/null | awk 'NF { print; exit }')
326
330
  [ "$found" = "1" ]
327
331
  }
328
332
 
@@ -330,11 +334,16 @@ eagle_project_from_existing_ancestor() {
330
334
  local path="${1:-}"
331
335
  [ -n "$path" ] || return 1
332
336
 
333
- local current key
337
+ local start current key
334
338
  current=$(eagle_normalize_project_path "$path")
339
+ start="$current"
335
340
  eagle_is_ephemeral_project_path "$current" && return 1
336
341
 
337
342
  while [ -n "$current" ] && [ "$current" != "/" ]; do
343
+ if [ "$current" = "$HOME" ] && [ "$start" != "$HOME" ]; then
344
+ break
345
+ fi
346
+
338
347
  key=$(eagle_project_key_from_target_dir "$current")
339
348
  if [ -n "$key" ]; then
340
349
  # Prefer ancestors with durable memory/summary content. Session-only
@@ -413,7 +422,7 @@ eagle_project_from_statusline_input() {
413
422
  local project_dir="${2:-}"
414
423
  local cwd="${3:-}"
415
424
  local session_id="${4:-}"
416
- local project workspace_project_dir transcript_path
425
+ local project session_project workspace_project workspace_project_dir transcript_path
417
426
 
418
427
  if [ -n "${EAGLE_MEM_PROJECT:-}" ]; then
419
428
  printf '%s\n' "$EAGLE_MEM_PROJECT"
@@ -430,27 +439,49 @@ eagle_project_from_statusline_input() {
430
439
  [ -z "$cwd" ] && cwd=$(printf '%s' "$input" | jq -r '.workspace.current_dir // .cwd // empty' 2>/dev/null)
431
440
  fi
432
441
 
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; }
442
+ # Claude can report $HOME as workspace.project_dir for a child project before
443
+ # the project is fully initialized. Treat that as too broad for statusline
444
+ # stats; the current working directory is the more useful project boundary.
445
+ if [ -n "$workspace_project_dir" ] && [ -n "$cwd" ]; then
446
+ local workspace_resolved cwd_resolved
447
+ workspace_resolved=$(eagle_normalize_project_path "$workspace_project_dir")
448
+ cwd_resolved=$(eagle_normalize_project_path "$cwd")
449
+ if [ "$workspace_resolved" = "$HOME" ] && [ "$cwd_resolved" != "$HOME" ]; then
450
+ workspace_project_dir="$cwd_resolved"
451
+ fi
439
452
  fi
440
453
 
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
454
+ if [ -n "$workspace_project_dir" ]; then
455
+ workspace_project=$(eagle_project_from_workspace_path "$workspace_project_dir" || true)
446
456
  fi
447
457
 
448
458
  if [ -n "$session_id" ]; then
449
- if project=$(eagle_get_session_project_light "$session_id"); then
450
- printf '%s\n' "$project"
459
+ session_project=$(eagle_get_session_project_light "$session_id" || true)
460
+ if [ -n "$session_project" ]; then
461
+ # If an older row was captured under a nested folder key, a current
462
+ # workspace root should still be allowed to repair the display.
463
+ if [ -n "$workspace_project" ] && [ "$session_project" != "$workspace_project" ]; then
464
+ case "$session_project" in
465
+ "$workspace_project"/*)
466
+ printf '%s\n' "$workspace_project"
467
+ return
468
+ ;;
469
+ esac
470
+ fi
471
+ printf '%s\n' "$session_project"
451
472
  return
452
473
  fi
453
- if project=$(eagle_get_session_project_marker "$session_id"); then
474
+ fi
475
+
476
+ # Explicit workspace project and transcript start are stronger than generic
477
+ # path fallback because they repair pre-fix sessions that were stored under
478
+ # a nested folder key.
479
+ if [ -n "$workspace_project_dir" ]; then
480
+ [ -n "$workspace_project" ] && { printf '%s\n' "$workspace_project"; return; }
481
+ fi
482
+
483
+ if [ -n "$transcript_path" ]; then
484
+ if project=$(eagle_project_from_transcript_start "$transcript_path" "${cwd:-$project_dir}"); then
454
485
  printf '%s\n' "$project"
455
486
  return
456
487
  fi
@@ -1146,7 +1177,7 @@ eagle_statusline_script_from_command() {
1146
1177
  eagle_statusline_script_uses_input() {
1147
1178
  local sl_file="${1:-}"
1148
1179
  [ -f "$sl_file" ] || return 1
1149
- grep -Eq 'eagle_project_from_statusline_input|eagle_mem_statusline.*(\$\{input:-\}|\$input)' "$sl_file"
1180
+ grep -Eq 'eagle_project_from_statusline_input|eagle_mem_statusline.*(\$\{input:-\}|\$input)|statusline-em\.sh.*--hud' "$sl_file"
1150
1181
  }
1151
1182
 
1152
1183
  eagle_patch_statusline_script() {
@@ -1178,6 +1209,14 @@ eagle_patch_statusline_script() {
1178
1209
  fi
1179
1210
 
1180
1211
  perl -0pi -e '
1212
+ s{# [^\n]*EAGLE MEM[^\n]*\n.*?\n# [^\n]*CLOCK[^\n]*\n}{q~# -- EAGLE MEM (second line - branded) --------------------------------------
1213
+ em_line=""
1214
+ if [ -n "$cwd" ] && [ -f "$HOME/.eagle-mem/scripts/statusline-em.sh" ]; then
1215
+ em_line=$(printf "%s" "$input" | bash "$HOME/.eagle-mem/scripts/statusline-em.sh" --hud 2>/dev/null)
1216
+ fi
1217
+
1218
+ # -- CLOCK ---------------------------------------------------------------------
1219
+ ~}ems;
1181
1220
  s/(project_dir=\$\(echo "\$input" \| jq -r \x27)\.workspace\.current_dir \/\/ \.cwd/$1.workspace.project_dir \/\/ .workspace.current_dir \/\/ .cwd/g;
1182
1221
  s/(project_dir=\$\(echo "\$input" \| jq -r ")\.workspace\.current_dir \/\/ \.cwd/$1.workspace.project_dir \/\/ .workspace.current_dir \/\/ .cwd/g;
1183
1222
  s/eagle_mem_statusline "\$project_dir" "\$session_id" "\$\{input\}"/eagle_mem_statusline "\$project_dir" "\$session_id" "\${input:-}"/g;
@@ -1212,6 +1251,307 @@ eagle_patch_statusline_script() {
1212
1251
  return 0
1213
1252
  }
1214
1253
 
1254
+ eagle_runtime_change_plan() {
1255
+ local action="${1:-install}"
1256
+ local package_dir="${2:-}"
1257
+ local claude_found="${3:-false}"
1258
+ local codex_found="${4:-false}"
1259
+
1260
+ echo ""
1261
+ echo -e " ${BOLD:-}What will change${RESET:-}"
1262
+ echo -e " ${DIM:-}Eagle Mem shows this before touching runtime files or agent configs.${RESET:-}"
1263
+ echo ""
1264
+ echo -e " ${CYAN:-}->${RESET:-} Copy runtime files"
1265
+ echo -e " ${DIM:-}from:${RESET:-} ${package_dir:-current package}"
1266
+ echo -e " ${DIM:-}to: ${RESET:-} $EAGLE_MEM_DIR/{hooks,lib,db,scripts}"
1267
+ echo -e " ${CYAN:-}->${RESET:-} Open database"
1268
+ echo -e " ${DIM:-}path:${RESET:-} $EAGLE_MEM_DB"
1269
+ echo -e " ${DIM:-}mode:${RESET:-} migrate only when needed; existing memories are preserved"
1270
+
1271
+ if [ "$claude_found" = true ]; then
1272
+ echo -e " ${CYAN:-}->${RESET:-} Update Claude Code"
1273
+ echo -e " ${DIM:-}settings:${RESET:-} $EAGLE_SETTINGS"
1274
+ echo -e " ${DIM:-}hooks: ${RESET:-} SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, Stop, SessionEnd"
1275
+ echo -e " ${DIM:-}status: ${RESET:-} patch Eagle Mem statusline block when auto-detectable; backup first"
1276
+ echo -e " ${DIM:-}skills: ${RESET:-} $EAGLE_SKILLS_DIR/eagle-mem-*"
1277
+ echo -e " ${DIM:-}guide: ${RESET:-} $HOME/.claude/CLAUDE.md Eagle Mem section"
1278
+ else
1279
+ echo -e " ${DIM:-}-> Claude Code not detected; Claude hooks/skills skipped${RESET:-}"
1280
+ fi
1281
+
1282
+ if [ "$codex_found" = true ]; then
1283
+ echo -e " ${CYAN:-}->${RESET:-} Update Codex"
1284
+ echo -e " ${DIM:-}config:${RESET:-} $EAGLE_CODEX_CONFIG"
1285
+ echo -e " ${DIM:-}hooks: ${RESET:-} $EAGLE_CODEX_HOOKS"
1286
+ echo -e " ${DIM:-}skills:${RESET:-} $EAGLE_CODEX_SKILLS_DIR/eagle-mem-*"
1287
+ echo -e " ${DIM:-}guide: ${RESET:-} $EAGLE_CODEX_AGENTS_MD Eagle Mem section"
1288
+ else
1289
+ echo -e " ${DIM:-}-> Codex not detected; Codex hooks/skills skipped${RESET:-}"
1290
+ fi
1291
+
1292
+ if [ "$action" = "update" ]; then
1293
+ echo -e " ${CYAN:-}->${RESET:-} Refresh installed version metadata"
1294
+ fi
1295
+ echo ""
1296
+ }
1297
+
1298
+ eagle_uninstall_change_plan() {
1299
+ echo ""
1300
+ echo -e " ${BOLD:-}What will change${RESET:-}"
1301
+ echo -e " ${DIM:-}Uninstall removes Eagle Mem-owned integration points. Runtime data is deleted only if you confirm later.${RESET:-}"
1302
+ echo ""
1303
+ echo -e " ${CYAN:-}->${RESET:-} Remove Claude hooks from $EAGLE_SETTINGS"
1304
+ echo -e " ${CYAN:-}->${RESET:-} Remove Codex hooks from $EAGLE_CODEX_HOOKS"
1305
+ echo -e " ${CYAN:-}->${RESET:-} Remove Eagle Mem skill links from:"
1306
+ echo -e " ${DIM:-}$EAGLE_SKILLS_DIR${RESET:-}"
1307
+ echo -e " ${DIM:-}$EAGLE_CODEX_SKILLS_DIR${RESET:-}"
1308
+ echo -e " ${CYAN:-}->${RESET:-} Remove Eagle Mem instruction blocks from:"
1309
+ echo -e " ${DIM:-}$HOME/.claude/CLAUDE.md${RESET:-}"
1310
+ echo -e " ${DIM:-}$EAGLE_CODEX_AGENTS_MD${RESET:-}"
1311
+ echo -e " ${CYAN:-}->${RESET:-} Remove Eagle Mem statusline integration when auto-detectable"
1312
+ echo -e " ${DIM:-}-> Backups are written next to edited user config files.${RESET:-}"
1313
+ echo ""
1314
+ }
1315
+
1316
+ eagle_backup_user_file() {
1317
+ local file="${1:-}"
1318
+ [ -f "$file" ] || return 1
1319
+ local backup
1320
+ backup="${file}.eagle-mem.uninstall-bak-$(date -u +%Y%m%dT%H%M%SZ)"
1321
+ cp "$file" "$backup" 2>/dev/null || return 1
1322
+ printf '%s\n' "$backup"
1323
+ }
1324
+
1325
+ eagle_remove_marked_markdown_section() {
1326
+ local file="${1:-}"
1327
+ local marker="${2:-## Eagle Mem — Persistent Memory}"
1328
+ [ -f "$file" ] || return 1
1329
+ grep -qF "$marker" "$file" 2>/dev/null || return 1
1330
+
1331
+ local tmp
1332
+ tmp=$(mktemp) || return 1
1333
+ awk -v marker="$marker" '
1334
+ BEGIN { skip=0; pending_sep="" }
1335
+ $0 == "---" && !skip {
1336
+ pending_sep=$0 ORS
1337
+ next
1338
+ }
1339
+ index($0, marker) {
1340
+ skip=1
1341
+ pending_sep=""
1342
+ next
1343
+ }
1344
+ skip {
1345
+ if ($0 == "---") {
1346
+ skip=0
1347
+ pending_sep=""
1348
+ next
1349
+ }
1350
+ if ($0 ~ /^## /) {
1351
+ skip=0
1352
+ if (pending_sep != "") {
1353
+ printf "%s", pending_sep
1354
+ pending_sep=""
1355
+ }
1356
+ print
1357
+ }
1358
+ next
1359
+ }
1360
+ {
1361
+ if (pending_sep != "") {
1362
+ printf "%s", pending_sep
1363
+ pending_sep=""
1364
+ }
1365
+ print
1366
+ }
1367
+ END {
1368
+ if (!skip && pending_sep != "") {
1369
+ printf "%s", pending_sep
1370
+ }
1371
+ }
1372
+ ' "$file" > "$tmp" || { rm -f "$tmp"; return 1; }
1373
+
1374
+ if cmp -s "$file" "$tmp"; then
1375
+ rm -f "$tmp"
1376
+ return 1
1377
+ fi
1378
+ mv "$tmp" "$file"
1379
+ }
1380
+
1381
+ eagle_remove_statusline_block() {
1382
+ local sl_file="${1:-}"
1383
+ [ -f "$sl_file" ] || return 1
1384
+ command -v perl >/dev/null 2>&1 || return 1
1385
+ grep -Eq 'EAGLE MEM|Eagle Mem|eagle_mem_statusline|statusline-em\.sh|agent_memories|claude_memories' "$sl_file" 2>/dev/null || return 1
1386
+ grep -Eq '\.eagle-mem|eagle_mem_statusline|statusline-em\.sh|agent_memories|claude_memories' "$sl_file" 2>/dev/null || return 1
1387
+
1388
+ local tmp backup mode
1389
+ tmp=$(mktemp) || return 1
1390
+ cp "$sl_file" "$tmp" || { rm -f "$tmp"; return 1; }
1391
+
1392
+ perl -0pi -e '
1393
+ s{\n?# [^\n]*(?:EAGLE MEM|Eagle Mem)[^\n]*\n.*?\n(# [^\n]*CLOCK[^\n]*\n)}{\n$1}is;
1394
+ ' "$tmp" || { rm -f "$tmp"; return 1; }
1395
+
1396
+ if cmp -s "$sl_file" "$tmp"; then
1397
+ rm -f "$tmp"
1398
+ return 1
1399
+ fi
1400
+
1401
+ backup=$(eagle_backup_user_file "$sl_file" 2>/dev/null || true)
1402
+ mode=$(stat -f %Lp "$sl_file" 2>/dev/null || stat -c %a "$sl_file" 2>/dev/null || echo "")
1403
+ mv "$tmp" "$sl_file"
1404
+ [ -n "$mode" ] && chmod "$mode" "$sl_file" 2>/dev/null || chmod +x "$sl_file" 2>/dev/null || true
1405
+ [ -n "$backup" ] && printf '%s\n' "$backup"
1406
+ }
1407
+
1408
+ eagle_remove_statusline_integration() {
1409
+ local settings="${1:-$EAGLE_SETTINGS}"
1410
+ [ -f "$settings" ] || return 1
1411
+ command -v jq >/dev/null 2>&1 || return 1
1412
+
1413
+ local command sl_file wrapper tmp changed=1
1414
+ command=$(jq -r '.statusLine.command // .statusline.command // empty' "$settings" 2>/dev/null)
1415
+ [ -n "$command" ] || return 1
1416
+ sl_file=$(eagle_statusline_script_from_command "$command" 2>/dev/null || true)
1417
+ wrapper="$EAGLE_MEM_DIR/scripts/statusline-wrapper.sh"
1418
+
1419
+ if printf '%s' "$command" | grep -q "$wrapper"; then
1420
+ eagle_backup_user_file "$settings" >/dev/null 2>&1 || true
1421
+ tmp=$(mktemp) || return 1
1422
+ jq 'del(.statusLine) | del(.statusline)' "$settings" > "$tmp" && mv "$tmp" "$settings"
1423
+ return 0
1424
+ fi
1425
+
1426
+ if [ -n "$sl_file" ] && [ -f "$sl_file" ]; then
1427
+ if eagle_remove_statusline_block "$sl_file" >/dev/null; then
1428
+ changed=0
1429
+ fi
1430
+ fi
1431
+ return "$changed"
1432
+ }
1433
+
1434
+ eagle_runtime_manifest_path() {
1435
+ printf '%s/install-manifest.json\n' "$EAGLE_MEM_DIR"
1436
+ }
1437
+
1438
+ eagle_file_sha256() {
1439
+ local file="${1:-}"
1440
+ [ -f "$file" ] || return 1
1441
+ if command -v shasum >/dev/null 2>&1; then
1442
+ shasum -a 256 "$file" 2>/dev/null | awk '{print $1}'
1443
+ elif command -v sha256sum >/dev/null 2>&1; then
1444
+ sha256sum "$file" 2>/dev/null | awk '{print $1}'
1445
+ else
1446
+ return 1
1447
+ fi
1448
+ }
1449
+
1450
+ eagle_runtime_manifest_write() {
1451
+ local package_dir="${1:-}"
1452
+ local action="${2:-update}"
1453
+ local manifest tmp_files tmp_json
1454
+ manifest=$(eagle_runtime_manifest_path)
1455
+ mkdir -p "$EAGLE_MEM_DIR"
1456
+ tmp_files=$(mktemp) || return 1
1457
+ tmp_json=$(mktemp) || { rm -f "$tmp_files"; return 1; }
1458
+
1459
+ local group rel file sha size mode package_version sqlite_bin sqlite_version files_json
1460
+ for group in hooks lib db scripts; do
1461
+ [ -d "$EAGLE_MEM_DIR/$group" ] || continue
1462
+ while IFS= read -r rel; do
1463
+ [ -n "$rel" ] || continue
1464
+ file="$EAGLE_MEM_DIR/$rel"
1465
+ [ -f "$file" ] || continue
1466
+ sha=$(eagle_file_sha256 "$file" 2>/dev/null || true)
1467
+ size=$(wc -c < "$file" 2>/dev/null | tr -d ' ')
1468
+ mode=$(stat -f %Lp "$file" 2>/dev/null || stat -c %a "$file" 2>/dev/null || echo "")
1469
+ printf '%s\t%s\t%s\t%s\n' "$rel" "${sha:-unknown}" "${size:-0}" "$mode"
1470
+ done < <(cd "$EAGLE_MEM_DIR" && find "$group" -type f | sort 2>/dev/null)
1471
+ done > "$tmp_files"
1472
+
1473
+ package_version=$(jq -r .version "$package_dir/package.json" 2>/dev/null || tr -d '[:space:]' < "$EAGLE_MEM_DIR/.version" 2>/dev/null || echo "unknown")
1474
+ sqlite_bin=$(eagle_sqlite_path)
1475
+ sqlite_version=$(eagle_sqlite_version)
1476
+ files_json=$(jq -R -s '
1477
+ split("\n")
1478
+ | map(select(length > 0)
1479
+ | split("\t")
1480
+ | {
1481
+ path: .[0],
1482
+ sha256: .[1],
1483
+ size: ((.[2] // "0") | tonumber),
1484
+ mode: (.[3] // "")
1485
+ })
1486
+ ' "$tmp_files")
1487
+
1488
+ jq -nc \
1489
+ --arg schema "1" \
1490
+ --arg action "$action" \
1491
+ --arg package_name "eagle-mem" \
1492
+ --arg package_version "$package_version" \
1493
+ --arg package_dir "$package_dir" \
1494
+ --arg runtime_dir "$EAGLE_MEM_DIR" \
1495
+ --arg db "$EAGLE_MEM_DB" \
1496
+ --arg sqlite_bin "${sqlite_bin:-}" \
1497
+ --arg sqlite_version "${sqlite_version:-}" \
1498
+ --arg generated_at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
1499
+ --argjson files "$files_json" \
1500
+ '{schema:$schema,
1501
+ generated_at:$generated_at,
1502
+ action:$action,
1503
+ package:{name:$package_name, version:$package_version, dir:$package_dir},
1504
+ runtime:{dir:$runtime_dir, db:$db},
1505
+ sqlite:{path:$sqlite_bin, version:$sqlite_version},
1506
+ files:$files}' > "$tmp_json" || { rm -f "$tmp_files" "$tmp_json"; return 1; }
1507
+
1508
+ mv "$tmp_json" "$manifest"
1509
+ chmod 600 "$manifest" 2>/dev/null || true
1510
+ rm -f "$tmp_files"
1511
+ }
1512
+
1513
+ eagle_runtime_manifest_check() {
1514
+ local manifest
1515
+ manifest=$(eagle_runtime_manifest_path)
1516
+ if [ ! -f "$manifest" ]; then
1517
+ printf 'missing|0|0|0\n'
1518
+ return 0
1519
+ fi
1520
+ command -v jq >/dev/null 2>&1 || {
1521
+ printf 'unreadable|0|0|0\n'
1522
+ return 0
1523
+ }
1524
+
1525
+ local checked=0 missing=0 drift=0 rel expected file actual
1526
+ while IFS=$'\t' read -r rel expected; do
1527
+ [ -n "$rel" ] || continue
1528
+ checked=$((checked + 1))
1529
+ file="$EAGLE_MEM_DIR/$rel"
1530
+ if [ ! -f "$file" ]; then
1531
+ missing=$((missing + 1))
1532
+ continue
1533
+ fi
1534
+ actual=$(eagle_file_sha256 "$file" 2>/dev/null || true)
1535
+ if [ -z "$actual" ] || [ "$actual" != "$expected" ]; then
1536
+ drift=$((drift + 1))
1537
+ fi
1538
+ done < <(jq -r '.files[]? | [.path, .sha256] | @tsv' "$manifest" 2>/dev/null)
1539
+
1540
+ if [ "$missing" -eq 0 ] && [ "$drift" -eq 0 ]; then
1541
+ printf 'ok|%s|0|0\n' "$checked"
1542
+ else
1543
+ printf 'drift|%s|%s|%s\n' "$checked" "$missing" "$drift"
1544
+ fi
1545
+ }
1546
+
1547
+ eagle_runtime_manifest_field() {
1548
+ local field="${1:-}"
1549
+ local manifest
1550
+ manifest=$(eagle_runtime_manifest_path)
1551
+ [ -f "$manifest" ] || return 1
1552
+ jq -r "$field // empty" "$manifest" 2>/dev/null
1553
+ }
1554
+
1215
1555
  _eagle_claude_md_section() {
1216
1556
  cat << 'EAGLE_MD'
1217
1557
 
package/lib/db-mirrors.sh CHANGED
@@ -82,8 +82,7 @@ WHERE file_path = '$fp_sql'
82
82
  UPDATE agent_memories
83
83
  SET origin_session_id = COALESCE(NULLIF('$origin_sql', ''), origin_session_id),
84
84
  origin_agent = COALESCE(NULLIF('$agent_sql', ''), origin_agent),
85
- project = CASE WHEN '$proj_sql' != '' THEN '$proj_sql' ELSE project END,
86
- updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
85
+ project = CASE WHEN '$proj_sql' != '' THEN '$proj_sql' ELSE project END
87
86
  WHERE file_path = '$fp_sql'
88
87
  AND content_hash = '$hash_sql'
89
88
  AND (('$proj_sql' != '' AND project != '$proj_sql')
@@ -178,8 +177,7 @@ WHERE file_path = '$fp_sql'
178
177
  UPDATE agent_plans
179
178
  SET origin_session_id = COALESCE(NULLIF('$origin_sql', ''), origin_session_id),
180
179
  origin_agent = COALESCE(NULLIF('$agent_sql', ''), origin_agent),
181
- project = CASE WHEN '$proj_sql' != '' THEN '$proj_sql' ELSE project END,
182
- updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
180
+ project = CASE WHEN '$proj_sql' != '' THEN '$proj_sql' ELSE project END
183
181
  WHERE file_path = '$fp_sql'
184
182
  AND content_hash = '$hash_sql'
185
183
  AND (('$proj_sql' != '' AND project != '$proj_sql')
@@ -291,8 +289,7 @@ WHERE file_path = '$fp_sql'
291
289
 
292
290
  UPDATE agent_tasks
293
291
  SET origin_agent = COALESCE(NULLIF('$agent_sql', ''), origin_agent),
294
- project = CASE WHEN '$proj_sql' != '' THEN '$proj_sql' ELSE project END,
295
- updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
292
+ project = CASE WHEN '$proj_sql' != '' THEN '$proj_sql' ELSE project END
296
293
  WHERE file_path = '$fp_sql'
297
294
  AND content_hash = '$hash_sql'
298
295
  AND (('$proj_sql' != '' AND project != '$proj_sql')
@@ -89,8 +89,17 @@ eagle_search_summaries() {
89
89
  AND COALESCE(s.request, '') NOT LIKE '%<local-command-caveat>%'
90
90
  AND COALESCE(s.request, '') NOT LIKE '# AGENTS.md instructions%'
91
91
  AND COALESCE(s.request, '') NOT LIKE '<environment_context>%'
92
+ AND COALESCE(s.request, '') NOT LIKE '<subagent_notification>%'
93
+ AND COALESCE(s.request, '') NOT LIKE '</subagent_notification>%'
92
94
  $where_clause
93
- ORDER BY rank
95
+ ORDER BY
96
+ CASE
97
+ WHEN julianday('now') - julianday(s.created_at) <= 14 THEN 0
98
+ WHEN julianday('now') - julianday(s.created_at) <= 45 THEN 1
99
+ ELSE 2
100
+ END,
101
+ s.created_at DESC,
102
+ rank
94
103
  LIMIT $limit;"
95
104
  }
96
105
 
@@ -60,6 +60,7 @@ eagle_posttool_mirror_tasks() {
60
60
 
61
61
  eagle_posttool_stale_hint() {
62
62
  local tool_name="$1" fp="$2" project="$3"
63
+ local agent="${4:-$(eagle_agent_source)}"
63
64
 
64
65
  case "$tool_name" in
65
66
  Write|Edit|apply_patch)
@@ -77,9 +78,16 @@ eagle_posttool_stale_hint() {
77
78
  local stale_hit
78
79
  stale_hit=$(eagle_search_stale_memories "$project" "$fts_query")
79
80
  if [ -n "$stale_hit" ]; then
80
- local stale_msg="=== Eagle Mem: Memory Check ===
81
+ local stale_msg
82
+ if [ "$agent" = "codex" ]; then
83
+ stale_msg="Eagle Mem memory check:
84
+ - Memory '${stale_hit}' may reference '${fname}'.
85
+ - If this edit contradicts that memory, update or correct the memory before relying on it."
86
+ else
87
+ stale_msg="=== Eagle Mem: Memory Check ===
81
88
  Memory '${stale_hit}' may reference '${fname}'. If your edit contradicts it, update the memory.
82
89
  ================"
90
+ fi
83
91
  jq -nc --arg ctx "$stale_msg" '{"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":$ctx}}'
84
92
  fi
85
93
  fi
@@ -93,6 +101,7 @@ Memory '${stale_hit}' may reference '${fname}'. If your edit contradicts it, upd
93
101
 
94
102
  eagle_posttool_decision_surface() {
95
103
  local tool_name="$1" fp="$2" project="$3"
104
+ local agent="${4:-$(eagle_agent_source)}"
96
105
 
97
106
  case "$tool_name" in
98
107
  Read)
@@ -110,10 +119,17 @@ eagle_posttool_decision_surface() {
110
119
  local decision_hit
111
120
  decision_hit=$(eagle_search_decisions_for_file "$project" "$fts_query")
112
121
  if [ -n "$decision_hit" ]; then
113
- read_context+="=== Eagle Mem: Decision Recall ===
122
+ if [ "$agent" = "codex" ]; then
123
+ read_context+="Eagle Mem decision recall:
124
+ - ${fname}: ${decision_hit}
125
+ - Do not revert without explicit user request.
126
+ "
127
+ else
128
+ read_context+="=== Eagle Mem: Decision Recall ===
114
129
  ${fname}: ${decision_hit} — Do not revert without explicit user request.
115
130
  ================
116
131
  "
132
+ fi
117
133
  fi
118
134
  fi
119
135
  fi
@@ -123,17 +139,35 @@ ${fname}: ${decision_hit} — Do not revert without explicit user request.
123
139
  if [ -n "$feature_hit" ]; then
124
140
  while IFS='|' read -r feat_name feat_desc feat_verified _role feat_deps feat_other_files feat_smoke; do
125
141
  [ -z "$feat_name" ] && continue
126
- read_context+="=== Eagle Mem: Feature Guardrail ===
142
+ if [ "$agent" = "codex" ]; then
143
+ read_context+="Eagle Mem feature guardrail:
144
+ - '${fname}' is part of '${feat_name}'"
145
+ [ -n "$feat_desc" ] && read_context+=": ${feat_desc}"
146
+ read_context+=".
147
+ "
148
+ [ -n "$feat_verified" ] && read_context+="- Last verified: ${feat_verified}.
149
+ "
150
+ [ -n "$feat_deps" ] && read_context+="- Dependencies: ${feat_deps}.
151
+ "
152
+ [ -n "$feat_other_files" ] && read_context+="- Related files: ${feat_other_files}.
153
+ "
154
+ [ -n "$feat_smoke" ] && read_context+="- Smoke tests: ${feat_smoke}.
155
+ "
156
+ read_context+="- Retest after changes before release.
157
+ "
158
+ else
159
+ read_context+="=== Eagle Mem: Feature Guardrail ===
127
160
  '${fname}' is part of feature '${feat_name}'"
128
- [ -n "$feat_desc" ] && read_context+=" ($feat_desc)"
129
- read_context+="."
130
- [ -n "$feat_verified" ] && read_context+=" Last verified: ${feat_verified}."
131
- [ -n "$feat_deps" ] && read_context+=" Dependencies: ${feat_deps}."
132
- [ -n "$feat_other_files" ] && read_context+=" Other files in pipeline: ${feat_other_files}."
133
- [ -n "$feat_smoke" ] && read_context+=" Smoke tests: ${feat_smoke}."
134
- read_context+=" Changes require re-testing after deploy.
161
+ [ -n "$feat_desc" ] && read_context+=" ($feat_desc)"
162
+ read_context+="."
163
+ [ -n "$feat_verified" ] && read_context+=" Last verified: ${feat_verified}."
164
+ [ -n "$feat_deps" ] && read_context+=" Dependencies: ${feat_deps}."
165
+ [ -n "$feat_other_files" ] && read_context+=" Other files in pipeline: ${feat_other_files}."
166
+ [ -n "$feat_smoke" ] && read_context+=" Smoke tests: ${feat_smoke}."
167
+ read_context+=" Changes require re-testing after deploy.
135
168
  ================
136
169
  "
170
+ fi
137
171
  done <<< "$feature_hit"
138
172
  fi
139
173
 
package/lib/updater.sh CHANGED
@@ -185,6 +185,12 @@ eagle_update_backup_runtime() {
185
185
  fi
186
186
  done
187
187
 
188
+ for item in .version .latest-version config.toml install-manifest.json .last-update.json; do
189
+ if [ -f "$EAGLE_MEM_DIR/$item" ]; then
190
+ cp "$EAGLE_MEM_DIR/$item" "$backup_dir/$item" 2>/dev/null || true
191
+ fi
192
+ done
193
+
188
194
  if [ -f "$EAGLE_MEM_DB" ]; then
189
195
  local sqlite_bin
190
196
  sqlite_bin=$(eagle_sqlite_path)
@@ -205,6 +211,12 @@ eagle_update_restore_runtime() {
205
211
  fi
206
212
  done
207
213
 
214
+ for item in .version .latest-version config.toml install-manifest.json .last-update.json; do
215
+ if [ -f "$backup_dir/$item" ]; then
216
+ cp "$backup_dir/$item" "$EAGLE_MEM_DIR/$item" 2>/dev/null || true
217
+ fi
218
+ done
219
+
208
220
  if [ -f "$backup_dir/memory.db" ]; then
209
221
  cp "$backup_dir/memory.db" "$EAGLE_MEM_DB" 2>/dev/null || true
210
222
  fi
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eagle-mem",
3
- "version": "4.9.5",
3
+ "version": "4.9.7",
4
4
  "description": "Shared memory, release guardrails, RTK token protection, and worker lanes for Claude Code and Codex",
5
5
  "bin": {
6
6
  "eagle-mem": "bin/eagle-mem"
@@ -11,7 +11,9 @@
11
11
  "hooks/",
12
12
  "lib/",
13
13
  "db/",
14
- "skills/"
14
+ "skills/",
15
+ "docs/",
16
+ "architecture.html"
15
17
  ],
16
18
  "keywords": [
17
19
  "claude-code",