eagle-mem 4.10.5 → 4.10.7
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 +24 -0
- package/README.md +22 -1
- package/architecture.html +11 -0
- package/db/036_graph_constraints.sql +4 -4
- package/db/038_graph_node_types.sql +22 -0
- 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 +5 -34
- package/scripts/help.sh +4 -1
- package/scripts/index.sh +54 -8
- package/scripts/memories.sh +23 -0
- package/scripts/scan.sh +3 -3
- package/scripts/test.sh +2 -1
- 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 +157 -0
- package/integrations/__pycache__/google_antigravity_hook.cpython-314.pyc +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,30 @@ All notable changes to the **Eagle Mem** project are documented here.
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## v4.10.7 Graph Rebuild Hotfix
|
|
8
|
+
|
|
9
|
+
This hotfix closes an installed-runtime failure found after the v4.10.6 graph-memory release:
|
|
10
|
+
|
|
11
|
+
- **Import Parser Hardening**: Restricts quoted local import detection to `./` and `../` paths, and limits shell `source` parsing to shell-like files so SQL columns named `source` are not mistaken for shell commands.
|
|
12
|
+
- **SQL-Safe Import Lookup**: Escapes import lookup terms before querying graph file nodes, preventing single quotes in source files from breaking `eagle-mem graph rebuild`.
|
|
13
|
+
- **Regression Coverage**: Extends the graph-memory regression test with a SQL fixture containing `source TEXT NOT NULL DEFAULT 'manual'`, matching the installed-runtime failure mode.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## v4.10.6 Graph Memory Rebuild Release
|
|
18
|
+
|
|
19
|
+
This patch turns the local graph-memory workarounds into supported product behavior:
|
|
20
|
+
|
|
21
|
+
- **Official Graph Rebuild Path**: Added `eagle-mem graph rebuild` and `eagle-mem index --force` so stale code chunks, declaration nodes, file nodes, and import edges can be rebuilt without manual SQLite deletes.
|
|
22
|
+
- **Graph Node Type Migration**: Added migration `db/038_graph_node_types.sql` to recreate graph node validation triggers with all declaration node types emitted by the indexer: `class`, `struct`, `function`, `func`, `fn`, and `def`.
|
|
23
|
+
- **File-Scoped Declarations**: Declaration nodes now use file-scoped names like `path/to/file.sh::finishDictation`, avoiding collisions when multiple files define the same function/class name.
|
|
24
|
+
- **Dream Cycle Batching**: Replaced per-edge sqlite subprocess calls in session-to-file graph wiring with one batched transaction, and normalized absolute observation paths back to project-relative graph file nodes.
|
|
25
|
+
- **Stale File Filtering**: `eagle_collect_files` now filters deleted-but-tracked paths from `git ls-files`, so scans and rebuilds represent the current filesystem.
|
|
26
|
+
- **Overview Graph Sync**: `eagle-mem overview set` now syncs the graph project node value, keeping graph search aligned with the canonical overview.
|
|
27
|
+
- **Four-Agent Update Surface**: `eagle-mem update` now refreshes Antigravity integrations and Grok skill links in addition to Claude Code and Codex hooks/skills.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
7
31
|
## v4.10.5 Hardening Release
|
|
8
32
|
|
|
9
33
|
This patch release hardens the database architecture, improves CLI usability, and increases programmatic test coverage for all core features:
|
package/README.md
CHANGED
|
@@ -161,7 +161,28 @@ Eagle Mem prevents Claude from repeating past mistakes:
|
|
|
161
161
|
| `eagle-mem test` | Run basic smoke tests for the memory layer |
|
|
162
162
|
| `eagle-mem prune` | Clean old sessions and stale data |
|
|
163
163
|
| `eagle-mem scan` | Scan codebase and generate overview |
|
|
164
|
-
| `eagle-mem index` | Index source files for FTS5 code search |
|
|
164
|
+
| `eagle-mem index` | Index source files for FTS5 code search and static graph declarations |
|
|
165
|
+
| `eagle-mem index --force` | Rebuild current source chunks, declarations, and import edges |
|
|
166
|
+
| `eagle-mem graph rebuild` | Clear and rebuild the current project's code graph and chunks |
|
|
167
|
+
|
|
168
|
+
### Graph Memory
|
|
169
|
+
|
|
170
|
+
Graph Memory has two layers:
|
|
171
|
+
|
|
172
|
+
- `eagle-mem scan --force` refreshes the project overview and file graph from the current working tree.
|
|
173
|
+
- `eagle-mem index --force` rebuilds FTS5 source chunks plus file-scoped declarations and import edges.
|
|
174
|
+
- `eagle-mem graph rebuild` does both code-graph cleanup and forced indexing without requiring manual SQLite deletes.
|
|
175
|
+
|
|
176
|
+
Useful checks:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
eagle-mem graph
|
|
180
|
+
eagle-mem graph query EscapeKeyMonitor
|
|
181
|
+
eagle-mem graph neighbors lib/db-graph.sh
|
|
182
|
+
eagle-mem overview set "Current project briefing..."
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
If graph search shows stale deleted files, run `eagle-mem graph rebuild` from the project root. The rebuild command filters missing tracked paths, clears stale code chunks and declaration nodes, preserves manual overviews, and rewires declarations with file-scoped names such as `apps/mac/DictationController.swift::finishDictation`.
|
|
165
186
|
|
|
166
187
|
### Trust and Recovery
|
|
167
188
|
|
package/architecture.html
CHANGED
|
@@ -1294,6 +1294,11 @@
|
|
|
1294
1294
|
<td>Which source files look relevant to this prompt?</td>
|
|
1295
1295
|
<td>UserPromptSubmit, index CLI</td>
|
|
1296
1296
|
</tr>
|
|
1297
|
+
<tr>
|
|
1298
|
+
<td><code>graph_nodes</code>, <code>graph_edges</code>, <code>graph_nodes_fts</code></td>
|
|
1299
|
+
<td>Which files, declarations, sessions, memories, and relationships make up the codebase graph?</td>
|
|
1300
|
+
<td>graph CLI, scan, index, overview set, curator Dream Cycle</td>
|
|
1301
|
+
</tr>
|
|
1297
1302
|
<tr>
|
|
1298
1303
|
<td><code>agent_memories</code>, <code>agent_plans</code>, <code>agent_tasks</code></td>
|
|
1299
1304
|
<td>What durable agent artifacts should be shared across Claude Code, Codex, Grok, and Antigravity?</td>
|
|
@@ -1316,6 +1321,11 @@
|
|
|
1316
1321
|
</tr>
|
|
1317
1322
|
</tbody>
|
|
1318
1323
|
</table>
|
|
1324
|
+
|
|
1325
|
+
<div class="callout">
|
|
1326
|
+
<h3>Graph rebuild path</h3>
|
|
1327
|
+
<p><code>eagle-mem graph rebuild</code> is the supported recovery command when graph search shows stale deleted files or stale declarations. It filters missing tracked paths, clears code graph nodes and chunks for the current project, preserves the canonical overview, rewires file nodes, and runs <code>eagle-mem index --force</code> so declarations are file-scoped instead of colliding by bare function name.</p>
|
|
1328
|
+
</div>
|
|
1319
1329
|
</div>
|
|
1320
1330
|
</section>
|
|
1321
1331
|
|
|
@@ -1792,6 +1802,7 @@ eagle-mem install</code></pre>
|
|
|
1792
1802
|
<ul>
|
|
1793
1803
|
<li><code>lib/common.sh</code> - project detection, agent detection, command parsing, redaction, release command detection.</li>
|
|
1794
1804
|
<li><code>lib/db-core.sh</code> and <code>lib/db.sh</code> - SQLite connection setup and module loading.</li>
|
|
1805
|
+
<li><code>lib/db-graph.sh</code> - graph node/edge helpers, code graph rebuild, file path normalization, and Dream Cycle batching.</li>
|
|
1795
1806
|
<li><code>lib/db-features.sh</code> - feature lookup, pending verification, verify/waive behavior.</li>
|
|
1796
1807
|
<li><code>db/*.sql</code> - schema and migrations for all persistent state.</li>
|
|
1797
1808
|
</ul>
|
|
@@ -6,16 +6,16 @@
|
|
|
6
6
|
CREATE TRIGGER IF NOT EXISTS val_graph_nodes_insert BEFORE INSERT ON graph_nodes
|
|
7
7
|
BEGIN
|
|
8
8
|
SELECT CASE
|
|
9
|
-
WHEN NEW.node_type NOT IN ('project', 'file', 'feature', 'memory', 'task', 'session', 'tag', 'class', 'def')
|
|
10
|
-
THEN RAISE(ABORT, 'Data Integrity Error: Invalid node type. Must be one of project, file, feature, memory, task, session, tag, class, def')
|
|
9
|
+
WHEN NEW.node_type NOT IN ('project', 'file', 'feature', 'memory', 'task', 'session', 'tag', 'class', 'struct', 'function', 'func', 'fn', 'def')
|
|
10
|
+
THEN RAISE(ABORT, 'Data Integrity Error: Invalid node type. Must be one of project, file, feature, memory, task, session, tag, class, struct, function, func, fn, def')
|
|
11
11
|
END;
|
|
12
12
|
END;
|
|
13
13
|
|
|
14
14
|
CREATE TRIGGER IF NOT EXISTS val_graph_nodes_update BEFORE UPDATE OF node_type ON graph_nodes
|
|
15
15
|
BEGIN
|
|
16
16
|
SELECT CASE
|
|
17
|
-
WHEN NEW.node_type NOT IN ('project', 'file', 'feature', 'memory', 'task', 'session', 'tag', 'class', 'def')
|
|
18
|
-
THEN RAISE(ABORT, 'Data Integrity Error: Invalid node type. Must be one of project, file, feature, memory, task, session, tag, class, def')
|
|
17
|
+
WHEN NEW.node_type NOT IN ('project', 'file', 'feature', 'memory', 'task', 'session', 'tag', 'class', 'struct', 'function', 'func', 'fn', 'def')
|
|
18
|
+
THEN RAISE(ABORT, 'Data Integrity Error: Invalid node type. Must be one of project, file, feature, memory, task, session, tag, class, struct, function, func, fn, def')
|
|
19
19
|
END;
|
|
20
20
|
END;
|
|
21
21
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
-- ═══════════════════════════════════════════════════════════
|
|
2
|
+
-- Migration 038: Expand graph node type validation
|
|
3
|
+
-- ═══════════════════════════════════════════════════════════
|
|
4
|
+
|
|
5
|
+
DROP TRIGGER IF EXISTS val_graph_nodes_insert;
|
|
6
|
+
DROP TRIGGER IF EXISTS val_graph_nodes_update;
|
|
7
|
+
|
|
8
|
+
CREATE TRIGGER val_graph_nodes_insert BEFORE INSERT ON graph_nodes
|
|
9
|
+
BEGIN
|
|
10
|
+
SELECT CASE
|
|
11
|
+
WHEN NEW.node_type NOT IN ('project', 'file', 'feature', 'memory', 'task', 'session', 'tag', 'class', 'struct', 'function', 'func', 'fn', 'def')
|
|
12
|
+
THEN RAISE(ABORT, 'Data Integrity Error: Invalid node type. Must be one of project, file, feature, memory, task, session, tag, class, struct, function, func, fn, def')
|
|
13
|
+
END;
|
|
14
|
+
END;
|
|
15
|
+
|
|
16
|
+
CREATE TRIGGER val_graph_nodes_update BEFORE UPDATE OF node_type ON graph_nodes
|
|
17
|
+
BEGIN
|
|
18
|
+
SELECT CASE
|
|
19
|
+
WHEN NEW.node_type NOT IN ('project', 'file', 'feature', 'memory', 'task', 'session', 'tag', 'class', 'struct', 'function', 'func', 'fn', 'def')
|
|
20
|
+
THEN RAISE(ABORT, 'Data Integrity Error: Invalid node type. Must be one of project, file, feature, memory, task, session, tag, class, struct, function, func, fn, def')
|
|
21
|
+
END;
|
|
22
|
+
END;
|
package/lib/common.sh
CHANGED
|
@@ -1240,7 +1240,12 @@ eagle_collect_files() {
|
|
|
1240
1240
|
local output_file="$2"
|
|
1241
1241
|
|
|
1242
1242
|
if git -C "$target_dir" rev-parse --is-inside-work-tree &>/dev/null; then
|
|
1243
|
-
git -C "$target_dir" ls-files --cached --others --exclude-standard
|
|
1243
|
+
git -C "$target_dir" ls-files --cached --others --exclude-standard \
|
|
1244
|
+
| while IFS= read -r file; do
|
|
1245
|
+
[ -n "$file" ] || continue
|
|
1246
|
+
[ -f "$target_dir/$file" ] || continue
|
|
1247
|
+
printf '%s\n' "$file"
|
|
1248
|
+
done > "$output_file"
|
|
1244
1249
|
else
|
|
1245
1250
|
(cd "$target_dir" && find . -type f \
|
|
1246
1251
|
-not -path '*/node_modules/*' \
|
package/lib/db-graph.sh
CHANGED
|
@@ -6,6 +6,66 @@
|
|
|
6
6
|
[ -n "${_EAGLE_DB_GRAPH_LOADED:-}" ] && return 0
|
|
7
7
|
_EAGLE_DB_GRAPH_LOADED=1
|
|
8
8
|
|
|
9
|
+
eagle_graph_decl_types_sql() {
|
|
10
|
+
printf "'class','struct','function','func','fn','def'"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
eagle_graph_project_root() {
|
|
14
|
+
local project="${1:-}"
|
|
15
|
+
local candidate=""
|
|
16
|
+
|
|
17
|
+
case "$project" in
|
|
18
|
+
/*) candidate="$project" ;;
|
|
19
|
+
"")
|
|
20
|
+
candidate="$(pwd)"
|
|
21
|
+
;;
|
|
22
|
+
*)
|
|
23
|
+
if [ -d "$HOME/$project" ]; then
|
|
24
|
+
candidate="$HOME/$project"
|
|
25
|
+
elif [ -d "/$project" ]; then
|
|
26
|
+
candidate="/$project"
|
|
27
|
+
else
|
|
28
|
+
candidate="$(pwd)"
|
|
29
|
+
fi
|
|
30
|
+
;;
|
|
31
|
+
esac
|
|
32
|
+
|
|
33
|
+
(cd "$candidate" 2>/dev/null && pwd) || printf '%s\n' "$candidate"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
eagle_graph_normalize_file_path() {
|
|
37
|
+
local project="${1:-}"
|
|
38
|
+
local raw="${2:-}"
|
|
39
|
+
local root="${3:-}"
|
|
40
|
+
|
|
41
|
+
raw=$(printf '%s' "$raw" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
|
42
|
+
raw="${raw#file://}"
|
|
43
|
+
raw="${raw#./}"
|
|
44
|
+
[ -z "$raw" ] && return 0
|
|
45
|
+
|
|
46
|
+
case "$raw" in
|
|
47
|
+
"~/"*) raw="$HOME/${raw#~/}" ;;
|
|
48
|
+
esac
|
|
49
|
+
|
|
50
|
+
[ -z "$root" ] && root=$(eagle_graph_project_root "$project")
|
|
51
|
+
root="${root%/}"
|
|
52
|
+
|
|
53
|
+
case "$raw" in
|
|
54
|
+
"$root"/*) raw="${raw#"$root"/}" ;;
|
|
55
|
+
"$project"/*) raw="${raw#"$project"/}" ;;
|
|
56
|
+
esac
|
|
57
|
+
|
|
58
|
+
raw="${raw#./}"
|
|
59
|
+
printf '%s\n' "$raw"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
eagle_graph_declaration_node_name() {
|
|
63
|
+
local file="${1:-}"
|
|
64
|
+
local declaration="${2:-}"
|
|
65
|
+
[ -z "$file" ] && { printf '%s\n' "$declaration"; return; }
|
|
66
|
+
printf '%s::%s\n' "$file" "$declaration"
|
|
67
|
+
}
|
|
68
|
+
|
|
9
69
|
eagle_graph_add_node() {
|
|
10
70
|
local project; project=$(eagle_sql_escape "$1")
|
|
11
71
|
local node_type; node_type=$(eagle_sql_escape "$2")
|
|
@@ -52,6 +112,243 @@ eagle_graph_add_edge() {
|
|
|
52
112
|
weight = weight + excluded.weight;"
|
|
53
113
|
}
|
|
54
114
|
|
|
115
|
+
eagle_graph_sync_project_overview() {
|
|
116
|
+
local project_raw="${1:-}"
|
|
117
|
+
local overview_raw="${2:-}"
|
|
118
|
+
[ -n "$project_raw" ] || return 0
|
|
119
|
+
|
|
120
|
+
local exists
|
|
121
|
+
exists=$(eagle_db "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'graph_nodes' LIMIT 1;" 2>/dev/null || true)
|
|
122
|
+
[ "$exists" = "graph_nodes" ] || return 0
|
|
123
|
+
|
|
124
|
+
eagle_graph_add_node "$project_raw" "project" "$project_raw" "$overview_raw" ""
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
eagle_graph_reset_file_static_edges() {
|
|
128
|
+
local project_raw="${1:-}"
|
|
129
|
+
local file_raw="${2:-}"
|
|
130
|
+
local file_node_id="${3:-}"
|
|
131
|
+
[ -n "$project_raw" ] && [ -n "$file_raw" ] || return 0
|
|
132
|
+
|
|
133
|
+
local project file type_sql
|
|
134
|
+
project=$(eagle_sql_escape "$project_raw")
|
|
135
|
+
file=$(eagle_sql_escape "$file_raw")
|
|
136
|
+
type_sql=$(eagle_graph_decl_types_sql)
|
|
137
|
+
|
|
138
|
+
if [ -z "$file_node_id" ]; then
|
|
139
|
+
file_node_id=$(eagle_graph_get_node_id "$project_raw" "file" "$file_raw")
|
|
140
|
+
fi
|
|
141
|
+
if [ -n "$file_node_id" ]; then
|
|
142
|
+
file_node_id=$(eagle_sql_int "$file_node_id")
|
|
143
|
+
eagle_db "DELETE FROM graph_edges
|
|
144
|
+
WHERE project = '$project'
|
|
145
|
+
AND source_node_id = $file_node_id
|
|
146
|
+
AND edge_type IN ('declares', 'imports');" >/dev/null
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
eagle_db "DELETE FROM graph_nodes
|
|
150
|
+
WHERE project = '$project'
|
|
151
|
+
AND node_type IN ($type_sql)
|
|
152
|
+
AND source_path = '$file';" >/dev/null
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
eagle_graph_clear_index_state() {
|
|
156
|
+
local project_raw="${1:-}"
|
|
157
|
+
[ -n "$project_raw" ] || return 0
|
|
158
|
+
|
|
159
|
+
local project type_sql
|
|
160
|
+
project=$(eagle_sql_escape "$project_raw")
|
|
161
|
+
type_sql=$(eagle_graph_decl_types_sql)
|
|
162
|
+
|
|
163
|
+
eagle_db_pipe <<SQL >/dev/null
|
|
164
|
+
BEGIN;
|
|
165
|
+
DELETE FROM code_chunks WHERE project = '$project';
|
|
166
|
+
DELETE FROM graph_edges
|
|
167
|
+
WHERE project = '$project'
|
|
168
|
+
AND edge_type IN ('declares', 'imports');
|
|
169
|
+
DELETE FROM graph_nodes
|
|
170
|
+
WHERE project = '$project'
|
|
171
|
+
AND node_type IN ($type_sql);
|
|
172
|
+
COMMIT;
|
|
173
|
+
SQL
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
eagle_graph_clear_code_state() {
|
|
177
|
+
local project_raw="${1:-}"
|
|
178
|
+
[ -n "$project_raw" ] || return 0
|
|
179
|
+
|
|
180
|
+
local project type_sql
|
|
181
|
+
project=$(eagle_sql_escape "$project_raw")
|
|
182
|
+
type_sql=$(eagle_graph_decl_types_sql)
|
|
183
|
+
|
|
184
|
+
eagle_db_pipe <<SQL >/dev/null
|
|
185
|
+
BEGIN;
|
|
186
|
+
DELETE FROM code_chunks WHERE project = '$project';
|
|
187
|
+
DELETE FROM graph_nodes
|
|
188
|
+
WHERE project = '$project'
|
|
189
|
+
AND node_type IN ('project', 'file', $type_sql);
|
|
190
|
+
COMMIT;
|
|
191
|
+
SQL
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
eagle_graph_rebuild_codebase() {
|
|
195
|
+
local project_raw="${1:-}"
|
|
196
|
+
local target_dir="${2:-.}"
|
|
197
|
+
[ -n "$project_raw" ] || return 1
|
|
198
|
+
|
|
199
|
+
target_dir="$(cd "$target_dir" && pwd)"
|
|
200
|
+
local tmp_files overview project_node_id file file_node_id file_count
|
|
201
|
+
tmp_files=$(mktemp)
|
|
202
|
+
eagle_collect_files "$target_dir" "$tmp_files"
|
|
203
|
+
|
|
204
|
+
eagle_graph_clear_code_state "$project_raw"
|
|
205
|
+
|
|
206
|
+
overview=""
|
|
207
|
+
if declare -F eagle_get_overview >/dev/null 2>&1; then
|
|
208
|
+
overview=$(eagle_get_overview "$project_raw" 2>/dev/null || true)
|
|
209
|
+
fi
|
|
210
|
+
eagle_graph_add_node "$project_raw" "project" "$project_raw" "$overview" ""
|
|
211
|
+
project_node_id=$(eagle_graph_get_node_id "$project_raw" "project" "$project_raw")
|
|
212
|
+
|
|
213
|
+
file_count=0
|
|
214
|
+
if [ -n "$project_node_id" ]; then
|
|
215
|
+
while IFS= read -r file; do
|
|
216
|
+
[ -n "$file" ] || continue
|
|
217
|
+
[ -f "$target_dir/$file" ] || continue
|
|
218
|
+
eagle_graph_add_node "$project_raw" "file" "$file" "" "$target_dir/$file"
|
|
219
|
+
file_node_id=$(eagle_graph_get_node_id "$project_raw" "file" "$file")
|
|
220
|
+
if [ -n "$file_node_id" ]; then
|
|
221
|
+
eagle_graph_add_edge "$project_raw" "$project_node_id" "$file_node_id" "contains" 1.0 >/dev/null || true
|
|
222
|
+
fi
|
|
223
|
+
file_count=$((file_count + 1))
|
|
224
|
+
done < "$tmp_files"
|
|
225
|
+
fi
|
|
226
|
+
|
|
227
|
+
rm -f "$tmp_files"
|
|
228
|
+
printf '%s\n' "$file_count"
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
eagle_graph_wire_recent_session_edges() {
|
|
232
|
+
local project_raw="${1:-}"
|
|
233
|
+
local limit="${2:-15}"
|
|
234
|
+
[ -n "$project_raw" ] || { printf '0\n'; return 0; }
|
|
235
|
+
if ! [[ "$limit" =~ ^[0-9]+$ ]] || [ "$limit" -lt 1 ]; then
|
|
236
|
+
limit=15
|
|
237
|
+
fi
|
|
238
|
+
|
|
239
|
+
local project rows session_count root sql_file
|
|
240
|
+
project=$(eagle_sql_escape "$project_raw")
|
|
241
|
+
rows=$(eagle_db_json "WITH recent_sessions AS (
|
|
242
|
+
SELECT id, started_at, model
|
|
243
|
+
FROM sessions
|
|
244
|
+
WHERE project = '$project'
|
|
245
|
+
ORDER BY started_at DESC
|
|
246
|
+
LIMIT $limit
|
|
247
|
+
)
|
|
248
|
+
SELECT r.id AS session_id,
|
|
249
|
+
r.started_at AS started_at,
|
|
250
|
+
r.model AS model,
|
|
251
|
+
COALESCE(o.files_read, '[]') AS files_read,
|
|
252
|
+
COALESCE(o.files_modified, '[]') AS files_modified
|
|
253
|
+
FROM recent_sessions r
|
|
254
|
+
LEFT JOIN observations o ON o.session_id = r.id
|
|
255
|
+
ORDER BY r.started_at DESC;" 2>/dev/null || true)
|
|
256
|
+
|
|
257
|
+
if [ -z "$rows" ] || [ "$rows" = "[]" ]; then
|
|
258
|
+
printf '0\n'
|
|
259
|
+
return 0
|
|
260
|
+
fi
|
|
261
|
+
|
|
262
|
+
session_count=$(printf '%s' "$rows" | jq -r '.[].session_id // empty' 2>/dev/null | sort -u | wc -l | tr -d ' ')
|
|
263
|
+
root=$(eagle_graph_project_root "$project_raw")
|
|
264
|
+
sql_file=$(mktemp)
|
|
265
|
+
|
|
266
|
+
{
|
|
267
|
+
echo "BEGIN;"
|
|
268
|
+
printf '%s' "$rows" | jq -c '.[]' 2>/dev/null | while IFS= read -r row; do
|
|
269
|
+
local sid started model sid_sql value_sql
|
|
270
|
+
sid=$(printf '%s' "$row" | jq -r '.session_id // empty')
|
|
271
|
+
[ -n "$sid" ] || continue
|
|
272
|
+
started=$(printf '%s' "$row" | jq -r '.started_at // "unknown"')
|
|
273
|
+
model=$(printf '%s' "$row" | jq -r '.model // "unknown"')
|
|
274
|
+
sid_sql=$(eagle_sql_escape "$sid")
|
|
275
|
+
value_sql=$(eagle_sql_escape "Session run on $started using $model")
|
|
276
|
+
|
|
277
|
+
cat <<SQL
|
|
278
|
+
INSERT INTO graph_nodes (project, node_type, node_name, node_value, source_path)
|
|
279
|
+
VALUES ('$project', 'session', '$sid_sql', '$value_sql', '')
|
|
280
|
+
ON CONFLICT(project, node_type, node_name) DO UPDATE SET
|
|
281
|
+
node_value = excluded.node_value,
|
|
282
|
+
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now');
|
|
283
|
+
SQL
|
|
284
|
+
|
|
285
|
+
printf '%s' "$row" | jq -r '.files_read | if type == "string" then (try fromjson catch []) else (. // []) end | .[]?' 2>/dev/null \
|
|
286
|
+
| while IFS= read -r file_path; do
|
|
287
|
+
[ -n "$file_path" ] || continue
|
|
288
|
+
eagle_graph_emit_session_file_edge_sql "$project_raw" "$sid" "$file_path" "$root" "read" "1.0"
|
|
289
|
+
done
|
|
290
|
+
printf '%s' "$row" | jq -r '.files_modified | if type == "string" then (try fromjson catch []) else (. // []) end | .[]?' 2>/dev/null \
|
|
291
|
+
| while IFS= read -r file_path; do
|
|
292
|
+
[ -n "$file_path" ] || continue
|
|
293
|
+
eagle_graph_emit_session_file_edge_sql "$project_raw" "$sid" "$file_path" "$root" "modified" "2.0"
|
|
294
|
+
done
|
|
295
|
+
done
|
|
296
|
+
echo "COMMIT;"
|
|
297
|
+
} > "$sql_file"
|
|
298
|
+
|
|
299
|
+
eagle_db_pipe < "$sql_file" >/dev/null
|
|
300
|
+
rm -f "$sql_file"
|
|
301
|
+
printf '%s\n' "${session_count:-0}"
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
eagle_graph_emit_session_file_edge_sql() {
|
|
305
|
+
local project_raw="${1:-}"
|
|
306
|
+
local session_id="${2:-}"
|
|
307
|
+
local file_path="${3:-}"
|
|
308
|
+
local root="${4:-}"
|
|
309
|
+
local edge_type="${5:-read}"
|
|
310
|
+
local weight="${6:-1.0}"
|
|
311
|
+
local normalized project sid file edge
|
|
312
|
+
|
|
313
|
+
normalized=$(eagle_graph_normalize_file_path "$project_raw" "$file_path" "$root")
|
|
314
|
+
[ -n "$normalized" ] || return 0
|
|
315
|
+
|
|
316
|
+
project=$(eagle_sql_escape "$project_raw")
|
|
317
|
+
sid=$(eagle_sql_escape "$session_id")
|
|
318
|
+
file=$(eagle_sql_escape "$normalized")
|
|
319
|
+
edge=$(eagle_sql_escape "$edge_type")
|
|
320
|
+
if ! [[ "$weight" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
|
|
321
|
+
weight="1.0"
|
|
322
|
+
fi
|
|
323
|
+
|
|
324
|
+
cat <<SQL
|
|
325
|
+
INSERT OR IGNORE INTO graph_edges (project, source_node_id, target_node_id, edge_type, weight)
|
|
326
|
+
SELECT '$project', s.id, f.id, '$edge', 0.0
|
|
327
|
+
FROM graph_nodes s, graph_nodes f
|
|
328
|
+
WHERE s.project = '$project'
|
|
329
|
+
AND s.node_type = 'session'
|
|
330
|
+
AND s.node_name = '$sid'
|
|
331
|
+
AND f.project = '$project'
|
|
332
|
+
AND f.node_type = 'file'
|
|
333
|
+
AND f.node_name = '$file'
|
|
334
|
+
AND s.id != f.id;
|
|
335
|
+
UPDATE graph_edges
|
|
336
|
+
SET weight = weight + $weight
|
|
337
|
+
WHERE project = '$project'
|
|
338
|
+
AND edge_type = '$edge'
|
|
339
|
+
AND source_node_id = (
|
|
340
|
+
SELECT id FROM graph_nodes
|
|
341
|
+
WHERE project = '$project' AND node_type = 'session' AND node_name = '$sid'
|
|
342
|
+
LIMIT 1
|
|
343
|
+
)
|
|
344
|
+
AND target_node_id = (
|
|
345
|
+
SELECT id FROM graph_nodes
|
|
346
|
+
WHERE project = '$project' AND node_type = 'file' AND node_name = '$file'
|
|
347
|
+
LIMIT 1
|
|
348
|
+
);
|
|
349
|
+
SQL
|
|
350
|
+
}
|
|
351
|
+
|
|
55
352
|
eagle_graph_query_neighbors() {
|
|
56
353
|
local node_id; node_id=$(eagle_sql_int "$1")
|
|
57
354
|
local direction="${2:-out}" # 'out', 'in', or 'both'
|
|
@@ -124,14 +421,24 @@ eagle_graph_delete_node() {
|
|
|
124
421
|
|
|
125
422
|
eagle_graph_prune_orphans() {
|
|
126
423
|
local project; project=$(eagle_sql_escape "$1")
|
|
424
|
+
local target_dir="${2:-}"
|
|
425
|
+
[ -n "$target_dir" ] && target_dir="${target_dir%/}"
|
|
127
426
|
# Delete file nodes that no longer exist on disk
|
|
128
427
|
local result
|
|
129
|
-
result=$(eagle_db "SELECT id, node_name FROM graph_nodes WHERE project = '$project' AND node_type = 'file';")
|
|
428
|
+
result=$(eagle_db "SELECT id, node_name, COALESCE(source_path, '') FROM graph_nodes WHERE project = '$project' AND node_type = 'file';")
|
|
130
429
|
[ -z "$result" ] && return 0
|
|
131
430
|
|
|
132
|
-
while IFS='|' read -r nid nname; do
|
|
431
|
+
while IFS='|' read -r nid nname nsource; do
|
|
133
432
|
[ -z "$nid" ] && continue
|
|
134
|
-
|
|
433
|
+
local candidate="$nsource"
|
|
434
|
+
if [ -z "$candidate" ]; then
|
|
435
|
+
if [ -n "$target_dir" ]; then
|
|
436
|
+
candidate="$target_dir/$nname"
|
|
437
|
+
else
|
|
438
|
+
candidate="$nname"
|
|
439
|
+
fi
|
|
440
|
+
fi
|
|
441
|
+
if [ ! -f "$candidate" ]; then
|
|
135
442
|
eagle_graph_delete_node "$nid"
|
|
136
443
|
fi
|
|
137
444
|
done <<< "$result"
|
package/lib/db-summaries.sh
CHANGED
|
@@ -124,6 +124,10 @@ eagle_upsert_overview() {
|
|
|
124
124
|
content = excluded.content,
|
|
125
125
|
source = excluded.source,
|
|
126
126
|
updated_at = excluded.updated_at;"
|
|
127
|
+
|
|
128
|
+
if declare -F eagle_graph_sync_project_overview >/dev/null 2>&1; then
|
|
129
|
+
eagle_graph_sync_project_overview "$1" "$raw_content" || true
|
|
130
|
+
fi
|
|
127
131
|
}
|
|
128
132
|
|
|
129
133
|
eagle_get_overview_source() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eagle-mem",
|
|
3
|
-
"version": "4.10.
|
|
3
|
+
"version": "4.10.7",
|
|
4
4
|
"description": "Shared memory, release guardrails, RTK token protection, and worker lanes for Claude Code, Codex, Grok, and Google Antigravity",
|
|
5
5
|
"bin": {
|
|
6
6
|
"eagle-mem": "bin/eagle-mem"
|
|
@@ -12,9 +12,11 @@
|
|
|
12
12
|
"lib/",
|
|
13
13
|
"db/",
|
|
14
14
|
"skills/",
|
|
15
|
+
"tests/*.py",
|
|
16
|
+
"tests/*.sh",
|
|
15
17
|
"docs/",
|
|
16
18
|
"architecture.html",
|
|
17
|
-
"integrations
|
|
19
|
+
"integrations/*.py",
|
|
18
20
|
"CHANGELOG.md"
|
|
19
21
|
],
|
|
20
22
|
"keywords": [
|
package/scripts/curate.sh
CHANGED
|
@@ -563,41 +563,12 @@ if [ -n "$co_edit_data" ]; then
|
|
|
563
563
|
fi
|
|
564
564
|
|
|
565
565
|
# 7.2 Wire session nodes and access edges
|
|
566
|
-
recent_sessions=$(eagle_db "SELECT id
|
|
566
|
+
recent_sessions=$(eagle_db "SELECT id FROM sessions WHERE project = '$p_esc' ORDER BY started_at DESC LIMIT 15;")
|
|
567
567
|
if [ -n "$recent_sessions" ]; then
|
|
568
|
-
session_wire_count
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
eagle_graph_add_node "$project" "session" "$sid" "Session run on $sstart using $smodel" ""
|
|
573
|
-
sid_node=$(eagle_graph_get_node_id "$project" "session" "$sid")
|
|
574
|
-
if [ -n "$sid_node" ]; then
|
|
575
|
-
# Find read/modified files in this session from observations
|
|
576
|
-
session_files=$(eagle_db "SELECT files_read, files_modified FROM observations WHERE session_id = '$(eagle_sql_escape "$sid")';")
|
|
577
|
-
if [ -n "$session_files" ]; then
|
|
578
|
-
while IFS='|' read -r f_read f_mod; do
|
|
579
|
-
# Parse files_read JSON list
|
|
580
|
-
if [ -n "$f_read" ] && [ "$f_read" != "[]" ]; then
|
|
581
|
-
echo "$f_read" | grep -oE '"[^"]+"' | tr -d '"' | while read -r rf; do
|
|
582
|
-
if [ -z "$rf" ]; then continue; fi
|
|
583
|
-
rfid=$(eagle_graph_get_node_id "$project" "file" "$rf")
|
|
584
|
-
[ -n "$rfid" ] && eagle_graph_add_edge "$project" "$sid_node" "$rfid" "read" 1.0
|
|
585
|
-
done
|
|
586
|
-
fi
|
|
587
|
-
# Parse files_modified JSON list
|
|
588
|
-
if [ -n "$f_mod" ] && [ "$f_mod" != "[]" ]; then
|
|
589
|
-
echo "$f_mod" | grep -oE '"[^"]+"' | tr -d '"' | while read -r mf; do
|
|
590
|
-
if [ -z "$mf" ]; then continue; fi
|
|
591
|
-
mfid=$(eagle_graph_get_node_id "$project" "file" "$mf")
|
|
592
|
-
[ -n "$mfid" ] && eagle_graph_add_edge "$project" "$sid_node" "$mfid" "modified" 2.0
|
|
593
|
-
done
|
|
594
|
-
fi
|
|
595
|
-
done <<< "$session_files"
|
|
596
|
-
fi
|
|
597
|
-
fi
|
|
598
|
-
fi
|
|
599
|
-
session_wire_count=$((session_wire_count + 1))
|
|
600
|
-
done <<< "$recent_sessions"
|
|
568
|
+
session_wire_count=$(printf '%s\n' "$recent_sessions" | awk 'NF {count++} END {print count + 0}')
|
|
569
|
+
if [ "$DRY_RUN" -eq 0 ]; then
|
|
570
|
+
session_wire_count=$(eagle_graph_wire_recent_session_edges "$project" 15)
|
|
571
|
+
fi
|
|
601
572
|
eagle_ok "Wired $session_wire_count recent session nodes and edges"
|
|
602
573
|
fi
|
|
603
574
|
|
package/scripts/help.sh
CHANGED
|
@@ -14,7 +14,7 @@ eagle_banner
|
|
|
14
14
|
|
|
15
15
|
echo -e " ${BOLD}Eagle Mem${RESET} ${DIM}v${version}${RESET}"
|
|
16
16
|
echo -e " ${DIM}Shared memory, release guardrails, RTK token protection, and worker lanes${RESET}"
|
|
17
|
-
echo -e " ${DIM}for Claude Code, Codex, and
|
|
17
|
+
echo -e " ${DIM}for Claude Code, Codex, Grok, and Google Antigravity.${RESET}"
|
|
18
18
|
echo ""
|
|
19
19
|
echo -e " ${BOLD}Core commands:${RESET}"
|
|
20
20
|
echo -e " ${CYAN}install${RESET} First-time setup: hooks, database, skills"
|
|
@@ -28,6 +28,7 @@ echo -e " ${CYAN}overview${RESET} Build or view project overview"
|
|
|
28
28
|
echo -e " ${CYAN}session${RESET} Save a manual session summary"
|
|
29
29
|
echo -e " ${CYAN}memories${RESET} View/sync agent memories"
|
|
30
30
|
echo -e " ${CYAN}tasks${RESET} View mirrored tasks"
|
|
31
|
+
echo -e " ${CYAN}graph${RESET} View, query, or rebuild the codebase knowledge graph"
|
|
31
32
|
echo ""
|
|
32
33
|
echo -e " ${BOLD}Safety and token controls:${RESET}"
|
|
33
34
|
echo -e " ${CYAN}feature${RESET} Track, verify, and unblock feature changes"
|
|
@@ -59,6 +60,8 @@ echo -e " ${DIM}\$${RESET} eagle-mem search --tasks ${DIM}# in-flight
|
|
|
59
60
|
echo -e " ${DIM}\$${RESET} eagle-mem search --files ${DIM}# hot files${RESET}"
|
|
60
61
|
echo -e " ${DIM}\$${RESET} eagle-mem search --stats ${DIM}# project stats${RESET}"
|
|
61
62
|
echo -e " ${DIM}\$${RESET} eagle-mem session save --summary \"fixed auth\""
|
|
63
|
+
echo -e " ${DIM}\$${RESET} eagle-mem graph rebuild ${DIM}# rebuild code graph + chunks${RESET}"
|
|
64
|
+
echo -e " ${DIM}\$${RESET} eagle-mem index --force ${DIM}# force source chunk/declaration indexing${RESET}"
|
|
62
65
|
echo ""
|
|
63
66
|
echo -e " ${BOLD}Anti-regression:${RESET}"
|
|
64
67
|
echo -e " ${DIM}\$${RESET} eagle-mem feature pending ${DIM}# pending release blockers${RESET}"
|
package/scripts/index.sh
CHANGED
|
@@ -15,7 +15,28 @@ LIB_DIR="$SCRIPTS_DIR/../lib"
|
|
|
15
15
|
|
|
16
16
|
eagle_ensure_db
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
force=false
|
|
19
|
+
args=()
|
|
20
|
+
|
|
21
|
+
show_help() {
|
|
22
|
+
echo -e " ${BOLD}eagle-mem index${RESET} — Index source files and wire code graph declarations"
|
|
23
|
+
echo ""
|
|
24
|
+
echo -e " ${BOLD}Usage:${RESET}"
|
|
25
|
+
echo -e " eagle-mem index [path] ${DIM}# incrementally index changed files${RESET}"
|
|
26
|
+
echo -e " eagle-mem index ${CYAN}--force${RESET} [path] ${DIM}# rebuild chunks and static code graph edges${RESET}"
|
|
27
|
+
echo ""
|
|
28
|
+
exit 0
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
while [ $# -gt 0 ]; do
|
|
32
|
+
case "$1" in
|
|
33
|
+
--force|-f) force=true; shift ;;
|
|
34
|
+
--help|-h) show_help ;;
|
|
35
|
+
*) args+=("$1"); shift ;;
|
|
36
|
+
esac
|
|
37
|
+
done
|
|
38
|
+
|
|
39
|
+
TARGET_DIR="${args[0]:-.}"
|
|
19
40
|
TARGET_DIR="$(cd "$TARGET_DIR" && pwd)"
|
|
20
41
|
PROJECT=$(eagle_project_from_cwd "$TARGET_DIR")
|
|
21
42
|
|
|
@@ -106,10 +127,20 @@ NEEDS_INDEX="$TMPDIR_IDX/needs_index"
|
|
|
106
127
|
|
|
107
128
|
skipped_count=0
|
|
108
129
|
|
|
130
|
+
if [ "$force" = true ]; then
|
|
131
|
+
eagle_info "Force rebuild requested: clearing chunks, declarations, and import edges"
|
|
132
|
+
eagle_graph_clear_index_state "$PROJECT"
|
|
133
|
+
fi
|
|
134
|
+
|
|
109
135
|
while IFS= read -r file; do
|
|
110
136
|
full_path="$TARGET_DIR/$file"
|
|
111
137
|
current_mtime=$(stat -f '%m' "$full_path" 2>/dev/null || stat -c '%Y' "$full_path" 2>/dev/null || echo "0")
|
|
112
138
|
|
|
139
|
+
if [ "$force" = true ]; then
|
|
140
|
+
echo "$file"
|
|
141
|
+
continue
|
|
142
|
+
fi
|
|
143
|
+
|
|
113
144
|
stored_mtime=$(eagle_db "SELECT MAX(mtime) FROM code_chunks WHERE project = '$project_sql' AND file_path = '$(eagle_sql_escape "$file")';")
|
|
114
145
|
|
|
115
146
|
if [ -n "$stored_mtime" ] && [ "$stored_mtime" = "$current_mtime" ]; then
|
|
@@ -180,16 +211,26 @@ COMMIT;"
|
|
|
180
211
|
|
|
181
212
|
# Static syntax relation extraction & graph wiring
|
|
182
213
|
# Wire node & edge static parser
|
|
214
|
+
overview=$(eagle_get_overview "$PROJECT" 2>/dev/null || true)
|
|
215
|
+
eagle_graph_add_node "$PROJECT" "project" "$PROJECT" "$overview" ""
|
|
216
|
+
project_node_id=$(eagle_graph_get_node_id "$PROJECT" "project" "$PROJECT")
|
|
217
|
+
eagle_graph_add_node "$PROJECT" "file" "$file" "" "$full_path"
|
|
183
218
|
file_node_id=$(eagle_graph_get_node_id "$PROJECT" "file" "$file")
|
|
184
219
|
if [ -n "$file_node_id" ]; then
|
|
220
|
+
if [ -n "$project_node_id" ]; then
|
|
221
|
+
eagle_graph_add_edge "$PROJECT" "$project_node_id" "$file_node_id" "contains" 1.0 >/dev/null || true
|
|
222
|
+
fi
|
|
223
|
+
eagle_graph_reset_file_static_edges "$PROJECT" "$file" "$file_node_id"
|
|
224
|
+
|
|
185
225
|
# 1. Parse function/class declarations
|
|
186
226
|
# e.g., "def name", "class name", "fn name", "function name", "func name"
|
|
187
227
|
declarations=$(grep -oE '\<(class|struct|function|def|fn|func)[[:space:]]+[A-Za-z0-9_]+' "$full_path" 2>/dev/null | awk '{print $1 ":" $2}' | sort -u || true)
|
|
188
228
|
if [ -n "$declarations" ]; then
|
|
189
229
|
while IFS=':' read -r dtype dname; do
|
|
190
230
|
[ -z "$dname" ] && continue
|
|
191
|
-
|
|
192
|
-
|
|
231
|
+
decl_node_name=$(eagle_graph_declaration_node_name "$file" "$dname")
|
|
232
|
+
eagle_graph_add_node "$PROJECT" "$dtype" "$decl_node_name" "Declared $dname in $file" "$file"
|
|
233
|
+
decl_node_id=$(eagle_graph_get_node_id "$PROJECT" "$dtype" "$decl_node_name")
|
|
193
234
|
if [ -n "$decl_node_id" ]; then
|
|
194
235
|
eagle_graph_add_edge "$PROJECT" "$file_node_id" "$decl_node_id" "declares" 1.0
|
|
195
236
|
fi
|
|
@@ -197,10 +238,14 @@ COMMIT;"
|
|
|
197
238
|
fi
|
|
198
239
|
|
|
199
240
|
# 2. Parse local relative imports/requires/sources
|
|
200
|
-
# Matches paths starting with
|
|
201
|
-
local_imports=$(grep -oE "['\"](
|
|
202
|
-
|
|
203
|
-
|
|
241
|
+
# Matches quoted paths starting with ./ or ../ and shell source lines.
|
|
242
|
+
local_imports=$(grep -oE "['\"](\./[^'\"]+|\.\./[^'\"]+)['\"]" "$full_path" 2>/dev/null | tr -d "'\"" || true)
|
|
243
|
+
shell_sources=""
|
|
244
|
+
case "$file" in
|
|
245
|
+
*.sh|*.bash|*.zsh|*.envrc|.envrc)
|
|
246
|
+
shell_sources=$(grep -E "^[[:space:]]*(source[[:space:]]+|\. [^[:space:]])" "$full_path" 2>/dev/null | sed -E "s/^[[:space:]]*(source[[:space:]]+|\. )([^#[:space:]]+).*/\\2/" || true)
|
|
247
|
+
;;
|
|
248
|
+
esac
|
|
204
249
|
|
|
205
250
|
all_refs=$(printf "%s\n%s\n" "$local_imports" "$shell_sources" | sort -u)
|
|
206
251
|
if [ -n "$all_refs" ]; then
|
|
@@ -209,9 +254,10 @@ COMMIT;"
|
|
|
209
254
|
# Clean up path variables in shell sources (e.g. $_eagle_db_dir/db-core.sh -> db-core.sh)
|
|
210
255
|
ref_clean=$(echo "$ref" | sed -E 's/.*\///; s/\.sh$//; s/\.js$//; s/\.ts$//')
|
|
211
256
|
[ -z "$ref_clean" ] && continue
|
|
257
|
+
ref_sql=$(eagle_sql_escape "$ref_clean")
|
|
212
258
|
|
|
213
259
|
# Check if there is a known file node in our graph that matches this basename or path
|
|
214
|
-
matched_file=$(eagle_db "SELECT node_name FROM graph_nodes WHERE project = '$project_sql' AND node_type = 'file' AND (node_name LIKE '%/$
|
|
260
|
+
matched_file=$(eagle_db "SELECT node_name FROM graph_nodes WHERE project = '$project_sql' AND node_type = 'file' AND (node_name LIKE '%/$ref_sql%' OR node_name = '$ref_sql') LIMIT 1;")
|
|
215
261
|
if [ -n "$matched_file" ]; then
|
|
216
262
|
target_file_id=$(eagle_graph_get_node_id "$PROJECT" "file" "$matched_file")
|
|
217
263
|
if [ -n "$target_file_id" ]; then
|
package/scripts/memories.sh
CHANGED
|
@@ -48,6 +48,7 @@ show_help() {
|
|
|
48
48
|
echo -e " eagle-mem memories graph ${DIM}# view codebase knowledge graph summary${RESET}"
|
|
49
49
|
echo -e " eagle-mem memories graph query ${CYAN}<term>${RESET} ${DIM}# search knowledge graph nodes${RESET}"
|
|
50
50
|
echo -e " eagle-mem memories graph neighbors ${CYAN}<name>${RESET} ${DIM}# view a node's local network connections${RESET}"
|
|
51
|
+
echo -e " eagle-mem memories graph rebuild ${DIM}# rebuild current project's code graph and chunks${RESET}"
|
|
51
52
|
echo ""
|
|
52
53
|
echo -e " ${BOLD}Options:${RESET}"
|
|
53
54
|
echo -e " ${CYAN}-p, --project${RESET} <name> Filter by project (default: current project)"
|
|
@@ -943,6 +944,28 @@ memories_graph() {
|
|
|
943
944
|
fi
|
|
944
945
|
echo ""
|
|
945
946
|
;;
|
|
947
|
+
rebuild)
|
|
948
|
+
if [ -z "$project" ]; then
|
|
949
|
+
eagle_err "Cannot determine project for graph rebuild."
|
|
950
|
+
exit 1
|
|
951
|
+
fi
|
|
952
|
+
|
|
953
|
+
local target_dir file_count
|
|
954
|
+
target_dir=$(eagle_graph_project_root "$project")
|
|
955
|
+
if [ ! -d "$target_dir" ]; then
|
|
956
|
+
target_dir="$(pwd)"
|
|
957
|
+
fi
|
|
958
|
+
|
|
959
|
+
eagle_header "Knowledge Graph Rebuild"
|
|
960
|
+
eagle_info "Project: $project"
|
|
961
|
+
eagle_info "Path: $target_dir"
|
|
962
|
+
echo ""
|
|
963
|
+
|
|
964
|
+
file_count=$(eagle_graph_rebuild_codebase "$project" "$target_dir")
|
|
965
|
+
eagle_ok "Rebuilt file graph ($file_count files)"
|
|
966
|
+
bash "$SCRIPTS_DIR/index.sh" --force "$target_dir"
|
|
967
|
+
eagle_footer "Graph rebuild complete."
|
|
968
|
+
;;
|
|
946
969
|
summary|*)
|
|
947
970
|
eagle_header "Knowledge Graph Summary"
|
|
948
971
|
echo ""
|
package/scripts/scan.sh
CHANGED
|
@@ -210,9 +210,9 @@ detect_framework "mix.exs" "Elixir/Mix" || true
|
|
|
210
210
|
|
|
211
211
|
top_dirs=""
|
|
212
212
|
if [ "$is_git" = true ]; then
|
|
213
|
-
|
|
213
|
+
cut -d/ -f1 "$TMPFILE" | sort -u | while read -r item; do
|
|
214
214
|
if [ -d "$TARGET_DIR/$item" ]; then
|
|
215
|
-
count=$(
|
|
215
|
+
count=$(grep -c "^$item/" "$TMPFILE" 2>/dev/null || true)
|
|
216
216
|
echo "$item/ ($count)"
|
|
217
217
|
fi
|
|
218
218
|
done > "${TMPFILE}.dirs"
|
|
@@ -372,7 +372,7 @@ eagle_graph_add_node "$PROJECT" "project" "$PROJECT" "$overview" ""
|
|
|
372
372
|
project_node_id=$(eagle_graph_get_node_id "$PROJECT" "project" "$PROJECT")
|
|
373
373
|
|
|
374
374
|
# Prune deleted/removed files from graph
|
|
375
|
-
eagle_graph_prune_orphans "$PROJECT"
|
|
375
|
+
eagle_graph_prune_orphans "$PROJECT" "$TARGET_DIR"
|
|
376
376
|
|
|
377
377
|
file_node_count=0
|
|
378
378
|
if [ -n "$project_node_id" ]; then
|
package/scripts/test.sh
CHANGED
|
@@ -51,6 +51,7 @@ run_check "Agent Orchestration (orchestrate help)" "\"$EAGLE_BIN\" orchestrate -
|
|
|
51
51
|
run_check "Cross Agent Memory (memories query)" "\"$EAGLE_BIN\" memories --json > /dev/null"
|
|
52
52
|
run_check "Installer And Updater (install / update syntax)" "bash -n \"$SCRIPTS_DIR/install.sh\" && bash -n \"$SCRIPTS_DIR/update.sh\""
|
|
53
53
|
run_check "Code Scan And Index (scan / index syntax)" "bash -n \"$SCRIPTS_DIR/scan.sh\" && bash -n \"$SCRIPTS_DIR/index.sh\""
|
|
54
|
+
run_check "Graph Memory Rebuild (isolated regression suite)" "bash \"$SCRIPTS_DIR/../tests/test_graph_memory.sh\""
|
|
54
55
|
|
|
55
56
|
echo ""
|
|
56
57
|
if [ "$errors" -eq 0 ]; then
|
|
@@ -67,4 +68,4 @@ else
|
|
|
67
68
|
fi
|
|
68
69
|
|
|
69
70
|
echo ""
|
|
70
|
-
eagle_info "Run 'eagle-mem test' regularly to guard the memory layer itself."
|
|
71
|
+
eagle_info "Run 'eagle-mem test' regularly to guard the memory layer itself."
|
package/scripts/update.sh
CHANGED
|
@@ -20,6 +20,7 @@ LIB_DIR="$SCRIPTS_DIR/../lib"
|
|
|
20
20
|
SETTINGS="$EAGLE_SETTINGS"
|
|
21
21
|
claude_found=false
|
|
22
22
|
codex_found=false
|
|
23
|
+
grok_found=false
|
|
23
24
|
|
|
24
25
|
eagle_header "Update"
|
|
25
26
|
|
|
@@ -39,18 +40,22 @@ fi
|
|
|
39
40
|
if [ -d "$EAGLE_CODEX_DIR" ] || command -v codex &>/dev/null; then
|
|
40
41
|
codex_found=true
|
|
41
42
|
fi
|
|
43
|
+
if [ -d "$EAGLE_GROK_DIR" ]; then
|
|
44
|
+
grok_found=true
|
|
45
|
+
fi
|
|
42
46
|
|
|
43
47
|
eagle_runtime_change_plan "update" "$PACKAGE_DIR" "$claude_found" "$codex_found"
|
|
44
48
|
|
|
45
49
|
# ─── Update files ──────────────────────────────────────────
|
|
46
50
|
|
|
47
|
-
mkdir -p "$EAGLE_MEM_DIR"/{hooks,lib,db,scripts}
|
|
51
|
+
mkdir -p "$EAGLE_MEM_DIR"/{hooks,lib,db,scripts,integrations}
|
|
48
52
|
|
|
49
53
|
cp "$PACKAGE_DIR"/hooks/*.sh "$EAGLE_MEM_DIR/hooks/"
|
|
50
54
|
cp "$PACKAGE_DIR"/lib/*.sh "$EAGLE_MEM_DIR/lib/"
|
|
51
55
|
cp "$PACKAGE_DIR"/db/*.sh "$EAGLE_MEM_DIR/db/"
|
|
52
56
|
cp "$PACKAGE_DIR"/db/*.sql "$EAGLE_MEM_DIR/db/"
|
|
53
57
|
cp "$PACKAGE_DIR"/scripts/*.sh "$EAGLE_MEM_DIR/scripts/" 2>/dev/null
|
|
58
|
+
cp -r "$PACKAGE_DIR"/integrations/* "$EAGLE_MEM_DIR/integrations/" 2>/dev/null || true
|
|
54
59
|
|
|
55
60
|
chmod +x "$EAGLE_MEM_DIR"/hooks/*.sh
|
|
56
61
|
chmod +x "$EAGLE_MEM_DIR"/db/migrate.sh
|
|
@@ -140,6 +145,25 @@ if [ "$codex_found" = true ] && [ -d "$PACKAGE_DIR/skills" ]; then
|
|
|
140
145
|
eagle_ok "Codex skills updated"
|
|
141
146
|
fi
|
|
142
147
|
|
|
148
|
+
if [ "$grok_found" = true ] && [ -d "$PACKAGE_DIR/skills" ]; then
|
|
149
|
+
mkdir -p "$EAGLE_GROK_SKILLS_DIR"
|
|
150
|
+
find "$EAGLE_GROK_SKILLS_DIR" -maxdepth 1 -name "eagle-mem-*" -type l 2>/dev/null | while read -r existing; do
|
|
151
|
+
skill_name=$(basename "$existing")
|
|
152
|
+
if [ ! -d "$PACKAGE_DIR/skills/$skill_name" ]; then
|
|
153
|
+
rm "$existing"
|
|
154
|
+
eagle_ok "Removed stale Grok skill: $skill_name"
|
|
155
|
+
fi
|
|
156
|
+
done
|
|
157
|
+
for skill_dir in "$PACKAGE_DIR"/skills/*/; do
|
|
158
|
+
[ ! -d "$skill_dir" ] && continue
|
|
159
|
+
skill_name=$(basename "$skill_dir")
|
|
160
|
+
dst="$EAGLE_GROK_SKILLS_DIR/$skill_name"
|
|
161
|
+
[ -L "$dst" ] && rm "$dst"
|
|
162
|
+
ln -sf "$skill_dir" "$dst"
|
|
163
|
+
done
|
|
164
|
+
eagle_ok "Grok skills updated"
|
|
165
|
+
fi
|
|
166
|
+
|
|
143
167
|
# ─── Refresh generated Claude statusline wrapper ───────────
|
|
144
168
|
|
|
145
169
|
statusline_wrapper="$EAGLE_MEM_DIR/scripts/statusline-wrapper.sh"
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: eagle-mem-memories
|
|
3
3
|
description: >
|
|
4
|
-
View and sync Claude Code and
|
|
4
|
+
View and sync Claude Code, Codex, Grok, and Antigravity memories, plans, tasks,
|
|
5
|
+
and graph memory mirrored in Eagle Mem. Use when:
|
|
5
6
|
'eagle memories', 'show memories', 'sync memories', 'what does the agent remember',
|
|
6
7
|
'show plans', 'show tasks', 'mirror memories', 'onboard project',
|
|
7
8
|
'what did past sessions learn'. Uses the eagle-mem CLI.
|
|
@@ -11,9 +12,9 @@ description: >
|
|
|
11
12
|
|
|
12
13
|
## Purpose
|
|
13
14
|
|
|
14
|
-
**For the user:** Claude Code and
|
|
15
|
+
**For the user:** Claude Code, Codex, Grok, and Antigravity remember across sessions. Decisions, preferences, project context, graph relationships, and architectural plans survive session boundaries. The user never has to re-explain "we chose Postgres because..." or "don't use semicolons in this project."
|
|
15
16
|
|
|
16
|
-
**For you:** Access to what past
|
|
17
|
+
**For you:** Access to what past agent sessions learned about this project. Memories tell you *why* decisions were made. Plans tell you *what's coming*. Tasks tell you *what's in flight*. Graph memory tells you *which files, declarations, and sessions are connected*. Together they're the knowledge bridge that makes you effective from message one.
|
|
17
18
|
|
|
18
19
|
## Judgment
|
|
19
20
|
|
|
@@ -64,6 +65,14 @@ eagle-mem memories tasks search "refactor"
|
|
|
64
65
|
eagle-mem memories tasks show <file_path>
|
|
65
66
|
```
|
|
66
67
|
|
|
68
|
+
**Graph memory** -- codebase file nodes, file-scoped declarations, session/file access edges, memory nodes, and relationship edges.
|
|
69
|
+
```bash
|
|
70
|
+
eagle-mem graph # graph summary
|
|
71
|
+
eagle-mem graph query "finishDictation" # search nodes
|
|
72
|
+
eagle-mem graph neighbors "lib/db-graph.sh"
|
|
73
|
+
eagle-mem graph rebuild # rebuild stale code graph + chunks
|
|
74
|
+
```
|
|
75
|
+
|
|
67
76
|
### 2. Understand how data flows in
|
|
68
77
|
|
|
69
78
|
Two paths feed the mirror:
|
|
@@ -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,157 @@
|
|
|
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
|
+
mkdir -p "$repo/db"
|
|
72
|
+
cat > "$repo/db/source_column.sql" <<'EOF'
|
|
73
|
+
CREATE TABLE graph_fixture (
|
|
74
|
+
id TEXT PRIMARY KEY,
|
|
75
|
+
source TEXT NOT NULL DEFAULT 'manual',
|
|
76
|
+
note TEXT DEFAULT './not-a-real-import'
|
|
77
|
+
);
|
|
78
|
+
EOF
|
|
79
|
+
|
|
80
|
+
git -C "$repo" add a.sh b.sh old.sh db/source_column.sql
|
|
81
|
+
|
|
82
|
+
"$EAGLE_BIN" scan --force "$repo" >/dev/null
|
|
83
|
+
"$EAGLE_BIN" index --force "$repo" >/dev/null
|
|
84
|
+
|
|
85
|
+
decl_count=$(eagle_db "SELECT COUNT(*)
|
|
86
|
+
FROM graph_nodes
|
|
87
|
+
WHERE project = 'project'
|
|
88
|
+
AND node_type = 'function'
|
|
89
|
+
AND node_name LIKE '%::finishDictation';")
|
|
90
|
+
if [ "$decl_count" != "2" ]; then
|
|
91
|
+
echo "expected duplicate declarations to remain file-scoped, got $decl_count" >&2
|
|
92
|
+
exit 1
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
(cd "$repo" && "$EAGLE_BIN" overview set "Fresh offline-only overview" >/dev/null)
|
|
96
|
+
overview_value=$(eagle_db "SELECT node_value
|
|
97
|
+
FROM graph_nodes
|
|
98
|
+
WHERE project = 'project'
|
|
99
|
+
AND node_type = 'project'
|
|
100
|
+
AND node_name = 'project'
|
|
101
|
+
LIMIT 1;")
|
|
102
|
+
case "$overview_value" in
|
|
103
|
+
*"Fresh offline-only overview"*) ;;
|
|
104
|
+
*)
|
|
105
|
+
echo "overview set did not sync graph project node" >&2
|
|
106
|
+
exit 1
|
|
107
|
+
;;
|
|
108
|
+
esac
|
|
109
|
+
|
|
110
|
+
eagle_db "INSERT INTO sessions (id, project, cwd, model, status)
|
|
111
|
+
VALUES ('session-graph-test', 'project', '$repo', 'test-model', 'completed');" >/dev/null
|
|
112
|
+
eagle_db "INSERT INTO observations (session_id, project, tool_name, files_read, files_modified)
|
|
113
|
+
VALUES ('session-graph-test', 'project', 'Read',
|
|
114
|
+
'[\"$repo/a.sh\"]',
|
|
115
|
+
'[\"$repo/b.sh\"]');" >/dev/null
|
|
116
|
+
|
|
117
|
+
eagle_graph_wire_recent_session_edges "project" 15 >/dev/null
|
|
118
|
+
read_edges=$(eagle_db "SELECT COUNT(*)
|
|
119
|
+
FROM graph_edges e
|
|
120
|
+
JOIN graph_nodes s ON s.id = e.source_node_id
|
|
121
|
+
JOIN graph_nodes f ON f.id = e.target_node_id
|
|
122
|
+
WHERE e.project = 'project'
|
|
123
|
+
AND e.edge_type = 'read'
|
|
124
|
+
AND s.node_type = 'session'
|
|
125
|
+
AND s.node_name = 'session-graph-test'
|
|
126
|
+
AND f.node_type = 'file'
|
|
127
|
+
AND f.node_name = 'a.sh';")
|
|
128
|
+
modified_edges=$(eagle_db "SELECT COUNT(*)
|
|
129
|
+
FROM graph_edges e
|
|
130
|
+
JOIN graph_nodes s ON s.id = e.source_node_id
|
|
131
|
+
JOIN graph_nodes f ON f.id = e.target_node_id
|
|
132
|
+
WHERE e.project = 'project'
|
|
133
|
+
AND e.edge_type = 'modified'
|
|
134
|
+
AND s.node_type = 'session'
|
|
135
|
+
AND s.node_name = 'session-graph-test'
|
|
136
|
+
AND f.node_type = 'file'
|
|
137
|
+
AND f.node_name = 'b.sh';")
|
|
138
|
+
if [ "$read_edges" != "1" ] || [ "$modified_edges" != "1" ]; then
|
|
139
|
+
echo "batched session graph wiring did not normalize absolute paths" >&2
|
|
140
|
+
exit 1
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
rm "$repo/old.sh"
|
|
144
|
+
git -C "$repo" add -u old.sh
|
|
145
|
+
(cd "$repo" && "$EAGLE_BIN" graph rebuild >/dev/null)
|
|
146
|
+
|
|
147
|
+
stale_count=$(eagle_db "SELECT COUNT(*)
|
|
148
|
+
FROM graph_nodes
|
|
149
|
+
WHERE project = 'project'
|
|
150
|
+
AND (node_name LIKE '%CloudDictationPipeline%'
|
|
151
|
+
OR node_name = 'old.sh');")
|
|
152
|
+
if [ "$stale_count" != "0" ]; then
|
|
153
|
+
echo "graph rebuild left stale removed-file nodes" >&2
|
|
154
|
+
exit 1
|
|
155
|
+
fi
|
|
156
|
+
|
|
157
|
+
echo "graph memory regressions passed"
|
|
Binary file
|