eagle-mem 4.12.0 → 4.12.1

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/CHANGELOG.md CHANGED
@@ -4,6 +4,16 @@ All notable changes to the **Eagle Mem** project are documented here.
4
4
 
5
5
  ---
6
6
 
7
+ ## v4.12.1 Deploy-Path Fixes for v4.12.0
8
+
9
+ Two install/update-path bugs that defeated or destabilized the v4.12.0 rollout:
10
+
11
+ - **CLAUDE.md capture doctrine never updated (critical)**: `eagle_patch_claude_md` detected the outdated section with `grep -qF 'request: \[what user asked\] | completed:'`. Under `grep -F` the backslashes are literal, so the pattern never matched the real `[what user asked]` text — the installed CLAUDE.md kept telling agents to emit `<eagle-summary>`, silently defeating the whole clean-capture feature. Detection now keys on the *absence* of the current section's unique `session save --session-id` sentinel, so any pre-CLI-first section is rewritten (and the rewrite is idempotent). New regression test `tests/test_claude_md_capture_doctrine.sh`.
12
+ - **Migration runner fail-open on lock**: `db/migrate.sh`'s "already applied?" guard ran a fresh `sqlite3` without `busy_timeout`; a momentary `SQLITE_BUSY` (a hook touching the DB mid-update) made it exit non-zero, the `|| echo 0` fail-open then re-ran an already-applied migration and the body errored with `duplicate column` / UNIQUE-constraint failures. The guard now sets `busy_timeout=5000` and waits for the lock instead of guessing.
13
+ - Registered `tests/test_clean_session_capture.sh` and the new doctrine test in `scripts/test.sh` (both were running only when invoked directly).
14
+
15
+ ---
16
+
7
17
  ## v4.12.0 Clean, Branded Session Capture
8
18
 
9
19
  Session endings are now clean across every agent — no more raw `<eagle-summary>` XML blocks in the visible reply.
package/db/migrate.sh CHANGED
@@ -33,8 +33,14 @@ run_migration() {
33
33
  local name="$1"
34
34
  local file="$2"
35
35
 
36
+ # The guard query MUST set busy_timeout: without it a momentary SQLITE_BUSY
37
+ # (a hook touching the DB mid-update) makes sqlite3 exit non-zero, the `|| echo 0`
38
+ # fail-open then re-runs an already-applied migration and the body explodes with
39
+ # "duplicate column" / UNIQUE-constraint errors. busy_timeout makes the guard wait
40
+ # for the lock instead of guessing. tail -n1 drops the PRAGMA echo line.
36
41
  local already_applied
37
- already_applied=$("$SQLITE_BIN" "$DB" "SELECT COUNT(*) FROM _migrations WHERE name = '$name';" 2>/dev/null || echo "0")
42
+ already_applied=$("$SQLITE_BIN" "$DB" "PRAGMA busy_timeout=5000; SELECT COUNT(*) FROM _migrations WHERE name = '$name';" 2>/dev/null | tail -n1)
43
+ [ -z "$already_applied" ] && already_applied="0"
38
44
 
39
45
  if [ "$already_applied" = "0" ]; then
40
46
  # Strip PRAGMAs from migration body (they can't run inside transactions
@@ -28,6 +28,7 @@ Last verified: 2026-06-10
28
28
  - The installer adds `permissions.allow: ["Bash(eagle-mem session save:*)"]` to Claude settings so the capture runs without a permission prompt. The instruction must use that exact command prefix (no leading path, no `cd &&`) for the permission to match.
29
29
  - Agent-authored captures are authoritative (`capture_source = agent`). The `Stop` hook still parses any `<eagle-summary>` block for backward compatibility, but when an agent row already exists its heuristics only fill empty fields and background enrichment is skipped — neither can clobber agent data.
30
30
  - `UserPromptSubmit` context-pressure nudges (≥20 / ≥30 turns) also point at `eagle-mem session save`, not the raw block.
31
+ - On install/update the managed `## Eagle Mem — Persistent Memory` section in `~/.claude/CLAUDE.md` is rewritten to this CLI-first doctrine whenever it predates it. Detection keys on the absence of the current section's `session save --session-id` sentinel (v4.12.1 fixed a `grep -F` escaping bug that left the section — and therefore the clean-capture behavior — un-updated). Covered by `tests/test_claude_md_capture_doctrine.sh`.
31
32
 
32
33
  ## Eagle Mem Files Depending On This
33
34
 
package/lib/common.sh CHANGED
@@ -1919,11 +1919,13 @@ eagle_patch_claude_md() {
1919
1919
  mkdir -p "$HOME/.claude"
1920
1920
 
1921
1921
  if [ -f "$claude_md" ] && grep -qF "$marker" "$claude_md" 2>/dev/null; then
1922
- # Rewrite when the section uses an outdated capture doctrine:
1923
- # - the old pipe-separated <eagle-summary> template, or
1924
- # - the superseded "collapsed <details>" recommendation (pre-CLI-first).
1925
- if grep -qF 'request: \[what user asked\] | completed:' "$claude_md" 2>/dev/null \
1926
- || grep -qF 'collapsed `<details>` element' "$claude_md" 2>/dev/null; then
1922
+ # Rewrite whenever the section predates the CLI-first capture doctrine.
1923
+ # The current section is the only one that mentions `session save --session-id`,
1924
+ # so its ABSENCE is a reliable "this section is outdated" signal — this catches
1925
+ # the old `<eagle-summary>` template AND the superseded `<details>` recommendation
1926
+ # without fragile fixed-string matching (the prior `grep -F '...\[...\]...'` never
1927
+ # matched because -F treats the backslashes literally).
1928
+ if ! grep -qF 'session save --session-id' "$claude_md" 2>/dev/null; then
1927
1929
  # Replace the outdated section: remove old, append new
1928
1930
  local tmp_md
1929
1931
  tmp_md=$(mktemp)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eagle-mem",
3
- "version": "4.12.0",
3
+ "version": "4.12.1",
4
4
  "description": "Shared memory, release guardrails, RTK token protection, and worker lanes for Claude Code, Codex, OpenCode, Grok, and Google Antigravity",
5
5
  "bin": {
6
6
  "eagle-mem": "bin/eagle-mem"
package/scripts/test.sh CHANGED
@@ -68,6 +68,8 @@ run_check "Trust Surfaces (DB integrity, JSON errors, statusline)" "bash \"$SCRI
68
68
  run_check "Recall Observability (UserPromptSubmit recall event)" "bash \"$SCRIPTS_DIR/../tests/test_recall_observability.sh\""
69
69
  run_check "Eagle Event Log (hook/action observability)" "bash \"$SCRIPTS_DIR/../tests/test_eagle_events.sh\""
70
70
  run_check "Dashboard Surface (local HTML memory view)" "bash \"$SCRIPTS_DIR/../tests/test_dashboard.sh\""
71
+ run_check "Clean Session Capture (capture_source, fill-only upsert, no clobber)" "bash \"$SCRIPTS_DIR/../tests/test_clean_session_capture.sh\""
72
+ run_check "CLAUDE.md Capture Doctrine (installer rewrites outdated section)" "bash \"$SCRIPTS_DIR/../tests/test_claude_md_capture_doctrine.sh\""
71
73
 
72
74
  echo ""
73
75
  if [ "$errors" -eq 0 ]; then
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env bash
2
+ # ═══════════════════════════════════════════════════════════
3
+ # Regression: eagle_patch_claude_md must rewrite an outdated
4
+ # Eagle Mem capture section to the CLI-first doctrine.
5
+ #
6
+ # Guards against the v4.12.0 bug where the detection used
7
+ # `grep -qF 'request: \[what user asked\] | completed:'` — under
8
+ # grep -F the backslashes are literal, so it never matched the
9
+ # real `[what user asked]` text and the section was never updated,
10
+ # silently defeating the clean-capture feature on every install.
11
+ # ═══════════════════════════════════════════════════════════
12
+ set -uo pipefail
13
+
14
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
15
+ REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
16
+
17
+ pass=0
18
+ fail=0
19
+ ok() { echo " ok: $1"; pass=$((pass+1)); }
20
+ bad() { echo " FAIL: $1"; fail=$((fail+1)); }
21
+
22
+ run_case() {
23
+ local label="$1" body="$2"
24
+ local tmp; tmp=$(mktemp -d)
25
+ mkdir -p "$tmp/.claude"
26
+ printf '%s\n' "$body" > "$tmp/.claude/CLAUDE.md"
27
+
28
+ ( export HOME="$tmp"; . "$REPO_DIR/lib/common.sh" >/dev/null 2>&1; eagle_patch_claude_md >/dev/null 2>&1 )
29
+
30
+ if grep -qF 'session save --session-id' "$tmp/.claude/CLAUDE.md"; then
31
+ ok "$label: CLI-first doctrine present after patch"
32
+ else
33
+ bad "$label: section was NOT rewritten to CLI-first"
34
+ fi
35
+ if [ "$(grep -c '## Eagle Mem — Persistent Memory' "$tmp/.claude/CLAUDE.md")" = "1" ]; then
36
+ ok "$label: exactly one Eagle Mem section (no duplication)"
37
+ else
38
+ bad "$label: section count != 1"
39
+ fi
40
+ rm -rf "$tmp"
41
+ }
42
+
43
+ # Case 1: old <eagle-summary>-emit section with surrounding user content.
44
+ run_case "old-eagle-summary" '# Custom global instructions
45
+
46
+ Pre-section user content.
47
+
48
+ ---
49
+
50
+ ## Eagle Mem — Persistent Memory
51
+
52
+ **Rule:** Before your final response, emit an `<eagle-summary>` block.
53
+
54
+ ```
55
+ <eagle-summary>
56
+ request: [what user asked] | completed: [what shipped]
57
+ </eagle-summary>
58
+ ```
59
+
60
+ ---
61
+
62
+ ## Behavioral Rules
63
+
64
+ Post-section user content that must survive.'
65
+
66
+ # Verify surrounding content survives the rewrite (separate explicit check).
67
+ TMP=$(mktemp -d); mkdir -p "$TMP/.claude"
68
+ printf '%s\n' '# Custom
69
+
70
+ KEEP_BEFORE_MARKER
71
+
72
+ ---
73
+
74
+ ## Eagle Mem — Persistent Memory
75
+
76
+ **Rule:** emit an `<eagle-summary>` block.
77
+
78
+ ---
79
+
80
+ ## Behavioral Rules
81
+
82
+ KEEP_AFTER_MARKER' > "$TMP/.claude/CLAUDE.md"
83
+ ( export HOME="$TMP"; . "$REPO_DIR/lib/common.sh" >/dev/null 2>&1; eagle_patch_claude_md >/dev/null 2>&1 )
84
+ grep -q 'KEEP_BEFORE_MARKER' "$TMP/.claude/CLAUDE.md" && ok "preserve: content before section kept" || bad "preserve: clobbered content before section"
85
+ grep -q 'KEEP_AFTER_MARKER' "$TMP/.claude/CLAUDE.md" && ok "preserve: content after section kept" || bad "preserve: clobbered content after section"
86
+ grep -q 'emit an `<eagle-summary>` block' "$TMP/.claude/CLAUDE.md" && bad "preserve: stale emit instruction still present" || ok "preserve: stale emit instruction removed"
87
+
88
+ # Idempotency: rewriting again must not add a second section.
89
+ before=$(cksum "$TMP/.claude/CLAUDE.md" | awk '{print $1}')
90
+ ( export HOME="$TMP"; . "$REPO_DIR/lib/common.sh" >/dev/null 2>&1; eagle_patch_claude_md >/dev/null 2>&1 )
91
+ [ "$(grep -c '## Eagle Mem — Persistent Memory' "$TMP/.claude/CLAUDE.md")" = "1" ] && ok "idempotent: still one section after 2nd run" || bad "idempotent: section duplicated on 2nd run"
92
+ rm -rf "$TMP"
93
+
94
+ echo ""
95
+ echo "test_claude_md_capture_doctrine: $pass passed, $fail failed"
96
+ [ "$fail" -eq 0 ] || exit 1