eagle-mem 4.10.4 → 4.10.6
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 +26 -0
- package/README.md +23 -2
- package/architecture.html +11 -0
- package/db/036_graph_constraints.sql +4 -4
- package/db/037_task_dedup.sql +20 -0
- package/db/038_graph_node_types.sql +22 -0
- package/hooks/post-tool-use.sh +3 -3
- package/lib/common.sh +6 -1
- package/lib/db-graph.sh +310 -3
- package/lib/db-summaries.sh +4 -0
- package/package.json +4 -2
- package/scripts/curate.sh +37 -46
- package/scripts/help.sh +4 -1
- package/scripts/index.sh +44 -3
- package/scripts/install.sh +211 -112
- package/scripts/memories.sh +23 -0
- package/scripts/scan.sh +3 -3
- package/scripts/test.sh +21 -2
- package/scripts/update.sh +25 -1
- package/skills/eagle-mem-memories/SKILL.md +12 -3
- package/tests/test_antigravity_hook.py +114 -0
- package/tests/test_graph_memory.sh +148 -0
- package/integrations/__pycache__/google_antigravity_hook.cpython-314.pyc +0 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Mock Unit Test Suite for Native Eagle Mem Google Antigravity Hooks.
|
|
3
|
+
Verifies that all 5 lifecycle hooks run without any errors, trigger appropriate
|
|
4
|
+
asynchronous subprocess mock calls, and correctly output and format findings.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import asyncio
|
|
10
|
+
import unittest
|
|
11
|
+
|
|
12
|
+
# Ensure the integrations folder is on the Python path
|
|
13
|
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
|
14
|
+
|
|
15
|
+
from integrations.google_antigravity_hook import (
|
|
16
|
+
EagleMemAntigravityHook,
|
|
17
|
+
get_session_id,
|
|
18
|
+
map_tool_name,
|
|
19
|
+
run_cmd_async,
|
|
20
|
+
run_hook_async,
|
|
21
|
+
HAS_ANTIGRAVITY
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
class MockToolCall:
|
|
25
|
+
def __init__(self, name, arguments):
|
|
26
|
+
self.name = name
|
|
27
|
+
self.arguments = arguments
|
|
28
|
+
|
|
29
|
+
class MockToolCallResult:
|
|
30
|
+
def __init__(self, tool_call, output):
|
|
31
|
+
self.tool_call = tool_call
|
|
32
|
+
self.output = output
|
|
33
|
+
|
|
34
|
+
class TestAntigravityHooks(unittest.IsolatedAsyncioTestCase):
|
|
35
|
+
|
|
36
|
+
async def asyncSetUp(self):
|
|
37
|
+
self.hook = EagleMemAntigravityHook(agent_name="antigravity-test")
|
|
38
|
+
self.session_id = get_session_id()
|
|
39
|
+
self.assertTrue(self.session_id.startswith("agy-") or "EAGLE_SESSION_ID" in os.environ)
|
|
40
|
+
|
|
41
|
+
def test_tool_mapping(self):
|
|
42
|
+
self.assertEqual(map_tool_name("run_command"), "Bash")
|
|
43
|
+
self.assertEqual(map_tool_name("exec_command"), "Bash")
|
|
44
|
+
self.assertEqual(map_tool_name("view_file"), "Read")
|
|
45
|
+
self.assertEqual(map_tool_name("edit_file"), "Edit")
|
|
46
|
+
self.assertEqual(map_tool_name("create_file"), "Write")
|
|
47
|
+
self.assertEqual(map_tool_name("custom_tool"), "custom_tool")
|
|
48
|
+
|
|
49
|
+
async def test_session_start_hook(self):
|
|
50
|
+
print("\n--- Testing SessionStart Hook ---")
|
|
51
|
+
# Run on_session_start. Should trigger native start or fallback search gracefully.
|
|
52
|
+
try:
|
|
53
|
+
await self.hook.on_session_start()
|
|
54
|
+
print("✓ SessionStart executed successfully.")
|
|
55
|
+
except Exception as e:
|
|
56
|
+
self.fail(f"on_session_start failed: {e}")
|
|
57
|
+
|
|
58
|
+
async def test_pre_tool_call_decide_hook_allow(self):
|
|
59
|
+
print("\n--- Testing PreToolCallDecide Hook (Allow) ---")
|
|
60
|
+
tool_call = MockToolCall("run_command", {"CommandLine": "echo 'Hello World'"})
|
|
61
|
+
result = await self.hook.pre_tool_call_decide(tool_call)
|
|
62
|
+
self.assertTrue(result.allow)
|
|
63
|
+
print("✓ PreToolCallDecide (Allow) executed successfully.")
|
|
64
|
+
|
|
65
|
+
async def test_pre_tool_call_decide_hook_deny(self):
|
|
66
|
+
print("\n--- Testing PreToolCallDecide Hook (Deny - Release Boundary) ---")
|
|
67
|
+
# Under normal conditions, git push might be blocked if features are pending.
|
|
68
|
+
# Let's verify that the hook runs cleanly when processing git push
|
|
69
|
+
tool_call = MockToolCall("run_command", {"CommandLine": "git push"})
|
|
70
|
+
result = await self.hook.pre_tool_call_decide(tool_call)
|
|
71
|
+
self.assertIn(result.allow, [True, False])
|
|
72
|
+
print(f"✓ PreToolCallDecide (Deny Check) executed successfully with allow={result.allow}.")
|
|
73
|
+
|
|
74
|
+
async def test_post_tool_call_hook(self):
|
|
75
|
+
print("\n--- Testing PostToolCall Hook ---")
|
|
76
|
+
tool_call = MockToolCall("run_command", {"CommandLine": "echo 'Test'"})
|
|
77
|
+
tool_call_result = MockToolCallResult(tool_call, "Test stdout output")
|
|
78
|
+
try:
|
|
79
|
+
await self.hook.post_tool_call(tool_call_result)
|
|
80
|
+
# Give a small slice of time for background tasks to start
|
|
81
|
+
await asyncio.sleep(0.1)
|
|
82
|
+
print("✓ PostToolCall executed successfully.")
|
|
83
|
+
except Exception as e:
|
|
84
|
+
self.fail(f"post_tool_call failed: {e}")
|
|
85
|
+
|
|
86
|
+
async def test_post_turn_hook(self):
|
|
87
|
+
print("\n--- Testing PostTurn Hook ---")
|
|
88
|
+
final_response = "I have successfully resolved the issue by editing the configuration files."
|
|
89
|
+
try:
|
|
90
|
+
await self.hook.post_turn(final_response)
|
|
91
|
+
await asyncio.sleep(0.1)
|
|
92
|
+
print("✓ PostTurn executed successfully.")
|
|
93
|
+
except Exception as e:
|
|
94
|
+
self.fail(f"post_turn failed: {e}")
|
|
95
|
+
|
|
96
|
+
async def test_compaction_hook(self):
|
|
97
|
+
print("\n--- Testing Compaction Hook ---")
|
|
98
|
+
try:
|
|
99
|
+
await self.hook.on_compaction(data=None)
|
|
100
|
+
print("✓ Compaction executed successfully.")
|
|
101
|
+
except Exception as e:
|
|
102
|
+
self.fail(f"on_compaction failed: {e}")
|
|
103
|
+
|
|
104
|
+
async def test_session_end_hook(self):
|
|
105
|
+
print("\n--- Testing SessionEnd Hook ---")
|
|
106
|
+
try:
|
|
107
|
+
await self.hook.on_session_end()
|
|
108
|
+
await asyncio.sleep(0.1)
|
|
109
|
+
print("✓ SessionEnd executed successfully.")
|
|
110
|
+
except Exception as e:
|
|
111
|
+
self.fail(f"on_session_end failed: {e}")
|
|
112
|
+
|
|
113
|
+
if __name__ == "__main__":
|
|
114
|
+
unittest.main()
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Focused graph-memory regressions. Runs in an isolated HOME/EAGLE_MEM_DIR.
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
6
|
+
EAGLE_BIN="$ROOT_DIR/bin/eagle-mem"
|
|
7
|
+
|
|
8
|
+
tmp_dir=$(mktemp -d "$ROOT_DIR/.tmp-graph-memory.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="$HOME/project"
|
|
20
|
+
mkdir -p "$repo"
|
|
21
|
+
git -C "$repo" init -q
|
|
22
|
+
git -C "$repo" config user.email "test@example.com"
|
|
23
|
+
git -C "$repo" config user.name "Eagle Mem Test"
|
|
24
|
+
|
|
25
|
+
cat > "$repo/live.sh" <<'EOF'
|
|
26
|
+
function liveThing() {
|
|
27
|
+
echo live
|
|
28
|
+
}
|
|
29
|
+
EOF
|
|
30
|
+
|
|
31
|
+
cat > "$repo/gone.sh" <<'EOF'
|
|
32
|
+
function goneThing() {
|
|
33
|
+
echo gone
|
|
34
|
+
}
|
|
35
|
+
EOF
|
|
36
|
+
|
|
37
|
+
git -C "$repo" add live.sh gone.sh
|
|
38
|
+
rm "$repo/gone.sh"
|
|
39
|
+
|
|
40
|
+
collected="$tmp_dir/files.txt"
|
|
41
|
+
eagle_collect_files "$repo" "$collected"
|
|
42
|
+
grep -qx "live.sh" "$collected"
|
|
43
|
+
if grep -qx "gone.sh" "$collected"; then
|
|
44
|
+
echo "deleted tracked file was collected" >&2
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
for node_type in func struct function fn def class; do
|
|
49
|
+
eagle_db "INSERT INTO graph_nodes (project, node_type, node_name)
|
|
50
|
+
VALUES ('migration-test', '$node_type', '$node_type-node');" >/dev/null
|
|
51
|
+
done
|
|
52
|
+
|
|
53
|
+
cat > "$repo/a.sh" <<'EOF'
|
|
54
|
+
function finishDictation() {
|
|
55
|
+
echo a
|
|
56
|
+
}
|
|
57
|
+
EOF
|
|
58
|
+
|
|
59
|
+
cat > "$repo/b.sh" <<'EOF'
|
|
60
|
+
function finishDictation() {
|
|
61
|
+
echo b
|
|
62
|
+
}
|
|
63
|
+
EOF
|
|
64
|
+
|
|
65
|
+
cat > "$repo/old.sh" <<'EOF'
|
|
66
|
+
function CloudDictationPipeline() {
|
|
67
|
+
echo old
|
|
68
|
+
}
|
|
69
|
+
EOF
|
|
70
|
+
|
|
71
|
+
git -C "$repo" add a.sh b.sh old.sh
|
|
72
|
+
|
|
73
|
+
"$EAGLE_BIN" scan --force "$repo" >/dev/null
|
|
74
|
+
"$EAGLE_BIN" index --force "$repo" >/dev/null
|
|
75
|
+
|
|
76
|
+
decl_count=$(eagle_db "SELECT COUNT(*)
|
|
77
|
+
FROM graph_nodes
|
|
78
|
+
WHERE project = 'project'
|
|
79
|
+
AND node_type = 'function'
|
|
80
|
+
AND node_name LIKE '%::finishDictation';")
|
|
81
|
+
if [ "$decl_count" != "2" ]; then
|
|
82
|
+
echo "expected duplicate declarations to remain file-scoped, got $decl_count" >&2
|
|
83
|
+
exit 1
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
(cd "$repo" && "$EAGLE_BIN" overview set "Fresh offline-only overview" >/dev/null)
|
|
87
|
+
overview_value=$(eagle_db "SELECT node_value
|
|
88
|
+
FROM graph_nodes
|
|
89
|
+
WHERE project = 'project'
|
|
90
|
+
AND node_type = 'project'
|
|
91
|
+
AND node_name = 'project'
|
|
92
|
+
LIMIT 1;")
|
|
93
|
+
case "$overview_value" in
|
|
94
|
+
*"Fresh offline-only overview"*) ;;
|
|
95
|
+
*)
|
|
96
|
+
echo "overview set did not sync graph project node" >&2
|
|
97
|
+
exit 1
|
|
98
|
+
;;
|
|
99
|
+
esac
|
|
100
|
+
|
|
101
|
+
eagle_db "INSERT INTO sessions (id, project, cwd, model, status)
|
|
102
|
+
VALUES ('session-graph-test', 'project', '$repo', 'test-model', 'completed');" >/dev/null
|
|
103
|
+
eagle_db "INSERT INTO observations (session_id, project, tool_name, files_read, files_modified)
|
|
104
|
+
VALUES ('session-graph-test', 'project', 'Read',
|
|
105
|
+
'[\"$repo/a.sh\"]',
|
|
106
|
+
'[\"$repo/b.sh\"]');" >/dev/null
|
|
107
|
+
|
|
108
|
+
eagle_graph_wire_recent_session_edges "project" 15 >/dev/null
|
|
109
|
+
read_edges=$(eagle_db "SELECT COUNT(*)
|
|
110
|
+
FROM graph_edges e
|
|
111
|
+
JOIN graph_nodes s ON s.id = e.source_node_id
|
|
112
|
+
JOIN graph_nodes f ON f.id = e.target_node_id
|
|
113
|
+
WHERE e.project = 'project'
|
|
114
|
+
AND e.edge_type = 'read'
|
|
115
|
+
AND s.node_type = 'session'
|
|
116
|
+
AND s.node_name = 'session-graph-test'
|
|
117
|
+
AND f.node_type = 'file'
|
|
118
|
+
AND f.node_name = 'a.sh';")
|
|
119
|
+
modified_edges=$(eagle_db "SELECT COUNT(*)
|
|
120
|
+
FROM graph_edges e
|
|
121
|
+
JOIN graph_nodes s ON s.id = e.source_node_id
|
|
122
|
+
JOIN graph_nodes f ON f.id = e.target_node_id
|
|
123
|
+
WHERE e.project = 'project'
|
|
124
|
+
AND e.edge_type = 'modified'
|
|
125
|
+
AND s.node_type = 'session'
|
|
126
|
+
AND s.node_name = 'session-graph-test'
|
|
127
|
+
AND f.node_type = 'file'
|
|
128
|
+
AND f.node_name = 'b.sh';")
|
|
129
|
+
if [ "$read_edges" != "1" ] || [ "$modified_edges" != "1" ]; then
|
|
130
|
+
echo "batched session graph wiring did not normalize absolute paths" >&2
|
|
131
|
+
exit 1
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
rm "$repo/old.sh"
|
|
135
|
+
git -C "$repo" add -u old.sh
|
|
136
|
+
(cd "$repo" && "$EAGLE_BIN" graph rebuild >/dev/null)
|
|
137
|
+
|
|
138
|
+
stale_count=$(eagle_db "SELECT COUNT(*)
|
|
139
|
+
FROM graph_nodes
|
|
140
|
+
WHERE project = 'project'
|
|
141
|
+
AND (node_name LIKE '%CloudDictationPipeline%'
|
|
142
|
+
OR node_name = 'old.sh');")
|
|
143
|
+
if [ "$stale_count" != "0" ]; then
|
|
144
|
+
echo "graph rebuild left stale removed-file nodes" >&2
|
|
145
|
+
exit 1
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
echo "graph memory regressions passed"
|
|
Binary file
|