eagle-mem 4.10.13 → 4.11.0

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.
Files changed (69) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +20 -20
  3. package/architecture.html +26 -14
  4. package/bin/eagle-mem +4 -0
  5. package/db/039_recall_events.sql +27 -0
  6. package/db/040_graph_decision_nodes.sql +21 -0
  7. package/db/041_graph_semantic_edge_types.sql +21 -0
  8. package/db/042_orchestration_auto_events.sql +23 -0
  9. package/db/043_eagle_events.sql +22 -0
  10. package/docs/agent-compatibility/README.md +38 -0
  11. package/docs/agent-compatibility/claude-code.md +50 -0
  12. package/docs/agent-compatibility/codex.md +51 -0
  13. package/docs/agent-compatibility/opencode.md +71 -0
  14. package/hooks/post-tool-use.sh +8 -0
  15. package/hooks/pre-tool-use.sh +10 -1
  16. package/hooks/session-end.sh +3 -0
  17. package/hooks/session-start.sh +7 -0
  18. package/hooks/stop.sh +10 -1
  19. package/hooks/user-prompt-submit.sh +79 -6
  20. package/integrations/opencode_eagle_mem_plugin.js +387 -0
  21. package/lib/codex-hooks.sh +13 -6
  22. package/lib/common.sh +63 -0
  23. package/lib/db-events.sh +89 -0
  24. package/lib/db-graph.sh +154 -0
  25. package/lib/db-observations.sh +34 -0
  26. package/lib/db-orchestration.sh +149 -0
  27. package/lib/db.sh +2 -0
  28. package/lib/hooks.sh +12 -7
  29. package/lib/opencode-hooks.sh +105 -0
  30. package/lib/provider.sh +2 -2
  31. package/package.json +5 -2
  32. package/scripts/compaction.sh +108 -8
  33. package/scripts/dashboard.sh +372 -0
  34. package/scripts/doctor.sh +30 -3
  35. package/scripts/health.sh +40 -2
  36. package/scripts/help.sh +10 -2
  37. package/scripts/inspect.sh +285 -0
  38. package/scripts/install.sh +31 -7
  39. package/scripts/memories.sh +13 -0
  40. package/scripts/repair.sh +187 -0
  41. package/scripts/replay.sh +248 -0
  42. package/scripts/search.sh +44 -3
  43. package/scripts/statusline-em.sh +34 -7
  44. package/scripts/tasks.sh +34 -0
  45. package/scripts/test.sh +13 -0
  46. package/scripts/uninstall.sh +9 -0
  47. package/scripts/update.sh +18 -2
  48. package/tests/fixtures/agent-hooks/claude-statusline.json +32 -0
  49. package/tests/fixtures/agent-hooks/claude-user-prompt-submit.json +9 -0
  50. package/tests/fixtures/agent-hooks/codex-pre-tool-use.json +10 -0
  51. package/tests/fixtures/agent-hooks/codex-user-prompt-submit.json +7 -0
  52. package/tests/fixtures/agent-hooks/opencode-chat-message.json +36 -0
  53. package/tests/fixtures/agent-hooks/opencode-session-compacting.json +9 -0
  54. package/tests/fixtures/agent-hooks/opencode-todo-updated.json +13 -0
  55. package/tests/fixtures/agent-hooks/opencode-tool-execute-after.json +15 -0
  56. package/tests/fixtures/agent-hooks/opencode-tool-execute-before.json +12 -0
  57. package/tests/test_agent_compatibility_docs_gate.sh +123 -0
  58. package/tests/test_auto_orchestration_detection.sh +109 -0
  59. package/tests/test_claude_stop_hook_registration.sh +56 -0
  60. package/tests/test_codex_hooks_config.sh +73 -0
  61. package/tests/test_compaction_survival_matrix.sh +237 -0
  62. package/tests/test_dashboard.sh +96 -0
  63. package/tests/test_eagle_events.sh +96 -0
  64. package/tests/test_opencode_hooks_config.sh +56 -0
  65. package/tests/test_opencode_plugin_adapter.sh +202 -0
  66. package/tests/test_recall_observability.sh +144 -0
  67. package/tests/test_repair.sh +63 -0
  68. package/tests/test_rust_migration_plan.sh +75 -0
  69. package/tests/test_trust_surfaces.sh +123 -0
package/lib/db-graph.sh CHANGED
@@ -301,6 +301,160 @@ SQL
301
301
  printf '%s\n' "${session_count:-0}"
302
302
  }
303
303
 
304
+ eagle_graph_wire_project_context_edges() {
305
+ local project_raw="${1:-}"
306
+ [ -n "$project_raw" ] || { printf '0\n'; return 0; }
307
+
308
+ local project
309
+ project=$(eagle_sql_escape "$project_raw")
310
+
311
+ eagle_db_pipe <<SQL >/dev/null
312
+ BEGIN;
313
+
314
+ INSERT OR IGNORE INTO graph_nodes (project, node_type, node_name, node_value, source_path)
315
+ SELECT project, 'session', id,
316
+ 'Session run on ' || COALESCE(started_at, 'unknown') || ' using ' || COALESCE(model, 'unknown'),
317
+ ''
318
+ FROM sessions
319
+ WHERE project = '$project';
320
+
321
+ INSERT OR IGNORE INTO graph_nodes (project, node_type, node_name, node_value, source_path)
322
+ SELECT project, 'decision', session_id || ':decision', decisions, ''
323
+ FROM summaries
324
+ WHERE project = '$project'
325
+ AND COALESCE(decisions, '') != '';
326
+
327
+ INSERT OR IGNORE INTO graph_nodes (project, node_type, node_name, node_value, source_path)
328
+ SELECT project, 'feature', name, COALESCE(description, ''), ''
329
+ FROM features
330
+ WHERE project = '$project'
331
+ AND status = 'active';
332
+
333
+ INSERT OR IGNORE INTO graph_nodes (project, node_type, node_name, node_value, source_path)
334
+ SELECT project, 'memory', memory_name, COALESCE(description, ''), COALESCE(file_path, '')
335
+ FROM agent_memories
336
+ WHERE project = '$project'
337
+ AND COALESCE(memory_name, '') != '';
338
+
339
+ INSERT OR IGNORE INTO graph_nodes (project, node_type, node_name, node_value, source_path)
340
+ SELECT project, 'task', COALESCE(NULLIF(source_task_id, ''), subject), COALESCE(description, ''), COALESCE(file_path, '')
341
+ FROM agent_tasks
342
+ WHERE project = '$project'
343
+ AND COALESCE(COALESCE(NULLIF(source_task_id, ''), subject), '') != '';
344
+
345
+ INSERT OR IGNORE INTO graph_nodes (project, node_type, node_name, node_value, source_path)
346
+ SELECT f.project, 'file', ff.file_path, '', ff.file_path
347
+ FROM feature_files ff
348
+ JOIN features f ON f.id = ff.feature_id
349
+ WHERE f.project = '$project';
350
+
351
+ INSERT OR IGNORE INTO graph_nodes (project, node_type, node_name, node_value, source_path)
352
+ SELECT DISTINCT sf.project, 'file', sf.file_path, '', sf.file_path
353
+ FROM (
354
+ SELECT project, json_each.value AS file_path
355
+ FROM summaries, json_each(CASE WHEN json_valid(files_read) THEN files_read ELSE '[]' END)
356
+ WHERE project = '$project'
357
+ UNION
358
+ SELECT project, json_each.value AS file_path
359
+ FROM summaries, json_each(CASE WHEN json_valid(files_modified) THEN files_modified ELSE '[]' END)
360
+ WHERE project = '$project'
361
+ ) sf
362
+ WHERE COALESCE(sf.file_path, '') != '';
363
+
364
+ INSERT OR IGNORE INTO graph_edges (project, source_node_id, target_node_id, edge_type, weight)
365
+ SELECT '$project', d.id, s.id, 'recorded_in', 1.0
366
+ FROM summaries sm
367
+ JOIN graph_nodes d ON d.project = sm.project AND d.node_type = 'decision' AND d.node_name = sm.session_id || ':decision'
368
+ JOIN graph_nodes s ON s.project = sm.project AND s.node_type = 'session' AND s.node_name = sm.session_id
369
+ WHERE sm.project = '$project'
370
+ AND COALESCE(sm.decisions, '') != ''
371
+ AND d.id != s.id;
372
+
373
+ INSERT OR IGNORE INTO graph_edges (project, source_node_id, target_node_id, edge_type, weight)
374
+ SELECT '$project', d.id, fnode.id, 'touches', 1.0
375
+ FROM summaries sm
376
+ JOIN graph_nodes d ON d.project = sm.project AND d.node_type = 'decision' AND d.node_name = sm.session_id || ':decision'
377
+ JOIN (
378
+ SELECT session_id, project, json_each.value AS file_path
379
+ FROM summaries, json_each(CASE WHEN json_valid(files_read) THEN files_read ELSE '[]' END)
380
+ WHERE project = '$project'
381
+ UNION
382
+ SELECT session_id, project, json_each.value AS file_path
383
+ FROM summaries, json_each(CASE WHEN json_valid(files_modified) THEN files_modified ELSE '[]' END)
384
+ WHERE project = '$project'
385
+ ) sf ON sf.session_id = sm.session_id AND sf.project = sm.project
386
+ JOIN graph_nodes fnode ON fnode.project = sf.project AND fnode.node_type = 'file' AND fnode.node_name = sf.file_path
387
+ WHERE sm.project = '$project'
388
+ AND COALESCE(sm.decisions, '') != ''
389
+ AND COALESCE(sf.file_path, '') != ''
390
+ AND d.id != fnode.id;
391
+
392
+ INSERT OR IGNORE INTO graph_edges (project, source_node_id, target_node_id, edge_type, weight)
393
+ SELECT '$project', feat.id, file.id, 'covers', 1.0
394
+ FROM features f
395
+ JOIN feature_files ff ON ff.feature_id = f.id
396
+ JOIN graph_nodes feat ON feat.project = f.project AND feat.node_type = 'feature' AND feat.node_name = f.name
397
+ JOIN graph_nodes file ON file.project = f.project AND file.node_type = 'file' AND file.node_name = ff.file_path
398
+ WHERE f.project = '$project'
399
+ AND feat.id != file.id;
400
+
401
+ INSERT OR IGNORE INTO graph_edges (project, source_node_id, target_node_id, edge_type, weight)
402
+ SELECT '$project', memory.id, session.id, 'originated_in', 1.0
403
+ FROM agent_memories m
404
+ JOIN graph_nodes memory ON memory.project = m.project AND memory.node_type = 'memory' AND memory.node_name = m.memory_name
405
+ JOIN graph_nodes session ON session.project = m.project AND session.node_type = 'session' AND session.node_name = m.origin_session_id
406
+ WHERE m.project = '$project'
407
+ AND COALESCE(m.origin_session_id, '') != ''
408
+ AND memory.id != session.id;
409
+
410
+ INSERT OR IGNORE INTO graph_edges (project, source_node_id, target_node_id, edge_type, weight)
411
+ SELECT '$project', memory.id, feat.id, 'mentions', 1.0
412
+ FROM agent_memories m
413
+ JOIN features f ON f.project = m.project AND f.status = 'active'
414
+ JOIN graph_nodes memory ON memory.project = m.project AND memory.node_type = 'memory' AND memory.node_name = m.memory_name
415
+ JOIN graph_nodes feat ON feat.project = f.project AND feat.node_type = 'feature' AND feat.node_name = f.name
416
+ WHERE m.project = '$project'
417
+ AND LOWER(COALESCE(m.memory_name, '') || ' ' || COALESCE(m.description, '') || ' ' || COALESCE(m.content, '')) LIKE '%' || LOWER(f.name) || '%'
418
+ AND memory.id != feat.id;
419
+
420
+ INSERT OR IGNORE INTO graph_edges (project, source_node_id, target_node_id, edge_type, weight)
421
+ SELECT '$project', task.id, session.id, 'planned_in', 1.0
422
+ FROM agent_tasks t
423
+ JOIN graph_nodes task ON task.project = t.project AND task.node_type = 'task' AND task.node_name = COALESCE(NULLIF(t.source_task_id, ''), t.subject)
424
+ JOIN graph_nodes session ON session.project = t.project AND session.node_type = 'session' AND session.node_name = t.source_session_id
425
+ WHERE t.project = '$project'
426
+ AND COALESCE(t.source_session_id, '') != ''
427
+ AND task.id != session.id;
428
+
429
+ INSERT OR IGNORE INTO graph_edges (project, source_node_id, target_node_id, edge_type, weight)
430
+ SELECT '$project', task.id, feat.id, 'mentions', 1.0
431
+ FROM agent_tasks t
432
+ JOIN features f ON f.project = t.project AND f.status = 'active'
433
+ JOIN graph_nodes task ON task.project = t.project AND task.node_type = 'task' AND task.node_name = COALESCE(NULLIF(t.source_task_id, ''), t.subject)
434
+ JOIN graph_nodes feat ON feat.project = f.project AND feat.node_type = 'feature' AND feat.node_name = f.name
435
+ WHERE t.project = '$project'
436
+ AND LOWER(COALESCE(t.subject, '') || ' ' || COALESCE(t.description, '')) LIKE '%' || LOWER(f.name) || '%'
437
+ AND task.id != feat.id;
438
+
439
+ INSERT OR IGNORE INTO graph_edges (project, source_node_id, target_node_id, edge_type, weight)
440
+ SELECT '$project', decision.id, feat.id, 'mentions', 1.0
441
+ FROM summaries sm
442
+ JOIN features f ON f.project = sm.project AND f.status = 'active'
443
+ JOIN graph_nodes decision ON decision.project = sm.project AND decision.node_type = 'decision' AND decision.node_name = sm.session_id || ':decision'
444
+ JOIN graph_nodes feat ON feat.project = f.project AND feat.node_type = 'feature' AND feat.node_name = f.name
445
+ WHERE sm.project = '$project'
446
+ AND LOWER(COALESCE(sm.decisions, '')) LIKE '%' || LOWER(f.name) || '%'
447
+ AND decision.id != feat.id;
448
+
449
+ COMMIT;
450
+ SQL
451
+
452
+ eagle_db "SELECT COUNT(*)
453
+ FROM graph_nodes
454
+ WHERE project = '$project'
455
+ AND node_type IN ('feature', 'memory', 'task', 'session', 'decision');"
456
+ }
457
+
304
458
  eagle_graph_emit_session_file_edge_sql() {
305
459
  local project_raw="${1:-}"
306
460
  local session_id="${2:-}"
@@ -35,6 +35,40 @@ eagle_insert_observation() {
35
35
  );"
36
36
  }
37
37
 
38
+ eagle_insert_recall_event() {
39
+ local session_id; session_id=$(eagle_sql_escape "${1:-}")
40
+ local project; project=$(eagle_sql_escape "${2:-}")
41
+ local cwd; cwd=$(eagle_sql_escape "${3:-}")
42
+ local agent; agent=$(eagle_sql_escape "${4:-$(eagle_agent_source)}")
43
+ local prompt_snippet; prompt_snippet=$(eagle_sql_escape "$(eagle_trim_text "${5:-}" 240)")
44
+ local fts_query; fts_query=$(eagle_sql_escape "${6:-}")
45
+ local summary_matches; summary_matches=$(eagle_sql_int "${7:-0}")
46
+ local memory_matches; memory_matches=$(eagle_sql_int "${8:-0}")
47
+ local code_matches; code_matches=$(eagle_sql_int "${9:-0}")
48
+ local injected_chars; injected_chars=$(eagle_sql_int "${10:-0}")
49
+ local status; status=$(eagle_sql_escape "${11:-ok}")
50
+ local error; error=$(eagle_sql_escape "${12:-}")
51
+ local summary_refs; summary_refs=$(eagle_sql_escape "${13:-[]}")
52
+ local memory_refs; memory_refs=$(eagle_sql_escape "${14:-[]}")
53
+ local code_refs; code_refs=$(eagle_sql_escape "${15:-[]}")
54
+ local injected_token_estimate=$(( (injected_chars + 3) / 4 ))
55
+
56
+ [ -n "$project" ] || project="unknown"
57
+
58
+ eagle_db "INSERT INTO recall_events (
59
+ session_id, project, cwd, agent, prompt_snippet, fts_query,
60
+ summary_matches, memory_matches, code_matches,
61
+ summary_refs, memory_refs, code_refs, injected_chars,
62
+ injected_token_estimate, status, error
63
+ )
64
+ VALUES (
65
+ '$session_id', '$project', '$cwd', '$agent', '$prompt_snippet', '$fts_query',
66
+ $summary_matches, $memory_matches, $code_matches,
67
+ '$summary_refs', '$memory_refs', '$code_refs', $injected_chars,
68
+ $injected_token_estimate, '$status', '$error'
69
+ );"
70
+ }
71
+
38
72
  eagle_prune_observations() {
39
73
  local days; days=$(eagle_sql_int "${1:-90}")
40
74
  local project_filter=""
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env bash
2
+ # ═══════════════════════════════════════════════════════════
3
+ # Eagle Mem — Orchestration DB helpers
4
+ # Lightweight helpers safe for hooks; full worker management lives in scripts/orchestrate.sh.
5
+ # ═══════════════════════════════════════════════════════════
6
+ [ -n "${_EAGLE_DB_ORCHESTRATION_LOADED:-}" ] && return 0
7
+ _EAGLE_DB_ORCHESTRATION_LOADED=1
8
+
9
+ eagle_auto_orchestration_agent() {
10
+ local agent="${1:-}"
11
+ case "$agent" in
12
+ codex|claude-code) printf '%s\n' "$agent" ;;
13
+ *) printf 'codex\n' ;;
14
+ esac
15
+ }
16
+
17
+ eagle_auto_orchestration_lane_sql() {
18
+ local oid="$1" project="$2" name="$3" lane_key="$4" title="$5" description="$6" agent="$7" validation="$8"
19
+ local source_task_id file_path content_hash
20
+ local oid_sql project_sql name_sql key_sql title_sql desc_sql agent_sql validation_sql task_sql fp_sql hash_sql
21
+
22
+ oid_sql=$(eagle_sql_int "$oid")
23
+ project_sql=$(eagle_sql_escape "$project")
24
+ name_sql=$(eagle_sql_escape "$name")
25
+ key_sql=$(eagle_sql_escape "$lane_key")
26
+ title_sql=$(eagle_sql_escape "$title")
27
+ desc_sql=$(eagle_sql_escape "$description")
28
+ agent_sql=$(eagle_sql_escape "$(eagle_auto_orchestration_agent "$agent")")
29
+ validation_sql=$(eagle_sql_escape "$validation")
30
+ source_task_id="lane-${name}-${lane_key}"
31
+ file_path="orchestration-lane://${project}/${name}/${lane_key}"
32
+ task_sql=$(eagle_sql_escape "$source_task_id")
33
+ fp_sql=$(eagle_sql_escape "$file_path")
34
+ content_hash=$(printf '%s|%s|%s|%s|%s' "$lane_key" "$title" "$description" "$agent" "$validation" | eagle_sha256_stream)
35
+ hash_sql=$(eagle_sql_escape "$content_hash")
36
+
37
+ cat <<SQL
38
+ INSERT INTO orchestration_lanes (orchestration_id, project, lane_key, title, description, agent, worktree_path, validation, status, source_task_id)
39
+ VALUES ($oid_sql, '$project_sql', '$key_sql', '$title_sql', '$desc_sql', '$agent_sql', '', '$validation_sql', 'pending', '$task_sql')
40
+ ON CONFLICT(orchestration_id, lane_key) DO UPDATE SET
41
+ title = excluded.title,
42
+ description = excluded.description,
43
+ agent = excluded.agent,
44
+ validation = excluded.validation,
45
+ source_task_id = excluded.source_task_id,
46
+ status = CASE
47
+ WHEN orchestration_lanes.status IN ('completed', 'cancelled') THEN orchestration_lanes.status
48
+ ELSE orchestration_lanes.status
49
+ END,
50
+ updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now');
51
+
52
+ INSERT INTO agent_tasks (project, source_session_id, source_task_id, file_path, subject, description, active_form, status, blocks, blocked_by, content_hash, origin_agent)
53
+ VALUES ('$project_sql', 'orchestration', '$task_sql', '$fp_sql', '$title_sql', '$desc_sql', '$validation_sql', 'pending', '[]', '[]', '$hash_sql', '$agent_sql')
54
+ ON CONFLICT(file_path) DO UPDATE SET
55
+ source_task_id = excluded.source_task_id,
56
+ file_path = excluded.file_path,
57
+ subject = excluded.subject,
58
+ description = excluded.description,
59
+ active_form = excluded.active_form,
60
+ status = CASE
61
+ WHEN agent_tasks.status IN ('completed', 'cancelled') THEN agent_tasks.status
62
+ ELSE agent_tasks.status
63
+ END,
64
+ content_hash = excluded.content_hash,
65
+ origin_agent = excluded.origin_agent,
66
+ updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now');
67
+ SQL
68
+ }
69
+
70
+ eagle_auto_orchestrate_from_prompt() {
71
+ local project_raw="${1:-}"
72
+ local session_id_raw="${2:-}"
73
+ local agent_raw="${3:-$(eagle_agent_source)}"
74
+ local prompt_raw="${4:-}"
75
+ local cwd_raw="${5:-}"
76
+ local trigger_raw="${6:-broad_prompt}"
77
+ [ -n "$project_raw" ] && [ -n "$prompt_raw" ] || return 0
78
+
79
+ local has_tables
80
+ has_tables=$(eagle_db "SELECT COUNT(*)
81
+ FROM sqlite_master
82
+ WHERE type = 'table'
83
+ AND name IN ('orchestrations', 'orchestration_lanes', 'agent_tasks');" 2>/dev/null || printf '0')
84
+ [ "${has_tables:-0}" -ge 3 ] 2>/dev/null || return 0
85
+
86
+ local name="auto"
87
+ local project_sql name_sql goal_sql baseline_sql run_key_sql session_sql cwd_sql agent_sql trigger_sql prompt_sql
88
+ local oid lanes_json lanes_sql lane_count
89
+ project_sql=$(eagle_sql_escape "$project_raw")
90
+ name_sql=$(eagle_sql_escape "$name")
91
+ goal_sql=$(eagle_sql_escape "$(eagle_trim_text "$prompt_raw" 700)")
92
+ baseline_sql=$(eagle_sql_escape "$(git -C "${cwd_raw:-$(pwd)}" rev-parse --short HEAD 2>/dev/null || true)")
93
+ run_key_sql=$(eagle_sql_escape "auto-$(printf '%s' "$project_raw|$prompt_raw" | eagle_sha256_stream | cut -c1-12)")
94
+ session_sql=$(eagle_sql_escape "$session_id_raw")
95
+ cwd_sql=$(eagle_sql_escape "$cwd_raw")
96
+ agent_sql=$(eagle_sql_escape "$(eagle_auto_orchestration_agent "$agent_raw")")
97
+ trigger_sql=$(eagle_sql_escape "$trigger_raw")
98
+ prompt_sql=$(eagle_sql_escape "$(eagle_trim_text "$prompt_raw" 300)")
99
+
100
+ eagle_db_pipe <<SQL >/dev/null
101
+ INSERT INTO orchestrations (project, name, goal, status, baseline_ref, run_key)
102
+ VALUES ('$project_sql', '$name_sql', '$goal_sql', 'active', '$baseline_sql', '$run_key_sql')
103
+ ON CONFLICT(project, name) DO UPDATE SET
104
+ goal = excluded.goal,
105
+ status = 'active',
106
+ baseline_ref = COALESCE(NULLIF(excluded.baseline_ref, ''), orchestrations.baseline_ref),
107
+ run_key = CASE
108
+ WHEN orchestrations.status IN ('completed', 'cancelled')
109
+ OR orchestrations.run_key IS NULL
110
+ OR orchestrations.run_key = ''
111
+ THEN excluded.run_key
112
+ ELSE orchestrations.run_key
113
+ END,
114
+ updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now');
115
+ SQL
116
+
117
+ oid=$(eagle_db "SELECT id FROM orchestrations WHERE project = '$project_sql' AND name = '$name_sql' LIMIT 1;" 2>/dev/null || true)
118
+ [ -n "$oid" ] || return 0
119
+
120
+ eagle_db_pipe <<SQL >/dev/null
121
+ BEGIN;
122
+ $(eagle_auto_orchestration_lane_sql "$oid" "$project_raw" "$name" "scope" "Scope broad request" "Map requirements, affected files, risks, and existing docs before implementation." "$agent_raw" "Document scope and affected surfaces")
123
+ $(eagle_auto_orchestration_lane_sql "$oid" "$project_raw" "$name" "implementation" "Implement scoped changes" "Make the smallest safe changes needed for the broad request, preserving existing behavior." "$agent_raw" "Run targeted implementation checks")
124
+ $(eagle_auto_orchestration_lane_sql "$oid" "$project_raw" "$name" "verification" "Verify and report" "Run targeted tests, smoke checks, and update backlog or handoff notes after implementation." "$agent_raw" "Run relevant tests and smoke checks")
125
+ UPDATE orchestrations SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = $oid;
126
+ COMMIT;
127
+ SQL
128
+
129
+ lanes_json=$(eagle_db_json "SELECT lane_key, title, agent, status
130
+ FROM orchestration_lanes
131
+ WHERE orchestration_id = $oid
132
+ ORDER BY CASE lane_key WHEN 'scope' THEN 0 WHEN 'implementation' THEN 1 WHEN 'verification' THEN 2 ELSE 3 END, lane_key;" 2>/dev/null || printf '[]')
133
+ [ -n "$lanes_json" ] || lanes_json="[]"
134
+ lanes_sql=$(eagle_sql_escape "$lanes_json")
135
+ lane_count=$(printf '%s' "$lanes_json" | jq 'length' 2>/dev/null || printf '0')
136
+
137
+ if [ -n "$(eagle_db "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'orchestration_auto_events' LIMIT 1;" 2>/dev/null || true)" ]; then
138
+ eagle_db "INSERT INTO orchestration_auto_events (
139
+ session_id, project, cwd, agent, prompt_snippet, trigger,
140
+ orchestration_id, orchestration_name, lanes, status
141
+ )
142
+ VALUES (
143
+ '$session_sql', '$project_sql', '$cwd_sql', '$agent_sql', '$prompt_sql', '$trigger_sql',
144
+ $oid, '$name_sql', '$lanes_sql', 'created'
145
+ );" >/dev/null 2>&1 || true
146
+ fi
147
+
148
+ printf '%s|%s|%s|%s\n' "$name" "$oid" "${lane_count:-0}" "$lanes_json"
149
+ }
package/lib/db.sh CHANGED
@@ -9,11 +9,13 @@ _EAGLE_DB_LOADED=1
9
9
  _eagle_db_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
10
 
11
11
  . "$_eagle_db_dir/db-core.sh"
12
+ . "$_eagle_db_dir/db-events.sh"
12
13
  . "$_eagle_db_dir/db-sessions.sh"
13
14
  . "$_eagle_db_dir/db-observations.sh"
14
15
  . "$_eagle_db_dir/db-summaries.sh"
15
16
  . "$_eagle_db_dir/db-mirrors.sh"
16
17
  . "$_eagle_db_dir/db-features.sh"
18
+ . "$_eagle_db_dir/db-orchestration.sh"
17
19
  . "$_eagle_db_dir/db-hints.sh"
18
20
  . "$_eagle_db_dir/db-backfill.sh"
19
21
  . "$_eagle_db_dir/db-guardrails.sh"
package/lib/hooks.sh CHANGED
@@ -42,15 +42,20 @@ eagle_patch_hook() {
42
42
 
43
43
  # Check both command AND matcher to avoid skipping entries with different matchers
44
44
  # (e.g. PreToolUse with "Bash" vs "Read" matcher using the same script)
45
- local match_query
46
45
  if [ -n "$matcher" ]; then
47
- match_query=".hooks.${event}[]? | select(.matcher == \"$matcher\" and (.hooks[]?.command == \"$command\"))"
46
+ if jq -e --arg m "$matcher" --arg c "$command" \
47
+ ".hooks.${event}[]? | select(.matcher == \$m and (.hooks[]?.command == \$c))" \
48
+ "$settings" &>/dev/null; then
49
+ [ -n "$description" ] && eagle_ok "$description ${DIM}(already registered)${RESET}"
50
+ return 0
51
+ fi
48
52
  else
49
- match_query=".hooks.${event}[]? | select(.matcher == null and (.hooks[]?.command == \"$command\"))"
50
- fi
51
- if jq -e "$match_query" "$settings" &>/dev/null; then
52
- [ -n "$description" ] && eagle_ok "$description ${DIM}(already registered)${RESET}"
53
- return 0
53
+ if jq -e --arg c "$command" \
54
+ ".hooks.${event}[]? | select(.matcher == null and (.hooks[]?.command == \$c))" \
55
+ "$settings" &>/dev/null; then
56
+ [ -n "$description" ] && eagle_ok "$description ${DIM}(already registered)${RESET}"
57
+ return 0
58
+ fi
54
59
  fi
55
60
 
56
61
  local entry
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env bash
2
+ # ═══════════════════════════════════════════════════════════
3
+ # Eagle Mem — OpenCode plugin registration helpers
4
+ # Shared by install.sh, update.sh, uninstall.sh, and doctor.sh
5
+ # ═══════════════════════════════════════════════════════════
6
+ [ -n "${_EAGLE_OPENCODE_HOOKS_LOADED:-}" ] && return 0
7
+ _EAGLE_OPENCODE_HOOKS_LOADED=1
8
+
9
+ eagle_opencode_detected() {
10
+ [ -d "$EAGLE_OPENCODE_DIR" ] && return 0
11
+ command -v opencode >/dev/null 2>&1
12
+ }
13
+
14
+ eagle_opencode_plugin_source() {
15
+ local package_dir="${1:-.}"
16
+ printf '%s\n' "$package_dir/integrations/opencode_eagle_mem_plugin.js"
17
+ }
18
+
19
+ eagle_install_opencode_plugin() {
20
+ local package_dir="${1:-.}" dry_run="${2:-0}"
21
+ local src
22
+ src=$(eagle_opencode_plugin_source "$package_dir")
23
+
24
+ [ -f "$src" ] || {
25
+ eagle_warn "OpenCode plugin source missing: $src"
26
+ return 1
27
+ }
28
+
29
+ if [ "$dry_run" = "1" ]; then
30
+ eagle_info "Would install OpenCode plugin: $EAGLE_OPENCODE_PLUGIN"
31
+ return 0
32
+ fi
33
+
34
+ mkdir -p "$EAGLE_OPENCODE_PLUGINS_DIR"
35
+ if [ -f "$EAGLE_OPENCODE_PLUGIN" ] && ! grep -q "EAGLE_MEM_OPENCODE_PLUGIN" "$EAGLE_OPENCODE_PLUGIN" 2>/dev/null; then
36
+ eagle_warn "OpenCode plugin path already exists and is not Eagle Mem-owned: $EAGLE_OPENCODE_PLUGIN"
37
+ return 1
38
+ fi
39
+ cp "$src" "$EAGLE_OPENCODE_PLUGIN"
40
+ chmod 600 "$EAGLE_OPENCODE_PLUGIN" 2>/dev/null || true
41
+ eagle_ok "OpenCode plugin ${DIM}(global local plugin)${RESET}"
42
+ }
43
+
44
+ eagle_install_opencode_skills() {
45
+ local package_dir="${1:-.}" dry_run="${2:-0}"
46
+ [ -d "$package_dir/skills" ] || return 0
47
+
48
+ if [ "$dry_run" = "1" ]; then
49
+ eagle_info "Would symlink OpenCode skills to: $EAGLE_OPENCODE_SKILLS_DIR"
50
+ return 0
51
+ fi
52
+
53
+ mkdir -p "$EAGLE_OPENCODE_SKILLS_DIR"
54
+ find "$EAGLE_OPENCODE_SKILLS_DIR" -maxdepth 1 -name "eagle-mem-*" -type l 2>/dev/null | while read -r existing; do
55
+ skill_name=$(basename "$existing")
56
+ if [ ! -d "$package_dir/skills/$skill_name" ]; then
57
+ rm "$existing"
58
+ eagle_ok "Removed stale OpenCode skill: $skill_name"
59
+ fi
60
+ done
61
+ for skill_dir in "$package_dir"/skills/*/; do
62
+ [ -d "$skill_dir" ] || continue
63
+ skill_name=$(basename "$skill_dir")
64
+ dst="$EAGLE_OPENCODE_SKILLS_DIR/$skill_name"
65
+ [ -L "$dst" ] && rm "$dst"
66
+ ln -sf "$skill_dir" "$dst"
67
+ done
68
+ eagle_ok "OpenCode skills updated"
69
+ }
70
+
71
+ eagle_remove_opencode_plugin() {
72
+ [ -f "$EAGLE_OPENCODE_PLUGIN" ] || return 1
73
+
74
+ if grep -q "EAGLE_MEM_OPENCODE_PLUGIN" "$EAGLE_OPENCODE_PLUGIN" 2>/dev/null; then
75
+ rm -f "$EAGLE_OPENCODE_PLUGIN"
76
+ return 0
77
+ fi
78
+
79
+ return 1
80
+ }
81
+
82
+ eagle_remove_opencode_skills() {
83
+ [ -d "$EAGLE_OPENCODE_SKILLS_DIR" ] || return 1
84
+
85
+ local removed=1
86
+ for target in "$EAGLE_OPENCODE_SKILLS_DIR"/eagle-mem-*; do
87
+ [ -L "$target" ] || continue
88
+ rm -rf "$target"
89
+ eagle_ok "OpenCode skill removed: $(basename "$target")"
90
+ removed=0
91
+ done
92
+ return "$removed"
93
+ }
94
+
95
+ eagle_opencode_plugin_state() {
96
+ if ! eagle_opencode_detected; then
97
+ printf '%s\n' "not found"
98
+ elif [ -f "$EAGLE_OPENCODE_PLUGIN" ] && grep -q "EAGLE_MEM_OPENCODE_PLUGIN" "$EAGLE_OPENCODE_PLUGIN" 2>/dev/null; then
99
+ printf '%s\n' "registered"
100
+ elif [ -f "$EAGLE_OPENCODE_PLUGIN" ]; then
101
+ printf '%s\n' "custom"
102
+ else
103
+ printf '%s\n' "not registered"
104
+ fi
105
+ }
package/lib/provider.sh CHANGED
@@ -513,7 +513,7 @@ _eagle_call_codex_cli() {
513
513
  --ephemeral \
514
514
  --skip-git-repo-check \
515
515
  --ignore-rules \
516
- -c features.codex_hooks=false \
516
+ -c features.hooks=false \
517
517
  --sandbox read-only \
518
518
  --cd "${EAGLE_AGENT_CWD:-$(pwd)}" \
519
519
  --model "$model" \
@@ -525,7 +525,7 @@ _eagle_call_codex_cli() {
525
525
  --ephemeral \
526
526
  --skip-git-repo-check \
527
527
  --ignore-rules \
528
- -c features.codex_hooks=false \
528
+ -c features.hooks=false \
529
529
  --sandbox read-only \
530
530
  --cd "${EAGLE_AGENT_CWD:-$(pwd)}" \
531
531
  --output-last-message "$out_file" \
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "eagle-mem",
3
- "version": "4.10.13",
4
- "description": "Shared memory, release guardrails, RTK token protection, and worker lanes for Claude Code, Codex, Grok, and Google Antigravity",
3
+ "version": "4.11.0",
4
+ "description": "Shared memory, release guardrails, RTK token protection, and worker lanes for Claude Code, Codex, OpenCode, Grok, and Google Antigravity",
5
5
  "bin": {
6
6
  "eagle-mem": "bin/eagle-mem"
7
7
  },
@@ -20,11 +20,14 @@
20
20
  "docs/",
21
21
  "architecture.html",
22
22
  "integrations/*.py",
23
+ "integrations/*.js",
24
+ "tests/fixtures/",
23
25
  "CHANGELOG.md"
24
26
  ],
25
27
  "keywords": [
26
28
  "claude-code",
27
29
  "codex",
30
+ "opencode",
28
31
  "grok",
29
32
  "memory",
30
33
  "sqlite",