eagle-mem 4.10.10 → 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 +26 -0
- package/README.md +7 -0
- package/bin/eagle-mem +1 -0
- package/hooks/post-tool-use.sh +53 -17
- package/hooks/pre-tool-use.sh +68 -1
- package/hooks/stop.sh +1 -1
- package/hooks/user-prompt-submit.sh +1 -1
- package/lib/common.sh +129 -2
- package/lib/hooks-sessionstart.sh +60 -7
- package/lib/provider.sh +193 -39
- package/package.json +1 -1
- package/scripts/config.sh +2 -0
- package/scripts/curate.sh +25 -18
- package/scripts/health.sh +5 -7
- package/scripts/help.sh +1 -0
- package/scripts/index.sh +12 -1
- package/scripts/logs.sh +144 -0
- package/scripts/scan.sh +11 -1
- package/scripts/test.sh +1 -0
- package/tests/test_curate_graph_memories.sh +8 -0
- package/tests/test_reliability_guards.sh +195 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,32 @@ 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
|
+
|
|
20
|
+
## v4.10.11 Reliability Guards and Provider Fallback
|
|
21
|
+
|
|
22
|
+
This patch closes the active reliability items that remained after the Dream Cycle hotfix:
|
|
23
|
+
|
|
24
|
+
- **Command-Scoped Logs**: `scan`, `index`, and `curate` now write per-run logs under `~/.eagle-mem/runs`, preserve normal CLI output, and print the log path on command failure. Added `eagle-mem logs list|tail|show` for inspection.
|
|
25
|
+
- **Provider Fallback Transparency**: Provider calls now use an explicit fallback chain. `agent_cli` can fall through from a failed preferred Codex call to Claude Code when available, and provider display now shows the actual chain instead of `unknown`.
|
|
26
|
+
- **Read Prediction / Token Guard Scoring**: `PreToolUse` now scores repeated, large, or recently modified reads and emits a targeted read-score nudge. A configurable `read_guard.mode=block` path is available for stricter high-confidence duplicate-read gating.
|
|
27
|
+
- **Auto-Scan Retry Reliability**: SessionStart auto-scan/index freshness markers are now cleared when the background job fails, so failed scans do not block retries for the next 24 hours.
|
|
28
|
+
- **Hook Field Parsing**: Hook JSON field extraction now uses the intended unit separator in `PreToolUse`, `UserPromptSubmit`, and `Stop`, preserving clean `tool_name`, `session_id`, and `cwd` parsing.
|
|
29
|
+
- **Regression Coverage**: Added an isolated reliability test for provider fallback, read scoring, auto-scan failure state cleanup, and run-log creation.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
7
33
|
## v4.10.10 Dream Cycle Consolidation Hardening
|
|
8
34
|
|
|
9
35
|
This patch closes the review findings from the multi-model Spectral pass:
|
package/README.md
CHANGED
|
@@ -146,6 +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 and prune command-scoped `scan`, `index`, and `curate` run logs |
|
|
149
150
|
| `eagle-mem config` | View or change LLM provider and token-guard settings |
|
|
150
151
|
| `eagle-mem updates` | View or change auto-update policy |
|
|
151
152
|
| `eagle-mem guard` | Manage regression guardrails for files |
|
|
@@ -346,6 +347,10 @@ eagle-mem config set agent_cli.preferred current
|
|
|
346
347
|
|
|
347
348
|
Provider preference is local-first: Ollama is auto-detected when running, then Eagle Mem can use the installed Codex/Claude CLI via `agent_cli` before falling back to explicit Anthropic/OpenAI API providers. Eagle Mem works fully without a provider — LLM features gracefully degrade to heuristic fallbacks.
|
|
348
349
|
|
|
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
|
+
|
|
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
|
+
|
|
349
354
|
RTK is configured separately from the LLM provider:
|
|
350
355
|
|
|
351
356
|
```bash
|
|
@@ -353,6 +358,8 @@ eagle-mem config set token_guard.rtk auto # default: use RTK when available
|
|
|
353
358
|
eagle-mem config set token_guard.rtk enforce # block known raw-output commands if RTK is missing
|
|
354
359
|
eagle-mem config set token_guard.rtk off # disable RTK behavior
|
|
355
360
|
eagle-mem config set token_guard.raw_bash block
|
|
361
|
+
eagle-mem config set read_guard.mode advisory # score repeated reads and nudge
|
|
362
|
+
eagle-mem config set read_guard.mode block # optionally block high-confidence duplicate reads
|
|
356
363
|
```
|
|
357
364
|
|
|
358
365
|
## Long-term Direction
|
package/bin/eagle-mem
CHANGED
|
@@ -22,6 +22,7 @@ case "$command" in
|
|
|
22
22
|
search) bash "$SCRIPTS_DIR/search.sh" "$@" ;;
|
|
23
23
|
health) bash "$SCRIPTS_DIR/health.sh" "$@" ;;
|
|
24
24
|
doctor) bash "$SCRIPTS_DIR/doctor.sh" "$PACKAGE_DIR" "$@" ;;
|
|
25
|
+
logs) bash "$SCRIPTS_DIR/logs.sh" "$@" ;;
|
|
25
26
|
config) bash "$SCRIPTS_DIR/config.sh" "$@" ;;
|
|
26
27
|
updates) bash "$SCRIPTS_DIR/updates.sh" "$@" ;;
|
|
27
28
|
statusline) "$SCRIPTS_DIR/statusline-em.sh" "$@" ;;
|
package/hooks/post-tool-use.sh
CHANGED
|
@@ -17,8 +17,52 @@ 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
|
-
"$(echo "$input" | jq -r '[.session_id, .cwd, .tool_name, .hook_event_name] | map(. // "") | join("
|
|
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")
|
|
23
67
|
|
|
24
68
|
if [ -z "$session_id" ]; then exit 0; fi
|
|
@@ -167,24 +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
|
-
_mod_tmp=$(mktemp "${mod_file}.XXXXXX" 2>/dev/null) || _mod_tmp="${mod_file}.$$"
|
|
181
|
-
tail -3 "$mod_file" > "$_mod_tmp" && mv "$_mod_tmp" "$mod_file" || rm -f "$_mod_tmp"
|
|
182
|
-
fi
|
|
183
|
-
|
|
184
|
-
# Full edit history for stuck loop detection (not truncated)
|
|
185
|
-
edit_dir="$EAGLE_MEM_DIR/edit-tracker"
|
|
186
|
-
mkdir -p "$edit_dir" 2>/dev/null
|
|
187
|
-
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"
|
|
188
224
|
;;
|
|
189
225
|
esac
|
|
190
226
|
fi
|
package/hooks/pre-tool-use.sh
CHANGED
|
@@ -22,7 +22,7 @@ input=$(eagle_read_stdin)
|
|
|
22
22
|
[ -z "$input" ] && exit 0
|
|
23
23
|
|
|
24
24
|
IFS=$'\x1f' read -r tool_name session_id cwd <<< \
|
|
25
|
-
"$(echo "$input" | jq -r '[.tool_name, .session_id, .cwd] | map(. // "") | join("")')"
|
|
25
|
+
"$(echo "$input" | jq -r '[.tool_name, .session_id, .cwd] | map(. // "") | join("\u001f")')"
|
|
26
26
|
agent=$(eagle_agent_source_from_json "$input")
|
|
27
27
|
|
|
28
28
|
case "$tool_name" in
|
|
@@ -295,11 +295,15 @@ Edit|Write|apply_patch)
|
|
|
295
295
|
Read)
|
|
296
296
|
fp=$(echo "$input" | jq -r '.tool_input.file_path // empty')
|
|
297
297
|
if [ -n "$fp" ] && [ -n "$session_id" ] && eagle_validate_session_id "$session_id"; then
|
|
298
|
+
read_score=0
|
|
299
|
+
read_reasons=""
|
|
298
300
|
|
|
299
301
|
# ─── Read-after-modify detection ──────────────────────
|
|
300
302
|
mod_file="$EAGLE_MEM_DIR/mod-tracker/${session_id}"
|
|
301
303
|
if [ -f "$mod_file" ] && grep -qFx -- "$fp" "$mod_file" 2>/dev/null; then
|
|
302
304
|
context+="Eagle Mem recall: '$(basename "$fp")' was just edited/written — the diff is already in context from the tool output. "
|
|
305
|
+
read_score=$((read_score + 45))
|
|
306
|
+
read_reasons="${read_reasons}recently modified; "
|
|
303
307
|
fi
|
|
304
308
|
|
|
305
309
|
# ─── Read dedup tracker (soft nudge) ──────────────────
|
|
@@ -312,6 +316,69 @@ Read)
|
|
|
312
316
|
if [ "$read_count" -ge 3 ]; then
|
|
313
317
|
context+="Eagle Mem recall: '$(basename "$fp")' has been read ${read_count} times this session. Its contents are likely already in context."
|
|
314
318
|
fi
|
|
319
|
+
|
|
320
|
+
if [ "$read_count" -ge 2 ]; then
|
|
321
|
+
repeat_score=$((20 + (read_count - 2) * 10))
|
|
322
|
+
[ "$repeat_score" -gt 40 ] && repeat_score=40
|
|
323
|
+
read_score=$((read_score + repeat_score))
|
|
324
|
+
read_reasons="${read_reasons}${read_count} reads this session; "
|
|
325
|
+
fi
|
|
326
|
+
|
|
327
|
+
hot_files=$(eagle_get_hot_files "$project" 2>/dev/null || true)
|
|
328
|
+
if [ -n "$hot_files" ]; then
|
|
329
|
+
fp_base=$(basename "$fp")
|
|
330
|
+
case ",$hot_files," in
|
|
331
|
+
*"/$fp_base,"*|*",$fp_base,"*)
|
|
332
|
+
read_score=$((read_score + 10))
|
|
333
|
+
read_reasons="${read_reasons}hot file; "
|
|
334
|
+
;;
|
|
335
|
+
esac
|
|
336
|
+
fi
|
|
337
|
+
|
|
338
|
+
full_fp="$fp"
|
|
339
|
+
if [ ! -f "$full_fp" ] && [ -n "$cwd" ] && [ -f "$cwd/$fp" ]; then
|
|
340
|
+
full_fp="$cwd/$fp"
|
|
341
|
+
fi
|
|
342
|
+
if [ -f "$full_fp" ]; then
|
|
343
|
+
file_size=$(wc -c < "$full_fp" 2>/dev/null | tr -d ' ')
|
|
344
|
+
file_size=${file_size:-0}
|
|
345
|
+
if [ "$file_size" -ge 500000 ] 2>/dev/null; then
|
|
346
|
+
read_score=$((read_score + 20))
|
|
347
|
+
read_reasons="${read_reasons}large file; "
|
|
348
|
+
elif [ "$file_size" -ge 150000 ] 2>/dev/null; then
|
|
349
|
+
read_score=$((read_score + 10))
|
|
350
|
+
read_reasons="${read_reasons}medium-large file; "
|
|
351
|
+
fi
|
|
352
|
+
fi
|
|
353
|
+
|
|
354
|
+
[ "$read_score" -gt 100 ] && read_score=100
|
|
355
|
+
score_threshold=$(eagle_read_guard_score_threshold)
|
|
356
|
+
block_threshold=$(eagle_read_guard_block_threshold)
|
|
357
|
+
read_guard_mode=$(eagle_read_guard_mode)
|
|
358
|
+
read_reasons=${read_reasons%; }
|
|
359
|
+
if [ "$read_score" -ge "$score_threshold" ] 2>/dev/null; then
|
|
360
|
+
context+=" Eagle Mem read score: ${read_score}/100 for '$(basename "$fp")'"
|
|
361
|
+
[ -n "$read_reasons" ] && context+=" (${read_reasons})"
|
|
362
|
+
context+=". Prefer the existing context, recent diff, or targeted search unless you need exact fresh lines."
|
|
363
|
+
fi
|
|
364
|
+
if [ "$read_guard_mode" = "block" ] && [ "$read_score" -ge "$block_threshold" ] 2>/dev/null && ! eagle_raw_bash_unlock_active; then
|
|
365
|
+
reason="Eagle Mem blocked this high-confidence duplicate read to save context tokens.
|
|
366
|
+
|
|
367
|
+
File: $(basename "$fp")
|
|
368
|
+
Score: ${read_score}/100
|
|
369
|
+
Reason: ${read_reasons:-repeated read}
|
|
370
|
+
|
|
371
|
+
Use the existing context, run a narrower search, or bypass once with:
|
|
372
|
+
touch $EAGLE_RAW_BASH_UNLOCK"
|
|
373
|
+
jq -nc --arg reason "$reason" '{
|
|
374
|
+
"hookSpecificOutput":{
|
|
375
|
+
"hookEventName":"PreToolUse",
|
|
376
|
+
"permissionDecision":"deny",
|
|
377
|
+
"permissionDecisionReason":$reason
|
|
378
|
+
}
|
|
379
|
+
}'
|
|
380
|
+
exit 0
|
|
381
|
+
fi
|
|
315
382
|
fi
|
|
316
383
|
;;
|
|
317
384
|
esac
|
package/hooks/stop.sh
CHANGED
|
@@ -22,7 +22,7 @@ input=$(eagle_read_stdin)
|
|
|
22
22
|
[ -z "$input" ] && exit 0
|
|
23
23
|
|
|
24
24
|
IFS=$'\x1f' read -r session_id cwd transcript_path agent_type <<< \
|
|
25
|
-
"$(echo "$input" | jq -r '[.session_id, .cwd, .transcript_path, .agent_type] | map(. // "") | join("")')"
|
|
25
|
+
"$(echo "$input" | jq -r '[.session_id, .cwd, .transcript_path, .agent_type] | map(. // "") | join("\u001f")')"
|
|
26
26
|
last_assistant_message=$(echo "$input" | jq -r '.last_assistant_message // empty')
|
|
27
27
|
agent=$(eagle_agent_source_from_json "$input")
|
|
28
28
|
|
|
@@ -19,7 +19,7 @@ input=$(eagle_read_stdin)
|
|
|
19
19
|
[ -z "$input" ] && exit 0
|
|
20
20
|
|
|
21
21
|
IFS=$'\x1f' read -r session_id cwd <<< \
|
|
22
|
-
"$(echo "$input" | jq -r '[.session_id, .cwd] | map(. // "") | join("")')"
|
|
22
|
+
"$(echo "$input" | jq -r '[.session_id, .cwd] | map(. // "") | join("\u001f")')"
|
|
23
23
|
user_prompt=$(echo "$input" | jq -r '.prompt // empty')
|
|
24
24
|
agent=$(eagle_agent_source_from_json "$input")
|
|
25
25
|
|
package/lib/common.sh
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
EAGLE_MEM_DIR="${EAGLE_MEM_DIR:-$HOME/.eagle-mem}"
|
|
8
8
|
EAGLE_MEM_DB="$EAGLE_MEM_DIR/memory.db"
|
|
9
9
|
EAGLE_MEM_LOG="$EAGLE_MEM_DIR/eagle-mem.log"
|
|
10
|
+
EAGLE_RUNS_DIR="${EAGLE_RUNS_DIR:-$EAGLE_MEM_DIR/runs}"
|
|
10
11
|
EAGLE_SETTINGS="${EAGLE_SETTINGS:-$HOME/.claude/settings.json}"
|
|
11
12
|
EAGLE_SKILLS_DIR="${EAGLE_SKILLS_DIR:-$HOME/.claude/skills}"
|
|
12
13
|
EAGLE_CLAUDE_PROJECTS_DIR="${EAGLE_CLAUDE_PROJECTS_DIR:-$HOME/.claude/projects}"
|
|
@@ -105,11 +106,105 @@ eagle_require_sqlite_fts5() {
|
|
|
105
106
|
eagle_log() {
|
|
106
107
|
local level="$1"
|
|
107
108
|
shift
|
|
109
|
+
local ts msg
|
|
110
|
+
ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
111
|
+
msg="[$ts] [$level] $*"
|
|
108
112
|
# Ensure log file is owner-only (may contain debug data)
|
|
109
113
|
if [ ! -f "$EAGLE_MEM_LOG" ]; then
|
|
110
114
|
touch "$EAGLE_MEM_LOG" 2>/dev/null && chmod 600 "$EAGLE_MEM_LOG" 2>/dev/null
|
|
111
115
|
fi
|
|
112
|
-
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
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
eagle_run_slug() {
|
|
123
|
+
printf '%s' "${1:-command}" \
|
|
124
|
+
| tr -c 'A-Za-z0-9._-' '-' \
|
|
125
|
+
| sed 's/^-*//;s/-*$//' \
|
|
126
|
+
| cut -c1-48
|
|
127
|
+
}
|
|
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
|
+
|
|
162
|
+
eagle_run_start() {
|
|
163
|
+
[ "${EAGLE_RUN_ACTIVE:-0}" = "1" ] && return 0
|
|
164
|
+
|
|
165
|
+
local command_name="$1" project="${2:-}" target="${3:-}"
|
|
166
|
+
local slug
|
|
167
|
+
slug=$(eagle_run_slug "$command_name")
|
|
168
|
+
[ -n "$slug" ] || slug="command"
|
|
169
|
+
|
|
170
|
+
mkdir -p "$EAGLE_RUNS_DIR" "$EAGLE_MEM_DIR" 2>/dev/null || true
|
|
171
|
+
eagle_run_prune_logs >/dev/null 2>&1 || true
|
|
172
|
+
EAGLE_RUN_ID="$(date -u +%Y%m%dT%H%M%SZ)-${slug}-$$"
|
|
173
|
+
EAGLE_RUN_LOG="$EAGLE_RUNS_DIR/${EAGLE_RUN_ID}.log"
|
|
174
|
+
EAGLE_RUN_COMMAND="$command_name"
|
|
175
|
+
EAGLE_RUN_PROJECT="$project"
|
|
176
|
+
EAGLE_RUN_TARGET="$target"
|
|
177
|
+
EAGLE_RUN_ACTIVE=1
|
|
178
|
+
export EAGLE_RUN_ID EAGLE_RUN_LOG EAGLE_RUN_COMMAND EAGLE_RUN_PROJECT EAGLE_RUN_TARGET EAGLE_RUN_ACTIVE
|
|
179
|
+
|
|
180
|
+
touch "$EAGLE_RUN_LOG" 2>/dev/null && chmod 600 "$EAGLE_RUN_LOG" 2>/dev/null || true
|
|
181
|
+
{
|
|
182
|
+
printf '[%s] [INFO] run_start id=%s command=%s project=%s target=%s cwd=%s\n' \
|
|
183
|
+
"$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$EAGLE_RUN_ID" "$command_name" "$project" "$target" "$(pwd)"
|
|
184
|
+
} >> "$EAGLE_RUN_LOG" 2>/dev/null || true
|
|
185
|
+
|
|
186
|
+
# Keep normal CLI output intact while also preserving a command-scoped log.
|
|
187
|
+
exec > >(tee -a "$EAGLE_RUN_LOG") 2> >(tee -a "$EAGLE_RUN_LOG" >&2)
|
|
188
|
+
eagle_log "INFO" "Run started: id=$EAGLE_RUN_ID command=$command_name project=$project log=$EAGLE_RUN_LOG"
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
eagle_run_step() {
|
|
192
|
+
[ "${EAGLE_RUN_ACTIVE:-0}" = "1" ] || return 0
|
|
193
|
+
printf '[%s] [STEP] %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$*" >> "$EAGLE_RUN_LOG" 2>/dev/null || true
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
eagle_run_finish() {
|
|
197
|
+
[ "${EAGLE_RUN_ACTIVE:-0}" = "1" ] || return 0
|
|
198
|
+
local rc="${1:-0}" line="${2:-unknown}"
|
|
199
|
+
printf '[%s] [INFO] run_finish id=%s rc=%s line=%s\n' \
|
|
200
|
+
"$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$EAGLE_RUN_ID" "$rc" "$line" >> "$EAGLE_RUN_LOG" 2>/dev/null || true
|
|
201
|
+
if [ "$rc" -ne 0 ] 2>/dev/null; then
|
|
202
|
+
eagle_log "ERROR" "Run failed: id=$EAGLE_RUN_ID command=${EAGLE_RUN_COMMAND:-unknown} rc=$rc line=$line log=${EAGLE_RUN_LOG:-}"
|
|
203
|
+
printf '\nEagle Mem command failed: %s (exit %s, line %s)\nLog: %s\n' \
|
|
204
|
+
"${EAGLE_RUN_COMMAND:-unknown}" "$rc" "$line" "${EAGLE_RUN_LOG:-unknown}" >&2
|
|
205
|
+
else
|
|
206
|
+
eagle_log "INFO" "Run finished: id=$EAGLE_RUN_ID command=${EAGLE_RUN_COMMAND:-unknown} rc=0"
|
|
207
|
+
fi
|
|
113
208
|
}
|
|
114
209
|
|
|
115
210
|
eagle_normalize_project_path() {
|
|
@@ -763,7 +858,9 @@ eagle_project_file_path() {
|
|
|
763
858
|
}
|
|
764
859
|
|
|
765
860
|
eagle_extract_apply_patch_files() {
|
|
766
|
-
sed -n -E
|
|
861
|
+
sed -n -E \
|
|
862
|
+
-e 's/^\*\*\* (Add|Update|Delete) File: //p' \
|
|
863
|
+
-e 's/^\*\*\* Move to: //p'
|
|
767
864
|
}
|
|
768
865
|
|
|
769
866
|
eagle_agent_source() {
|
|
@@ -925,6 +1022,36 @@ eagle_token_guard_raw_bash_mode() {
|
|
|
925
1022
|
fi
|
|
926
1023
|
}
|
|
927
1024
|
|
|
1025
|
+
eagle_read_guard_mode() {
|
|
1026
|
+
if declare -F eagle_config_get >/dev/null 2>&1; then
|
|
1027
|
+
eagle_config_get "read_guard" "mode" "advisory"
|
|
1028
|
+
else
|
|
1029
|
+
eagle_config_get_light "read_guard" "mode" "advisory"
|
|
1030
|
+
fi
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
eagle_read_guard_score_threshold() {
|
|
1034
|
+
local threshold
|
|
1035
|
+
if declare -F eagle_config_get >/dev/null 2>&1; then
|
|
1036
|
+
threshold=$(eagle_config_get "read_guard" "score_threshold" "70")
|
|
1037
|
+
else
|
|
1038
|
+
threshold=$(eagle_config_get_light "read_guard" "score_threshold" "70")
|
|
1039
|
+
fi
|
|
1040
|
+
case "$threshold" in *[!0-9]*|"") threshold=70 ;; esac
|
|
1041
|
+
printf '%s\n' "$threshold"
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
eagle_read_guard_block_threshold() {
|
|
1045
|
+
local threshold
|
|
1046
|
+
if declare -F eagle_config_get >/dev/null 2>&1; then
|
|
1047
|
+
threshold=$(eagle_config_get "read_guard" "block_threshold" "90")
|
|
1048
|
+
else
|
|
1049
|
+
threshold=$(eagle_config_get_light "read_guard" "block_threshold" "90")
|
|
1050
|
+
fi
|
|
1051
|
+
case "$threshold" in *[!0-9]*|"") threshold=90 ;; esac
|
|
1052
|
+
printf '%s\n' "$threshold"
|
|
1053
|
+
}
|
|
1054
|
+
|
|
928
1055
|
eagle_raw_output_command_needs_guard() {
|
|
929
1056
|
local cmd="$1"
|
|
930
1057
|
local first
|
|
@@ -12,18 +12,23 @@ _eagle_state_slug() {
|
|
|
12
12
|
printf '%s' "$1" | shasum | cut -c1-12
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
_eagle_state_file() {
|
|
16
|
+
local key="$1" project="$2"
|
|
17
|
+
local safe_project; safe_project=$(_eagle_state_slug "$project")
|
|
18
|
+
printf '%s/%s-%s\n' "$_state_dir" "$key" "$safe_project"
|
|
19
|
+
}
|
|
20
|
+
|
|
15
21
|
_eagle_state_fresh() {
|
|
16
22
|
local key="$1" project="$2" max_age_days="${3:-1}"
|
|
17
|
-
local
|
|
18
|
-
local state_file="$_state_dir/${key}-${safe_project}"
|
|
23
|
+
local state_file; state_file=$(_eagle_state_file "$key" "$project")
|
|
19
24
|
[ -f "$state_file" ] && [ -z "$(find "$state_file" -mtime +${max_age_days} 2>/dev/null)" ]
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
_eagle_state_touch() {
|
|
23
28
|
local key="$1" project="$2"
|
|
24
|
-
local
|
|
29
|
+
local state_file; state_file=$(_eagle_state_file "$key" "$project")
|
|
25
30
|
mkdir -p "$_state_dir" 2>/dev/null
|
|
26
|
-
touch "$
|
|
31
|
+
touch "$state_file"
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
eagle_sessionstart_auto_provision() {
|
|
@@ -51,15 +56,63 @@ eagle_sessionstart_auto_provision() {
|
|
|
51
56
|
eagle_log "INFO" "SessionStart: first-session provision — scan then index"
|
|
52
57
|
_eagle_state_touch "scan" "$project"
|
|
53
58
|
_eagle_state_touch "index" "$project"
|
|
54
|
-
|
|
59
|
+
scan_state=$(_eagle_state_file "scan" "$project")
|
|
60
|
+
index_state=$(_eagle_state_file "index" "$project")
|
|
61
|
+
nohup bash -c '
|
|
62
|
+
scripts_dir="$1"; cwd="$2"; log="$3"; scan_state="$4"; index_state="$5"
|
|
63
|
+
bash "$scripts_dir/scan.sh" "$cwd" >> "$log" 2>&1
|
|
64
|
+
scan_rc=$?
|
|
65
|
+
if [ "$scan_rc" -eq 0 ]; then
|
|
66
|
+
touch "$scan_state" 2>/dev/null || true
|
|
67
|
+
else
|
|
68
|
+
rm -f "$scan_state" 2>/dev/null || true
|
|
69
|
+
printf "[%s] [ERROR] SessionStart: auto-scan failed rc=%s\n" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$scan_rc" >> "$log" 2>/dev/null || true
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
bash "$scripts_dir/index.sh" "$cwd" >> "$log" 2>&1
|
|
73
|
+
index_rc=$?
|
|
74
|
+
if [ "$index_rc" -eq 0 ]; then
|
|
75
|
+
touch "$index_state" 2>/dev/null || true
|
|
76
|
+
else
|
|
77
|
+
rm -f "$index_state" 2>/dev/null || true
|
|
78
|
+
printf "[%s] [ERROR] SessionStart: auto-index failed rc=%s\n" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$index_rc" >> "$log" 2>/dev/null || true
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
[ "$scan_rc" -eq 0 ] && exit "$index_rc"
|
|
82
|
+
exit "$scan_rc"
|
|
83
|
+
' eagle-auto "$scripts_dir" "$cwd" "$EAGLE_MEM_LOG" "$scan_state" "$index_state" &
|
|
55
84
|
elif [ "$needs_scan" = true ]; then
|
|
56
85
|
eagle_log "INFO" "SessionStart: auto-scan triggered"
|
|
57
86
|
_eagle_state_touch "scan" "$project"
|
|
58
|
-
|
|
87
|
+
scan_state=$(_eagle_state_file "scan" "$project")
|
|
88
|
+
nohup bash -c '
|
|
89
|
+
scripts_dir="$1"; cwd="$2"; log="$3"; scan_state="$4"
|
|
90
|
+
bash "$scripts_dir/scan.sh" "$cwd" >> "$log" 2>&1
|
|
91
|
+
rc=$?
|
|
92
|
+
if [ "$rc" -eq 0 ]; then
|
|
93
|
+
touch "$scan_state" 2>/dev/null || true
|
|
94
|
+
else
|
|
95
|
+
rm -f "$scan_state" 2>/dev/null || true
|
|
96
|
+
printf "[%s] [ERROR] SessionStart: auto-scan failed rc=%s\n" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$rc" >> "$log" 2>/dev/null || true
|
|
97
|
+
fi
|
|
98
|
+
exit "$rc"
|
|
99
|
+
' eagle-auto "$scripts_dir" "$cwd" "$EAGLE_MEM_LOG" "$scan_state" &
|
|
59
100
|
elif [ "$needs_index" = true ]; then
|
|
60
101
|
eagle_log "INFO" "SessionStart: auto-index triggered"
|
|
61
102
|
_eagle_state_touch "index" "$project"
|
|
62
|
-
|
|
103
|
+
index_state=$(_eagle_state_file "index" "$project")
|
|
104
|
+
nohup bash -c '
|
|
105
|
+
scripts_dir="$1"; cwd="$2"; log="$3"; index_state="$4"
|
|
106
|
+
bash "$scripts_dir/index.sh" "$cwd" >> "$log" 2>&1
|
|
107
|
+
rc=$?
|
|
108
|
+
if [ "$rc" -eq 0 ]; then
|
|
109
|
+
touch "$index_state" 2>/dev/null || true
|
|
110
|
+
else
|
|
111
|
+
rm -f "$index_state" 2>/dev/null || true
|
|
112
|
+
printf "[%s] [ERROR] SessionStart: auto-index failed rc=%s\n" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$rc" >> "$log" 2>/dev/null || true
|
|
113
|
+
fi
|
|
114
|
+
exit "$rc"
|
|
115
|
+
' eagle-auto "$scripts_dir" "$cwd" "$EAGLE_MEM_LOG" "$index_state" &
|
|
63
116
|
fi
|
|
64
117
|
}
|
|
65
118
|
|