eagle-mem 4.10.13 → 4.12.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 (74) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +22 -22
  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/db/044_summary_capture_source.sql +12 -0
  11. package/docs/agent-compatibility/README.md +38 -0
  12. package/docs/agent-compatibility/claude-code.md +58 -0
  13. package/docs/agent-compatibility/codex.md +57 -0
  14. package/docs/agent-compatibility/opencode.md +72 -0
  15. package/hooks/post-tool-use.sh +8 -0
  16. package/hooks/pre-tool-use.sh +10 -1
  17. package/hooks/session-end.sh +3 -0
  18. package/hooks/session-start.sh +15 -17
  19. package/hooks/stop.sh +34 -5
  20. package/hooks/user-prompt-submit.sh +85 -10
  21. package/integrations/opencode_eagle_mem_plugin.js +387 -0
  22. package/lib/codex-hooks.sh +13 -6
  23. package/lib/common.sh +77 -7
  24. package/lib/db-events.sh +89 -0
  25. package/lib/db-graph.sh +154 -0
  26. package/lib/db-observations.sh +34 -0
  27. package/lib/db-orchestration.sh +149 -0
  28. package/lib/db-summaries.sh +70 -3
  29. package/lib/db.sh +2 -0
  30. package/lib/hooks.sh +41 -7
  31. package/lib/opencode-hooks.sh +105 -0
  32. package/lib/provider.sh +2 -2
  33. package/package.json +5 -2
  34. package/scripts/compaction.sh +109 -9
  35. package/scripts/dashboard.sh +372 -0
  36. package/scripts/doctor.sh +30 -3
  37. package/scripts/enrich-summary.sh +8 -2
  38. package/scripts/health.sh +40 -2
  39. package/scripts/help.sh +10 -2
  40. package/scripts/inspect.sh +285 -0
  41. package/scripts/install.sh +36 -7
  42. package/scripts/memories.sh +13 -0
  43. package/scripts/repair.sh +187 -0
  44. package/scripts/replay.sh +248 -0
  45. package/scripts/search.sh +44 -3
  46. package/scripts/session.sh +155 -18
  47. package/scripts/statusline-em.sh +34 -7
  48. package/scripts/tasks.sh +34 -0
  49. package/scripts/test.sh +13 -0
  50. package/scripts/uninstall.sh +9 -0
  51. package/scripts/update.sh +21 -2
  52. package/tests/fixtures/agent-hooks/claude-statusline.json +32 -0
  53. package/tests/fixtures/agent-hooks/claude-user-prompt-submit.json +9 -0
  54. package/tests/fixtures/agent-hooks/codex-pre-tool-use.json +10 -0
  55. package/tests/fixtures/agent-hooks/codex-user-prompt-submit.json +7 -0
  56. package/tests/fixtures/agent-hooks/opencode-chat-message.json +36 -0
  57. package/tests/fixtures/agent-hooks/opencode-session-compacting.json +9 -0
  58. package/tests/fixtures/agent-hooks/opencode-todo-updated.json +13 -0
  59. package/tests/fixtures/agent-hooks/opencode-tool-execute-after.json +15 -0
  60. package/tests/fixtures/agent-hooks/opencode-tool-execute-before.json +12 -0
  61. package/tests/test_agent_compatibility_docs_gate.sh +123 -0
  62. package/tests/test_auto_orchestration_detection.sh +109 -0
  63. package/tests/test_claude_stop_hook_registration.sh +56 -0
  64. package/tests/test_clean_session_capture.sh +105 -0
  65. package/tests/test_codex_hooks_config.sh +73 -0
  66. package/tests/test_compaction_survival_matrix.sh +237 -0
  67. package/tests/test_dashboard.sh +96 -0
  68. package/tests/test_eagle_events.sh +96 -0
  69. package/tests/test_opencode_hooks_config.sh +56 -0
  70. package/tests/test_opencode_plugin_adapter.sh +202 -0
  71. package/tests/test_recall_observability.sh +144 -0
  72. package/tests/test_repair.sh +63 -0
  73. package/tests/test_rust_migration_plan.sh +75 -0
  74. package/tests/test_trust_surfaces.sh +123 -0
@@ -0,0 +1,13 @@
1
+ {
2
+ "type": "todo.updated",
3
+ "properties": {
4
+ "sessionID": "opencode-session-fixture",
5
+ "todos": [
6
+ {
7
+ "content": "Verify OAuth token refresh after OpenCode compact",
8
+ "status": "in_progress",
9
+ "priority": "high"
10
+ }
11
+ ]
12
+ }
13
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "input": {
3
+ "tool": "read",
4
+ "sessionID": "opencode-session-fixture",
5
+ "callID": "opencode-call-fixture",
6
+ "args": {
7
+ "filePath": "/tmp/eagle-mem/project/lib/auth.ts"
8
+ }
9
+ },
10
+ "output": {
11
+ "title": "Read lib/auth.ts",
12
+ "output": "export function refreshToken() {}",
13
+ "metadata": {}
14
+ }
15
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "input": {
3
+ "tool": "bash",
4
+ "sessionID": "opencode-session-fixture",
5
+ "callID": "opencode-call-fixture"
6
+ },
7
+ "output": {
8
+ "args": {
9
+ "command": "git push origin main"
10
+ }
11
+ }
12
+ }
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env bash
2
+ # Guard against stale agent lifecycle assumptions when editing hook, statusline,
3
+ # config-installation, or instruction-file integration code.
4
+ set -euo pipefail
5
+
6
+ ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
7
+ DOC_DIR="$ROOT_DIR/docs/agent-compatibility"
8
+ FIXTURE_DIR="$ROOT_DIR/tests/fixtures/agent-hooks"
9
+
10
+ fail() {
11
+ echo "agent compatibility docs gate failed: $*" >&2
12
+ exit 1
13
+ }
14
+
15
+ require_file() {
16
+ local file="$1"
17
+ [ -f "$file" ] || fail "missing required file: ${file#$ROOT_DIR/}"
18
+ }
19
+
20
+ require_contains() {
21
+ local file="$1"
22
+ local pattern="$2"
23
+ local label="$3"
24
+ if ! grep -Eq "$pattern" "$file"; then
25
+ fail "${file#$ROOT_DIR/} does not mention $label"
26
+ fi
27
+ }
28
+
29
+ require_json() {
30
+ local file="$1"
31
+ jq -e . "$file" >/dev/null || fail "invalid JSON fixture: ${file#$ROOT_DIR/}"
32
+ }
33
+
34
+ require_file "$DOC_DIR/README.md"
35
+ require_file "$DOC_DIR/claude-code.md"
36
+ require_file "$DOC_DIR/codex.md"
37
+ require_file "$DOC_DIR/opencode.md"
38
+ require_file "$FIXTURE_DIR/claude-user-prompt-submit.json"
39
+ require_file "$FIXTURE_DIR/claude-statusline.json"
40
+ require_file "$FIXTURE_DIR/codex-user-prompt-submit.json"
41
+ require_file "$FIXTURE_DIR/codex-pre-tool-use.json"
42
+ require_file "$FIXTURE_DIR/opencode-chat-message.json"
43
+ require_file "$FIXTURE_DIR/opencode-tool-execute-before.json"
44
+ require_file "$FIXTURE_DIR/opencode-tool-execute-after.json"
45
+ require_file "$FIXTURE_DIR/opencode-todo-updated.json"
46
+ require_file "$FIXTURE_DIR/opencode-session-compacting.json"
47
+
48
+ require_json "$FIXTURE_DIR/claude-user-prompt-submit.json"
49
+ require_json "$FIXTURE_DIR/claude-statusline.json"
50
+ require_json "$FIXTURE_DIR/codex-user-prompt-submit.json"
51
+ require_json "$FIXTURE_DIR/codex-pre-tool-use.json"
52
+ require_json "$FIXTURE_DIR/opencode-chat-message.json"
53
+ require_json "$FIXTURE_DIR/opencode-tool-execute-before.json"
54
+ require_json "$FIXTURE_DIR/opencode-tool-execute-after.json"
55
+ require_json "$FIXTURE_DIR/opencode-todo-updated.json"
56
+ require_json "$FIXTURE_DIR/opencode-session-compacting.json"
57
+
58
+ require_contains "$DOC_DIR/README.md" "Before modifying" "the required pre-edit workflow"
59
+ require_contains "$DOC_DIR/README.md" "official documentation" "official documentation"
60
+ require_contains "$DOC_DIR/README.md" "fixture|golden-test" "fixture or golden-test coverage"
61
+
62
+ require_contains "$DOC_DIR/claude-code.md" "Last verified: [0-9]{4}-[0-9]{2}-[0-9]{2}" "a Last verified date"
63
+ require_contains "$DOC_DIR/claude-code.md" "https://code\\.claude\\.com/docs/en/hooks" "Claude Code hooks source"
64
+ require_contains "$DOC_DIR/claude-code.md" "https://code\\.claude\\.com/docs/en/statusline" "Claude Code statusline source"
65
+ require_contains "$DOC_DIR/claude-code.md" "UserPromptSubmit" "Claude UserPromptSubmit"
66
+ require_contains "$DOC_DIR/claude-code.md" "PreCompact" "Claude PreCompact"
67
+ require_contains "$DOC_DIR/claude-code.md" "PostCompact" "Claude PostCompact"
68
+ require_contains "$DOC_DIR/claude-code.md" "statusLine" "Claude statusLine config"
69
+ require_contains "$DOC_DIR/claude-code.md" "tests/fixtures/agent-hooks/claude-statusline\\.json" "Claude statusline fixture"
70
+
71
+ require_contains "$DOC_DIR/codex.md" "Last verified: [0-9]{4}-[0-9]{2}-[0-9]{2}" "a Last verified date"
72
+ require_contains "$DOC_DIR/codex.md" "https://developers\\.openai\\.com/codex/codex-manual\\.md" "Codex manual source"
73
+ require_contains "$DOC_DIR/codex.md" "https://developers\\.openai\\.com/codex/hooks\\.md" "Codex hooks source"
74
+ require_contains "$DOC_DIR/codex.md" "AGENTS\\.md" "Codex AGENTS.md behavior"
75
+ require_contains "$DOC_DIR/codex.md" "features\\.hooks" "canonical Codex hooks feature flag"
76
+ require_contains "$DOC_DIR/codex.md" "deprecated alias" "deprecated Codex hook alias"
77
+ require_contains "$DOC_DIR/codex.md" "UserPromptSubmit" "Codex UserPromptSubmit"
78
+ require_contains "$DOC_DIR/codex.md" "tests/fixtures/agent-hooks/codex-pre-tool-use\\.json" "Codex PreToolUse fixture"
79
+
80
+ require_contains "$DOC_DIR/opencode.md" "Last verified: [0-9]{4}-[0-9]{2}-[0-9]{2}" "an OpenCode Last verified date"
81
+ require_contains "$DOC_DIR/opencode.md" "https://opencode\\.ai/docs/plugins/" "OpenCode plugins source"
82
+ require_contains "$DOC_DIR/opencode.md" "https://opencode\\.ai/docs/config/" "OpenCode config source"
83
+ require_contains "$DOC_DIR/opencode.md" "https://opencode\\.ai/config\\.json" "OpenCode config schema source"
84
+ require_contains "$DOC_DIR/opencode.md" "~/.config/opencode/plugins/" "OpenCode global local plugin directory"
85
+ require_contains "$DOC_DIR/opencode.md" "chat\\.message" "OpenCode chat.message hook"
86
+ require_contains "$DOC_DIR/opencode.md" "tool\\.execute\\.before" "OpenCode tool.execute.before hook"
87
+ require_contains "$DOC_DIR/opencode.md" "tool\\.execute\\.after" "OpenCode tool.execute.after hook"
88
+ require_contains "$DOC_DIR/opencode.md" "todo\\.updated" "OpenCode todo.updated event"
89
+ require_contains "$DOC_DIR/opencode.md" "experimental\\.session\\.compacting" "OpenCode compaction hook"
90
+ require_contains "$DOC_DIR/opencode.md" "opencode --pure" "OpenCode pure-mode caveat"
91
+ require_contains "$DOC_DIR/opencode.md" "tests/fixtures/agent-hooks/opencode-tool-execute-before\\.json" "OpenCode PreToolUse fixture"
92
+ require_contains "$DOC_DIR/opencode.md" "tests/test_opencode_plugin_adapter\\.sh" "OpenCode adapter regression test"
93
+
94
+ if grep -R "features\\.codex_hooks" "$ROOT_DIR/lib" "$ROOT_DIR/scripts" "$ROOT_DIR/hooks" "$ROOT_DIR/bin" >/dev/null 2>&1; then
95
+ fail "implementation still uses deprecated Codex features.codex_hooks instead of features.hooks"
96
+ fi
97
+
98
+ if grep -R "codex_hooks[[:space:]]*=[[:space:]]*true" "$ROOT_DIR/lib" "$ROOT_DIR/scripts" "$ROOT_DIR/hooks" "$ROOT_DIR/bin" >/dev/null 2>&1; then
99
+ fail "implementation still writes deprecated Codex codex_hooks config instead of hooks"
100
+ fi
101
+
102
+ changed_sensitive=$(
103
+ {
104
+ git -C "$ROOT_DIR" diff --name-only HEAD -- 2>/dev/null || true
105
+ git -C "$ROOT_DIR" ls-files --others --exclude-standard 2>/dev/null || true
106
+ } | awk '
107
+ /^(hooks\/.*\.sh|lib\/hooks\.sh|lib\/codex-hooks\.sh|lib\/opencode-hooks\.sh|integrations\/opencode_eagle_mem_plugin\.js|scripts\/install\.sh|scripts\/update\.sh|scripts\/uninstall\.sh|scripts\/doctor\.sh|scripts\/statusline-em\.sh|lib\/common\.sh|AGENTS\.md|CLAUDE\.md)$/ { print }
108
+ ' | sort -u
109
+ )
110
+
111
+ if [ -n "$changed_sensitive" ]; then
112
+ changed_evidence=$(
113
+ {
114
+ git -C "$ROOT_DIR" diff --name-only HEAD -- 2>/dev/null || true
115
+ git -C "$ROOT_DIR" ls-files --others --exclude-standard 2>/dev/null || true
116
+ } | awk '
117
+ /^(docs\/agent-compatibility\/.*\.md|tests\/fixtures\/agent-hooks\/.*\.json|tests\/test_agent_compatibility_docs_gate\.sh)$/ { print }
118
+ ' | sort -u
119
+ )
120
+ [ -n "$changed_evidence" ] || fail "sensitive integration files changed without updated compatibility docs or fixtures: $changed_sensitive"
121
+ fi
122
+
123
+ echo "agent compatibility docs gate passed"
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env bash
2
+ # Broad prompts should create durable orchestration state automatically. This
3
+ # proves Eagle detects the need for lanes instead of relying only on agent
4
+ # obedience to instructions.
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-auto-orchestration.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
+ project="project-auto-orchestration"
44
+ repo="$HOME/$project"
45
+ mkdir -p "$repo"
46
+
47
+ hook_input=$(jq -nc \
48
+ --arg sid "auto-orchestration-session" \
49
+ --arg cwd "$repo" \
50
+ --arg prompt "Please plan and get started on a broad full codebase release. Split the work into lanes, coordinate workers, implement the fixes, and verify the ship path." \
51
+ '{session_id:$sid, cwd:$cwd, prompt:$prompt, transcript_path:"/tmp/transcript.jsonl"}')
52
+
53
+ 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")
54
+ assert_contains "$hook_output" "created durable orchestration 'auto'" "broad prompt did not report auto orchestration creation"
55
+ assert_contains "$hook_output" "Continue from the durable lanes" "hook did not tell the agent to continue from durable lanes"
56
+
57
+ orchestration_json=$(cd "$repo" && EAGLE_MEM_PROJECT="$project" EAGLE_MEM_DIR="$EAGLE_MEM_DIR" "$EAGLE_BIN" orchestrate --name auto --json)
58
+ assert_json "$orchestration_json" '
59
+ length == 3
60
+ and ([.[].lane_key] | sort == ["implementation","scope","verification"])
61
+ and all(.[]; .status == "pending")
62
+ ' "auto orchestration did not create the expected pending lanes"
63
+
64
+ task_json=$(cd "$repo" && EAGLE_MEM_PROJECT="$project" EAGLE_MEM_DIR="$EAGLE_MEM_DIR" "$EAGLE_BIN" tasks --json)
65
+ assert_json "$task_json" '
66
+ length >= 3
67
+ and ([.[] | select(.source_task_id | startswith("lane-auto-"))] | length == 3)
68
+ ' "auto orchestration did not mirror lanes into durable tasks"
69
+
70
+ event_json=$(eagle_db_json "SELECT session_id, project, agent, prompt_snippet, orchestration_name, lanes, status
71
+ FROM orchestration_auto_events
72
+ WHERE project = '$project'
73
+ ORDER BY id DESC;")
74
+ assert_json "$event_json" '
75
+ length == 1
76
+ and .[0].session_id == "auto-orchestration-session"
77
+ and .[0].orchestration_name == "auto"
78
+ and .[0].status == "created"
79
+ and ((.[0].lanes | fromjson) | length == 3)
80
+ ' "auto orchestration event was not recorded"
81
+
82
+ hook_output_again=$(EAGLE_MEM_PROJECT="$project" EAGLE_MEM_DIR="$EAGLE_MEM_DIR" EAGLE_AGENT_SOURCE="codex" bash "$ROOT_DIR/hooks/user-prompt-submit.sh" <<< "$hook_input")
83
+ assert_contains "$hook_output_again" "created durable orchestration 'auto'" "repeated broad prompt did not reuse auto orchestration"
84
+
85
+ lane_count=$(eagle_db "SELECT COUNT(*)
86
+ FROM orchestration_lanes l
87
+ JOIN orchestrations o ON o.id = l.orchestration_id
88
+ WHERE o.project = '$project'
89
+ AND o.name = 'auto';")
90
+ task_count=$(eagle_db "SELECT COUNT(*)
91
+ FROM agent_tasks
92
+ WHERE project = '$project'
93
+ AND source_task_id LIKE 'lane-auto-%';")
94
+ event_count=$(eagle_db "SELECT COUNT(*) FROM orchestration_auto_events WHERE project = '$project';")
95
+
96
+ [ "$lane_count" = "3" ] || { echo "expected 3 auto lanes after repeat, got $lane_count" >&2; exit 1; }
97
+ [ "$task_count" = "3" ] || { echo "expected 3 auto tasks after repeat, got $task_count" >&2; exit 1; }
98
+ [ "$event_count" = "2" ] || { echo "expected 2 auto events after repeat, got $event_count" >&2; exit 1; }
99
+
100
+ quiet_input=$(jq -nc \
101
+ --arg sid "quiet-session" \
102
+ --arg cwd "$repo" \
103
+ --arg prompt "Please inspect the config value." \
104
+ '{session_id:$sid, cwd:$cwd, prompt:$prompt}')
105
+ EAGLE_MEM_PROJECT="$project" EAGLE_MEM_DIR="$EAGLE_MEM_DIR" EAGLE_AGENT_SOURCE="codex" bash "$ROOT_DIR/hooks/user-prompt-submit.sh" <<< "$quiet_input" >/dev/null
106
+ quiet_event_count=$(eagle_db "SELECT COUNT(*) FROM orchestration_auto_events WHERE project = '$project';")
107
+ [ "$quiet_event_count" = "2" ] || { echo "quiet prompt should not create auto orchestration event" >&2; exit 1; }
108
+
109
+ echo "auto orchestration detection regressions passed"
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env bash
2
+ # Regression coverage for Claude Stop hook registration.
3
+ set -euo pipefail
4
+
5
+ ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
6
+ tmp_dir=$(mktemp -d "$ROOT_DIR/.tmp-claude-stop.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/hooks"
12
+
13
+ . "$ROOT_DIR/scripts/style.sh"
14
+ . "$ROOT_DIR/lib/common.sh"
15
+ . "$ROOT_DIR/lib/hooks.sh"
16
+
17
+ settings="$tmp_dir/settings.json"
18
+ old_stop="$EAGLE_MEM_DIR/hooks/stop.sh"
19
+ new_stop="bash \"$EAGLE_MEM_DIR/hooks/stop.sh\""
20
+
21
+ fail() {
22
+ echo "claude stop hook registration test failed: $*" >&2
23
+ exit 1
24
+ }
25
+
26
+ cat > "$settings" <<JSON
27
+ {
28
+ "hooks": {
29
+ "Stop": [
30
+ {
31
+ "hooks": [
32
+ {
33
+ "type": "command",
34
+ "command": "$old_stop"
35
+ }
36
+ ]
37
+ }
38
+ ]
39
+ }
40
+ }
41
+ JSON
42
+
43
+ eagle_clean_hook_entries "$settings" "Stop" "$old_stop"
44
+ eagle_patch_hook "$settings" "Stop" "" "$new_stop" >/dev/null
45
+
46
+ jq -e --arg cmd "$old_stop" 'all(.hooks.Stop[]?.hooks[]?; .command != $cmd)' "$settings" >/dev/null \
47
+ || fail "path-only Stop hook registration remained"
48
+
49
+ jq -e --arg cmd "$new_stop" '.hooks.Stop[]? | select(.matcher == null and (.hooks[]?.command == $cmd))' "$settings" >/dev/null \
50
+ || fail "bash-wrapped Stop hook registration missing"
51
+
52
+ eagle_patch_hook "$settings" "Stop" "" "$new_stop" >/dev/null
53
+ count=$(jq --arg cmd "$new_stop" '[.hooks.Stop[]?.hooks[]? | select(.command == $cmd)] | length' "$settings")
54
+ [ "$count" -eq 1 ] || fail "bash-wrapped Stop hook registration duplicated: $count"
55
+
56
+ echo "claude stop hook registration test passed"
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env bash
2
+ # ═══════════════════════════════════════════════════════════
3
+ # Eagle Mem — Clean, branded session capture regression test
4
+ # Covers: CLI-first capture, clobber prevention, backward-compat
5
+ # <eagle-summary> override, and enrichment fill-only.
6
+ # ═══════════════════════════════════════════════════════════
7
+ set -uo pipefail
8
+
9
+ ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
10
+ tmp_dir=$(mktemp -d)
11
+ trap 'rm -rf "$tmp_dir"' EXIT
12
+
13
+ export EAGLE_MEM_DIR="$tmp_dir/em"
14
+ export EAGLE_AGENT_SOURCE="claude-code"
15
+ export EAGLE_MEM_DISABLE_HOOKS=0
16
+ # Pin the project so both the CLI save and the Stop hook resolve identically
17
+ # (the temp cwd lives under /var/folders, which Eagle Mem treats as ephemeral).
18
+ export EAGLE_MEM_PROJECT="test/clean-capture"
19
+ mkdir -p "$EAGLE_MEM_DIR"
20
+
21
+ pass=0; fail=0
22
+ ok() { echo " ok: $1"; pass=$((pass+1)); }
23
+ bad() { echo " FAIL: $1" >&2; fail=$((fail+1)); }
24
+
25
+ bash "$ROOT_DIR/db/migrate.sh" >/dev/null 2>&1
26
+
27
+ . "$ROOT_DIR/lib/common.sh"
28
+ . "$ROOT_DIR/lib/db.sh"
29
+
30
+ # A real cwd for the hook payload; project is pinned via EAGLE_MEM_PROJECT.
31
+ work="$tmp_dir/work"
32
+ mkdir -p "$work"
33
+ PROJECT="$EAGLE_MEM_PROJECT"
34
+ ok "pinned project: $PROJECT"
35
+
36
+ field() { eagle_db "SELECT COALESCE($2,'') FROM summaries WHERE session_id='$1';"; }
37
+
38
+ # ── Scenario A: CLI capture then a heuristic Stop must NOT clobber it ──
39
+ SID="clean-capture-aaa111"
40
+ eagle_upsert_session "$SID" "$PROJECT" "$work" "" "startup" "claude-code"
41
+
42
+ bash "$ROOT_DIR/scripts/session.sh" save --session-id "$SID" --project "$PROJECT" \
43
+ --completed "Rich agent completed" \
44
+ --decisions "Pin v1 — why safety; Use TOTP — why no SMTP" \
45
+ --gotchas "needs PUBLIC_URL" >/dev/null 2>&1
46
+
47
+ [ "$(field "$SID" capture_source)" = "agent" ] && ok "A: capture_source=agent after CLI save" || bad "A: capture_source not agent"
48
+
49
+ # Heuristic Stop: transcript has an Edit (fills files), NO <eagle-summary> block.
50
+ tA="$tmp_dir/transcriptA.jsonl"
51
+ {
52
+ printf '%s\n' '{"type":"user","message":{"content":[{"type":"text","text":"please harden the pipeline"}]}}'
53
+ printf '%s\n' '{"type":"assistant","message":{"content":[{"type":"tool_use","name":"Edit","input":{"file_path":"'"$work"'/foo.sh"}}]}}'
54
+ printf '%s\n' '{"type":"assistant","message":{"content":[{"type":"text","text":"Done. We hardened the pipeline this session."}]}}'
55
+ } > "$tA"
56
+
57
+ stopA=$(jq -nc --arg s "$SID" --arg c "$work" --arg t "$tA" \
58
+ '{session_id:$s, cwd:$c, transcript_path:$t}')
59
+ echo "$stopA" | EAGLE_MEM_STOP_BACKGROUND_ENRICH=0 bash "$ROOT_DIR/hooks/stop.sh" >/dev/null 2>&1
60
+
61
+ [ "$(field "$SID" completed)" = "Rich agent completed" ] && ok "A: completed NOT clobbered by heuristic" || bad "A: completed was clobbered -> '$(field "$SID" completed)'"
62
+ [ "$(field "$SID" capture_source)" = "agent" ] && ok "A: capture_source still agent after Stop" || bad "A: capture_source downgraded"
63
+ case "$(field "$SID" files_modified)" in *foo.sh*) ok "A: files_modified gap-filled by heuristic" ;; *) bad "A: files_modified not filled -> '$(field "$SID" files_modified)'" ;; esac
64
+ case "$(field "$SID" decisions)" in *"Pin v1"*) ok "A: decisions preserved" ;; *) bad "A: decisions lost" ;; esac
65
+ mrows=$(eagle_db "SELECT COUNT(*) FROM summaries WHERE session_id LIKE 'manual-%';")
66
+ [ "$mrows" = "0" ] && ok "A: no stray manual-* row" || bad "A: $mrows manual-* rows created"
67
+ sstatus=$(eagle_db "SELECT status FROM sessions WHERE id='$SID';")
68
+ [ "$sstatus" = "active" ] && ok "A: live session still active (not ended)" || bad "A: session status=$sstatus"
69
+
70
+ # ── Scenario B: a <eagle-summary> block still overrides (backward compat) ──
71
+ SIDB="clean-capture-bbb222"
72
+ eagle_upsert_session "$SIDB" "$PROJECT" "$work" "" "startup" "claude-code"
73
+ tB="$tmp_dir/transcriptB.jsonl"
74
+ blocktext='Summary below.
75
+
76
+ <eagle-summary>
77
+ request: legacy block path
78
+ completed: shipped via block
79
+ decisions: keep parser — why backward compat
80
+ gotchas: none
81
+ </eagle-summary>'
82
+ {
83
+ printf '%s\n' '{"type":"user","message":{"content":[{"type":"text","text":"do the legacy thing"}]}}'
84
+ printf '%s\n' "$(jq -nc --arg t "$blocktext" '{type:"assistant",message:{content:[{type:"text",text:$t}]}}')"
85
+ } > "$tB"
86
+ stopB=$(jq -nc --arg s "$SIDB" --arg c "$work" --arg t "$tB" '{session_id:$s, cwd:$c, transcript_path:$t}')
87
+ echo "$stopB" | EAGLE_MEM_STOP_BACKGROUND_ENRICH=0 bash "$ROOT_DIR/hooks/stop.sh" >/dev/null 2>&1
88
+
89
+ [ "$(field "$SIDB" capture_source)" = "agent" ] && ok "B: block sets capture_source=agent" || bad "B: capture_source=$(field "$SIDB" capture_source)"
90
+ case "$(field "$SIDB" completed)" in *"shipped via block"*) ok "B: completed parsed from block" ;; *) bad "B: block completed not parsed" ;; esac
91
+ case "$(field "$SIDB" decisions)" in *"keep parser"*) ok "B: decisions parsed from block" ;; *) bad "B: block decisions not parsed" ;; esac
92
+
93
+ # ── Scenario C: enrichment must not overwrite an agent row ──
94
+ SIDC="clean-capture-ccc333"
95
+ eagle_upsert_session "$SIDC" "$PROJECT" "$work" "" "startup" "claude-code"
96
+ eagle_insert_summary "$SIDC" "$PROJECT" "req" "" "" "agent completed C" "" "[]" "[]" "" "agent decision C" "" "" "claude-code" "agent"
97
+ # fill-only path used by enrich when source=agent: simulate enrich trying to overwrite
98
+ eagle_insert_summary_fill_only "$SIDC" "$PROJECT" "" "" "enriched learned" "ENRICH OVERWRITE" "" "[]" "[]" "" "ENRICH DECISION" "" "" "claude-code" "enrich"
99
+ [ "$(field "$SIDC" completed)" = "agent completed C" ] && ok "C: enrich fill-only did NOT overwrite completed" || bad "C: enrich overwrote -> '$(field "$SIDC" completed)'"
100
+ case "$(field "$SIDC" learned)" in *"enriched learned"*) ok "C: enrich filled empty learned gap" ;; *) bad "C: enrich did not fill empty learned" ;; esac
101
+ [ "$(field "$SIDC" capture_source)" = "agent" ] && ok "C: capture_source stays agent through enrich" || bad "C: capture_source changed"
102
+
103
+ echo ""
104
+ echo "test_clean_session_capture: $pass passed, $fail failed"
105
+ [ "$fail" -eq 0 ]
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env bash
2
+ # Codex hook enablement should use the current canonical features.hooks flag
3
+ # while cleaning up the older codex_hooks alias when encountered.
4
+ set -euo pipefail
5
+
6
+ ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
7
+
8
+ tmp_dir=$(mktemp -d "$ROOT_DIR/.tmp-codex-hooks-config.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
+ export EAGLE_CODEX_DIR="$HOME/.codex"
14
+ export EAGLE_CODEX_CONFIG="$EAGLE_CODEX_DIR/config.toml"
15
+ export EAGLE_CODEX_HOOKS="$EAGLE_CODEX_DIR/hooks.json"
16
+
17
+ mkdir -p "$HOME" "$EAGLE_MEM_DIR" "$EAGLE_CODEX_DIR"
18
+
19
+ . "$ROOT_DIR/lib/common.sh"
20
+ . "$ROOT_DIR/lib/codex-hooks.sh"
21
+
22
+ fail() {
23
+ echo "codex hooks config regression failed: $*" >&2
24
+ exit 1
25
+ }
26
+
27
+ assert_has_hooks_true() {
28
+ grep -q '^hooks = true$' "$EAGLE_CODEX_CONFIG" || fail "missing canonical hooks = true"
29
+ }
30
+
31
+ assert_no_legacy_write() {
32
+ if grep -q '^codex_hooks[[:space:]]*=' "$EAGLE_CODEX_CONFIG"; then
33
+ fail "deprecated codex_hooks key was left in generated config"
34
+ fi
35
+ }
36
+
37
+ rm -f "$EAGLE_CODEX_CONFIG"
38
+ eagle_enable_codex_hooks
39
+ assert_has_hooks_true
40
+ assert_no_legacy_write
41
+
42
+ cat > "$EAGLE_CODEX_CONFIG" <<'TOML'
43
+ [other]
44
+ name = "preserve-me"
45
+
46
+ [features]
47
+ codex_hooks = false
48
+ memories = true
49
+
50
+ [sandbox]
51
+ mode = "workspace-write"
52
+ TOML
53
+
54
+ eagle_enable_codex_hooks
55
+ assert_has_hooks_true
56
+ assert_no_legacy_write
57
+ grep -q '^memories = true$' "$EAGLE_CODEX_CONFIG" || fail "features table values were not preserved"
58
+ grep -q '^name = "preserve-me"$' "$EAGLE_CODEX_CONFIG" || fail "other config sections were not preserved"
59
+ grep -q '^mode = "workspace-write"$' "$EAGLE_CODEX_CONFIG" || fail "later config sections were not preserved"
60
+
61
+ cat > "$EAGLE_CODEX_CONFIG" <<'TOML'
62
+ [features]
63
+ hooks = false
64
+ TOML
65
+
66
+ eagle_enable_codex_hooks
67
+ assert_has_hooks_true
68
+ assert_no_legacy_write
69
+ hook_lines=$(grep -c '^hooks = true$' "$EAGLE_CODEX_CONFIG")
70
+ [ "$hook_lines" -eq 1 ] || fail "expected one hooks = true line, found $hook_lines"
71
+
72
+ echo "codex hooks config regressions passed"
73
+