eagle-mem 4.9.7 → 4.9.8

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.
@@ -37,6 +37,12 @@ project=$(eagle_project_from_hook_input "$input")
37
37
  [ -z "$project" ] && exit 0
38
38
 
39
39
  p_esc=$(eagle_sql_escape "$project")
40
+ recall_scope=$(eagle_recall_project_scope_from_cwd "$cwd" "$project")
41
+ [ -z "$recall_scope" ] && recall_scope="$project"
42
+ recall_scope_label=$(eagle_project_scope_label "$recall_scope")
43
+ recall_project_filter=$(eagle_sql_project_scope_condition "project" "$recall_scope")
44
+ recall_memory_filter=$(eagle_sql_project_scope_condition "m.project" "$recall_scope")
45
+ recall_lane_filter=$(eagle_sql_project_scope_condition "l.project" "$recall_scope")
40
46
 
41
47
  eagle_log "INFO" "SessionStart: session=$session_id project=$project source=$source_type agent=$agent"
42
48
 
@@ -96,7 +102,7 @@ while IFS='|' read -r key val; do
96
102
  last_active) stat_last_active="$val" ;;
97
103
  last_summary) stat_last_summary="$val" ;;
98
104
  esac
99
- done <<< "$(eagle_get_project_stats "$project")"
105
+ done <<< "$(eagle_get_project_stats "$recall_scope")"
100
106
 
101
107
  # ─── Build compressed banner (elide zero-value lines) ────
102
108
 
@@ -119,6 +125,10 @@ eagle_banner="======================================
119
125
  Project | $project
120
126
  Agent | $agent_label
121
127
  Sessions | $stat_sessions ($stat_with_summaries with summaries)"
128
+ if [ "$recall_scope_label" != "$project" ]; then
129
+ eagle_banner+="
130
+ Recall Scope | $recall_scope_label"
131
+ fi
122
132
  if [ "$stat_sessions_codex" -gt 0 ] || [ "$stat_sessions_claude" -gt 0 ]; then
123
133
  eagle_banner+="
124
134
  Sources | Claude $stat_sessions_claude, Codex $stat_sessions_codex"
@@ -137,12 +147,18 @@ eagle_banner+="
137
147
  ======================================"
138
148
 
139
149
  context="$eagle_banner
150
+ Eagle Mem recalls: use the project memory below before making design or implementation claims. If it affects your answer, mention the recall in one short attribution line.
140
151
  "
141
152
 
142
153
  if [ "$codex_compact" -eq 1 ]; then
143
154
  codex_context="Eagle Mem Recall Ready
144
155
  Project: $project | Agent: $agent_label
145
156
  Memory: $stat_sessions sessions"
157
+ if [ "$recall_scope_label" != "$project" ]; then
158
+ codex_context="Eagle Mem Recall Ready
159
+ Project: $project | Recall: $recall_scope_label | Agent: $agent_label
160
+ Memory: $stat_sessions sessions"
161
+ fi
146
162
  [ "$stat_with_summaries" -gt 0 ] && codex_context+=", $stat_with_summaries summarized"
147
163
  [ "$stat_memories" -gt 0 ] && codex_context+=", $stat_memories memories"
148
164
  [ "$stat_chunks" -gt 0 ] && codex_context+=", $stat_chunks code chunks"
@@ -172,7 +188,7 @@ Project brief: no overview yet; background scan is running.
172
188
  "
173
189
  fi
174
190
 
175
- recent=$(eagle_get_recent_summaries "$project" 1)
191
+ recent=$(eagle_get_recent_summaries "$recall_scope" 1)
176
192
  if [ -n "$recent" ]; then
177
193
  codex_context+="
178
194
  Relevant now:
@@ -194,16 +210,18 @@ Relevant now:
194
210
  memories=$(eagle_db "SELECT memory_name, memory_type, description, origin_agent,
195
211
  CAST(julianday('now') - julianday(updated_at) AS INTEGER) as days_ago
196
212
  FROM agent_memories
197
- WHERE project = '$p_esc'
213
+ WHERE $recall_project_filter
214
+ AND memory_name NOT IN ('Codex Memory Registry', 'Codex Memory Summary')
198
215
  ORDER BY
199
216
  CASE
200
217
  WHEN lower(memory_name) LIKE 'current status%' THEN 4
201
- WHEN memory_type IN ('feedback', 'user', 'reference') THEN 0
202
- WHEN memory_type = 'project' THEN 1
203
- ELSE 2
218
+ WHEN memory_type = 'project' THEN 0
219
+ WHEN memory_type = 'feedback' THEN 1
220
+ WHEN memory_type IN ('user', 'reference') THEN 2
221
+ ELSE 3
204
222
  END,
205
223
  updated_at DESC
206
- LIMIT 2;")
224
+ LIMIT 4;")
207
225
  if [ -n "$memories" ]; then
208
226
  [ -z "$recent" ] && codex_context+="
209
227
  Relevant now:
@@ -225,7 +243,7 @@ Relevant now:
225
243
  fi
226
244
 
227
245
  synced_tasks=$(eagle_db "SELECT subject, status, blocked_by, origin_agent FROM agent_tasks
228
- WHERE project = '$p_esc'
246
+ WHERE $recall_project_filter
229
247
  AND status IN ('in_progress', 'pending')
230
248
  AND source_session_id != 'orchestration'
231
249
  AND updated_at > strftime('%Y-%m-%dT%H:%M:%fZ', 'now', '-7 days')
@@ -234,7 +252,7 @@ Relevant now:
234
252
  orchestration_lanes=$(eagle_db "SELECT o.name, l.lane_key, l.title, l.agent, l.status
235
253
  FROM orchestration_lanes l
236
254
  JOIN orchestrations o ON o.id = l.orchestration_id
237
- WHERE l.project = '$p_esc'
255
+ WHERE $recall_lane_filter
238
256
  AND o.status = 'active'
239
257
  AND l.status IN ('in_progress', 'pending', 'blocked')
240
258
  ORDER BY CASE l.status WHEN 'in_progress' THEN 0 WHEN 'blocked' THEN 1 ELSE 2 END, l.updated_at DESC
@@ -361,7 +379,7 @@ else
361
379
  _summary_limit=2
362
380
  fi
363
381
 
364
- recent=$(eagle_get_recent_summaries "$project" "$_summary_limit")
382
+ recent=$(eagle_get_recent_summaries "$recall_scope" "$_summary_limit")
365
383
 
366
384
  if [ -n "$recent" ]; then
367
385
  context+="
@@ -410,18 +428,20 @@ fi
410
428
 
411
429
  # ─── Memories (skip if none) ─────────────────────────────
412
430
 
413
- memory_limit=3
414
- [ "$codex_compact" -eq 1 ] && memory_limit=2
431
+ memory_limit=5
432
+ [ "$codex_compact" -eq 1 ] && memory_limit=3
415
433
  memories=$(eagle_db "SELECT memory_name, memory_type, description, file_path, updated_at, origin_agent,
416
434
  CAST(julianday('now') - julianday(updated_at) AS INTEGER) as days_ago
417
435
  FROM agent_memories
418
- WHERE project = '$p_esc'
436
+ WHERE $recall_project_filter
437
+ AND memory_name NOT IN ('Codex Memory Registry', 'Codex Memory Summary')
419
438
  ORDER BY
420
439
  CASE
421
440
  WHEN lower(memory_name) LIKE 'current status%' THEN 4
422
- WHEN memory_type IN ('feedback', 'user', 'reference') THEN 0
423
- WHEN memory_type = 'project' THEN 1
424
- ELSE 2
441
+ WHEN memory_type = 'project' THEN 0
442
+ WHEN memory_type = 'feedback' THEN 1
443
+ WHEN memory_type IN ('user', 'reference') THEN 2
444
+ ELSE 3
425
445
  END,
426
446
  updated_at DESC
427
447
  LIMIT $memory_limit;")
@@ -450,7 +470,7 @@ fi
450
470
 
451
471
  # ─── Plans (skip if none) ────────────────────────────────
452
472
 
453
- plans=$(eagle_list_agent_plans "$project" 3)
473
+ plans=$(eagle_list_agent_plans "$recall_scope" 3)
454
474
  if [ -n "$plans" ]; then
455
475
  context+="
456
476
  === Eagle Mem: Plans ===
@@ -468,7 +488,7 @@ fi
468
488
  task_limit=5
469
489
  [ "$codex_compact" -eq 1 ] && task_limit=3
470
490
  synced_tasks=$(eagle_db "SELECT subject, status, blocked_by, origin_agent FROM agent_tasks
471
- WHERE project = '$p_esc'
491
+ WHERE $recall_project_filter
472
492
  AND status IN ('in_progress', 'pending')
473
493
  AND source_session_id != 'orchestration'
474
494
  AND updated_at > strftime('%Y-%m-%dT%H:%M:%fZ', 'now', '-7 days')
@@ -501,7 +521,7 @@ orchestration_lanes=$(eagle_db "SELECT o.name, o.goal, l.lane_key, l.title,
501
521
  l.agent, l.status, l.validation, l.worktree_path, l.notes
502
522
  FROM orchestration_lanes l
503
523
  JOIN orchestrations o ON o.id = l.orchestration_id
504
- WHERE l.project = '$p_esc'
524
+ WHERE $recall_lane_filter
505
525
  AND o.status = 'active'
506
526
  AND l.status IN ('in_progress', 'pending', 'blocked')
507
527
  ORDER BY
@@ -26,6 +26,8 @@ agent=$(eagle_agent_source_from_json "$input")
26
26
  [ -z "$user_prompt" ] && exit 0
27
27
 
28
28
  project=$(eagle_project_from_hook_input "$input")
29
+ recall_scope=$(eagle_recall_project_scope_from_cwd "$cwd" "$project")
30
+ [ -z "$recall_scope" ] && recall_scope="$project"
29
31
  codex_compact=0
30
32
  [ "$agent" = "codex" ] && codex_compact=1
31
33
 
@@ -93,8 +95,10 @@ fts_query=$(echo "$user_prompt" | tr -cs '[:alnum:]' ' ' | tr '[:upper:]' '[:low
93
95
  split("the a an is are was were be been being have has had do does did will would shall should may might can could of in to for on with at by from", sw, " ")
94
96
  for (i in sw) stop[sw[i]]=1
95
97
  n=0
98
+ split("ad ai ui ux db", keep, " ")
99
+ for (i in keep) short_keep[keep[i]]=1
96
100
  for (i=1; i<=NF && n<6; i++) {
97
- if (length($i) > 2 && !($i in stop)) {
101
+ if ((length($i) > 2 || ($i in short_keep)) && !($i in stop)) {
98
102
  printf "%s%s", (n>0?" OR ":""), $i; n++
99
103
  }
100
104
  }
@@ -137,15 +141,21 @@ if [ "$codex_compact" -eq 1 ]; then
137
141
  code_limit=2
138
142
  fi
139
143
 
140
- results=$(eagle_search_summaries "$fts_query" "$project" "$summary_limit")
144
+ memory_limit=3
145
+ [ "$codex_compact" -eq 1 ] && memory_limit=2
141
146
 
142
- if [ -n "$results" ]; then
147
+ results=$(eagle_search_summaries "$fts_query" "$recall_scope" "$summary_limit")
148
+ memory_results=$(eagle_search_agent_memories "$fts_query" "$recall_scope" "$memory_limit" 2>/dev/null || true)
149
+
150
+ if [ -n "$results" ] || [ -n "$memory_results" ]; then
143
151
  if [ "$codex_compact" -eq 1 ]; then
144
152
  context+="
145
153
  Eagle Mem recalls:
146
154
  "
147
155
  else
148
- context+="=== Eagle Mem: Relevant Recall ===
156
+ context+="Eagle Mem recalls: apply these retrieved project facts before answering. If they are relevant to the user's prompt, start with one short \"Eagle Mem recalls:\" attribution line.
157
+
158
+ === Eagle Mem: Relevant Recall ===
149
159
  "
150
160
  fi
151
161
  while IFS='|' read -r req completed learned _next_steps created_at _proj decisions gotchas key_files summary_agent; do
@@ -186,6 +196,30 @@ Eagle Mem recalls:
186
196
  "
187
197
  fi
188
198
  done <<< "$results"
199
+
200
+ while IFS='|' read -r mname mtype mdesc msnippet _mfile _mupdated morigin; do
201
+ [ -z "$mname" ] && continue
202
+ case "$mname" in
203
+ "Codex Memory Registry"|"Codex Memory Summary") continue ;;
204
+ esac
205
+ origin_label=$(eagle_agent_label "$morigin")
206
+ if [ "$codex_compact" -eq 1 ]; then
207
+ mdesc=$(eagle_trim_text "$mdesc" 120)
208
+ context+="- Memory [$mtype][$origin_label]: $mname"
209
+ [ -n "$mdesc" ] && context+=" — $mdesc"
210
+ context+="
211
+ "
212
+ else
213
+ mdesc=$(eagle_trim_text "$mdesc" 180)
214
+ msnippet=$(eagle_trim_text "$msnippet" 220)
215
+ context+="[Memory][$origin_label][$mtype] $mname"
216
+ [ -n "$mdesc" ] && context+=" — $mdesc"
217
+ [ -n "$msnippet" ] && context+="
218
+ Snippet: $msnippet"
219
+ context+="
220
+ "
221
+ fi
222
+ done <<< "$memory_results"
189
223
  fi
190
224
 
191
225
  # Search indexed code chunks (if any exist for this project)
package/lib/common.sh CHANGED
@@ -362,6 +362,121 @@ eagle_project_from_existing_ancestor() {
362
362
  return 1
363
363
  }
364
364
 
365
+ eagle_project_has_recall_rows() {
366
+ local project="${1:-}"
367
+ [ -n "$project" ] || return 1
368
+
369
+ eagle_project_has_table_row "agent_memories" "$project" \
370
+ || eagle_project_has_table_row "agent_plans" "$project" \
371
+ || eagle_project_has_table_row "agent_tasks" "$project" \
372
+ || eagle_project_has_table_row "summaries" "$project"
373
+ }
374
+
375
+ eagle_recall_ancestor_project_from_cwd() {
376
+ local path="${1:-$(pwd)}"
377
+ local current_project="${2:-}"
378
+ local current key
379
+
380
+ current=$(eagle_normalize_project_path "$path")
381
+ eagle_is_ephemeral_project_path "$current" && return 1
382
+
383
+ [ -d "$current" ] || current=$(dirname "$current")
384
+ current=$(dirname "$current")
385
+
386
+ while [ -n "$current" ] && [ "$current" != "/" ]; do
387
+ if [ "$current" = "$HOME" ]; then
388
+ break
389
+ fi
390
+
391
+ key=$(eagle_project_key_from_target_dir "$current")
392
+ if [ -n "$key" ] && [ "$key" != "$current_project" ]; then
393
+ if eagle_project_has_recall_rows "$key"; then
394
+ printf '%s\n' "$key"
395
+ return 0
396
+ fi
397
+ fi
398
+
399
+ current=$(dirname "$current")
400
+ done
401
+
402
+ return 1
403
+ }
404
+
405
+ eagle_recall_project_scope_from_cwd() {
406
+ local path="${1:-$(pwd)}"
407
+ local project="${2:-}"
408
+ local ancestor
409
+
410
+ [ -z "$project" ] && project=$(eagle_project_from_cwd "$path")
411
+ if ancestor=$(eagle_recall_ancestor_project_from_cwd "$path" "$project"); then
412
+ if [ -n "$project" ] && [ "$ancestor" != "$project" ]; then
413
+ printf '%s|%s\n' "$project" "$ancestor"
414
+ return 0
415
+ fi
416
+ printf '%s\n' "$ancestor"
417
+ return 0
418
+ fi
419
+
420
+ printf '%s\n' "$project"
421
+ }
422
+
423
+ eagle_sql_project_scope_condition() {
424
+ local column="${1:-project}"
425
+ local scope="${2:-}"
426
+ local values="" count=0 item escaped
427
+
428
+ while IFS= read -r item; do
429
+ [ -n "$item" ] || continue
430
+ escaped=$(eagle_sql_escape "$item")
431
+ values="${values},'${escaped}'"
432
+ count=$((count + 1))
433
+ done <<EOF
434
+ $(printf '%s' "$scope" | tr '|' '\n')
435
+ EOF
436
+
437
+ if [ "$count" -eq 0 ]; then
438
+ printf '1 = 0\n'
439
+ elif [ "$count" -eq 1 ]; then
440
+ printf "%s = %s\n" "$column" "${values#,}"
441
+ else
442
+ printf "%s IN (%s)\n" "$column" "${values#,}"
443
+ fi
444
+ }
445
+
446
+ eagle_project_scope_label() {
447
+ local scope="${1:-}"
448
+ printf '%s\n' "$scope" | tr '|' ','
449
+ }
450
+
451
+ eagle_project_scope_contains() {
452
+ local scope="${1:-}"
453
+ local target="${2:-}"
454
+ local item
455
+ [ -n "$target" ] || return 1
456
+
457
+ while IFS= read -r item; do
458
+ [ -z "$item" ] && continue
459
+ [ "$item" = "$target" ] && return 0
460
+ done <<EOF
461
+ $(printf '%s' "$scope" | tr '|' '\n')
462
+ EOF
463
+
464
+ return 1
465
+ }
466
+
467
+ eagle_claude_project_dir_for_key() {
468
+ local project="${1:-}"
469
+ [ -n "$project" ] || return 1
470
+
471
+ local abs slug
472
+ case "$project" in
473
+ /*) abs="$project" ;;
474
+ *) abs="$HOME/$project" ;;
475
+ esac
476
+ slug=$(printf '%s' "$abs" | sed -E 's#[^[:alnum:].]+#-#g')
477
+ printf '%s/%s\n' "$EAGLE_CLAUDE_PROJECTS_DIR" "$slug"
478
+ }
479
+
365
480
  eagle_project_from_workspace_path() {
366
481
  if [ -n "${EAGLE_MEM_PROJECT:-}" ]; then
367
482
  printf '%s\n' "$EAGLE_MEM_PROJECT"
@@ -728,20 +843,15 @@ eagle_emit_context_for_agent() {
728
843
 
729
844
  [ -z "$context" ] && return 0
730
845
 
731
- if [ "$agent" = "codex" ]; then
732
- jq -cn \
733
- --arg event "$hook_event" \
734
- --arg context "$context" \
735
- '{
736
- hookSpecificOutput: {
737
- hookEventName: $event,
738
- additionalContext: $context
739
- }
740
- }'
741
- return 0
742
- fi
743
-
744
- printf '%s\n' "$context"
846
+ jq -cn \
847
+ --arg event "$hook_event" \
848
+ --arg context "$context" \
849
+ '{
850
+ hookSpecificOutput: {
851
+ hookEventName: $event,
852
+ additionalContext: $context
853
+ }
854
+ }'
745
855
  }
746
856
 
747
857
  eagle_config_get_light() {
package/lib/db-mirrors.sh CHANGED
@@ -103,8 +103,7 @@ eagle_search_agent_memories() {
103
103
 
104
104
  local where_clause=""
105
105
  if [ -n "$project" ]; then
106
- project=$(eagle_sql_escape "$project")
107
- where_clause="AND m.project = '$project'"
106
+ where_clause="AND $(eagle_sql_project_scope_condition "m.project" "$project")"
108
107
  fi
109
108
 
110
109
  eagle_db "SELECT m.memory_name, m.memory_type, m.description,
@@ -124,8 +123,7 @@ eagle_list_agent_memories() {
124
123
 
125
124
  local where_clause=""
126
125
  if [ -n "$project" ]; then
127
- project=$(eagle_sql_escape "$project")
128
- where_clause="WHERE project = '$project'"
126
+ where_clause="WHERE $(eagle_sql_project_scope_condition "project" "$project")"
129
127
  fi
130
128
 
131
129
  eagle_db "SELECT memory_name, memory_type, description, file_path, updated_at, origin_agent
@@ -198,8 +196,7 @@ eagle_search_agent_plans() {
198
196
 
199
197
  local where_clause=""
200
198
  if [ -n "$project" ]; then
201
- project=$(eagle_sql_escape "$project")
202
- where_clause="AND p.project = '$project'"
199
+ where_clause="AND $(eagle_sql_project_scope_condition "p.project" "$project")"
203
200
  fi
204
201
 
205
202
  eagle_db "SELECT p.title, p.project,
@@ -219,8 +216,7 @@ eagle_list_agent_plans() {
219
216
 
220
217
  local where_clause=""
221
218
  if [ -n "$project" ]; then
222
- project=$(eagle_sql_escape "$project")
223
- where_clause="WHERE project = '$project'"
219
+ where_clause="WHERE $(eagle_sql_project_scope_condition "project" "$project")"
224
220
  fi
225
221
 
226
222
  eagle_db "SELECT title, project, file_path, updated_at, origin_agent
@@ -303,8 +299,7 @@ eagle_list_agent_tasks() {
303
299
 
304
300
  local where_clause=""
305
301
  if [ -n "$project" ]; then
306
- project=$(eagle_sql_escape "$project")
307
- where_clause="WHERE project = '$project'"
302
+ where_clause="WHERE $(eagle_sql_project_scope_condition "project" "$project")"
308
303
  fi
309
304
 
310
305
  eagle_db "SELECT subject, status, source_session_id, source_task_id, updated_at, origin_agent
@@ -326,8 +321,7 @@ eagle_search_agent_tasks() {
326
321
 
327
322
  local where_clause=""
328
323
  if [ -n "$project" ]; then
329
- project=$(eagle_sql_escape "$project")
330
- where_clause="AND t.project = '$project'"
324
+ where_clause="AND $(eagle_sql_project_scope_condition "t.project" "$project")"
331
325
  fi
332
326
 
333
327
  eagle_db "SELECT t.subject, t.status,
@@ -167,24 +167,31 @@ eagle_abandon_stale_sessions() {
167
167
  }
168
168
 
169
169
  eagle_get_project_stats() {
170
- local project; project=$(eagle_sql_escape "$1")
170
+ local project_scope="${1:-}"
171
+ local session_filter memory_filter plan_filter task_filter chunk_filter observation_filter
172
+ session_filter=$(eagle_sql_project_scope_condition "project" "$project_scope")
173
+ memory_filter=$(eagle_sql_project_scope_condition "project" "$project_scope")
174
+ plan_filter=$(eagle_sql_project_scope_condition "project" "$project_scope")
175
+ task_filter=$(eagle_sql_project_scope_condition "project" "$project_scope")
176
+ chunk_filter=$(eagle_sql_project_scope_condition "project" "$project_scope")
177
+ observation_filter=$(eagle_sql_project_scope_condition "project" "$project_scope")
171
178
  eagle_db_pipe <<SQL
172
- SELECT 'sessions|' || COUNT(*) FROM sessions WHERE project = '$project';
173
- SELECT 'sessions_claude|' || COUNT(*) FROM sessions WHERE project = '$project' AND agent = 'claude-code';
174
- SELECT 'sessions_codex|' || COUNT(*) FROM sessions WHERE project = '$project' AND agent = 'codex';
175
- SELECT 'summaries|' || COUNT(*) FROM summaries WHERE project = '$project';
176
- SELECT 'with_summaries|' || COUNT(*) FROM summaries WHERE project = '$project' AND request IS NOT NULL AND request != '';
177
- SELECT 'memories|' || COUNT(*) FROM agent_memories WHERE project = '$project';
178
- SELECT 'plans|' || COUNT(*) FROM agent_plans WHERE project = '$project';
179
- SELECT 'tasks_pending|' || COUNT(*) FROM agent_tasks WHERE project = '$project' AND status = 'pending';
180
- SELECT 'tasks_progress|' || COUNT(*) FROM agent_tasks WHERE project = '$project' AND status = 'in_progress';
181
- SELECT 'tasks_done|' || COUNT(*) FROM agent_tasks WHERE project = '$project' AND status = 'completed';
182
- SELECT 'chunks|' || COUNT(*) FROM code_chunks WHERE project = '$project';
183
- SELECT 'observations|' || COUNT(*) FROM observations WHERE session_id IN (SELECT id FROM sessions WHERE project = '$project');
184
- SELECT 'last_active|' || COALESCE(MAX(date(COALESCE(last_activity_at, started_at))), 'never') FROM sessions WHERE project = '$project';
179
+ SELECT 'sessions|' || COUNT(*) FROM sessions WHERE $session_filter;
180
+ SELECT 'sessions_claude|' || COUNT(*) FROM sessions WHERE $session_filter AND agent = 'claude-code';
181
+ SELECT 'sessions_codex|' || COUNT(*) FROM sessions WHERE $session_filter AND agent = 'codex';
182
+ SELECT 'summaries|' || COUNT(*) FROM summaries WHERE $session_filter;
183
+ SELECT 'with_summaries|' || COUNT(*) FROM summaries WHERE $session_filter AND request IS NOT NULL AND request != '';
184
+ SELECT 'memories|' || COUNT(*) FROM agent_memories WHERE $memory_filter;
185
+ SELECT 'plans|' || COUNT(*) FROM agent_plans WHERE $plan_filter;
186
+ SELECT 'tasks_pending|' || COUNT(*) FROM agent_tasks WHERE $task_filter AND status = 'pending';
187
+ SELECT 'tasks_progress|' || COUNT(*) FROM agent_tasks WHERE $task_filter AND status = 'in_progress';
188
+ SELECT 'tasks_done|' || COUNT(*) FROM agent_tasks WHERE $task_filter AND status = 'completed';
189
+ SELECT 'chunks|' || COUNT(*) FROM code_chunks WHERE $chunk_filter;
190
+ SELECT 'observations|' || COUNT(*) FROM observations WHERE $observation_filter;
191
+ SELECT 'last_active|' || COALESCE(MAX(date(COALESCE(last_activity_at, started_at))), 'never') FROM sessions WHERE $session_filter;
185
192
  SELECT 'last_summary|' || COALESCE((SELECT substr(request, 1, 60)
186
193
  FROM summaries
187
- WHERE project = '$project'
194
+ WHERE $session_filter
188
195
  AND COALESCE(request, '') NOT LIKE '# AGENTS.md instructions%'
189
196
  AND COALESCE(request, '') NOT LIKE '<environment_context>%'
190
197
  ORDER BY created_at DESC
@@ -57,12 +57,16 @@ SQL
57
57
  }
58
58
 
59
59
  eagle_get_recent_summaries() {
60
- local project; project=$(eagle_sql_escape "$1")
60
+ local project_scope="${1:-}"
61
61
  local limit; limit=$(eagle_sql_int "${2:-5}")
62
+ local project_filter="1 = 1"
63
+ if [ -n "$project_scope" ]; then
64
+ project_filter=$(eagle_sql_project_scope_condition "s.project" "$project_scope")
65
+ fi
62
66
 
63
67
  eagle_db "SELECT s.request, s.completed, s.learned, s.next_steps, s.created_at, s.decisions, s.gotchas, s.key_files, s.agent
64
68
  FROM summaries s
65
- WHERE s.project = '$project'
69
+ WHERE $project_filter
66
70
  AND COALESCE(s.request, '') NOT LIKE '%<local-command-caveat>%'
67
71
  AND COALESCE(s.request, '') NOT LIKE '# AGENTS.md instructions%'
68
72
  AND COALESCE(s.request, '') NOT LIKE '<environment_context>%'
@@ -73,13 +77,12 @@ eagle_get_recent_summaries() {
73
77
  eagle_search_summaries() {
74
78
  local query; query=$(eagle_fts_sanitize "$1")
75
79
  query=$(eagle_sql_escape "$query")
76
- local project="${2:-}"
80
+ local project_scope="${2:-}"
77
81
  local limit; limit=$(eagle_sql_int "${3:-10}")
78
82
 
79
83
  local where_clause=""
80
- if [ -n "$project" ]; then
81
- project=$(eagle_sql_escape "$project")
82
- where_clause="AND s.project = '$project'"
84
+ if [ -n "$project_scope" ]; then
85
+ where_clause="AND $(eagle_sql_project_scope_condition "s.project" "$project_scope")"
83
86
  fi
84
87
 
85
88
  eagle_db "SELECT s.request, s.completed, s.learned, s.next_steps, s.created_at, s.project, s.decisions, s.gotchas, s.key_files, s.agent
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eagle-mem",
3
- "version": "4.9.7",
3
+ "version": "4.9.8",
4
4
  "description": "Shared memory, release guardrails, RTK token protection, and worker lanes for Claude Code and Codex",
5
5
  "bin": {
6
6
  "eagle-mem": "bin/eagle-mem"
@@ -24,6 +24,7 @@ case "${1:-}" in
24
24
  esac
25
25
 
26
26
  project=""
27
+ project_was_explicit=false
27
28
  limit=20
28
29
  query=""
29
30
  raw_output=false
@@ -76,7 +77,7 @@ esac
76
77
 
77
78
  while [ $# -gt 0 ]; do
78
79
  case "$1" in
79
- --project|-p) project="$2"; shift 2 ;;
80
+ --project|-p) project="$2"; project_was_explicit=true; shift 2 ;;
80
81
  --limit|-l) limit="$2"; shift 2 ;;
81
82
  --all|-a) cross_project=true; shift ;;
82
83
  --raw|--debug) raw_output=true; shift ;;
@@ -95,6 +96,9 @@ done
95
96
  if [ -z "$project" ] && [ "$cross_project" = false ]; then
96
97
  project=$(eagle_project_from_cwd "$(pwd)")
97
98
  fi
99
+ if [ "$cross_project" = false ] && [ "$project_was_explicit" = false ]; then
100
+ project=$(eagle_recall_project_scope_from_cwd "$(pwd)" "$project")
101
+ fi
98
102
 
99
103
  eagle_age_label() {
100
104
  local updated="${1:-}"
@@ -230,7 +234,7 @@ memories_show() {
230
234
  query_sql=$(eagle_sql_escape "$query")
231
235
  project_filter=""
232
236
  if [ -n "$project" ]; then
233
- project_filter="AND project = '$(eagle_sql_escape "$project")'"
237
+ project_filter="AND $(eagle_sql_project_scope_condition "project" "$project")"
234
238
  fi
235
239
 
236
240
  meta_json=$(eagle_db_json "SELECT memory_name, memory_type, description, content, file_path, updated_at, origin_session_id, origin_agent
@@ -370,7 +374,7 @@ plans_show() {
370
374
  query_sql=$(eagle_sql_escape "$query")
371
375
  project_filter=""
372
376
  if [ -n "$project" ]; then
373
- project_filter="AND project = '$(eagle_sql_escape "$project")'"
377
+ project_filter="AND $(eagle_sql_project_scope_condition "project" "$project")"
374
378
  fi
375
379
  meta_json=$(eagle_db_json "SELECT title, project, content, file_path, updated_at, origin_session_id, origin_agent
376
380
  FROM agent_plans
@@ -517,7 +521,7 @@ tasks_show() {
517
521
  query_sql=$(eagle_sql_escape "$query")
518
522
  project_filter=""
519
523
  if [ -n "$project" ]; then
520
- project_filter="AND project = '$(eagle_sql_escape "$project")'"
524
+ project_filter="AND $(eagle_sql_project_scope_condition "project" "$project")"
521
525
  fi
522
526
  meta_json=$(eagle_db_json "SELECT subject, status, description, active_form, source_session_id, source_task_id, file_path, updated_at, origin_agent
523
527
  FROM agent_tasks
@@ -575,6 +579,33 @@ tasks_show() {
575
579
  memories_sync() {
576
580
  eagle_header "Memory, Plan & Task Sync"
577
581
 
582
+ local lock_root="$EAGLE_MEM_DIR/locks"
583
+ local lock_dir="$lock_root/memories-sync.lock"
584
+ mkdir -p "$lock_root" 2>/dev/null || true
585
+ if mkdir "$lock_dir" 2>/dev/null; then
586
+ printf '%s\n' "$$" > "$lock_dir/pid" 2>/dev/null || true
587
+ _EAGLE_MEMORIES_SYNC_LOCK_DIR="$lock_dir"
588
+ trap 'rm -rf "${_EAGLE_MEMORIES_SYNC_LOCK_DIR:-}" 2>/dev/null || true' EXIT
589
+ else
590
+ local lock_pid=""
591
+ [ -f "$lock_dir/pid" ] && lock_pid=$(cat "$lock_dir/pid" 2>/dev/null | tr -cd '0-9')
592
+ if [ -n "$lock_pid" ] && kill -0 "$lock_pid" 2>/dev/null; then
593
+ eagle_info "Sync already running (pid $lock_pid). Skipping this duplicate run."
594
+ echo ""
595
+ eagle_footer "Sync skipped."
596
+ return 0
597
+ fi
598
+ rm -rf "$lock_dir" 2>/dev/null || true
599
+ if mkdir "$lock_dir" 2>/dev/null; then
600
+ printf '%s\n' "$$" > "$lock_dir/pid" 2>/dev/null || true
601
+ _EAGLE_MEMORIES_SYNC_LOCK_DIR="$lock_dir"
602
+ trap 'rm -rf "${_EAGLE_MEMORIES_SYNC_LOCK_DIR:-}" 2>/dev/null || true' EXIT
603
+ else
604
+ eagle_err "Could not acquire memories sync lock."
605
+ return 1
606
+ fi
607
+ fi
608
+
578
609
  # ─── Sync memories ───────────────────────────────────
579
610
  eagle_info "Scanning for agent memory files..."
580
611
  echo ""
@@ -583,15 +614,21 @@ memories_sync() {
583
614
  local mem_synced=0
584
615
  local mem_skipped=0
585
616
 
586
- if [ -d "$claude_mem_root" ]; then
587
- while IFS= read -r -d '' memfile; do
617
+ sync_one_memory_file() {
618
+ local memfile="$1"
619
+ [ -f "$memfile" ] || return 0
620
+
588
621
  local base
589
622
  base=$(basename "$memfile")
590
- [ "$base" = "MEMORY.md" ] && continue
623
+ [ "$base" = "MEMORY.md" ] && return 0
591
624
 
592
625
  local mem_project mem_project_dir existing_row existing_hash existing_project
593
626
  mem_project_dir="${memfile%/memory/*}"
594
627
  mem_project=$(eagle_project_from_claude_project_dir "$mem_project_dir" 2>/dev/null || true)
628
+ if [ "$cross_project" = false ] && [ -n "$project" ]; then
629
+ eagle_project_scope_contains "$project" "$mem_project" || return 0
630
+ fi
631
+
595
632
  existing_row=$(eagle_db "SELECT content_hash, project FROM agent_memories WHERE file_path = '$(eagle_sql_escape "$memfile")';")
596
633
  IFS='|' read -r existing_hash existing_project <<< "$existing_row"
597
634
  local new_hash
@@ -605,7 +642,25 @@ memories_sync() {
605
642
  eagle_capture_agent_memory "$memfile" "" "$mem_project" "claude-code"
606
643
  mem_synced=$((mem_synced + 1))
607
644
  eagle_ok "Memory: $base"
608
- done < <(find "$claude_mem_root" -path "*/memory/*.md" -print0 2>/dev/null)
645
+ }
646
+
647
+ if [ -d "$claude_mem_root" ]; then
648
+ if [ "$cross_project" = true ] || [ -z "$project" ]; then
649
+ while IFS= read -r -d '' memfile; do
650
+ sync_one_memory_file "$memfile"
651
+ done < <(find "$claude_mem_root" -path "*/memory/*.md" -print0 2>/dev/null)
652
+ else
653
+ while IFS= read -r scope_project; do
654
+ [ -z "$scope_project" ] && continue
655
+ scope_dir=$(eagle_claude_project_dir_for_key "$scope_project")
656
+ [ -d "$scope_dir/memory" ] || continue
657
+ while IFS= read -r -d '' memfile; do
658
+ sync_one_memory_file "$memfile"
659
+ done < <(find "$scope_dir/memory" -maxdepth 1 -type f -name "*.md" -print0 2>/dev/null)
660
+ done <<EOF
661
+ $(printf '%s' "$project" | tr '|' '\n')
662
+ EOF
663
+ fi
609
664
  fi
610
665
 
611
666
  local codex_mem_root="$EAGLE_CODEX_MEMORIES_DIR"
@@ -710,12 +765,16 @@ memories_sync() {
710
765
  # ─── Backfill project names ──────────────────────────
711
766
  eagle_info "Resolving project names from agent transcripts..."
712
767
 
713
- local backfilled
714
- backfilled=$(eagle_backfill_projects)
715
- if [ "${backfilled:-0}" -gt 0 ]; then
716
- eagle_ok "$backfilled rows updated with correct project names"
768
+ if [ "$cross_project" = true ]; then
769
+ local backfilled
770
+ backfilled=$(eagle_backfill_projects)
771
+ if [ "${backfilled:-0}" -gt 0 ]; then
772
+ eagle_ok "$backfilled rows updated with correct project names"
773
+ else
774
+ eagle_ok "All project names up to date"
775
+ fi
717
776
  else
718
- eagle_ok "All project names up to date"
777
+ eagle_ok "Scoped sync complete; use --all for global transcript backfill"
719
778
  fi
720
779
 
721
780
  eagle_footer "Sync complete."
package/scripts/search.sh CHANGED
@@ -43,6 +43,9 @@ show_help() {
43
43
 
44
44
  mode="keyword"
45
45
  project=""
46
+ project_was_explicit=false
47
+ project_scope=""
48
+ project_label=""
46
49
  limit=10
47
50
  session_id=""
48
51
  json_output=false
@@ -59,7 +62,7 @@ while [ $# -gt 0 ]; do
59
62
  --overview|-o) mode="overview"; shift ;;
60
63
  --memories|-m) mode="memories"; shift ;;
61
64
  --tasks) mode="tasks"; shift ;;
62
- --project|-p) project="$2"; shift 2 ;;
65
+ --project|-p) project="$2"; project_was_explicit=true; shift 2 ;;
63
66
  --limit|-n) limit="$2"; shift 2 ;;
64
67
  --all|-a) cross_project=true; shift ;;
65
68
  --json|-j) json_output=true; shift ;;
@@ -75,6 +78,12 @@ while [ $# -gt 0 ]; do
75
78
  done
76
79
 
77
80
  [ -z "$project" ] && project=$(eagle_project_from_cwd "$(pwd)")
81
+ project_scope="$project"
82
+ if [ "$cross_project" = false ] && [ "$project_was_explicit" = false ]; then
83
+ project_scope=$(eagle_recall_project_scope_from_cwd "$(pwd)" "$project")
84
+ fi
85
+ project_label=$(eagle_project_scope_label "$project_scope")
86
+ [ -z "$project_label" ] && project_label="$project"
78
87
  limit=$(eagle_sql_int "$limit")
79
88
  [ "$limit" -eq 0 ] && limit=10
80
89
 
@@ -88,11 +97,12 @@ search_keyword() {
88
97
  exit 1
89
98
  fi
90
99
  local q; q=$(eagle_sql_escape "$sanitized_q")
91
- local p; p=$(eagle_sql_escape "$project")
100
+ local project_condition
101
+ project_condition=$(eagle_sql_project_scope_condition "s.project" "$project_scope")
92
102
 
93
103
  local where_project=""
94
104
  if [ "$cross_project" = false ]; then
95
- where_project="AND s.project = '$p'"
105
+ where_project="AND $project_condition"
96
106
  fi
97
107
 
98
108
  if [ "$json_output" = true ]; then
@@ -173,12 +183,13 @@ search_keyword() {
173
183
  # ─── Timeline ────────────────────────────────────────────
174
184
 
175
185
  search_timeline() {
176
- local p; p=$(eagle_sql_escape "$project")
186
+ local project_condition
187
+ project_condition=$(eagle_sql_project_scope_condition "s.project" "$project_scope")
177
188
 
178
189
  if [ "$json_output" = true ]; then
179
190
  eagle_db_json "SELECT s.id, s.agent, s.request, s.completed, s.learned, s.next_steps, s.created_at
180
191
  FROM summaries s
181
- WHERE s.project = '$p'
192
+ WHERE $project_condition
182
193
  ORDER BY s.created_at DESC
183
194
  LIMIT $limit;"
184
195
  return
@@ -187,7 +198,7 @@ search_timeline() {
187
198
  local results
188
199
  results=$(eagle_db "SELECT s.id, s.agent, s.request, s.completed, s.learned, s.next_steps, s.created_at
189
200
  FROM summaries s
190
- WHERE s.project = '$p'
201
+ WHERE $project_condition
191
202
  ORDER BY s.created_at DESC
192
203
  LIMIT $limit;")
193
204
 
@@ -197,7 +208,7 @@ search_timeline() {
197
208
  fi
198
209
 
199
210
  echo ""
200
- echo -e " ${BOLD}Recent sessions${RESET} ${DIM}($project)${RESET}"
211
+ echo -e " ${BOLD}Recent sessions${RESET} ${DIM}($project_label)${RESET}"
201
212
  echo -e " ${DIM}─────────────────────────────────────${RESET}"
202
213
  echo ""
203
214
 
@@ -401,20 +412,25 @@ search_files() {
401
412
  # ─── Stats ────────────────────────────────────────────────
402
413
 
403
414
  search_stats() {
404
- local p; p=$(eagle_sql_escape "$project")
415
+ local sessions_condition summaries_condition observations_condition tasks_condition chunks_condition
416
+ sessions_condition=$(eagle_sql_project_scope_condition "project" "$project_scope")
417
+ summaries_condition=$(eagle_sql_project_scope_condition "project" "$project_scope")
418
+ observations_condition=$(eagle_sql_project_scope_condition "s.project" "$project_scope")
419
+ tasks_condition=$(eagle_sql_project_scope_condition "project" "$project_scope")
420
+ chunks_condition=$(eagle_sql_project_scope_condition "project" "$project_scope")
405
421
 
406
422
  local sessions sessions_claude sessions_codex summaries observations tasks
407
- sessions=$(eagle_db "SELECT COUNT(*) FROM sessions WHERE project = '$p';")
408
- sessions_claude=$(eagle_db "SELECT COUNT(*) FROM sessions WHERE project = '$p' AND agent = 'claude-code';")
409
- sessions_codex=$(eagle_db "SELECT COUNT(*) FROM sessions WHERE project = '$p' AND agent = 'codex';")
410
- summaries=$(eagle_db "SELECT COUNT(*) FROM summaries WHERE project = '$p';")
411
- observations=$(eagle_db "SELECT COUNT(*) FROM observations o JOIN sessions s ON s.id = o.session_id WHERE s.project = '$p';")
412
- tasks=$(eagle_db "SELECT COUNT(*) FROM agent_tasks WHERE project = '$p';")
423
+ sessions=$(eagle_db "SELECT COUNT(*) FROM sessions WHERE $sessions_condition;")
424
+ sessions_claude=$(eagle_db "SELECT COUNT(*) FROM sessions WHERE $sessions_condition AND agent = 'claude-code';")
425
+ sessions_codex=$(eagle_db "SELECT COUNT(*) FROM sessions WHERE $sessions_condition AND agent = 'codex';")
426
+ summaries=$(eagle_db "SELECT COUNT(*) FROM summaries WHERE $summaries_condition;")
427
+ observations=$(eagle_db "SELECT COUNT(*) FROM observations o JOIN sessions s ON s.id = o.session_id WHERE $observations_condition;")
428
+ tasks=$(eagle_db "SELECT COUNT(*) FROM agent_tasks WHERE $tasks_condition;")
413
429
  local chunks
414
- chunks=$(eagle_db "SELECT COUNT(*) FROM code_chunks WHERE project = '$p';")
430
+ chunks=$(eagle_db "SELECT COUNT(*) FROM code_chunks WHERE $chunks_condition;")
415
431
 
416
432
  if [ "$json_output" = true ]; then
417
- jq -nc --arg project "$project" \
433
+ jq -nc --arg project "$project_label" \
418
434
  --argjson sessions "${sessions:-0}" \
419
435
  --argjson sessions_claude "${sessions_claude:-0}" \
420
436
  --argjson sessions_codex "${sessions_codex:-0}" \
@@ -427,7 +443,7 @@ search_stats() {
427
443
  fi
428
444
 
429
445
  echo ""
430
- echo -e " ${BOLD}Stats${RESET} ${DIM}($project)${RESET}"
446
+ echo -e " ${BOLD}Stats${RESET} ${DIM}($project_label)${RESET}"
431
447
  echo -e " ${DIM}─────────────────────────────────────${RESET}"
432
448
  echo ""
433
449
  eagle_kv "Sessions:" "${sessions:-0}"
@@ -474,11 +490,13 @@ search_overview() {
474
490
  # ─── Memories ────────────────────────────────────────────
475
491
 
476
492
  search_memories() {
477
- local p; p=$(eagle_sql_escape "$project")
493
+ local project_condition memory_condition
494
+ project_condition=$(eagle_sql_project_scope_condition "project" "$project_scope")
495
+ memory_condition=$(eagle_sql_project_scope_condition "m.project" "$project_scope")
478
496
 
479
497
  local where_project=""
480
498
  if [ "$cross_project" = false ]; then
481
- where_project="WHERE project = '$p'"
499
+ where_project="WHERE $project_condition"
482
500
  fi
483
501
 
484
502
  if [ -n "$query" ]; then
@@ -491,7 +509,7 @@ search_memories() {
491
509
  local q; q=$(eagle_sql_escape "$sanitized_mq")
492
510
  local where_match="WHERE agent_memories_fts MATCH '$q'"
493
511
  if [ "$cross_project" = false ]; then
494
- where_match="$where_match AND m.project = '$p'"
512
+ where_match="$where_match AND $memory_condition"
495
513
  fi
496
514
 
497
515
  if [ "$json_output" = true ]; then
@@ -570,7 +588,7 @@ search_memories() {
570
588
  fi
571
589
 
572
590
  echo ""
573
- echo -e " ${BOLD}Memories${RESET} ${DIM}($project)${RESET}"
591
+ echo -e " ${BOLD}Memories${RESET} ${DIM}($project_label)${RESET}"
574
592
  echo -e " ${DIM}─────────────────────────────────────${RESET}"
575
593
  echo ""
576
594
 
@@ -585,13 +603,15 @@ search_memories() {
585
603
  # ─── Tasks ───────────────────────────────────────────────
586
604
 
587
605
  search_tasks() {
588
- local p; p=$(eagle_sql_escape "$project")
606
+ local task_condition bare_task_condition
607
+ task_condition=$(eagle_sql_project_scope_condition "t.project" "$project_scope")
608
+ bare_task_condition=$(eagle_sql_project_scope_condition "project" "$project_scope")
589
609
 
590
610
  local where_project=""
591
611
  local where_task_project=""
592
612
  if [ "$cross_project" = false ]; then
593
- where_project="AND project = '$p'"
594
- where_task_project="AND t.project = '$p'"
613
+ where_project="AND $bare_task_condition"
614
+ where_task_project="AND $task_condition"
595
615
  fi
596
616
 
597
617
  if [ -n "$query" ]; then
@@ -662,7 +682,7 @@ search_tasks() {
662
682
  LIMIT $limit;")
663
683
 
664
684
  if [ -z "$results" ]; then
665
- eagle_dim "No active tasks for project '$project'"
685
+ eagle_dim "No active tasks for project '$project_label'"
666
686
  return
667
687
  fi
668
688
 
@@ -58,17 +58,18 @@ eagle_mem_statusline_stats() {
58
58
  [ -z "$project_dir" ] && project_dir="$(pwd)"
59
59
  [ -z "$current_dir" ] && current_dir="$project_dir"
60
60
 
61
- local project_key project_sql stats sessions memories last_raw turns version latest
61
+ local project_key project_scope project_condition stats sessions memories last_raw turns version latest
62
62
  project_key=$(eagle_project_from_statusline_input "$statusline_input" "$project_dir" "$current_dir" "$session_id")
63
63
  [ -n "$project_key" ] || return
64
- project_sql=$(eagle_sql_escape "$project_key")
64
+ project_scope=$(eagle_recall_project_scope_from_cwd "${current_dir:-$project_dir}" "$project_key")
65
+ project_condition=$(eagle_sql_project_scope_condition "project" "$project_scope")
65
66
 
66
67
  stats=$("$sqlite_bin" "$em_db" "SELECT
67
68
  COUNT(*) || '|' ||
68
- (SELECT COUNT(*) FROM agent_memories WHERE project = '$project_sql') || '|' ||
69
+ (SELECT COUNT(*) FROM agent_memories WHERE $project_condition) || '|' ||
69
70
  COALESCE(MAX(COALESCE(last_activity_at, started_at)), 'never')
70
71
  FROM sessions
71
- WHERE project = '$project_sql';" 2>/dev/null)
72
+ WHERE $project_condition;" 2>/dev/null)
72
73
  IFS='|' read -r sessions memories last_raw <<< "${stats:-0|0|never}"
73
74
  sessions=${sessions:-0}
74
75
  memories=${memories:-0}