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.
- package/CHANGELOG.md +25 -0
- package/README.md +22 -22
- package/architecture.html +26 -14
- package/bin/eagle-mem +4 -0
- package/db/039_recall_events.sql +27 -0
- package/db/040_graph_decision_nodes.sql +21 -0
- package/db/041_graph_semantic_edge_types.sql +21 -0
- package/db/042_orchestration_auto_events.sql +23 -0
- package/db/043_eagle_events.sql +22 -0
- package/db/044_summary_capture_source.sql +12 -0
- package/docs/agent-compatibility/README.md +38 -0
- package/docs/agent-compatibility/claude-code.md +58 -0
- package/docs/agent-compatibility/codex.md +57 -0
- package/docs/agent-compatibility/opencode.md +72 -0
- package/hooks/post-tool-use.sh +8 -0
- package/hooks/pre-tool-use.sh +10 -1
- package/hooks/session-end.sh +3 -0
- package/hooks/session-start.sh +15 -17
- package/hooks/stop.sh +34 -5
- package/hooks/user-prompt-submit.sh +85 -10
- package/integrations/opencode_eagle_mem_plugin.js +387 -0
- package/lib/codex-hooks.sh +13 -6
- package/lib/common.sh +77 -7
- package/lib/db-events.sh +89 -0
- package/lib/db-graph.sh +154 -0
- package/lib/db-observations.sh +34 -0
- package/lib/db-orchestration.sh +149 -0
- package/lib/db-summaries.sh +70 -3
- package/lib/db.sh +2 -0
- package/lib/hooks.sh +41 -7
- package/lib/opencode-hooks.sh +105 -0
- package/lib/provider.sh +2 -2
- package/package.json +5 -2
- package/scripts/compaction.sh +109 -9
- package/scripts/dashboard.sh +372 -0
- package/scripts/doctor.sh +30 -3
- package/scripts/enrich-summary.sh +8 -2
- package/scripts/health.sh +40 -2
- package/scripts/help.sh +10 -2
- package/scripts/inspect.sh +285 -0
- package/scripts/install.sh +36 -7
- package/scripts/memories.sh +13 -0
- package/scripts/repair.sh +187 -0
- package/scripts/replay.sh +248 -0
- package/scripts/search.sh +44 -3
- package/scripts/session.sh +155 -18
- package/scripts/statusline-em.sh +34 -7
- package/scripts/tasks.sh +34 -0
- package/scripts/test.sh +13 -0
- package/scripts/uninstall.sh +9 -0
- package/scripts/update.sh +21 -2
- package/tests/fixtures/agent-hooks/claude-statusline.json +32 -0
- package/tests/fixtures/agent-hooks/claude-user-prompt-submit.json +9 -0
- package/tests/fixtures/agent-hooks/codex-pre-tool-use.json +10 -0
- package/tests/fixtures/agent-hooks/codex-user-prompt-submit.json +7 -0
- package/tests/fixtures/agent-hooks/opencode-chat-message.json +36 -0
- package/tests/fixtures/agent-hooks/opencode-session-compacting.json +9 -0
- package/tests/fixtures/agent-hooks/opencode-todo-updated.json +13 -0
- package/tests/fixtures/agent-hooks/opencode-tool-execute-after.json +15 -0
- package/tests/fixtures/agent-hooks/opencode-tool-execute-before.json +12 -0
- package/tests/test_agent_compatibility_docs_gate.sh +123 -0
- package/tests/test_auto_orchestration_detection.sh +109 -0
- package/tests/test_claude_stop_hook_registration.sh +56 -0
- package/tests/test_clean_session_capture.sh +105 -0
- package/tests/test_codex_hooks_config.sh +73 -0
- package/tests/test_compaction_survival_matrix.sh +237 -0
- package/tests/test_dashboard.sh +96 -0
- package/tests/test_eagle_events.sh +96 -0
- package/tests/test_opencode_hooks_config.sh +56 -0
- package/tests/test_opencode_plugin_adapter.sh +202 -0
- package/tests/test_recall_observability.sh +144 -0
- package/tests/test_repair.sh +63 -0
- package/tests/test_rust_migration_plan.sh +75 -0
- package/tests/test_trust_surfaces.sh +123 -0
|
@@ -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,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
|
+
|