eagle-mem 4.10.11 → 4.10.12
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 +13 -0
- package/README.md +3 -1
- package/hooks/post-tool-use.sh +52 -19
- package/lib/common.sh +44 -2
- package/lib/provider.sh +9 -2
- package/package.json +1 -1
- package/scripts/curate.sh +16 -17
- package/scripts/help.sh +1 -1
- package/scripts/logs.sh +73 -13
- package/tests/test_curate_graph_memories.sh +8 -0
- package/tests/test_reliability_guards.sh +88 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,19 @@ All notable changes to the **Eagle Mem** project are documented here.
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## v4.10.12 Spectral Review Closure
|
|
8
|
+
|
|
9
|
+
This patch closes the multi-CLI Spectral review findings on v4.10.11:
|
|
10
|
+
|
|
11
|
+
- **Run Log Containment**: `eagle-mem logs show|tail` now resolves only run-log IDs, filenames, or absolute paths under `~/.eagle-mem/runs`, preventing arbitrary file reads through the logs subcommand.
|
|
12
|
+
- **Run Log Retention**: Added `eagle-mem logs prune --days N --keep N` plus automatic pruning when command-scoped run logs start, defaulting to logs older than 14 days and retaining the latest 50.
|
|
13
|
+
- **Run Log Diagnostics**: `eagle_log` messages now mirror into the active command run log, so failure log paths include provider and internal diagnostic messages instead of only command stdout/stderr.
|
|
14
|
+
- **PostToolUse Tracker Locking**: Modification tracking now writes every modified file through the same lock path, retries lock acquisition, avoids unlocked appends to the trimmed tracker, and records all files from multi-file `apply_patch` operations.
|
|
15
|
+
- **Curator JSON Robustness**: Dream Cycle consolidation parsing now tolerates provider text wrapped around the JSON payload while preserving strict `jq` validation.
|
|
16
|
+
- **Regression Coverage**: Expanded reliability tests for log path rejection, log pruning, mirrored run diagnostics, unsupported agent target logging, and multi-file modification tracking.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
7
20
|
## v4.10.11 Reliability Guards and Provider Fallback
|
|
8
21
|
|
|
9
22
|
This patch closes the active reliability items that remained after the Dream Cycle hotfix:
|
package/README.md
CHANGED
|
@@ -146,7 +146,7 @@ Eagle Mem prevents Claude from repeating past mistakes:
|
|
|
146
146
|
| `eagle-mem search` | Search past sessions, memories, and code |
|
|
147
147
|
| `eagle-mem health` | Diagnose pipeline health and background automation |
|
|
148
148
|
| `eagle-mem doctor` | Verify installed runtime files, hooks, SQLite/FTS5, statusline, manifest, and drift |
|
|
149
|
-
| `eagle-mem logs` | Inspect command-scoped `scan`, `index`, and `curate` run logs |
|
|
149
|
+
| `eagle-mem logs` | Inspect and prune command-scoped `scan`, `index`, and `curate` run logs |
|
|
150
150
|
| `eagle-mem config` | View or change LLM provider and token-guard settings |
|
|
151
151
|
| `eagle-mem updates` | View or change auto-update policy |
|
|
152
152
|
| `eagle-mem guard` | Manage regression guardrails for files |
|
|
@@ -349,6 +349,8 @@ Provider preference is local-first: Ollama is auto-detected when running, then E
|
|
|
349
349
|
|
|
350
350
|
Provider calls use an explicit fallback chain by default. For example, `agent_cli` can try the preferred/current agent first and then fall through to another supported local CLI when available. `eagle-mem config`, `eagle-mem health`, and `eagle-mem curate` display the resolved provider path so failed or unavailable agent CLIs are visible instead of appearing as `unknown`.
|
|
351
351
|
|
|
352
|
+
Command-scoped run logs live under `~/.eagle-mem/runs`. Use `eagle-mem logs list`, `eagle-mem logs show <run-id>`, `eagle-mem logs tail <run-id>`, and `eagle-mem logs prune --days 14 --keep 50` to inspect or trim them. Log reads are constrained to the run-log directory.
|
|
353
|
+
|
|
352
354
|
RTK is configured separately from the LLM provider:
|
|
353
355
|
|
|
354
356
|
```bash
|
package/hooks/post-tool-use.sh
CHANGED
|
@@ -17,6 +17,50 @@ LIB_DIR="$SCRIPT_DIR/../lib"
|
|
|
17
17
|
input=$(eagle_read_stdin)
|
|
18
18
|
[ -z "$input" ] && exit 0
|
|
19
19
|
|
|
20
|
+
eagle_track_modified_path() {
|
|
21
|
+
local path="$1" sid="$2"
|
|
22
|
+
[ -n "$path" ] || return 0
|
|
23
|
+
[ -n "$sid" ] && eagle_validate_session_id "$sid" || return 0
|
|
24
|
+
|
|
25
|
+
local mod_dir mod_file mod_lock mod_tmp attempt
|
|
26
|
+
mod_dir="$EAGLE_MEM_DIR/mod-tracker"
|
|
27
|
+
mkdir -p "$mod_dir" 2>/dev/null || return 0
|
|
28
|
+
mod_file="$mod_dir/${sid}"
|
|
29
|
+
mod_lock="${mod_file}.lock"
|
|
30
|
+
|
|
31
|
+
for attempt in 1 2 3 4 5 6 7 8 9 10; do
|
|
32
|
+
if mkdir "$mod_lock" 2>/dev/null; then
|
|
33
|
+
mod_tmp=$(mktemp "${mod_file}.XXXXXX" 2>/dev/null) || mod_tmp="${mod_file}.$$"
|
|
34
|
+
(
|
|
35
|
+
cat "$mod_file" 2>/dev/null
|
|
36
|
+
for pending_file in "${mod_file}".pending.*; do
|
|
37
|
+
[ -f "$pending_file" ] && cat "$pending_file" 2>/dev/null
|
|
38
|
+
done
|
|
39
|
+
printf '%s\n' "$path"
|
|
40
|
+
) | tail -3 > "$mod_tmp"
|
|
41
|
+
mv "$mod_tmp" "$mod_file" 2>/dev/null || rm -f "$mod_tmp"
|
|
42
|
+
rm -f "${mod_file}".pending.* 2>/dev/null || true
|
|
43
|
+
rmdir "$mod_lock" 2>/dev/null || true
|
|
44
|
+
return 0
|
|
45
|
+
fi
|
|
46
|
+
sleep 0.05
|
|
47
|
+
done
|
|
48
|
+
|
|
49
|
+
printf '%s\n' "$path" >> "${mod_file}.pending.$$" 2>/dev/null || true
|
|
50
|
+
eagle_log "WARN" "PostToolUse: mod-tracker lock busy; queued pending modified file for session=$sid"
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
eagle_track_edit_history_path() {
|
|
54
|
+
local path="$1" sid="$2"
|
|
55
|
+
[ -n "$path" ] || return 0
|
|
56
|
+
[ -n "$sid" ] && eagle_validate_session_id "$sid" || return 0
|
|
57
|
+
|
|
58
|
+
local edit_dir
|
|
59
|
+
edit_dir="$EAGLE_MEM_DIR/edit-tracker"
|
|
60
|
+
mkdir -p "$edit_dir" 2>/dev/null || return 0
|
|
61
|
+
printf '%s\n' "$path" >> "$edit_dir/${sid}" 2>/dev/null || true
|
|
62
|
+
}
|
|
63
|
+
|
|
20
64
|
IFS=$'\x1f' read -r session_id cwd tool_name hook_event <<< \
|
|
21
65
|
"$(echo "$input" | jq -r '[.session_id, .cwd, .tool_name, .hook_event_name] | map(. // "") | join("\u001f")')"
|
|
22
66
|
agent=$(eagle_agent_source_from_json "$input")
|
|
@@ -167,27 +211,16 @@ esac
|
|
|
167
211
|
|
|
168
212
|
# ─── Track recent Edit/Write targets for Read-after-modify detection ──
|
|
169
213
|
|
|
170
|
-
if [ -n "$
|
|
214
|
+
if [ -n "$session_id" ] && eagle_validate_session_id "$session_id"; then
|
|
171
215
|
case "$tool_name" in
|
|
172
216
|
Edit|Write|apply_patch)
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
mv "$_mod_tmp" "$mod_file" 2>/dev/null || rm -f "$_mod_tmp"
|
|
181
|
-
rmdir "$mod_lock" 2>/dev/null || true
|
|
182
|
-
else
|
|
183
|
-
# If another hook is trimming, append is still safer than losing the edit.
|
|
184
|
-
printf '%s\n' "$fp" >> "$mod_file"
|
|
185
|
-
fi
|
|
186
|
-
|
|
187
|
-
# Full edit history for stuck loop detection (not truncated)
|
|
188
|
-
edit_dir="$EAGLE_MEM_DIR/edit-tracker"
|
|
189
|
-
mkdir -p "$edit_dir" 2>/dev/null
|
|
190
|
-
echo "$fp" >> "$edit_dir/${session_id}"
|
|
217
|
+
modified_paths=$(printf '%s' "$files_modified" | jq -r '.[]?' 2>/dev/null)
|
|
218
|
+
[ -n "$modified_paths" ] || modified_paths="$fp"
|
|
219
|
+
while IFS= read -r modified_path; do
|
|
220
|
+
[ -z "$modified_path" ] && continue
|
|
221
|
+
eagle_track_modified_path "$modified_path" "$session_id"
|
|
222
|
+
eagle_track_edit_history_path "$modified_path" "$session_id"
|
|
223
|
+
done <<< "$modified_paths"
|
|
191
224
|
;;
|
|
192
225
|
esac
|
|
193
226
|
fi
|
package/lib/common.sh
CHANGED
|
@@ -106,11 +106,17 @@ eagle_require_sqlite_fts5() {
|
|
|
106
106
|
eagle_log() {
|
|
107
107
|
local level="$1"
|
|
108
108
|
shift
|
|
109
|
+
local ts msg
|
|
110
|
+
ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
111
|
+
msg="[$ts] [$level] $*"
|
|
109
112
|
# Ensure log file is owner-only (may contain debug data)
|
|
110
113
|
if [ ! -f "$EAGLE_MEM_LOG" ]; then
|
|
111
114
|
touch "$EAGLE_MEM_LOG" 2>/dev/null && chmod 600 "$EAGLE_MEM_LOG" 2>/dev/null
|
|
112
115
|
fi
|
|
113
|
-
echo "
|
|
116
|
+
echo "$msg" >> "$EAGLE_MEM_LOG" 2>/dev/null || true
|
|
117
|
+
if [ "${EAGLE_RUN_ACTIVE:-0}" = "1" ] && [ -n "${EAGLE_RUN_LOG:-}" ] && [ "$EAGLE_RUN_LOG" != "$EAGLE_MEM_LOG" ]; then
|
|
118
|
+
echo "$msg" >> "$EAGLE_RUN_LOG" 2>/dev/null || true
|
|
119
|
+
fi
|
|
114
120
|
}
|
|
115
121
|
|
|
116
122
|
eagle_run_slug() {
|
|
@@ -120,6 +126,39 @@ eagle_run_slug() {
|
|
|
120
126
|
| cut -c1-48
|
|
121
127
|
}
|
|
122
128
|
|
|
129
|
+
eagle_run_prune_logs() {
|
|
130
|
+
local days="${1:-${EAGLE_RUN_LOG_RETENTION_DAYS:-14}}"
|
|
131
|
+
local keep="${2:-${EAGLE_RUN_LOG_MAX_COUNT:-50}}"
|
|
132
|
+
local rel_log
|
|
133
|
+
|
|
134
|
+
[ -d "$EAGLE_RUNS_DIR" ] || return 0
|
|
135
|
+
case "$days" in ""|*[!0-9]*) days=14 ;; esac
|
|
136
|
+
case "$keep" in ""|*[!0-9]*) keep=50 ;; esac
|
|
137
|
+
|
|
138
|
+
find "$EAGLE_RUNS_DIR" -type f -name '*.log' -print 2>/dev/null \
|
|
139
|
+
| while IFS= read -r stale_log; do
|
|
140
|
+
rel_log="${stale_log#"$EAGLE_RUNS_DIR"/}"
|
|
141
|
+
case "$rel_log" in
|
|
142
|
+
*/*) rm -f -- "$stale_log" 2>/dev/null || true ;;
|
|
143
|
+
esac
|
|
144
|
+
done
|
|
145
|
+
|
|
146
|
+
if [ "$days" -gt 0 ]; then
|
|
147
|
+
find "$EAGLE_RUNS_DIR" -type f -name '*.log' -mtime +"$days" -print 2>/dev/null \
|
|
148
|
+
| while IFS= read -r stale_log; do
|
|
149
|
+
rm -f -- "$stale_log" 2>/dev/null || true
|
|
150
|
+
done
|
|
151
|
+
fi
|
|
152
|
+
|
|
153
|
+
if [ "$keep" -gt 0 ]; then
|
|
154
|
+
ls -t "$EAGLE_RUNS_DIR"/*.log 2>/dev/null \
|
|
155
|
+
| awk -v keep="$keep" 'NR > keep' \
|
|
156
|
+
| while IFS= read -r stale_log; do
|
|
157
|
+
rm -f -- "$stale_log" 2>/dev/null || true
|
|
158
|
+
done
|
|
159
|
+
fi
|
|
160
|
+
}
|
|
161
|
+
|
|
123
162
|
eagle_run_start() {
|
|
124
163
|
[ "${EAGLE_RUN_ACTIVE:-0}" = "1" ] && return 0
|
|
125
164
|
|
|
@@ -129,6 +168,7 @@ eagle_run_start() {
|
|
|
129
168
|
[ -n "$slug" ] || slug="command"
|
|
130
169
|
|
|
131
170
|
mkdir -p "$EAGLE_RUNS_DIR" "$EAGLE_MEM_DIR" 2>/dev/null || true
|
|
171
|
+
eagle_run_prune_logs >/dev/null 2>&1 || true
|
|
132
172
|
EAGLE_RUN_ID="$(date -u +%Y%m%dT%H%M%SZ)-${slug}-$$"
|
|
133
173
|
EAGLE_RUN_LOG="$EAGLE_RUNS_DIR/${EAGLE_RUN_ID}.log"
|
|
134
174
|
EAGLE_RUN_COMMAND="$command_name"
|
|
@@ -818,7 +858,9 @@ eagle_project_file_path() {
|
|
|
818
858
|
}
|
|
819
859
|
|
|
820
860
|
eagle_extract_apply_patch_files() {
|
|
821
|
-
sed -n -E
|
|
861
|
+
sed -n -E \
|
|
862
|
+
-e 's/^\*\*\* (Add|Update|Delete) File: //p' \
|
|
863
|
+
-e 's/^\*\*\* Move to: //p'
|
|
822
864
|
}
|
|
823
865
|
|
|
824
866
|
eagle_agent_source() {
|
package/lib/provider.sh
CHANGED
|
@@ -409,7 +409,10 @@ _eagle_agent_cli_target_chain() {
|
|
|
409
409
|
claude|claude-code|cloud-code) preferred_target="claude-code" ;;
|
|
410
410
|
current) preferred_target="$current" ;;
|
|
411
411
|
auto|"") preferred_target="" ;;
|
|
412
|
-
*)
|
|
412
|
+
*)
|
|
413
|
+
eagle_log "WARN" "agent_cli unsupported preferred target: $preferred"
|
|
414
|
+
preferred_target=""
|
|
415
|
+
;;
|
|
413
416
|
esac
|
|
414
417
|
|
|
415
418
|
for candidate in "$preferred_target" "$current" codex claude-code; do
|
|
@@ -469,7 +472,11 @@ _eagle_call_agent_cli() {
|
|
|
469
472
|
case "$target" in
|
|
470
473
|
codex) result=$(_eagle_call_codex_cli "$prompt" "$system" "$max_tokens"); rc=$? ;;
|
|
471
474
|
claude-code) result=$(_eagle_call_claude_cli "$prompt" "$system" "$max_tokens"); rc=$? ;;
|
|
472
|
-
*)
|
|
475
|
+
*)
|
|
476
|
+
eagle_log "WARN" "agent_cli unsupported target: $target"
|
|
477
|
+
rc=1
|
|
478
|
+
result=""
|
|
479
|
+
;;
|
|
473
480
|
esac
|
|
474
481
|
if [ "$rc" -eq 0 ] && [ -n "$result" ]; then
|
|
475
482
|
[ "$tried" -gt 1 ] && eagle_log "INFO" "agent_cli fallback succeeded with $target"
|
package/package.json
CHANGED
package/scripts/curate.sh
CHANGED
|
@@ -39,22 +39,20 @@ EOF
|
|
|
39
39
|
|
|
40
40
|
parse_consolidations_json() {
|
|
41
41
|
local result="$1"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
printf '%s' "$json_payload" | jq -c '
|
|
42
|
+
printf '%s' "$result" | jq -Rrs -c '
|
|
43
|
+
def text_trim: gsub("^\\s+|\\s+$"; "");
|
|
44
|
+
def parse_payload:
|
|
45
|
+
gsub("\r"; "")
|
|
46
|
+
| gsub("^\\s*```json\\s*\\n"; "")
|
|
47
|
+
| gsub("^\\s*```\\s*\\n"; "")
|
|
48
|
+
| gsub("\\n\\s*```\\s*$"; "")
|
|
49
|
+
| text_trim
|
|
50
|
+
| if . == "" or . == "NONE" or . == "none" or . == "null" then []
|
|
51
|
+
else
|
|
52
|
+
try fromjson catch (
|
|
53
|
+
([match("(?s)(\\{.*\\}|\\[.*\\])")? | .string][0] // "[]") | fromjson
|
|
54
|
+
)
|
|
55
|
+
end;
|
|
58
56
|
def trim: gsub("^\\s+|\\s+$"; "");
|
|
59
57
|
def names:
|
|
60
58
|
if type == "array" then map(tostring | trim) | map(select(length > 0))
|
|
@@ -66,7 +64,8 @@ parse_consolidations_json() {
|
|
|
66
64
|
elif type == "object" then (.consolidations // .items // .instructions // [])
|
|
67
65
|
else []
|
|
68
66
|
end;
|
|
69
|
-
|
|
67
|
+
parse_payload
|
|
68
|
+
| root
|
|
70
69
|
| map({
|
|
71
70
|
source_names: ((.source_names // .sourceNames // .source_memories // .sourceMemories // .original_names // .originalNames // .originals // .names) | names),
|
|
72
71
|
new_name: ((.new_name // .newName // .name // .title // "") | tostring | trim),
|
package/scripts/help.sh
CHANGED
|
@@ -23,7 +23,7 @@ echo -e " ${CYAN}uninstall${RESET} Remove hooks and optionally delete data"
|
|
|
23
23
|
echo -e " ${CYAN}search${RESET} Search past sessions, memories, and code"
|
|
24
24
|
echo -e " ${CYAN}health${RESET} Diagnose pipeline health and background automation"
|
|
25
25
|
echo -e " ${CYAN}doctor${RESET} Show install footprint, hooks, SQLite, manifest, and runtime drift"
|
|
26
|
-
echo -e " ${CYAN}logs${RESET} Inspect command-scoped scan/index/curate logs"
|
|
26
|
+
echo -e " ${CYAN}logs${RESET} Inspect/prune command-scoped scan/index/curate logs"
|
|
27
27
|
echo -e " ${CYAN}updates${RESET} Auto-update status and policy"
|
|
28
28
|
echo -e " ${CYAN}overview${RESET} Build or view project overview"
|
|
29
29
|
echo -e " ${CYAN}session${RESET} Save a manual session summary"
|
package/scripts/logs.sh
CHANGED
|
@@ -15,36 +15,60 @@ cmd="${1:-list}"
|
|
|
15
15
|
|
|
16
16
|
show_help() {
|
|
17
17
|
cat <<EOF
|
|
18
|
-
Usage: eagle-mem logs [list|tail|show] [run-id-or-
|
|
18
|
+
Usage: eagle-mem logs [list|tail|show|prune] [run-id-or-filename]
|
|
19
19
|
|
|
20
20
|
Commands:
|
|
21
|
-
list
|
|
22
|
-
tail [id|
|
|
23
|
-
show <id|
|
|
21
|
+
list Show recent command-scoped run logs
|
|
22
|
+
tail [id|filename] Tail a run log, or the latest run log when omitted
|
|
23
|
+
show <id|filename> Print a run log
|
|
24
|
+
prune [--days N] [--keep N]
|
|
25
|
+
Delete old run logs (defaults: 14 days, latest 50)
|
|
24
26
|
EOF
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
resolve_log_path() {
|
|
28
30
|
local ref="${1:-}"
|
|
31
|
+
local runs_root="${EAGLE_RUNS_DIR%/}" rel_ref
|
|
29
32
|
if [ -z "$ref" ]; then
|
|
30
|
-
ls -t "$
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
printf '%s\n' "$ref"
|
|
33
|
+
ls -t "$runs_root"/*.log 2>/dev/null | while IFS= read -r candidate; do
|
|
34
|
+
[ -L "$candidate" ] && continue
|
|
35
|
+
[ -f "$candidate" ] && printf '%s\n' "$candidate" && break
|
|
36
|
+
done
|
|
35
37
|
return 0
|
|
36
38
|
fi
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
|
|
40
|
+
case "$ref" in
|
|
41
|
+
*$'\n'*|*..*) return 1 ;;
|
|
42
|
+
/*)
|
|
43
|
+
rel_ref="${ref#"$runs_root"/}"
|
|
44
|
+
[ "$rel_ref" != "$ref" ] || return 1
|
|
45
|
+
case "$rel_ref" in ""|*/*) return 1 ;; esac
|
|
46
|
+
[ -L "$runs_root/$rel_ref" ] && return 1
|
|
47
|
+
[ -f "$runs_root/$rel_ref" ] && printf '%s\n' "$runs_root/$rel_ref" && return 0
|
|
48
|
+
return 1
|
|
49
|
+
;;
|
|
50
|
+
*/*) return 1 ;;
|
|
51
|
+
esac
|
|
52
|
+
|
|
53
|
+
if [ ! -L "$runs_root/$ref" ] && [ -f "$runs_root/$ref" ]; then
|
|
54
|
+
printf '%s\n' "$runs_root/$ref"
|
|
39
55
|
return 0
|
|
40
56
|
fi
|
|
41
|
-
if [ -f "$
|
|
42
|
-
printf '%s\n' "$
|
|
57
|
+
if [ ! -L "$runs_root/$ref.log" ] && [ -f "$runs_root/$ref.log" ]; then
|
|
58
|
+
printf '%s\n' "$runs_root/$ref.log"
|
|
43
59
|
return 0
|
|
44
60
|
fi
|
|
45
61
|
return 1
|
|
46
62
|
}
|
|
47
63
|
|
|
64
|
+
run_log_count() {
|
|
65
|
+
[ -d "$EAGLE_RUNS_DIR" ] || {
|
|
66
|
+
printf '0\n'
|
|
67
|
+
return 0
|
|
68
|
+
}
|
|
69
|
+
find "$EAGLE_RUNS_DIR" -type f -name '*.log' -print 2>/dev/null | wc -l | tr -d ' '
|
|
70
|
+
}
|
|
71
|
+
|
|
48
72
|
case "$cmd" in
|
|
49
73
|
-h|--help|help)
|
|
50
74
|
show_help
|
|
@@ -57,6 +81,7 @@ case "$cmd" in
|
|
|
57
81
|
exit 0
|
|
58
82
|
fi
|
|
59
83
|
ls -t "$EAGLE_RUNS_DIR"/*.log 2>/dev/null | head -20 | while IFS= read -r log_path; do
|
|
84
|
+
[ -L "$log_path" ] && continue
|
|
60
85
|
first_line=$(sed -n '1p' "$log_path" 2>/dev/null)
|
|
61
86
|
run_id=$(basename "$log_path" .log)
|
|
62
87
|
printf ' %s %s\n' "$run_id" "$first_line"
|
|
@@ -76,6 +101,41 @@ case "$cmd" in
|
|
|
76
101
|
}
|
|
77
102
|
cat "$log_path"
|
|
78
103
|
;;
|
|
104
|
+
prune)
|
|
105
|
+
days="${EAGLE_RUN_LOG_RETENTION_DAYS:-14}"
|
|
106
|
+
keep="${EAGLE_RUN_LOG_MAX_COUNT:-50}"
|
|
107
|
+
while [ $# -gt 0 ]; do
|
|
108
|
+
case "$1" in
|
|
109
|
+
--days)
|
|
110
|
+
days="${2:-}"
|
|
111
|
+
shift 2
|
|
112
|
+
;;
|
|
113
|
+
--keep)
|
|
114
|
+
keep="${2:-}"
|
|
115
|
+
shift 2
|
|
116
|
+
;;
|
|
117
|
+
*)
|
|
118
|
+
eagle_err "Unknown prune option: $1"
|
|
119
|
+
show_help
|
|
120
|
+
exit 1
|
|
121
|
+
;;
|
|
122
|
+
esac
|
|
123
|
+
done
|
|
124
|
+
case "$days" in ""|*[!0-9]*)
|
|
125
|
+
eagle_err "Invalid --days value: $days"
|
|
126
|
+
exit 1
|
|
127
|
+
;;
|
|
128
|
+
esac
|
|
129
|
+
case "$keep" in ""|*[!0-9]*)
|
|
130
|
+
eagle_err "Invalid --keep value: $keep"
|
|
131
|
+
exit 1
|
|
132
|
+
;;
|
|
133
|
+
esac
|
|
134
|
+
before=$(run_log_count)
|
|
135
|
+
eagle_run_prune_logs "$days" "$keep"
|
|
136
|
+
after=$(run_log_count)
|
|
137
|
+
eagle_ok "Pruned run logs: before=$before after=$after days=$days keep=$keep"
|
|
138
|
+
;;
|
|
79
139
|
*)
|
|
80
140
|
eagle_err "Unknown logs command: $cmd"
|
|
81
141
|
show_help
|
|
@@ -104,6 +104,14 @@ assert_eq "2" "$(supersedes_edges project-json "Compiled AB JSON")" "consolidate
|
|
|
104
104
|
assert_eq "3" "$(memory_graph_nodes project-json)" "curate should keep memory graph nodes idempotent"
|
|
105
105
|
assert_eq "2" "$(supersedes_edges project-json "Compiled AB JSON")" "curate should keep supersedes edge count idempotent"
|
|
106
106
|
|
|
107
|
+
# Wrapped provider output: conversational text around JSON should still parse.
|
|
108
|
+
export EAGLE_CURATE_FAKE_RESPONSE=$'Here is the JSON you requested:\n{"consolidations":[{"source_names":["Wrapped A","Wrapped B"],"new_name":"Wrapped AB","description":"wrapped","value":"--- Compiled Truth ---\\nwrapped truth\\n\\n--- Evidence Trail ---\\n- Wrapped A\\n- Wrapped B"}]}\nDone.'
|
|
109
|
+
insert_memory "project-wrapped" "memory://wrapped-a" "Wrapped A" "First wrapped memory" "Content A"
|
|
110
|
+
insert_memory "project-wrapped" "memory://wrapped-b" "Wrapped B" "Second wrapped memory" "Content B"
|
|
111
|
+
"$EAGLE_BIN" curate -p project-wrapped >/dev/null
|
|
112
|
+
assert_eq "3" "$(memory_graph_nodes project-wrapped)" "wrapped JSON output should still create consolidated memory nodes"
|
|
113
|
+
assert_eq "2" "$(supersedes_edges project-wrapped "Wrapped AB")" "wrapped JSON output should still wire supersedes edges"
|
|
114
|
+
|
|
107
115
|
# No consolidation: source nodes are still wired, but no supersedes edges are created.
|
|
108
116
|
export EAGLE_CURATE_FAKE_RESPONSE='{"consolidations":[]}'
|
|
109
117
|
insert_memory "project-none" "memory://none-a" "Memory None A" "First none memory" "Content A"
|
|
@@ -47,6 +47,25 @@ provider_result=$(EAGLE_MEM_DIR="$provider_home" PATH="$fake_bin:$PATH" bash -c
|
|
|
47
47
|
eagle_llm_call 'say ok' 'system' 20
|
|
48
48
|
")
|
|
49
49
|
assert_contains "$provider_result" "claude fallback ok" "agent_cli fallback did not use Claude after Codex failed"
|
|
50
|
+
cat > "$provider_home/config.toml" <<'TOML'
|
|
51
|
+
[provider]
|
|
52
|
+
type = "agent_cli"
|
|
53
|
+
fallback = "auto"
|
|
54
|
+
|
|
55
|
+
[agent_cli]
|
|
56
|
+
preferred = "grok"
|
|
57
|
+
codex_model = ""
|
|
58
|
+
claude_model = ""
|
|
59
|
+
TOML
|
|
60
|
+
EAGLE_MEM_DIR="$provider_home" PATH="$fake_bin:$PATH" bash -c "
|
|
61
|
+
. '$ROOT_DIR/lib/common.sh'
|
|
62
|
+
. '$ROOT_DIR/lib/provider.sh'
|
|
63
|
+
_eagle_agent_cli_target_chain >/dev/null
|
|
64
|
+
"
|
|
65
|
+
grep -q "agent_cli unsupported preferred target: grok" "$provider_home/eagle-mem.log" || {
|
|
66
|
+
echo "unsupported agent_cli target should be logged" >&2
|
|
67
|
+
exit 1
|
|
68
|
+
}
|
|
50
69
|
|
|
51
70
|
# PreToolUse parsing + read scoring: repeated large read after modification should emit scored context.
|
|
52
71
|
hook_home="$tmp_dir/hook-home"
|
|
@@ -67,6 +86,10 @@ grep -q 'mod_file}.lock' "$ROOT_DIR/hooks/post-tool-use.sh" || {
|
|
|
67
86
|
echo "post-tool-use modification tracker should use a lock directory" >&2
|
|
68
87
|
exit 1
|
|
69
88
|
}
|
|
89
|
+
if grep -q '>> "$mod_file"' "$ROOT_DIR/hooks/post-tool-use.sh"; then
|
|
90
|
+
echo "post-tool-use modification tracker should not append to mod_file outside the lock" >&2
|
|
91
|
+
exit 1
|
|
92
|
+
fi
|
|
70
93
|
|
|
71
94
|
# Auto-scan state race: failed background scan must clear the freshness marker.
|
|
72
95
|
state_home="$tmp_dir/state-home"
|
|
@@ -103,5 +126,70 @@ printf '# demo\n' > "$log_repo/README.md"
|
|
|
103
126
|
EAGLE_MEM_DIR="$log_home" bash "$ROOT_DIR/scripts/scan.sh" "$log_repo" >/dev/null
|
|
104
127
|
log_list=$(EAGLE_MEM_DIR="$log_home" bash "$ROOT_DIR/bin/eagle-mem" logs list)
|
|
105
128
|
assert_contains "$log_list" "command=scan" "logs list did not show the scan command run"
|
|
129
|
+
run_path=$(ls -t "$log_home/runs"/*.log 2>/dev/null | sed -n '1p')
|
|
130
|
+
run_id=$(basename "$run_path" .log)
|
|
131
|
+
run_show=$(EAGLE_MEM_DIR="$log_home" bash "$ROOT_DIR/bin/eagle-mem" logs show "$run_id")
|
|
132
|
+
assert_contains "$run_show" "run_start" "logs show by run id did not print the run log"
|
|
133
|
+
if EAGLE_MEM_DIR="$log_home" bash "$ROOT_DIR/bin/eagle-mem" logs show /etc/hosts >/dev/null 2>&1; then
|
|
134
|
+
echo "logs show should reject absolute paths outside the run log directory" >&2
|
|
135
|
+
exit 1
|
|
136
|
+
fi
|
|
137
|
+
if EAGLE_MEM_DIR="$log_home" bash "$ROOT_DIR/bin/eagle-mem" logs tail ../memory.db >/dev/null 2>&1; then
|
|
138
|
+
echo "logs tail should reject traversal outside the run log directory" >&2
|
|
139
|
+
exit 1
|
|
140
|
+
fi
|
|
141
|
+
mkdir -p "$log_home/runs/nested"
|
|
142
|
+
printf '[nested] [INFO] nested run\n' > "$log_home/runs/nested/nested.log"
|
|
143
|
+
if EAGLE_MEM_DIR="$log_home" bash "$ROOT_DIR/bin/eagle-mem" logs show "$log_home/runs/nested/nested.log" >/dev/null 2>&1; then
|
|
144
|
+
echo "logs show should reject nested absolute paths inside the run log directory" >&2
|
|
145
|
+
exit 1
|
|
146
|
+
fi
|
|
147
|
+
ln -s /etc/hosts "$log_home/runs/symlink.log"
|
|
148
|
+
if EAGLE_MEM_DIR="$log_home" bash "$ROOT_DIR/bin/eagle-mem" logs show symlink >/dev/null 2>&1; then
|
|
149
|
+
echo "logs show should reject symlinked run logs" >&2
|
|
150
|
+
exit 1
|
|
151
|
+
fi
|
|
152
|
+
list_with_symlink=$(EAGLE_MEM_DIR="$log_home" bash "$ROOT_DIR/bin/eagle-mem" logs list)
|
|
153
|
+
case "$list_with_symlink" in
|
|
154
|
+
*symlink*)
|
|
155
|
+
echo "logs list should skip symlinked run logs" >&2
|
|
156
|
+
exit 1
|
|
157
|
+
;;
|
|
158
|
+
esac
|
|
159
|
+
printf '[old] [INFO] old run\n' > "$log_home/runs/20000101T000000Z-scan-1.log"
|
|
160
|
+
printf '[old] [INFO] old run\n' > "$log_home/runs/20000101T000001Z-scan-2.log"
|
|
161
|
+
EAGLE_MEM_DIR="$log_home" bash "$ROOT_DIR/bin/eagle-mem" logs prune --days 0 --keep 1 >/dev/null
|
|
162
|
+
remaining_logs=$(find "$log_home/runs" -type f -name '*.log' -print | wc -l | tr -d ' ')
|
|
163
|
+
if [ "$remaining_logs" != "1" ]; then
|
|
164
|
+
echo "logs prune --keep 1 should leave exactly one run log" >&2
|
|
165
|
+
exit 1
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
mirror_home="$tmp_dir/mirror-home"
|
|
169
|
+
mkdir -p "$mirror_home"
|
|
170
|
+
EAGLE_MEM_DIR="$mirror_home" EAGLE_MEM_LOG="$mirror_home/eagle-mem.log" bash -c "
|
|
171
|
+
. '$ROOT_DIR/lib/common.sh'
|
|
172
|
+
eagle_run_start 'mirror-test' 'project-mirror' '$tmp_dir'
|
|
173
|
+
eagle_log 'WARN' 'mirrored run log detail'
|
|
174
|
+
eagle_run_finish 0 0
|
|
175
|
+
" >/dev/null
|
|
176
|
+
mirror_log=$(ls "$mirror_home/runs"/*.log | sed -n '1p')
|
|
177
|
+
grep -q "mirrored run log detail" "$mirror_log" || {
|
|
178
|
+
echo "eagle_log messages should be mirrored into active run logs" >&2
|
|
179
|
+
exit 1
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
post_home="$tmp_dir/post-home"
|
|
183
|
+
post_repo="$tmp_dir/post-repo"
|
|
184
|
+
mkdir -p "$post_home" "$post_repo"
|
|
185
|
+
EAGLE_MEM_DIR="$post_home" "$ROOT_DIR/db/migrate.sh" >/dev/null
|
|
186
|
+
post_session="session_posttool_123"
|
|
187
|
+
patch_cmd=$'*** Begin Patch\n*** Update File: alpha.txt\n@@\n-old\n+new\n*** Update File: beta.txt\n@@\n-old\n+new\n*** Update File: old-name.txt\n*** Move to: gamma.txt\n@@\n-old\n+new\n*** End Patch'
|
|
188
|
+
post_input=$(jq -nc --arg sid "$post_session" --arg cwd "$post_repo" --arg cmd "$patch_cmd" \
|
|
189
|
+
'{tool_name:"apply_patch",session_id:$sid,cwd:$cwd,tool_input:{command:$cmd},tool_response:{}}')
|
|
190
|
+
EAGLE_MEM_DIR="$post_home" EAGLE_MEM_PROJECT="project-post" bash "$ROOT_DIR/hooks/post-tool-use.sh" <<< "$post_input"
|
|
191
|
+
mod_contents=$(cat "$post_home/mod-tracker/$post_session")
|
|
192
|
+
assert_contains "$mod_contents" "beta.txt" "multi-file apply_patch should track later modified files"
|
|
193
|
+
assert_contains "$mod_contents" "gamma.txt" "apply_patch move destinations should be tracked"
|
|
106
194
|
|
|
107
195
|
echo "reliability guard regressions passed"
|