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 CHANGED
@@ -4,6 +4,32 @@ All notable changes to the **Eagle Mem** project are documented here.
4
4
 
5
5
  ---
6
6
 
7
+ ## v4.10.6 Graph Memory Rebuild Release
8
+
9
+ This patch turns the local graph-memory workarounds into supported product behavior:
10
+
11
+ - **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.
12
+ - **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`.
13
+ - **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.
14
+ - **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.
15
+ - **Stale File Filtering**: `eagle_collect_files` now filters deleted-but-tracked paths from `git ls-files`, so scans and rebuilds represent the current filesystem.
16
+ - **Overview Graph Sync**: `eagle-mem overview set` now syncs the graph project node value, keeping graph search aligned with the canonical overview.
17
+ - **Four-Agent Update Surface**: `eagle-mem update` now refreshes Antigravity integrations and Grok skill links in addition to Claude Code and Codex hooks/skills.
18
+
19
+ ---
20
+
21
+ ## v4.10.5 Hardening Release
22
+
23
+ This patch release hardens the database architecture, improves CLI usability, and increases programmatic test coverage for all core features:
24
+
25
+ - **Database-Level Task Deduplication**: Normalized synthetic task file paths to `event://${task_id}` in `hooks/post-tool-use.sh`, making task tracking constant across sessions. Added a partial unique index `idx_agent_tasks_dedup` on `(project, source_task_id)` via migration `db/037_task_dedup.sql` to block duplicate task rows on repeated sync loops.
26
+ - **Resilient Curation Engine (`curate.sh`)**: Refactored vulnerable inline conditional `&& continue` statements to safe, standard `if` blocks, preventing pipeline subshell crashes under `set -e` and guaranteeing metadata and footer summaries complete successfully.
27
+ - **CLI Usability & Previews (`--help` / `--dry-run`)**: Integrated structured option parsing cases for `-h`/`--help` and `--dry-run` to both `curate.sh` and `install.sh`. Covered all installer filesystem writes, hook updates, and migrations in `install.sh --dry-run` to enable a zero-risk preview of planned changes.
28
+ - **Core Smoke Test suite (`test.sh`)**: Expanded the automated smoke test suite to run concrete checks for **7 core features** (`compaction-survival`, `feature-verification`, `grok-cli-integration`, `agent-orchestration`, `Cross Agent Memory`, `Installer And Updater`, `Code Scan And Index`), automatically updating the SQLite database to mark them verified upon success.
29
+ - **Stale Task Cleanup**: Resolved compaction warning overhead by marking stale, in-progress tasks (`840`, `895`, `968`, `970`) as `'completed'`.
30
+
31
+ ---
32
+
7
33
  ## v4.10.4 Minor Release
8
34
 
9
35
  This release introduces native relational **Knowledge Graph Memories** and an automated background **Dream Cycle** curator to consolidate multi-agent developer context:
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
 
12
12
  Eagle Mem turns AI coding sessions into compounding project knowledge. It gives Claude Code, Codex, and Google Antigravity hook-backed shared memory, gives Grok the same skills and CLI memory surface, labels which agent created each memory, blocks risky release commands until affected features are verified, and lets broad work split into durable worker lanes.
13
13
 
14
- **v4.10.4 and onward focuses on Graph Memory, Dream Cycle Curation, Grok, Google Antigravity support, and Compaction Survival:** Grok users get first-class skill linking and `eagle-mem grok-bootstrap`, while Antigravity users get native Python SDK hook integration via `google_antigravity_hook.py`. Claude Code, Codex, and Antigravity receive the deepest automatic lifecycle support through hooks; Grok currently uses the shared CLI and skill workflow.
14
+ **v4.10.5 and onward focuses on Graph Memory, Dream Cycle Curation, Grok, Google Antigravity support, and Compaction Survival:** Grok users get first-class skill linking and `eagle-mem grok-bootstrap`, while Antigravity users get native Python SDK hook integration via `google_antigravity_hook.py`. Claude Code, Codex, and Antigravity receive the deepest automatic lifecycle support through hooks; Grok currently uses the shared CLI and skill workflow.
15
15
 
16
16
  **Website:** [Product](https://eagleisbatman.github.io/eagle-mem/) |
17
17
  [Architecture](https://eagleisbatman.github.io/eagle-mem/architecture.html) |
@@ -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,20 @@
1
+ -- ═══════════════════════════════════════════════════════════
2
+ -- Migration 037: Deduplicate agent tasks
3
+ -- Deletes duplicate task entries keeping the latest one
4
+ -- and sets up a partial unique index on project + source_task_id.
5
+ -- ═══════════════════════════════════════════════════════════
6
+
7
+ -- Delete duplicate tasks, keeping the most recent (highest ID)
8
+ DELETE FROM agent_tasks
9
+ WHERE source_task_id IS NOT NULL AND source_task_id != ''
10
+ AND id NOT IN (
11
+ SELECT MAX(id)
12
+ FROM agent_tasks
13
+ WHERE source_task_id IS NOT NULL AND source_task_id != ''
14
+ GROUP BY project, source_task_id
15
+ );
16
+
17
+ -- Create partial unique index to guarantee no duplicates for valid source task IDs
18
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_tasks_dedup
19
+ ON agent_tasks(project, source_task_id)
20
+ WHERE source_task_id IS NOT NULL AND source_task_id != '';
@@ -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;
@@ -39,8 +39,8 @@ case "$hook_event" in
39
39
  local_status="pending"
40
40
  [ "$hook_event" = "TaskCompleted" ] && local_status="completed"
41
41
 
42
- # Synthetic file_path keyed on session+task — file_path is the UNIQUE column
43
- synthetic_fp="event://${session_id}/${task_id}"
42
+ # Synthetic file_path keyed on task — file_path is the UNIQUE column
43
+ synthetic_fp="event://${task_id}"
44
44
 
45
45
  tid_sql=$(eagle_sql_escape "$task_id")
46
46
  fp_sql=$(eagle_sql_escape "$synthetic_fp")
@@ -155,7 +155,7 @@ case "$tool_name" in
155
155
  task_status=$(echo "$input" | jq -r '.tool_input.status // empty')
156
156
  tool_summary="TaskUpdate: ${task_id} → ${task_status}"
157
157
  if [ -n "$task_id" ] && [ -n "$task_status" ]; then
158
- fp_sql=$(eagle_sql_escape "event://${session_id}/${task_id}")
158
+ fp_sql=$(eagle_sql_escape "event://${task_id}")
159
159
  stat_sql=$(eagle_sql_escape "$task_status")
160
160
  eagle_db_pipe <<SQL
161
161
  UPDATE agent_tasks SET status = '$stat_sql', updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
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 > "$output_file"
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
- if [ ! -f "$nname" ]; then
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"
@@ -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.4",
3
+ "version": "4.10.6",
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": [