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
@@ -0,0 +1,237 @@
1
+ #!/usr/bin/env bash
2
+ # End-to-end compaction survival matrix. A pre-compact session creates every
3
+ # durable entity Eagle Mem promises to preserve, then a post-compact prompt must
4
+ # recover it through search, recall, replay, status, and the project graph.
5
+ set -euo pipefail
6
+
7
+ ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
8
+ EAGLE_BIN="$ROOT_DIR/bin/eagle-mem"
9
+
10
+ tmp_dir=$(mktemp -d "$ROOT_DIR/.tmp-compaction-survival.XXXXXX")
11
+ trap 'rm -rf "$tmp_dir"' EXIT
12
+
13
+ export HOME="$tmp_dir/home"
14
+ export EAGLE_MEM_DIR="$tmp_dir/eagle-mem"
15
+ mkdir -p "$HOME" "$EAGLE_MEM_DIR"
16
+
17
+ . "$ROOT_DIR/lib/common.sh"
18
+ "$ROOT_DIR/db/migrate.sh" >/dev/null
19
+ . "$ROOT_DIR/lib/db.sh"
20
+
21
+ assert_json() {
22
+ local json="$1" filter="$2" message="$3"
23
+ if ! printf '%s' "$json" | jq -e "$filter" >/dev/null; then
24
+ echo "$message" >&2
25
+ echo "$json" >&2
26
+ exit 1
27
+ fi
28
+ }
29
+
30
+ assert_contains() {
31
+ local haystack="$1" needle="$2" message="$3"
32
+ case "$haystack" in
33
+ *"$needle"*) ;;
34
+ *)
35
+ echo "$message" >&2
36
+ echo "Expected to find: $needle" >&2
37
+ echo "$haystack" >&2
38
+ exit 1
39
+ ;;
40
+ esac
41
+ }
42
+
43
+ strip_ansi() {
44
+ sed -E $'s/\x1b\\[[0-9;]*m//g'
45
+ }
46
+
47
+ project="project-compaction-matrix"
48
+ export EAGLE_MEM_PROJECT="$project"
49
+ repo="$HOME/$project"
50
+ mkdir -p "$repo/src"
51
+
52
+ cat > "$repo/src/auth-client.ts" <<'EOF'
53
+ export function refreshOauthToken() {
54
+ return "oauth-token-refresh compaction survival rotates credentials safely";
55
+ }
56
+ EOF
57
+
58
+ pre_session="compact-pre-session"
59
+ post_session="compact-post-session"
60
+ files_json='["src/auth-client.ts"]'
61
+
62
+ seed_summary() {
63
+ local session_id="$1" request="$2" learned="$3" completed="$4" decisions="$5"
64
+ eagle_upsert_session "$session_id" "$project" "$repo" "test-model" "test" "codex" >/dev/null
65
+ eagle_insert_summary \
66
+ "$session_id" \
67
+ "$project" \
68
+ "$request" \
69
+ "Read src/auth-client.ts and the oauth-token-refresh path" \
70
+ "$learned" \
71
+ "$completed" \
72
+ "Verify oauth-token-refresh after compact in a new session" \
73
+ "$files_json" \
74
+ "$files_json" \
75
+ "compaction survival matrix seed" \
76
+ "$decisions" \
77
+ "Do not regress oauth-token-refresh token rotation after compaction" \
78
+ "src/auth-client.ts" \
79
+ "codex" >/dev/null
80
+ }
81
+
82
+ seed_summary \
83
+ "$pre_session" \
84
+ "Create oauth-token-refresh compaction survival context" \
85
+ "oauth-token-refresh must survive compaction through summaries, memories, tasks, features, recall, and graph edges" \
86
+ "Stored durable oauth-token-refresh compaction survival context" \
87
+ "Decision: oauth-token-refresh is the durable compaction survival feature for auth replay"
88
+ seed_summary \
89
+ "compact-pre-session-2" \
90
+ "Record oauth-token-refresh retry decision" \
91
+ "Retry safety depends on rotated credentials" \
92
+ "Stored second enriched oauth-token-refresh summary" \
93
+ "Decision: oauth-token-refresh keeps retry safety explicit"
94
+ seed_summary \
95
+ "compact-pre-session-3" \
96
+ "Record oauth-token-refresh stale-token gotcha" \
97
+ "Stale refresh tokens fail silently without durable recall" \
98
+ "Stored third enriched oauth-token-refresh summary" \
99
+ "Decision: oauth-token-refresh stale-token gotcha must remain searchable"
100
+
101
+ eagle_insert_observation \
102
+ "$pre_session" \
103
+ "$project" \
104
+ "Edit" \
105
+ "Updated oauth-token-refresh compaction fixture" \
106
+ "$files_json" \
107
+ "$files_json" \
108
+ "" "" "" "codex" >/dev/null
109
+
110
+ memory_file="$tmp_dir/oauth-token-refresh-memory.md"
111
+ cat > "$memory_file" <<'EOF'
112
+ ---
113
+ name: oauth-token-refresh memory
114
+ description: oauth-token-refresh compaction survival decision
115
+ type: decision
116
+ originSessionId: compact-pre-session
117
+ ---
118
+ oauth-token-refresh must survive compaction and recall the auth client before edits.
119
+ EOF
120
+ eagle_capture_agent_memory "$memory_file" "$pre_session" "$project" "codex" >/dev/null
121
+
122
+ task_file="$tmp_dir/oauth-token-refresh-task.json"
123
+ cat > "$task_file" <<'EOF'
124
+ {
125
+ "id": "compact-task-oauth",
126
+ "subject": "Verify oauth-token-refresh compaction survival",
127
+ "description": "Ensure oauth-token-refresh survives compact through summary, memory, feature, recall, replay, and graph lookup.",
128
+ "activeForm": "Verifying oauth-token-refresh survival",
129
+ "status": "in_progress",
130
+ "blocks": [],
131
+ "blockedBy": []
132
+ }
133
+ EOF
134
+ eagle_capture_agent_task "$task_file" "$pre_session" "$project" "codex" >/dev/null
135
+
136
+ eagle_upsert_feature "$project" "oauth-token-refresh" "OAuth token refresh compaction survival feature" >/dev/null
137
+ feature_id=$(eagle_get_feature_id "$project" "oauth-token-refresh")
138
+ [ -n "$feature_id" ] || { echo "feature id missing" >&2; exit 1; }
139
+ eagle_add_feature_file "$feature_id" "src/auth-client.ts" "auth client" >/dev/null
140
+ eagle_add_feature_smoke_test "$feature_id" "bash tests/test_compaction_survival_matrix.sh" "compaction survival matrix" >/dev/null
141
+
142
+ (cd "$repo" && "$EAGLE_BIN" graph rebuild >/dev/null)
143
+ eagle_graph_wire_recent_session_edges "$project" 10 >/dev/null
144
+
145
+ for node_type in feature memory task session decision file; do
146
+ count=$(eagle_db "SELECT COUNT(*) FROM graph_nodes WHERE project = '$project' AND node_type = '$node_type';")
147
+ [ "${count:-0}" -gt 0 ] || {
148
+ echo "expected graph node type $node_type to survive, got $count" >&2
149
+ exit 1
150
+ }
151
+ done
152
+
153
+ semantic_edges=$(eagle_db "SELECT COUNT(*)
154
+ FROM graph_edges e
155
+ JOIN graph_nodes s ON s.id = e.source_node_id
156
+ JOIN graph_nodes t ON t.id = e.target_node_id
157
+ WHERE e.project = '$project'
158
+ AND (
159
+ (s.node_type = 'feature' AND t.node_type = 'file' AND e.edge_type = 'covers')
160
+ OR (s.node_type = 'memory' AND t.node_type = 'feature' AND e.edge_type = 'mentions')
161
+ OR (s.node_type = 'task' AND t.node_type = 'feature' AND e.edge_type = 'mentions')
162
+ OR (s.node_type = 'decision' AND t.node_type = 'feature' AND e.edge_type = 'mentions')
163
+ OR (s.node_type = 'decision' AND t.node_type = 'file' AND e.edge_type = 'touches')
164
+ );")
165
+ [ "${semantic_edges:-0}" -ge 5 ] || {
166
+ echo "expected semantic graph edges for durable context, got $semantic_edges" >&2
167
+ exit 1
168
+ }
169
+
170
+ summary_search=$(cd "$repo" && "$EAGLE_BIN" search "oauth token refresh compaction" --json)
171
+ assert_json "$summary_search" 'length >= 1 and (.[0].completed | contains("oauth-token-refresh"))' "summary search did not recover pre-compact decision"
172
+
173
+ memory_search=$(cd "$repo" && "$EAGLE_BIN" search --memories "oauth token refresh compaction" --json)
174
+ assert_json "$memory_search" 'length >= 1 and (.[0].memory_name | contains("oauth-token-refresh"))' "memory search did not recover mirrored memory"
175
+
176
+ task_search=$(cd "$repo" && "$EAGLE_BIN" search --tasks "oauth token refresh compaction" --json)
177
+ assert_json "$task_search" 'length >= 1 and (.[0].subject | contains("oauth-token-refresh"))' "task search did not recover active task"
178
+
179
+ feature_show=$(cd "$repo" && "$EAGLE_BIN" feature show "oauth-token-refresh")
180
+ assert_contains "$feature_show" "src/auth-client.ts" "feature show did not recover feature file binding"
181
+
182
+ compaction_json=$(cd "$repo" && "$EAGLE_BIN" compaction --json)
183
+ assert_json "$compaction_json" '
184
+ .status == "ok"
185
+ and .readiness == "strong"
186
+ and .metrics.enriched_summaries >= 3
187
+ and .metrics.active_tasks >= 1
188
+ and .metrics.durable_memories >= 1
189
+ and .metrics.active_features >= 1
190
+ and .metrics.semantic_graph_nodes >= 5
191
+ ' "compaction --json did not report durable survival metrics"
192
+
193
+ eagle_upsert_session "$post_session" "$project" "$repo" "test-model" "test" "codex" >/dev/null
194
+ hook_input=$(jq -nc \
195
+ --arg sid "$post_session" \
196
+ --arg cwd "$repo" \
197
+ --arg prompt "Before editing auth client restore oauth-token-refresh compaction survival context" \
198
+ '{session_id:$sid, cwd:$cwd, prompt:$prompt}')
199
+
200
+ hook_output=$(EAGLE_MEM_PROJECT="$project" EAGLE_MEM_DIR="$EAGLE_MEM_DIR" bash "$ROOT_DIR/hooks/user-prompt-submit.sh" <<< "$hook_input")
201
+ assert_contains "$hook_output" "Eagle Mem recalls" "post-compact prompt did not receive recalled memory context"
202
+ assert_contains "$hook_output" "Relevant Code" "post-compact prompt did not receive indexed code context"
203
+
204
+ event_json=$(eagle_db_json "SELECT summary_matches, memory_matches, code_matches, summary_refs, memory_refs, code_refs, status
205
+ FROM recall_events
206
+ WHERE session_id = '$post_session'
207
+ ORDER BY id DESC
208
+ LIMIT 1;")
209
+ assert_json "$event_json" '
210
+ length == 1
211
+ and .[0].status == "ok"
212
+ and .[0].summary_matches >= 1
213
+ and .[0].memory_matches >= 1
214
+ and .[0].code_matches >= 1
215
+ and ((.[0].summary_refs | fromjson) | length >= 1)
216
+ and ((.[0].memory_refs | fromjson) | length >= 1)
217
+ and ((.[0].code_refs | fromjson) | length >= 1)
218
+ ' "post-compact recall event did not capture expected refs"
219
+
220
+ replay_json=$(cd "$repo" && "$EAGLE_BIN" replay "$post_session" --json)
221
+ assert_json "$replay_json" '
222
+ .status == "ok"
223
+ and .session_id == "compact-post-session"
224
+ and (.recall_events | length >= 1)
225
+ and (.recall_events[0].summary_refs | length >= 1)
226
+ and (.recall_events[0].memory_refs | length >= 1)
227
+ and (.recall_events[0].code_refs | length >= 1)
228
+ ' "replay did not preserve post-compact recall evidence"
229
+
230
+ neighbors_output=$(cd "$repo" && "$EAGLE_BIN" graph neighbors "oauth-token-refresh" | strip_ansi)
231
+ assert_contains "$neighbors_output" "src/auth-client.ts" "graph neighbors did not expose feature-file survival edge"
232
+ assert_contains "$neighbors_output" "compact-task-oauth" "graph neighbors did not expose task-feature survival edge"
233
+
234
+ compaction_after_recall=$(cd "$repo" && "$EAGLE_BIN" compaction --json)
235
+ assert_json "$compaction_after_recall" '.metrics.recall_events >= 1' "compaction metrics did not count post-compact recall event"
236
+
237
+ echo "compaction survival matrix regressions passed"
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env bash
2
+ # Dashboard regression: generated HTML should expose the human product surface
3
+ # promised by Eagle Mem's memory layer.
4
+ set -euo pipefail
5
+
6
+ ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
7
+
8
+ tmp_dir=$(mktemp -d "$ROOT_DIR/.tmp-dashboard.XXXXXX")
9
+ trap 'rm -rf "$tmp_dir"' EXIT
10
+
11
+ export HOME="$tmp_dir/home"
12
+ export EAGLE_MEM_DIR="$tmp_dir/eagle-mem"
13
+ mkdir -p "$HOME" "$EAGLE_MEM_DIR"
14
+
15
+ . "$ROOT_DIR/lib/common.sh"
16
+ "$ROOT_DIR/db/migrate.sh" >/dev/null
17
+ . "$ROOT_DIR/lib/db.sh"
18
+
19
+ repo="$tmp_dir/repo"
20
+ mkdir -p "$repo"
21
+ project="project-dashboard"
22
+ project_sql=$(eagle_sql_escape "$project")
23
+
24
+ eagle_upsert_session "dash-session-1" "$project" "$repo" "test-model" "test" "codex" >/dev/null
25
+ eagle_insert_summary \
26
+ "dash-session-1" \
27
+ "$project" \
28
+ "Review dashboard memory surface" \
29
+ "Read docs and hooks" \
30
+ "Dashboard should reveal recall events" \
31
+ "Generated dashboard" \
32
+ "" \
33
+ "[]" \
34
+ "[]" \
35
+ "dashboard note" \
36
+ "Keep HTML for humans" \
37
+ "" \
38
+ "scripts/dashboard.sh" \
39
+ "codex" >/dev/null
40
+
41
+ eagle_upsert_overview "$project" "Dashboard project overview with current architecture and risks." "test" >/dev/null
42
+ eagle_insert_observation "dash-session-1" "$project" "Edit" "edited dashboard" "[]" "[\"scripts/dashboard.sh\"]" "" "" "" "codex" >/dev/null
43
+ eagle_insert_recall_event \
44
+ "dash-session-1" "$project" "$repo" "codex" \
45
+ "Show me what Eagle recalled for dashboard work" \
46
+ "eagle OR dashboard OR recall" \
47
+ 2 1 3 960 "ok" "" >/dev/null
48
+
49
+ eagle_db "INSERT INTO agent_tasks (project, source_session_id, source_task_id, subject, description, active_form, status, content_hash, origin_agent)
50
+ VALUES ('$project_sql', 'dash-session-1', 'task-dashboard', 'Build dashboard surface',
51
+ 'Expose memory inspection in HTML', 'Build dashboard surface', 'in_progress',
52
+ 'dashboard-task-hash', 'codex');" >/dev/null
53
+
54
+ eagle_graph_add_node "$project" "project" "$project" "Dashboard graph project" "" >/dev/null
55
+ eagle_graph_add_node "$project" "file" "scripts/dashboard.sh" "" "$repo/scripts/dashboard.sh" >/dev/null
56
+
57
+ dashboard_file="$tmp_dir/dashboard/index.html"
58
+ dashboard_json=$(cd "$repo" && EAGLE_MEM_DIR="$EAGLE_MEM_DIR" "$ROOT_DIR/bin/eagle-mem" dashboard --project "$project" --output "$dashboard_file" --json)
59
+
60
+ printf '%s' "$dashboard_json" | jq -e '
61
+ .status == "ok"
62
+ and .output != ""
63
+ and .counts.summaries >= 1
64
+ and .counts.recall_events >= 1
65
+ and .counts.files >= 1
66
+ and .counts.active_tasks >= 1
67
+ and .counts.graph_node_types >= 1
68
+ ' >/dev/null || {
69
+ echo "dashboard --json did not report expected counts" >&2
70
+ echo "$dashboard_json" >&2
71
+ exit 1
72
+ }
73
+
74
+ [ -f "$dashboard_file" ] || {
75
+ echo "dashboard output file was not created" >&2
76
+ exit 1
77
+ }
78
+
79
+ for needle in \
80
+ "Eagle Mem Dashboard" \
81
+ "Project Brain" \
82
+ "Recall Inspector" \
83
+ "Timeline" \
84
+ "File Intelligence" \
85
+ "Agent Comparison" \
86
+ "Active Tasks" \
87
+ "Graph Readiness" \
88
+ "Show me what Eagle recalled" \
89
+ "scripts/dashboard.sh"; do
90
+ if ! grep -q "$needle" "$dashboard_file"; then
91
+ echo "dashboard missing expected content: $needle" >&2
92
+ exit 1
93
+ fi
94
+ done
95
+
96
+ echo "dashboard regressions passed"
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env bash
2
+ # Hooks should emit a general Eagle event log so users can inspect what Eagle
3
+ # did, not only the final memory rows.
4
+ set -euo pipefail
5
+
6
+ ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
7
+
8
+ tmp_dir=$(mktemp -d "$ROOT_DIR/.tmp-eagle-events.XXXXXX")
9
+ trap 'rm -rf "$tmp_dir"' EXIT
10
+
11
+ export HOME="$tmp_dir/home"
12
+ export EAGLE_MEM_DIR="$tmp_dir/eagle-mem"
13
+ mkdir -p "$HOME" "$EAGLE_MEM_DIR"
14
+
15
+ . "$ROOT_DIR/lib/common.sh"
16
+ "$ROOT_DIR/db/migrate.sh" >/dev/null
17
+ . "$ROOT_DIR/lib/db.sh"
18
+
19
+ repo="$tmp_dir/repo"
20
+ mkdir -p "$repo"
21
+ project="project-events"
22
+
23
+ eagle_upsert_session "seed-events-session" "$project" "$repo" "test-model" "test" "codex" >/dev/null
24
+ eagle_insert_summary \
25
+ "seed-events-session" \
26
+ "$project" \
27
+ "Implement hook event logging" \
28
+ "Read hook observability code" \
29
+ "Hooks need visible events" \
30
+ "Added event logging" \
31
+ "" \
32
+ "[]" \
33
+ "[]" \
34
+ "event note" \
35
+ "Record hook_started and hook_completed" \
36
+ "" \
37
+ "hooks/user-prompt-submit.sh" \
38
+ "codex" >/dev/null
39
+
40
+ hook_input=$(jq -nc \
41
+ --arg sid "event-hook-session" \
42
+ --arg cwd "$repo" \
43
+ --arg prompt "Please review hook event logging before editing observability" \
44
+ '{session_id:$sid, cwd:$cwd, prompt:$prompt, hook_event_name:"UserPromptSubmit"}')
45
+
46
+ hook_output=$(EAGLE_MEM_PROJECT="$project" EAGLE_MEM_DIR="$EAGLE_MEM_DIR" EAGLE_AGENT_SOURCE="codex" bash "$ROOT_DIR/hooks/user-prompt-submit.sh" <<< "$hook_input")
47
+ case "$hook_output" in
48
+ *"Eagle Mem recalls"*|*"Relevant"*) ;;
49
+ *)
50
+ echo "UserPromptSubmit did not inject event-test context" >&2
51
+ echo "$hook_output" >&2
52
+ exit 1
53
+ ;;
54
+ esac
55
+
56
+ events_json=$(eagle_db_json "SELECT event_type, hook_event_name, status, detail_json
57
+ FROM eagle_events
58
+ WHERE session_id = 'event-hook-session'
59
+ ORDER BY id ASC;")
60
+
61
+ printf '%s' "$events_json" | jq -e '
62
+ length >= 3
63
+ and any(.[]; .event_type == "hook_started" and .hook_event_name == "UserPromptSubmit")
64
+ and any(.[]; .event_type == "context_injected" and .hook_event_name == "UserPromptSubmit")
65
+ and any(.[]; .event_type == "hook_completed" and .hook_event_name == "UserPromptSubmit" and .status == "ok")
66
+ and any(.[]; .event_type == "hook_completed" and ((.detail_json | fromjson).injected_chars > 0))
67
+ ' >/dev/null || {
68
+ echo "eagle_events did not capture expected UserPromptSubmit lifecycle" >&2
69
+ echo "$events_json" >&2
70
+ exit 1
71
+ }
72
+
73
+ inspect_json=$(cd "$repo" && EAGLE_MEM_DIR="$EAGLE_MEM_DIR" "$ROOT_DIR/bin/eagle-mem" inspect events --project "$project" --session event-hook-session --json)
74
+ printf '%s' "$inspect_json" | jq -e '
75
+ .status == "ok"
76
+ and .action == "events"
77
+ and (.events | length >= 3)
78
+ and any(.events[]; .event_type == "context_injected" and .detail.injected_chars > 0)
79
+ ' >/dev/null || {
80
+ echo "inspect events --json did not return event details" >&2
81
+ echo "$inspect_json" >&2
82
+ exit 1
83
+ }
84
+
85
+ inspect_text=$(cd "$repo" && EAGLE_MEM_DIR="$EAGLE_MEM_DIR" "$ROOT_DIR/bin/eagle-mem" inspect events --project "$project" --session event-hook-session --last)
86
+ case "$inspect_text" in
87
+ *"Event Inspector"*hook_completed*UserPromptSubmit*) ;;
88
+ *)
89
+ echo "inspect events --last did not render hook event" >&2
90
+ echo "$inspect_text" >&2
91
+ exit 1
92
+ ;;
93
+ esac
94
+
95
+ echo "eagle event regressions passed"
96
+
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env bash
2
+ # Regression coverage for OpenCode plugin and skill registration helpers.
3
+ set -euo pipefail
4
+
5
+ ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
6
+ tmp_dir=$(mktemp -d "$ROOT_DIR/.tmp-opencode-hooks.XXXXXX")
7
+ trap 'rm -rf "$tmp_dir"' EXIT
8
+
9
+ export HOME="$tmp_dir/home"
10
+ export EAGLE_MEM_DIR="$tmp_dir/eagle-mem"
11
+ mkdir -p "$HOME" "$EAGLE_MEM_DIR"
12
+
13
+ . "$ROOT_DIR/scripts/style.sh"
14
+ . "$ROOT_DIR/lib/common.sh"
15
+ . "$ROOT_DIR/lib/opencode-hooks.sh"
16
+
17
+ fail() {
18
+ echo "opencode hooks config test failed: $*" >&2
19
+ exit 1
20
+ }
21
+
22
+ first_skill=$(find "$ROOT_DIR/skills" -mindepth 1 -maxdepth 1 -type d -name "eagle-mem-*" | sort | head -n 1)
23
+ [ -n "$first_skill" ] || fail "no Eagle Mem skills found for OpenCode symlink test"
24
+ first_skill_name=$(basename "$first_skill")
25
+
26
+ mkdir -p "$EAGLE_OPENCODE_DIR"
27
+ eagle_opencode_detected || fail "OpenCode should be detected when config directory exists"
28
+
29
+ eagle_install_opencode_plugin "$ROOT_DIR" "0" >/dev/null
30
+ [ -f "$EAGLE_OPENCODE_PLUGIN" ] || fail "plugin was not installed"
31
+ grep -q "EAGLE_MEM_OPENCODE_PLUGIN" "$EAGLE_OPENCODE_PLUGIN" || fail "installed plugin is missing Eagle Mem ownership marker"
32
+ [ "$(eagle_opencode_plugin_state)" = "registered" ] || fail "plugin state should be registered"
33
+
34
+ eagle_install_opencode_plugin "$ROOT_DIR" "0" >/dev/null
35
+ [ "$(eagle_opencode_plugin_state)" = "registered" ] || fail "plugin reinstall should be idempotent"
36
+
37
+ eagle_install_opencode_skills "$ROOT_DIR" "0" >/dev/null
38
+ [ -L "$EAGLE_OPENCODE_SKILLS_DIR/$first_skill_name" ] || fail "OpenCode skill symlink was not created"
39
+ mkdir -p "$EAGLE_OPENCODE_SKILLS_DIR/eagle-mem-user-directory"
40
+
41
+ eagle_remove_opencode_plugin || fail "owned plugin should be removable"
42
+ [ ! -f "$EAGLE_OPENCODE_PLUGIN" ] || fail "owned plugin still exists after removal"
43
+
44
+ eagle_remove_opencode_skills >/dev/null || true
45
+ [ ! -e "$EAGLE_OPENCODE_SKILLS_DIR/$first_skill_name" ] || fail "OpenCode skill symlink still exists after removal"
46
+ [ -d "$EAGLE_OPENCODE_SKILLS_DIR/eagle-mem-user-directory" ] || fail "OpenCode skill removal deleted a real directory"
47
+
48
+ mkdir -p "$EAGLE_OPENCODE_PLUGINS_DIR"
49
+ printf '%s\n' "custom opencode plugin" > "$EAGLE_OPENCODE_PLUGIN"
50
+ if eagle_install_opencode_plugin "$ROOT_DIR" "0" >/dev/null 2>&1; then
51
+ fail "custom OpenCode plugin should not be overwritten"
52
+ fi
53
+ grep -q "custom opencode plugin" "$EAGLE_OPENCODE_PLUGIN" || fail "custom plugin contents were overwritten"
54
+ [ "$(eagle_opencode_plugin_state)" = "custom" ] || fail "custom plugin state should be reported"
55
+
56
+ echo "opencode hooks config test passed"