eagle-mem 4.10.9 → 4.10.11

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/provider.sh CHANGED
@@ -152,6 +152,8 @@ eagle_config_init() {
152
152
  # Which LLM provider to use for the curator and analysis features
153
153
  # Options: "ollama" (free, local), "agent_cli" (Codex/Claude CLI auth), "anthropic", "openai"
154
154
  type = "$provider"
155
+ # "auto" retries local/agent/API fallbacks when the primary provider fails.
156
+ fallback = "auto"
155
157
 
156
158
  [ollama]
157
159
  url = "$ollama_url"
@@ -207,6 +209,12 @@ rtk = "auto"
207
209
  # "allow" keeps RTK advisory only.
208
210
  raw_bash = "block"
209
211
 
212
+ [read_guard]
213
+ # advisory = score repeated/expensive reads and nudge; block = deny high-score rereads.
214
+ mode = "advisory"
215
+ score_threshold = "70"
216
+ block_threshold = "90"
217
+
210
218
  [redaction]
211
219
  # Additional secret patterns (regex) beyond built-in defaults
212
220
  # extra_patterns = ["MY_CUSTOM_SECRET_.*"]
@@ -223,14 +231,39 @@ eagle_llm_call() {
223
231
  local system_prompt="${2:-You are a helpful assistant that analyzes software development sessions.}"
224
232
  local max_tokens="${3:-1024}"
225
233
 
226
- local provider
234
+ local provider chain candidate result rc tried=0
227
235
  provider=$(eagle_config_get "provider" "type" "none")
236
+ chain=$(_eagle_provider_chain "$provider")
237
+
238
+ if [ -z "$chain" ]; then
239
+ eagle_log "ERROR" "No LLM provider configured or available. Run: eagle-mem config"
240
+ return 1
241
+ fi
242
+
243
+ while IFS= read -r candidate; do
244
+ [ -z "$candidate" ] && continue
245
+ tried=$((tried + 1))
246
+ result=$(_eagle_call_provider_once "$candidate" "$prompt" "$system_prompt" "$max_tokens")
247
+ rc=$?
248
+ if [ "$rc" -eq 0 ] && [ -n "$result" ]; then
249
+ [ "$candidate" != "$provider" ] && eagle_log "INFO" "LLM provider fallback succeeded: primary=$provider used=$candidate"
250
+ printf '%s\n' "$result"
251
+ return 0
252
+ fi
253
+ eagle_log "WARN" "LLM provider candidate failed: primary=$provider candidate=$candidate rc=$rc"
254
+ done <<< "$chain"
255
+
256
+ eagle_log "ERROR" "All LLM provider candidates failed: primary=$provider tried=$tried"
257
+ return 1
258
+ }
228
259
 
260
+ _eagle_call_provider_once() {
261
+ local provider="$1" prompt="$2" system_prompt="$3" max_tokens="$4"
229
262
  case "$provider" in
230
- ollama) _eagle_call_ollama "$prompt" "$system_prompt" "$max_tokens" ;;
263
+ ollama) _eagle_call_ollama "$prompt" "$system_prompt" "$max_tokens" ;;
231
264
  agent_cli) _eagle_call_agent_cli "$prompt" "$system_prompt" "$max_tokens" ;;
232
265
  anthropic) _eagle_call_anthropic "$prompt" "$system_prompt" "$max_tokens" ;;
233
- openai) _eagle_call_openai "$prompt" "$system_prompt" "$max_tokens" ;;
266
+ openai) _eagle_call_openai "$prompt" "$system_prompt" "$max_tokens" ;;
234
267
  none)
235
268
  eagle_log "ERROR" "No LLM provider configured. Run: eagle-mem config"
236
269
  return 1
@@ -242,6 +275,79 @@ eagle_llm_call() {
242
275
  esac
243
276
  }
244
277
 
278
+ _eagle_provider_chain() {
279
+ local primary="${1:-none}" fallback
280
+ fallback=$(eagle_config_get "provider" "fallback" "auto")
281
+
282
+ local chain=""
283
+ _eagle_provider_chain_add() {
284
+ local candidate="$1" required="${2:-available}"
285
+ [ -n "$candidate" ] || return 0
286
+ case " $chain " in *" $candidate "*) return 0 ;; esac
287
+ if [ "$required" = "available" ] && ! _eagle_provider_available "$candidate"; then
288
+ return 0
289
+ fi
290
+ chain="${chain}${chain:+ }$candidate"
291
+ }
292
+
293
+ if [ "$primary" != "none" ]; then
294
+ _eagle_provider_chain_add "$primary" "always"
295
+ fi
296
+
297
+ if [ "$fallback" != "off" ]; then
298
+ case "$primary" in
299
+ ollama) _eagle_provider_chain_add "agent_cli"; _eagle_provider_chain_add "anthropic"; _eagle_provider_chain_add "openai" ;;
300
+ agent_cli) _eagle_provider_chain_add "ollama"; _eagle_provider_chain_add "anthropic"; _eagle_provider_chain_add "openai" ;;
301
+ anthropic) _eagle_provider_chain_add "ollama"; _eagle_provider_chain_add "agent_cli"; _eagle_provider_chain_add "openai" ;;
302
+ openai) _eagle_provider_chain_add "ollama"; _eagle_provider_chain_add "agent_cli"; _eagle_provider_chain_add "anthropic" ;;
303
+ none|"") _eagle_provider_chain_add "ollama"; _eagle_provider_chain_add "agent_cli"; _eagle_provider_chain_add "anthropic"; _eagle_provider_chain_add "openai" ;;
304
+ esac
305
+ fi
306
+
307
+ printf '%s\n' "$chain" | tr ' ' '\n' | sed '/^$/d'
308
+ }
309
+
310
+ _eagle_provider_available() {
311
+ case "$1" in
312
+ ollama)
313
+ [ -n "$(eagle_detect_ollama "$(eagle_config_get "ollama" "url" "$EAGLE_DEFAULT_OLLAMA_URL")" 2>/dev/null || true)" ]
314
+ ;;
315
+ agent_cli)
316
+ [ -n "$(_eagle_agent_cli_target_chain)" ]
317
+ ;;
318
+ anthropic)
319
+ [ -n "${ANTHROPIC_API_KEY:-}" ]
320
+ ;;
321
+ openai)
322
+ [ -n "${OPENAI_API_KEY:-}" ]
323
+ ;;
324
+ *) return 1 ;;
325
+ esac
326
+ }
327
+
328
+ _eagle_provider_model_label() {
329
+ case "$1" in
330
+ ollama) printf 'ollama:%s' "$(eagle_config_get "ollama" "model" "mistral")" ;;
331
+ agent_cli) printf 'agent_cli:%s' "$(_eagle_agent_cli_target_summary)" ;;
332
+ anthropic) printf 'anthropic:%s' "$(eagle_config_get "anthropic" "model" "claude-haiku-4-5-20251001")" ;;
333
+ openai) printf 'openai:%s' "$(eagle_config_get "openai" "model" "gpt-4o-mini")" ;;
334
+ *) printf '%s' "$1" ;;
335
+ esac
336
+ }
337
+
338
+ eagle_llm_provider_label() {
339
+ local provider chain labels="" candidate sep=""
340
+ provider=$(eagle_config_get "provider" "type" "none")
341
+ chain=$(_eagle_provider_chain "$provider")
342
+ while IFS= read -r candidate; do
343
+ [ -z "$candidate" ] && continue
344
+ labels="${labels}${sep}$(_eagle_provider_model_label "$candidate")"
345
+ sep=" -> "
346
+ done <<< "$chain"
347
+ [ -n "$labels" ] || labels="none"
348
+ printf '%s\n' "$labels"
349
+ }
350
+
245
351
  _eagle_call_ollama() {
246
352
  local prompt="$1" system="$2" max_tokens="$3"
247
353
  local url model
@@ -281,35 +387,57 @@ _eagle_call_ollama() {
281
387
  }
282
388
 
283
389
  _eagle_agent_cli_target() {
284
- local preferred
390
+ local first
391
+ first=$(_eagle_agent_cli_target_chain | sed -n '1p')
392
+ if [ -n "$first" ]; then
393
+ printf '%s\n' "$first"
394
+ else
395
+ printf 'none\n'
396
+ fi
397
+ }
398
+
399
+ _eagle_agent_cli_target_chain() {
400
+ local preferred current preferred_target targets="" candidate
285
401
  preferred=$(eagle_config_get "agent_cli" "preferred" "current")
402
+ current=""
403
+ if [ -n "${EAGLE_AGENT_SOURCE:-${EAGLE_AGENT:-}}" ]; then
404
+ current=$(eagle_agent_source)
405
+ fi
286
406
 
287
407
  case "$preferred" in
288
- codex|openai-codex) echo "codex"; return 0 ;;
289
- claude|claude-code|cloud-code) echo "claude-code"; return 0 ;;
290
- auto)
291
- if [ -n "${EAGLE_AGENT_SOURCE:-${EAGLE_AGENT:-}}" ]; then
292
- eagle_agent_source
293
- elif command -v codex &>/dev/null; then
294
- echo "codex"
295
- elif command -v claude &>/dev/null; then
296
- echo "claude-code"
297
- else
298
- echo "none"
299
- fi
300
- ;;
301
- current|*)
302
- if [ -n "${EAGLE_AGENT_SOURCE:-${EAGLE_AGENT:-}}" ]; then
303
- eagle_agent_source
304
- elif command -v codex &>/dev/null; then
305
- echo "codex"
306
- elif command -v claude &>/dev/null; then
307
- echo "claude-code"
308
- else
309
- echo "none"
310
- fi
311
- ;;
408
+ codex|openai-codex) preferred_target="codex" ;;
409
+ claude|claude-code|cloud-code) preferred_target="claude-code" ;;
410
+ current) preferred_target="$current" ;;
411
+ auto|"") preferred_target="" ;;
412
+ *) preferred_target="$preferred" ;;
312
413
  esac
414
+
415
+ for candidate in "$preferred_target" "$current" codex claude-code; do
416
+ case "$candidate" in
417
+ codex|claude-code) ;;
418
+ *) continue ;;
419
+ esac
420
+ case "$candidate" in
421
+ codex) command -v codex >/dev/null 2>&1 || continue ;;
422
+ claude-code) command -v claude >/dev/null 2>&1 || continue ;;
423
+ esac
424
+ case " $targets " in *" $candidate "*) continue ;; esac
425
+ targets="${targets}${targets:+ }$candidate"
426
+ done
427
+
428
+ printf '%s\n' "$targets" | tr ' ' '\n' | sed '/^$/d'
429
+ }
430
+
431
+ _eagle_agent_cli_target_summary() {
432
+ local chain summary="" sep="" target
433
+ chain=$(_eagle_agent_cli_target_chain)
434
+ while IFS= read -r target; do
435
+ [ -z "$target" ] && continue
436
+ summary="${summary}${sep}${target}"
437
+ sep=" -> "
438
+ done <<< "$chain"
439
+ [ -n "$summary" ] || summary="none"
440
+ printf '%s\n' "$summary"
313
441
  }
314
442
 
315
443
  _eagle_agent_cli_prompt_file() {
@@ -327,17 +455,32 @@ _eagle_agent_cli_prompt_file() {
327
455
 
328
456
  _eagle_call_agent_cli() {
329
457
  local prompt="$1" system="$2" max_tokens="$3"
330
- local target
331
- target=$(_eagle_agent_cli_target)
458
+ local target targets result rc tried=0
459
+ targets=$(_eagle_agent_cli_target_chain)
332
460
 
333
- case "$target" in
334
- codex) _eagle_call_codex_cli "$prompt" "$system" "$max_tokens" ;;
335
- claude-code) _eagle_call_claude_cli "$prompt" "$system" "$max_tokens" ;;
336
- *)
337
- eagle_log "ERROR" "agent_cli provider unavailable: no Codex or Claude CLI found"
338
- return 1
339
- ;;
340
- esac
461
+ if [ -z "$targets" ]; then
462
+ eagle_log "ERROR" "agent_cli provider unavailable: no supported Codex or Claude CLI found"
463
+ return 1
464
+ fi
465
+
466
+ while IFS= read -r target; do
467
+ [ -z "$target" ] && continue
468
+ tried=$((tried + 1))
469
+ case "$target" in
470
+ codex) result=$(_eagle_call_codex_cli "$prompt" "$system" "$max_tokens"); rc=$? ;;
471
+ claude-code) result=$(_eagle_call_claude_cli "$prompt" "$system" "$max_tokens"); rc=$? ;;
472
+ *) rc=1; result="" ;;
473
+ esac
474
+ if [ "$rc" -eq 0 ] && [ -n "$result" ]; then
475
+ [ "$tried" -gt 1 ] && eagle_log "INFO" "agent_cli fallback succeeded with $target"
476
+ printf '%s\n' "$result"
477
+ return 0
478
+ fi
479
+ eagle_log "WARN" "agent_cli target failed: target=$target rc=$rc"
480
+ done <<< "$targets"
481
+
482
+ eagle_log "ERROR" "All agent_cli targets failed: targets=$(printf '%s' "$targets" | tr '\n' ',')"
483
+ return 1
341
484
  }
342
485
 
343
486
  _eagle_call_codex_cli() {
@@ -539,16 +682,19 @@ eagle_show_config() {
539
682
  return 1
540
683
  fi
541
684
 
542
- local provider model
685
+ local provider model fallback
543
686
  provider=$(eagle_config_get "provider" "type" "none")
687
+ fallback=$(eagle_config_get "provider" "fallback" "auto")
544
688
  if [ "$provider" = "agent_cli" ]; then
545
- model=$(_eagle_agent_cli_target)
689
+ model=$(_eagle_agent_cli_target_summary)
546
690
  else
547
691
  model=$(eagle_config_get "$provider" "model" "unknown")
548
692
  fi
549
693
 
550
694
  echo "Provider: $provider"
551
695
  echo "Model: $model"
696
+ echo "Fallback: $fallback"
697
+ echo "Chain: $(eagle_llm_provider_label)"
552
698
 
553
699
  if [ "$provider" = "ollama" ]; then
554
700
  local url
@@ -564,6 +710,7 @@ eagle_show_config() {
564
710
  fi
565
711
  elif [ "$provider" = "agent_cli" ]; then
566
712
  echo "Preferred: $(eagle_config_get "agent_cli" "preferred" "current")"
713
+ echo "Targets: $(_eagle_agent_cli_target_summary)"
567
714
  echo "Codex: $(command -v codex 2>/dev/null || echo "not found")"
568
715
  echo "Claude: $(command -v claude 2>/dev/null || echo "not found")"
569
716
  fi
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eagle-mem",
3
- "version": "4.10.9",
3
+ "version": "4.10.11",
4
4
  "description": "Shared memory, release guardrails, RTK token protection, and worker lanes for Claude Code, Codex, Grok, and Google Antigravity",
5
5
  "bin": {
6
6
  "eagle-mem": "bin/eagle-mem"
package/scripts/config.sh CHANGED
@@ -36,6 +36,7 @@ show_help() {
36
36
  echo -e " eagle-mem config set updates.allow patch"
37
37
  echo -e " eagle-mem config set token_guard.rtk enforce"
38
38
  echo -e " eagle-mem config set token_guard.raw_bash block"
39
+ echo -e " eagle-mem config set read_guard.mode advisory"
39
40
  echo ""
40
41
  exit 0
41
42
  }
@@ -72,6 +73,7 @@ case "$subcommand" in
72
73
  eagle_info " eagle-mem config set anthropic.model claude-haiku-4-5-20251001"
73
74
  eagle_info " eagle-mem config set token_guard.rtk enforce"
74
75
  eagle_info " eagle-mem config set token_guard.raw_bash block"
76
+ eagle_info " eagle-mem config set read_guard.mode advisory"
75
77
  exit 1
76
78
  fi
77
79
  section="${key%%.*}"
package/scripts/curate.sh CHANGED
@@ -37,6 +37,46 @@ Options:
37
37
  EOF
38
38
  }
39
39
 
40
+ parse_consolidations_json() {
41
+ local result="$1"
42
+ local trimmed json_payload
43
+
44
+ trimmed=$(printf '%s' "$result" | tr -d '\r' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
45
+ case "$trimmed" in
46
+ ""|NONE|none|null)
47
+ printf '[]\n'
48
+ return 0
49
+ ;;
50
+ esac
51
+
52
+ json_payload=$(printf '%s' "$result" \
53
+ | sed -e '1s/^[[:space:]]*```json[[:space:]]*$//' \
54
+ -e '1s/^[[:space:]]*```[[:space:]]*$//' \
55
+ -e '$s/^[[:space:]]*```[[:space:]]*$//')
56
+
57
+ printf '%s' "$json_payload" | jq -c '
58
+ def trim: gsub("^\\s+|\\s+$"; "");
59
+ def names:
60
+ if type == "array" then map(tostring | trim) | map(select(length > 0))
61
+ elif type == "string" then split(",") | map(trim) | map(select(length > 0))
62
+ else []
63
+ end;
64
+ def root:
65
+ if type == "array" then .
66
+ elif type == "object" then (.consolidations // .items // .instructions // [])
67
+ else []
68
+ end;
69
+ root
70
+ | map({
71
+ source_names: ((.source_names // .sourceNames // .source_memories // .sourceMemories // .original_names // .originalNames // .originals // .names) | names),
72
+ new_name: ((.new_name // .newName // .name // .title // "") | tostring | trim),
73
+ description: ((.description // "") | tostring),
74
+ value: ((.value // .content // .compiled_truth // .compiledTruth // "") | tostring)
75
+ })
76
+ | map(select((.source_names | length) > 0 and (.new_name | length) > 0))
77
+ ' 2>/dev/null
78
+ }
79
+
40
80
  while [ $# -gt 0 ]; do
41
81
  case "$1" in
42
82
  -h|--help)
@@ -60,19 +100,27 @@ fi
60
100
 
61
101
  p_esc=$(eagle_sql_escape "$project")
62
102
 
103
+ cleanup_curate() {
104
+ local rc=$?
105
+ eagle_run_finish "$rc" "$LINENO"
106
+ }
107
+ eagle_run_start "curate" "$project" "$(pwd)"
108
+ trap cleanup_curate EXIT
109
+
63
110
  # Verify provider is configured
64
111
  provider=$(eagle_config_get "provider" "type" "none")
65
112
  if [ "$provider" = "none" ]; then
66
113
  eagle_err "No LLM provider configured. Run: eagle-mem config init"
67
114
  exit 1
68
115
  fi
69
- eagle_info "Provider: $provider ($(eagle_config_get "$provider" "model" "unknown"))"
116
+ eagle_info "Provider: $(eagle_llm_provider_label)"
70
117
  eagle_info "Project: $project"
71
118
  [ "$DRY_RUN" -eq 1 ] && eagle_info "Dry run — no changes will be made"
72
119
  echo ""
73
120
 
74
121
  # ─── 1. Analyze gotchas for promotion ─────────────────────
75
122
 
123
+ eagle_run_step "promote_gotchas"
76
124
  eagle_info "Analyzing gotchas for promotion..."
77
125
 
78
126
  recent_gotchas=$(eagle_db "SELECT gotchas, created_at
@@ -559,7 +607,11 @@ if [ -n "$co_edit_data" ]; then
559
607
  co_wire_count=$((co_wire_count + 1))
560
608
  fi
561
609
  done <<< "$co_edit_data"
562
- eagle_ok "Wired $co_wire_count co-edited file edges"
610
+ if [ "$DRY_RUN" -eq 1 ]; then
611
+ eagle_info "Would wire $co_wire_count co-edited file edges"
612
+ else
613
+ eagle_ok "Wired $co_wire_count co-edited file edges"
614
+ fi
563
615
  fi
564
616
 
565
617
  # 7.2 Wire session nodes and access edges
@@ -569,35 +621,55 @@ if [ -n "$recent_sessions" ]; then
569
621
  if [ "$DRY_RUN" -eq 0 ]; then
570
622
  session_wire_count=$(eagle_graph_wire_recent_session_edges "$project" 15)
571
623
  fi
572
- eagle_ok "Wired $session_wire_count recent session nodes and edges"
624
+ if [ "$DRY_RUN" -eq 1 ]; then
625
+ eagle_info "Would wire $session_wire_count recent session nodes and edges"
626
+ else
627
+ eagle_ok "Wired $session_wire_count recent session nodes and edges"
628
+ fi
573
629
  fi
574
630
 
575
631
  # 7.3 Offline Memory Consolidation (Compiled Truth vs Evidence)
576
- active_memories=$(eagle_db "SELECT memory_name, memory_type, description, content FROM agent_memories WHERE project = '$p_esc';")
577
- if [ -n "$active_memories" ]; then
578
- memory_node_count=0
579
- active_memory_nodes=$(eagle_db_json "SELECT memory_name, content FROM agent_memories WHERE project = '$p_esc';" || true)
580
- active_memory_node_count=$(printf '%s' "$active_memory_nodes" | jq 'length' 2>/dev/null || echo 0)
632
+ active_memory_rows=$(eagle_db_json "SELECT memory_name, memory_type, description, content FROM agent_memories WHERE project = '$p_esc';" || {
633
+ eagle_log "WARN" "Unable to read active agent memories for consolidation"
634
+ echo "[]"
635
+ })
636
+ active_memory_count=$(printf '%s' "$active_memory_rows" | jq 'length' 2>/dev/null || echo 0)
637
+ if [ "$active_memory_count" -gt 0 ]; then
638
+ wired_memory_count=0
581
639
  memory_node_index=0
582
- while [ "$memory_node_index" -lt "$active_memory_node_count" ]; do
583
- mname=$(printf '%s' "$active_memory_nodes" | jq -r ".[$memory_node_index].memory_name // empty")
584
- mcontent=$(printf '%s' "$active_memory_nodes" | jq -r ".[$memory_node_index].content // empty")
640
+ while [ "$memory_node_index" -lt "$active_memory_count" ]; do
641
+ mname=$(printf '%s' "$active_memory_rows" | jq -r ".[$memory_node_index].memory_name // empty")
642
+ mcontent=$(printf '%s' "$active_memory_rows" | jq -r ".[$memory_node_index].content // empty")
585
643
  memory_node_index=$((memory_node_index + 1))
586
644
  [ -n "$mname" ] || continue
587
645
  if [ "$DRY_RUN" -eq 0 ]; then
588
646
  eagle_graph_add_node "$project" "memory" "$mname" "$mcontent" ""
589
647
  fi
590
- memory_node_count=$((memory_node_count + 1))
648
+ wired_memory_count=$((wired_memory_count + 1))
591
649
  done
592
- eagle_ok "Wired $memory_node_count agent memory graph nodes"
650
+ if [ "$DRY_RUN" -eq 1 ]; then
651
+ eagle_info "Would wire $wired_memory_count agent memory graph nodes"
652
+ else
653
+ eagle_ok "Wired $wired_memory_count agent memory graph nodes"
654
+ fi
593
655
 
594
- consolidation_prompt="Analyze these mirrored agent memories for project '$project'. Identify any memories that are redundant, overlap in scope, or describe the same subsystem/gotcha/concept.
595
-
596
- MEMORIES:
597
- $active_memories
656
+ memory_prompt_json=$(printf '%s' "$active_memory_rows" | jq -c '[.[] | {
657
+ memory_name: .memory_name,
658
+ memory_type: .memory_type,
659
+ description: .description,
660
+ content: .content
661
+ }]')
598
662
 
599
- For any memories that should be consolidated, merge them into a single 'Compiled Truth' summary.
600
- The consolidated memory MUST be formatted exactly as:
663
+ if [ "$DRY_RUN" -eq 1 ]; then
664
+ eagle_info "Would analyze $wired_memory_count agent memories for consolidation"
665
+ else
666
+ consolidation_prompt="Analyze these mirrored agent memories for project '$project'. Identify memories that are redundant, overlap in scope, or describe the same subsystem/gotcha/concept.
667
+
668
+ INPUT_MEMORIES_JSON:
669
+ $memory_prompt_json
670
+
671
+ For any memories that should be consolidated, merge them into a single Compiled Truth summary.
672
+ The consolidated memory value MUST be formatted exactly as:
601
673
  --- Compiled Truth ---
602
674
  <A structured, clear, up-to-date summary of the topic, gotten by merging the duplicate/overlapping memories. Keep it extremely precise.>
603
675
 
@@ -605,55 +677,59 @@ The consolidated memory MUST be formatted exactly as:
605
677
  - <Original memory title 1>: <brief original description or timestamp>
606
678
  - <Original memory title 2>: <brief original description or timestamp>
607
679
 
608
- Format your output as a series of instructions:
609
- CONSOLIDATE: <original memory name 1>, <original memory name 2> -> <new consolidated memory name> | description: <new description> | value: <the merged compiled truth + evidence content>
680
+ Return strict JSON only, with this schema:
681
+ {
682
+ \"consolidations\": [
683
+ {
684
+ \"source_names\": [\"<exact source memory_name>\", \"<exact source memory_name>\"],
685
+ \"new_name\": \"<new consolidated memory name>\",
686
+ \"description\": \"<new description>\",
687
+ \"value\": \"<the merged compiled truth + evidence content>\"
688
+ }
689
+ ]
690
+ }
610
691
 
611
- If no memories need consolidation, output: NONE"
692
+ If no memories need consolidation, return {\"consolidations\":[]}."
612
693
 
613
- consolidation_result=$(eagle_llm_call "$consolidation_prompt" "You consolidate software development memories into a single compiled truth. Be precise. Output CONSOLIDATE lines or NONE." 1024 || true)
694
+ consolidation_result=$(eagle_llm_call "$consolidation_prompt" "You consolidate software development memories into a single compiled truth. Return strict JSON only." 1024 || true)
695
+ if ! parsed_consolidations=$(parse_consolidations_json "$consolidation_result"); then
696
+ eagle_log "WARN" "Unable to parse memory consolidation provider response"
697
+ parsed_consolidations="[]"
698
+ fi
614
699
 
615
- if [ -n "$consolidation_result" ] && ! echo "$consolidation_result" | grep -q "^NONE$"; then
616
- cons_count=0
617
- while IFS= read -r line; do
618
- case "$line" in
619
- CONSOLIDATE:*)
620
- cons_data=$(echo "$line" | sed 's/^CONSOLIDATE:[[:space:]]*//')
621
-
622
- # Parse matching: original_names -> new_name | description: desc | value: val
623
- names_part=$(echo "$cons_data" | cut -d'-' -f1 | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
624
- rest_part=$(echo "$cons_data" | cut -d'>' -f2-)
625
- new_name=$(echo "$rest_part" | cut -d'|' -f1 | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
626
-
627
- desc_part=$(echo "$rest_part" | grep -oE "description:[[:space:]]*[^|]+" | sed 's/description:[[:space:]]*//')
628
- val_part=$(echo "$rest_part" | grep -oE "value:[[:space:]]*.+" | sed 's/value:[[:space:]]*//')
629
-
630
- if [ -z "$new_name" ] || [ -z "$names_part" ]; then continue; fi
631
-
632
- if [ "$DRY_RUN" -eq 1 ]; then
633
- eagle_info " Would consolidate: $names_part → $new_name"
700
+ consolidation_count=$(printf '%s' "$parsed_consolidations" | jq 'length' 2>/dev/null || echo 0)
701
+ if [ "$consolidation_count" -gt 0 ]; then
702
+ cons_count=0
703
+ consolidation_index=0
704
+ while [ "$consolidation_index" -lt "$consolidation_count" ]; do
705
+ consolidation_item=$(printf '%s' "$parsed_consolidations" | jq -c ".[$consolidation_index]")
706
+ consolidation_index=$((consolidation_index + 1))
707
+ new_name=$(printf '%s' "$consolidation_item" | jq -r '.new_name // empty')
708
+ val_part=$(printf '%s' "$consolidation_item" | jq -r '.value // empty')
709
+ [ -n "$new_name" ] || continue
710
+
711
+ eagle_graph_add_node "$project" "memory" "$new_name" "$val_part" ""
712
+ new_node_id=$(eagle_graph_get_node_id "$project" "memory" "$new_name")
713
+
714
+ old_count=$(printf '%s' "$consolidation_item" | jq '.source_names | length' 2>/dev/null || echo 0)
715
+ old_index=0
716
+ while [ "$old_index" -lt "$old_count" ]; do
717
+ old_n=$(printf '%s' "$consolidation_item" | jq -r ".source_names[$old_index] // empty")
718
+ old_index=$((old_index + 1))
719
+ [ -n "$old_n" ] || continue
720
+ old_node_id=$(eagle_graph_get_node_id "$project" "memory" "$old_n")
721
+ if [ -n "$old_node_id" ] && [ -n "$new_node_id" ]; then
722
+ eagle_graph_add_edge "$project" "$new_node_id" "$old_node_id" "supersedes" 1.0
634
723
  else
635
- # 1. Add new consolidated memory node
636
- eagle_graph_add_node "$project" "memory" "$new_name" "$val_part" ""
637
- new_node_id=$(eagle_graph_get_node_id "$project" "memory" "$new_name")
638
-
639
- # 2. Wire supersedes edges from new node to old nodes, and mark old nodes as inactive/superseded
640
- IFS=',' read -ra name_arr <<< "$names_part"
641
- for old_n in "${name_arr[@]}"; do
642
- old_n=$(echo "$old_n" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
643
- if [ -z "$old_n" ]; then continue; fi
644
- old_node_id=$(eagle_graph_get_node_id "$project" "memory" "$old_n")
645
- if [ -n "$old_node_id" ] && [ -n "$new_node_id" ]; then
646
- eagle_graph_add_edge "$project" "$new_node_id" "$old_node_id" "supersedes" 1.0
647
- fi
648
- done
649
- cons_count=$((cons_count + 1))
724
+ eagle_log "WARN" "Unable to wire supersedes edge: source='$old_n' consolidated='$new_name'"
650
725
  fi
651
- ;;
652
- esac
653
- done <<< "$consolidation_result"
654
- eagle_ok "Consolidated $cons_count sets of overlapping agent memories"
655
- else
656
- eagle_ok "Agent memories are fully consolidated and up to date"
726
+ done
727
+ cons_count=$((cons_count + 1))
728
+ done
729
+ eagle_ok "Consolidated $cons_count sets of overlapping agent memories"
730
+ else
731
+ eagle_ok "Agent memories are fully consolidated and up to date"
732
+ fi
657
733
  fi
658
734
  else
659
735
  eagle_dim " No active agent memories to consolidate"
package/scripts/health.sh CHANGED
@@ -136,14 +136,11 @@ max_score=$((max_score + 15))
136
136
 
137
137
  provider=$(eagle_config_get "provider" "type" "none")
138
138
  if [ "$provider" != "none" ]; then
139
- if [ "$provider" = "agent_cli" ]; then
140
- model=$(_eagle_agent_cli_target)
141
- else
142
- model=$(eagle_config_get "$provider" "model" "default")
143
- fi
144
- eagle_ok "Provider: ${provider} (${model})"
139
+ provider_chain=$(eagle_llm_provider_label)
140
+ eagle_ok "Provider: ${provider_chain}"
145
141
  score=$((score + 15))
146
142
  else
143
+ provider_chain="none"
147
144
  eagle_fail "No LLM provider — curator and enrichment disabled"
148
145
  issues+=("Configure a provider: eagle-mem config init")
149
146
  fi
@@ -321,6 +318,7 @@ if [ "$JSON_OUT" -eq 1 ]; then
321
318
  --argjson enriched_summaries "${enriched_summaries:-0}" \
322
319
  --argjson features "${feature_count:-0}" \
323
320
  --arg provider "$provider" \
321
+ --arg provider_chain "$provider_chain" \
324
322
  --arg token_guard_rtk "$rtk_mode" \
325
323
  --arg token_guard_raw_bash "$raw_bash_mode" \
326
324
  --arg rtk_bin "${rtk_bin:-}" \
@@ -345,7 +343,7 @@ if [ "$JSON_OUT" -eq 1 ]; then
345
343
  '{project:$project, score:$score, max:$max_score, pct:$pct, grade:$grade,
346
344
  capture:{sessions:$total_sessions, summaries:$total_summaries, heuristic:$heuristic_summaries},
347
345
  enrichment:$enriched_summaries,
348
- features:$features, provider:$provider,
346
+ features:$features, provider:$provider, provider_chain:$provider_chain,
349
347
  token_guard:{rtk:$token_guard_rtk, raw_bash:$token_guard_raw_bash, rtk_bin:$rtk_bin},
350
348
  orchestration:{
351
349
  route:$orchestration_route,
package/scripts/help.sh CHANGED
@@ -23,6 +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
27
  echo -e " ${CYAN}updates${RESET} Auto-update status and policy"
27
28
  echo -e " ${CYAN}overview${RESET} Build or view project overview"
28
29
  echo -e " ${CYAN}session${RESET} Save a manual session summary"