eagle-mem 4.10.12 → 4.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/README.md +20 -20
- package/architecture.html +26 -14
- package/bin/eagle-mem +4 -0
- package/db/039_recall_events.sql +27 -0
- package/db/040_graph_decision_nodes.sql +21 -0
- package/db/041_graph_semantic_edge_types.sql +21 -0
- package/db/042_orchestration_auto_events.sql +23 -0
- package/db/043_eagle_events.sql +22 -0
- package/docs/agent-compatibility/README.md +38 -0
- package/docs/agent-compatibility/claude-code.md +50 -0
- package/docs/agent-compatibility/codex.md +51 -0
- package/docs/agent-compatibility/opencode.md +71 -0
- package/hooks/post-tool-use.sh +8 -0
- package/hooks/pre-tool-use.sh +11 -3
- package/hooks/session-end.sh +3 -0
- package/hooks/session-start.sh +7 -0
- package/hooks/stop.sh +10 -1
- package/hooks/user-prompt-submit.sh +79 -6
- package/integrations/opencode_eagle_mem_plugin.js +387 -0
- package/lib/codex-hooks.sh +13 -6
- package/lib/common.sh +71 -8
- package/lib/db-events.sh +89 -0
- package/lib/db-features.sh +26 -23
- package/lib/db-graph.sh +154 -0
- package/lib/db-observations.sh +34 -0
- package/lib/db-orchestration.sh +149 -0
- package/lib/db.sh +2 -0
- package/lib/hooks.sh +12 -7
- package/lib/opencode-hooks.sh +105 -0
- package/lib/provider.sh +2 -2
- package/package.json +5 -2
- package/scripts/compaction.sh +108 -8
- package/scripts/dashboard.sh +372 -0
- package/scripts/doctor.sh +30 -3
- package/scripts/health.sh +40 -2
- package/scripts/help.sh +10 -2
- package/scripts/inspect.sh +285 -0
- package/scripts/install.sh +31 -7
- package/scripts/memories.sh +13 -0
- package/scripts/repair.sh +187 -0
- package/scripts/replay.sh +248 -0
- package/scripts/search.sh +44 -3
- package/scripts/statusline-em.sh +34 -7
- package/scripts/tasks.sh +34 -0
- package/scripts/test.sh +14 -0
- package/scripts/uninstall.sh +9 -0
- package/scripts/update.sh +18 -2
- package/skills/eagle-mem-feature/SKILL.md +3 -3
- package/tests/fixtures/agent-hooks/claude-statusline.json +32 -0
- package/tests/fixtures/agent-hooks/claude-user-prompt-submit.json +9 -0
- package/tests/fixtures/agent-hooks/codex-pre-tool-use.json +10 -0
- package/tests/fixtures/agent-hooks/codex-user-prompt-submit.json +7 -0
- package/tests/fixtures/agent-hooks/opencode-chat-message.json +36 -0
- package/tests/fixtures/agent-hooks/opencode-session-compacting.json +9 -0
- package/tests/fixtures/agent-hooks/opencode-todo-updated.json +13 -0
- package/tests/fixtures/agent-hooks/opencode-tool-execute-after.json +15 -0
- package/tests/fixtures/agent-hooks/opencode-tool-execute-before.json +12 -0
- package/tests/test_agent_compatibility_docs_gate.sh +123 -0
- package/tests/test_auto_orchestration_detection.sh +109 -0
- package/tests/test_claude_stop_hook_registration.sh +56 -0
- package/tests/test_codex_hooks_config.sh +73 -0
- package/tests/test_compaction_survival_matrix.sh +237 -0
- package/tests/test_dashboard.sh +96 -0
- package/tests/test_eagle_events.sh +96 -0
- package/tests/test_feature_verification_gate.sh +230 -0
- package/tests/test_opencode_hooks_config.sh +56 -0
- package/tests/test_opencode_plugin_adapter.sh +202 -0
- package/tests/test_recall_observability.sh +144 -0
- package/tests/test_reliability_guards.sh +20 -0
- package/tests/test_repair.sh +63 -0
- package/tests/test_rust_migration_plan.sh +75 -0
- package/tests/test_trust_surfaces.sh +123 -0
package/lib/db-features.sh
CHANGED
|
@@ -58,14 +58,25 @@ eagle_verify_feature() {
|
|
|
58
58
|
WHERE project = '$project' AND name = '$name';"
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
eagle_feature_file_match_ff_sql() {
|
|
62
|
+
local file_path="$1"
|
|
63
|
+
local file_esc; file_esc=$(eagle_sql_escape "$file_path")
|
|
64
|
+
local file_like; file_like=$(eagle_like_escape "$file_esc")
|
|
65
|
+
|
|
66
|
+
cat <<SQL
|
|
67
|
+
(
|
|
68
|
+
ff.file_path = '$file_esc'
|
|
69
|
+
OR ff.file_path LIKE '%/$file_like' ESCAPE '\\'
|
|
70
|
+
OR substr('$file_esc', -length('/' || ff.file_path)) = '/' || ff.file_path
|
|
71
|
+
)
|
|
72
|
+
SQL
|
|
73
|
+
}
|
|
74
|
+
|
|
61
75
|
eagle_find_feature_impacts_for_file() {
|
|
62
76
|
local project; project=$(eagle_sql_escape "$1")
|
|
63
77
|
local file_path="$2"
|
|
64
|
-
local
|
|
65
|
-
|
|
66
|
-
local fname_esc; fname_esc=$(eagle_sql_escape "$fname")
|
|
67
|
-
local file_like; file_like=$(eagle_like_escape "$file_esc")
|
|
68
|
-
local fname_like; fname_like=$(eagle_like_escape "$fname_esc")
|
|
78
|
+
local file_match_sql
|
|
79
|
+
file_match_sql=$(eagle_feature_file_match_ff_sql "$file_path")
|
|
69
80
|
|
|
70
81
|
eagle_db "SELECT DISTINCT f.id, f.name, f.description, f.last_verified_at,
|
|
71
82
|
ff.file_path,
|
|
@@ -75,13 +86,7 @@ eagle_find_feature_impacts_for_file() {
|
|
|
75
86
|
JOIN feature_files ff ON ff.feature_id = f.id
|
|
76
87
|
WHERE f.project = '$project'
|
|
77
88
|
AND f.status = 'active'
|
|
78
|
-
AND
|
|
79
|
-
ff.file_path = '$file_esc'
|
|
80
|
-
OR ff.file_path LIKE '%/$file_like' ESCAPE '\\'
|
|
81
|
-
OR '$file_esc' LIKE '%' || ff.file_path ESCAPE '\\'
|
|
82
|
-
OR ff.file_path LIKE '%$fname_like' ESCAPE '\\'
|
|
83
|
-
OR ff.file_path LIKE '%$fname_like%' ESCAPE '\\'
|
|
84
|
-
)
|
|
89
|
+
AND $file_match_sql
|
|
85
90
|
ORDER BY f.updated_at DESC
|
|
86
91
|
LIMIT 10;"
|
|
87
92
|
}
|
|
@@ -114,10 +119,8 @@ eagle_record_pending_feature_verifications() {
|
|
|
114
119
|
WHERE project = '$p_esc'
|
|
115
120
|
AND feature_id = $fid
|
|
116
121
|
AND file_path = '$fp_esc'
|
|
117
|
-
AND
|
|
118
|
-
|
|
119
|
-
OR status = 'waived'
|
|
120
|
-
)
|
|
122
|
+
AND change_fingerprint = '$fp_hash_esc'
|
|
123
|
+
AND status IN ('verified', 'waived')
|
|
121
124
|
LIMIT 1;")
|
|
122
125
|
[ -n "$already_resolved" ] && continue
|
|
123
126
|
|
|
@@ -341,8 +344,9 @@ eagle_count_active_features() {
|
|
|
341
344
|
|
|
342
345
|
eagle_find_feature_for_push() {
|
|
343
346
|
local project; project=$(eagle_sql_escape "$1")
|
|
344
|
-
local
|
|
345
|
-
local
|
|
347
|
+
local file_path="$2"
|
|
348
|
+
local file_match_sql
|
|
349
|
+
file_match_sql=$(eagle_feature_file_match_ff_sql "$file_path")
|
|
346
350
|
|
|
347
351
|
eagle_db "SELECT DISTINCT f.name,
|
|
348
352
|
(SELECT GROUP_CONCAT(fst.command, '; ')
|
|
@@ -354,15 +358,14 @@ eagle_find_feature_for_push() {
|
|
|
354
358
|
JOIN feature_files ff ON ff.feature_id = f.id
|
|
355
359
|
WHERE f.project = '$project'
|
|
356
360
|
AND f.status = 'active'
|
|
357
|
-
AND
|
|
361
|
+
AND $file_match_sql;"
|
|
358
362
|
}
|
|
359
363
|
|
|
360
364
|
eagle_find_features_for_file() {
|
|
361
365
|
local project; project=$(eagle_sql_escape "$1")
|
|
362
366
|
local file_path="$2"
|
|
363
|
-
local
|
|
364
|
-
|
|
365
|
-
local fname_like; fname_like=$(eagle_like_escape "$fname_esc")
|
|
367
|
+
local file_match_sql
|
|
368
|
+
file_match_sql=$(eagle_feature_file_match_ff_sql "$file_path")
|
|
366
369
|
|
|
367
370
|
eagle_db "SELECT f.name, f.description, f.last_verified_at,
|
|
368
371
|
ff.role,
|
|
@@ -376,7 +379,7 @@ eagle_find_features_for_file() {
|
|
|
376
379
|
JOIN feature_files ff ON ff.feature_id = f.id
|
|
377
380
|
WHERE f.project = '$project'
|
|
378
381
|
AND f.status = 'active'
|
|
379
|
-
AND
|
|
382
|
+
AND $file_match_sql
|
|
380
383
|
ORDER BY f.updated_at DESC
|
|
381
384
|
LIMIT 3;"
|
|
382
385
|
}
|
package/lib/db-graph.sh
CHANGED
|
@@ -301,6 +301,160 @@ SQL
|
|
|
301
301
|
printf '%s\n' "${session_count:-0}"
|
|
302
302
|
}
|
|
303
303
|
|
|
304
|
+
eagle_graph_wire_project_context_edges() {
|
|
305
|
+
local project_raw="${1:-}"
|
|
306
|
+
[ -n "$project_raw" ] || { printf '0\n'; return 0; }
|
|
307
|
+
|
|
308
|
+
local project
|
|
309
|
+
project=$(eagle_sql_escape "$project_raw")
|
|
310
|
+
|
|
311
|
+
eagle_db_pipe <<SQL >/dev/null
|
|
312
|
+
BEGIN;
|
|
313
|
+
|
|
314
|
+
INSERT OR IGNORE INTO graph_nodes (project, node_type, node_name, node_value, source_path)
|
|
315
|
+
SELECT project, 'session', id,
|
|
316
|
+
'Session run on ' || COALESCE(started_at, 'unknown') || ' using ' || COALESCE(model, 'unknown'),
|
|
317
|
+
''
|
|
318
|
+
FROM sessions
|
|
319
|
+
WHERE project = '$project';
|
|
320
|
+
|
|
321
|
+
INSERT OR IGNORE INTO graph_nodes (project, node_type, node_name, node_value, source_path)
|
|
322
|
+
SELECT project, 'decision', session_id || ':decision', decisions, ''
|
|
323
|
+
FROM summaries
|
|
324
|
+
WHERE project = '$project'
|
|
325
|
+
AND COALESCE(decisions, '') != '';
|
|
326
|
+
|
|
327
|
+
INSERT OR IGNORE INTO graph_nodes (project, node_type, node_name, node_value, source_path)
|
|
328
|
+
SELECT project, 'feature', name, COALESCE(description, ''), ''
|
|
329
|
+
FROM features
|
|
330
|
+
WHERE project = '$project'
|
|
331
|
+
AND status = 'active';
|
|
332
|
+
|
|
333
|
+
INSERT OR IGNORE INTO graph_nodes (project, node_type, node_name, node_value, source_path)
|
|
334
|
+
SELECT project, 'memory', memory_name, COALESCE(description, ''), COALESCE(file_path, '')
|
|
335
|
+
FROM agent_memories
|
|
336
|
+
WHERE project = '$project'
|
|
337
|
+
AND COALESCE(memory_name, '') != '';
|
|
338
|
+
|
|
339
|
+
INSERT OR IGNORE INTO graph_nodes (project, node_type, node_name, node_value, source_path)
|
|
340
|
+
SELECT project, 'task', COALESCE(NULLIF(source_task_id, ''), subject), COALESCE(description, ''), COALESCE(file_path, '')
|
|
341
|
+
FROM agent_tasks
|
|
342
|
+
WHERE project = '$project'
|
|
343
|
+
AND COALESCE(COALESCE(NULLIF(source_task_id, ''), subject), '') != '';
|
|
344
|
+
|
|
345
|
+
INSERT OR IGNORE INTO graph_nodes (project, node_type, node_name, node_value, source_path)
|
|
346
|
+
SELECT f.project, 'file', ff.file_path, '', ff.file_path
|
|
347
|
+
FROM feature_files ff
|
|
348
|
+
JOIN features f ON f.id = ff.feature_id
|
|
349
|
+
WHERE f.project = '$project';
|
|
350
|
+
|
|
351
|
+
INSERT OR IGNORE INTO graph_nodes (project, node_type, node_name, node_value, source_path)
|
|
352
|
+
SELECT DISTINCT sf.project, 'file', sf.file_path, '', sf.file_path
|
|
353
|
+
FROM (
|
|
354
|
+
SELECT project, json_each.value AS file_path
|
|
355
|
+
FROM summaries, json_each(CASE WHEN json_valid(files_read) THEN files_read ELSE '[]' END)
|
|
356
|
+
WHERE project = '$project'
|
|
357
|
+
UNION
|
|
358
|
+
SELECT project, json_each.value AS file_path
|
|
359
|
+
FROM summaries, json_each(CASE WHEN json_valid(files_modified) THEN files_modified ELSE '[]' END)
|
|
360
|
+
WHERE project = '$project'
|
|
361
|
+
) sf
|
|
362
|
+
WHERE COALESCE(sf.file_path, '') != '';
|
|
363
|
+
|
|
364
|
+
INSERT OR IGNORE INTO graph_edges (project, source_node_id, target_node_id, edge_type, weight)
|
|
365
|
+
SELECT '$project', d.id, s.id, 'recorded_in', 1.0
|
|
366
|
+
FROM summaries sm
|
|
367
|
+
JOIN graph_nodes d ON d.project = sm.project AND d.node_type = 'decision' AND d.node_name = sm.session_id || ':decision'
|
|
368
|
+
JOIN graph_nodes s ON s.project = sm.project AND s.node_type = 'session' AND s.node_name = sm.session_id
|
|
369
|
+
WHERE sm.project = '$project'
|
|
370
|
+
AND COALESCE(sm.decisions, '') != ''
|
|
371
|
+
AND d.id != s.id;
|
|
372
|
+
|
|
373
|
+
INSERT OR IGNORE INTO graph_edges (project, source_node_id, target_node_id, edge_type, weight)
|
|
374
|
+
SELECT '$project', d.id, fnode.id, 'touches', 1.0
|
|
375
|
+
FROM summaries sm
|
|
376
|
+
JOIN graph_nodes d ON d.project = sm.project AND d.node_type = 'decision' AND d.node_name = sm.session_id || ':decision'
|
|
377
|
+
JOIN (
|
|
378
|
+
SELECT session_id, project, json_each.value AS file_path
|
|
379
|
+
FROM summaries, json_each(CASE WHEN json_valid(files_read) THEN files_read ELSE '[]' END)
|
|
380
|
+
WHERE project = '$project'
|
|
381
|
+
UNION
|
|
382
|
+
SELECT session_id, project, json_each.value AS file_path
|
|
383
|
+
FROM summaries, json_each(CASE WHEN json_valid(files_modified) THEN files_modified ELSE '[]' END)
|
|
384
|
+
WHERE project = '$project'
|
|
385
|
+
) sf ON sf.session_id = sm.session_id AND sf.project = sm.project
|
|
386
|
+
JOIN graph_nodes fnode ON fnode.project = sf.project AND fnode.node_type = 'file' AND fnode.node_name = sf.file_path
|
|
387
|
+
WHERE sm.project = '$project'
|
|
388
|
+
AND COALESCE(sm.decisions, '') != ''
|
|
389
|
+
AND COALESCE(sf.file_path, '') != ''
|
|
390
|
+
AND d.id != fnode.id;
|
|
391
|
+
|
|
392
|
+
INSERT OR IGNORE INTO graph_edges (project, source_node_id, target_node_id, edge_type, weight)
|
|
393
|
+
SELECT '$project', feat.id, file.id, 'covers', 1.0
|
|
394
|
+
FROM features f
|
|
395
|
+
JOIN feature_files ff ON ff.feature_id = f.id
|
|
396
|
+
JOIN graph_nodes feat ON feat.project = f.project AND feat.node_type = 'feature' AND feat.node_name = f.name
|
|
397
|
+
JOIN graph_nodes file ON file.project = f.project AND file.node_type = 'file' AND file.node_name = ff.file_path
|
|
398
|
+
WHERE f.project = '$project'
|
|
399
|
+
AND feat.id != file.id;
|
|
400
|
+
|
|
401
|
+
INSERT OR IGNORE INTO graph_edges (project, source_node_id, target_node_id, edge_type, weight)
|
|
402
|
+
SELECT '$project', memory.id, session.id, 'originated_in', 1.0
|
|
403
|
+
FROM agent_memories m
|
|
404
|
+
JOIN graph_nodes memory ON memory.project = m.project AND memory.node_type = 'memory' AND memory.node_name = m.memory_name
|
|
405
|
+
JOIN graph_nodes session ON session.project = m.project AND session.node_type = 'session' AND session.node_name = m.origin_session_id
|
|
406
|
+
WHERE m.project = '$project'
|
|
407
|
+
AND COALESCE(m.origin_session_id, '') != ''
|
|
408
|
+
AND memory.id != session.id;
|
|
409
|
+
|
|
410
|
+
INSERT OR IGNORE INTO graph_edges (project, source_node_id, target_node_id, edge_type, weight)
|
|
411
|
+
SELECT '$project', memory.id, feat.id, 'mentions', 1.0
|
|
412
|
+
FROM agent_memories m
|
|
413
|
+
JOIN features f ON f.project = m.project AND f.status = 'active'
|
|
414
|
+
JOIN graph_nodes memory ON memory.project = m.project AND memory.node_type = 'memory' AND memory.node_name = m.memory_name
|
|
415
|
+
JOIN graph_nodes feat ON feat.project = f.project AND feat.node_type = 'feature' AND feat.node_name = f.name
|
|
416
|
+
WHERE m.project = '$project'
|
|
417
|
+
AND LOWER(COALESCE(m.memory_name, '') || ' ' || COALESCE(m.description, '') || ' ' || COALESCE(m.content, '')) LIKE '%' || LOWER(f.name) || '%'
|
|
418
|
+
AND memory.id != feat.id;
|
|
419
|
+
|
|
420
|
+
INSERT OR IGNORE INTO graph_edges (project, source_node_id, target_node_id, edge_type, weight)
|
|
421
|
+
SELECT '$project', task.id, session.id, 'planned_in', 1.0
|
|
422
|
+
FROM agent_tasks t
|
|
423
|
+
JOIN graph_nodes task ON task.project = t.project AND task.node_type = 'task' AND task.node_name = COALESCE(NULLIF(t.source_task_id, ''), t.subject)
|
|
424
|
+
JOIN graph_nodes session ON session.project = t.project AND session.node_type = 'session' AND session.node_name = t.source_session_id
|
|
425
|
+
WHERE t.project = '$project'
|
|
426
|
+
AND COALESCE(t.source_session_id, '') != ''
|
|
427
|
+
AND task.id != session.id;
|
|
428
|
+
|
|
429
|
+
INSERT OR IGNORE INTO graph_edges (project, source_node_id, target_node_id, edge_type, weight)
|
|
430
|
+
SELECT '$project', task.id, feat.id, 'mentions', 1.0
|
|
431
|
+
FROM agent_tasks t
|
|
432
|
+
JOIN features f ON f.project = t.project AND f.status = 'active'
|
|
433
|
+
JOIN graph_nodes task ON task.project = t.project AND task.node_type = 'task' AND task.node_name = COALESCE(NULLIF(t.source_task_id, ''), t.subject)
|
|
434
|
+
JOIN graph_nodes feat ON feat.project = f.project AND feat.node_type = 'feature' AND feat.node_name = f.name
|
|
435
|
+
WHERE t.project = '$project'
|
|
436
|
+
AND LOWER(COALESCE(t.subject, '') || ' ' || COALESCE(t.description, '')) LIKE '%' || LOWER(f.name) || '%'
|
|
437
|
+
AND task.id != feat.id;
|
|
438
|
+
|
|
439
|
+
INSERT OR IGNORE INTO graph_edges (project, source_node_id, target_node_id, edge_type, weight)
|
|
440
|
+
SELECT '$project', decision.id, feat.id, 'mentions', 1.0
|
|
441
|
+
FROM summaries sm
|
|
442
|
+
JOIN features f ON f.project = sm.project AND f.status = 'active'
|
|
443
|
+
JOIN graph_nodes decision ON decision.project = sm.project AND decision.node_type = 'decision' AND decision.node_name = sm.session_id || ':decision'
|
|
444
|
+
JOIN graph_nodes feat ON feat.project = f.project AND feat.node_type = 'feature' AND feat.node_name = f.name
|
|
445
|
+
WHERE sm.project = '$project'
|
|
446
|
+
AND LOWER(COALESCE(sm.decisions, '')) LIKE '%' || LOWER(f.name) || '%'
|
|
447
|
+
AND decision.id != feat.id;
|
|
448
|
+
|
|
449
|
+
COMMIT;
|
|
450
|
+
SQL
|
|
451
|
+
|
|
452
|
+
eagle_db "SELECT COUNT(*)
|
|
453
|
+
FROM graph_nodes
|
|
454
|
+
WHERE project = '$project'
|
|
455
|
+
AND node_type IN ('feature', 'memory', 'task', 'session', 'decision');"
|
|
456
|
+
}
|
|
457
|
+
|
|
304
458
|
eagle_graph_emit_session_file_edge_sql() {
|
|
305
459
|
local project_raw="${1:-}"
|
|
306
460
|
local session_id="${2:-}"
|
package/lib/db-observations.sh
CHANGED
|
@@ -35,6 +35,40 @@ eagle_insert_observation() {
|
|
|
35
35
|
);"
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
eagle_insert_recall_event() {
|
|
39
|
+
local session_id; session_id=$(eagle_sql_escape "${1:-}")
|
|
40
|
+
local project; project=$(eagle_sql_escape "${2:-}")
|
|
41
|
+
local cwd; cwd=$(eagle_sql_escape "${3:-}")
|
|
42
|
+
local agent; agent=$(eagle_sql_escape "${4:-$(eagle_agent_source)}")
|
|
43
|
+
local prompt_snippet; prompt_snippet=$(eagle_sql_escape "$(eagle_trim_text "${5:-}" 240)")
|
|
44
|
+
local fts_query; fts_query=$(eagle_sql_escape "${6:-}")
|
|
45
|
+
local summary_matches; summary_matches=$(eagle_sql_int "${7:-0}")
|
|
46
|
+
local memory_matches; memory_matches=$(eagle_sql_int "${8:-0}")
|
|
47
|
+
local code_matches; code_matches=$(eagle_sql_int "${9:-0}")
|
|
48
|
+
local injected_chars; injected_chars=$(eagle_sql_int "${10:-0}")
|
|
49
|
+
local status; status=$(eagle_sql_escape "${11:-ok}")
|
|
50
|
+
local error; error=$(eagle_sql_escape "${12:-}")
|
|
51
|
+
local summary_refs; summary_refs=$(eagle_sql_escape "${13:-[]}")
|
|
52
|
+
local memory_refs; memory_refs=$(eagle_sql_escape "${14:-[]}")
|
|
53
|
+
local code_refs; code_refs=$(eagle_sql_escape "${15:-[]}")
|
|
54
|
+
local injected_token_estimate=$(( (injected_chars + 3) / 4 ))
|
|
55
|
+
|
|
56
|
+
[ -n "$project" ] || project="unknown"
|
|
57
|
+
|
|
58
|
+
eagle_db "INSERT INTO recall_events (
|
|
59
|
+
session_id, project, cwd, agent, prompt_snippet, fts_query,
|
|
60
|
+
summary_matches, memory_matches, code_matches,
|
|
61
|
+
summary_refs, memory_refs, code_refs, injected_chars,
|
|
62
|
+
injected_token_estimate, status, error
|
|
63
|
+
)
|
|
64
|
+
VALUES (
|
|
65
|
+
'$session_id', '$project', '$cwd', '$agent', '$prompt_snippet', '$fts_query',
|
|
66
|
+
$summary_matches, $memory_matches, $code_matches,
|
|
67
|
+
'$summary_refs', '$memory_refs', '$code_refs', $injected_chars,
|
|
68
|
+
$injected_token_estimate, '$status', '$error'
|
|
69
|
+
);"
|
|
70
|
+
}
|
|
71
|
+
|
|
38
72
|
eagle_prune_observations() {
|
|
39
73
|
local days; days=$(eagle_sql_int "${1:-90}")
|
|
40
74
|
local project_filter=""
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ═══════════════════════════════════════════════════════════
|
|
3
|
+
# Eagle Mem — Orchestration DB helpers
|
|
4
|
+
# Lightweight helpers safe for hooks; full worker management lives in scripts/orchestrate.sh.
|
|
5
|
+
# ═══════════════════════════════════════════════════════════
|
|
6
|
+
[ -n "${_EAGLE_DB_ORCHESTRATION_LOADED:-}" ] && return 0
|
|
7
|
+
_EAGLE_DB_ORCHESTRATION_LOADED=1
|
|
8
|
+
|
|
9
|
+
eagle_auto_orchestration_agent() {
|
|
10
|
+
local agent="${1:-}"
|
|
11
|
+
case "$agent" in
|
|
12
|
+
codex|claude-code) printf '%s\n' "$agent" ;;
|
|
13
|
+
*) printf 'codex\n' ;;
|
|
14
|
+
esac
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
eagle_auto_orchestration_lane_sql() {
|
|
18
|
+
local oid="$1" project="$2" name="$3" lane_key="$4" title="$5" description="$6" agent="$7" validation="$8"
|
|
19
|
+
local source_task_id file_path content_hash
|
|
20
|
+
local oid_sql project_sql name_sql key_sql title_sql desc_sql agent_sql validation_sql task_sql fp_sql hash_sql
|
|
21
|
+
|
|
22
|
+
oid_sql=$(eagle_sql_int "$oid")
|
|
23
|
+
project_sql=$(eagle_sql_escape "$project")
|
|
24
|
+
name_sql=$(eagle_sql_escape "$name")
|
|
25
|
+
key_sql=$(eagle_sql_escape "$lane_key")
|
|
26
|
+
title_sql=$(eagle_sql_escape "$title")
|
|
27
|
+
desc_sql=$(eagle_sql_escape "$description")
|
|
28
|
+
agent_sql=$(eagle_sql_escape "$(eagle_auto_orchestration_agent "$agent")")
|
|
29
|
+
validation_sql=$(eagle_sql_escape "$validation")
|
|
30
|
+
source_task_id="lane-${name}-${lane_key}"
|
|
31
|
+
file_path="orchestration-lane://${project}/${name}/${lane_key}"
|
|
32
|
+
task_sql=$(eagle_sql_escape "$source_task_id")
|
|
33
|
+
fp_sql=$(eagle_sql_escape "$file_path")
|
|
34
|
+
content_hash=$(printf '%s|%s|%s|%s|%s' "$lane_key" "$title" "$description" "$agent" "$validation" | eagle_sha256_stream)
|
|
35
|
+
hash_sql=$(eagle_sql_escape "$content_hash")
|
|
36
|
+
|
|
37
|
+
cat <<SQL
|
|
38
|
+
INSERT INTO orchestration_lanes (orchestration_id, project, lane_key, title, description, agent, worktree_path, validation, status, source_task_id)
|
|
39
|
+
VALUES ($oid_sql, '$project_sql', '$key_sql', '$title_sql', '$desc_sql', '$agent_sql', '', '$validation_sql', 'pending', '$task_sql')
|
|
40
|
+
ON CONFLICT(orchestration_id, lane_key) DO UPDATE SET
|
|
41
|
+
title = excluded.title,
|
|
42
|
+
description = excluded.description,
|
|
43
|
+
agent = excluded.agent,
|
|
44
|
+
validation = excluded.validation,
|
|
45
|
+
source_task_id = excluded.source_task_id,
|
|
46
|
+
status = CASE
|
|
47
|
+
WHEN orchestration_lanes.status IN ('completed', 'cancelled') THEN orchestration_lanes.status
|
|
48
|
+
ELSE orchestration_lanes.status
|
|
49
|
+
END,
|
|
50
|
+
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now');
|
|
51
|
+
|
|
52
|
+
INSERT INTO agent_tasks (project, source_session_id, source_task_id, file_path, subject, description, active_form, status, blocks, blocked_by, content_hash, origin_agent)
|
|
53
|
+
VALUES ('$project_sql', 'orchestration', '$task_sql', '$fp_sql', '$title_sql', '$desc_sql', '$validation_sql', 'pending', '[]', '[]', '$hash_sql', '$agent_sql')
|
|
54
|
+
ON CONFLICT(file_path) DO UPDATE SET
|
|
55
|
+
source_task_id = excluded.source_task_id,
|
|
56
|
+
file_path = excluded.file_path,
|
|
57
|
+
subject = excluded.subject,
|
|
58
|
+
description = excluded.description,
|
|
59
|
+
active_form = excluded.active_form,
|
|
60
|
+
status = CASE
|
|
61
|
+
WHEN agent_tasks.status IN ('completed', 'cancelled') THEN agent_tasks.status
|
|
62
|
+
ELSE agent_tasks.status
|
|
63
|
+
END,
|
|
64
|
+
content_hash = excluded.content_hash,
|
|
65
|
+
origin_agent = excluded.origin_agent,
|
|
66
|
+
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now');
|
|
67
|
+
SQL
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
eagle_auto_orchestrate_from_prompt() {
|
|
71
|
+
local project_raw="${1:-}"
|
|
72
|
+
local session_id_raw="${2:-}"
|
|
73
|
+
local agent_raw="${3:-$(eagle_agent_source)}"
|
|
74
|
+
local prompt_raw="${4:-}"
|
|
75
|
+
local cwd_raw="${5:-}"
|
|
76
|
+
local trigger_raw="${6:-broad_prompt}"
|
|
77
|
+
[ -n "$project_raw" ] && [ -n "$prompt_raw" ] || return 0
|
|
78
|
+
|
|
79
|
+
local has_tables
|
|
80
|
+
has_tables=$(eagle_db "SELECT COUNT(*)
|
|
81
|
+
FROM sqlite_master
|
|
82
|
+
WHERE type = 'table'
|
|
83
|
+
AND name IN ('orchestrations', 'orchestration_lanes', 'agent_tasks');" 2>/dev/null || printf '0')
|
|
84
|
+
[ "${has_tables:-0}" -ge 3 ] 2>/dev/null || return 0
|
|
85
|
+
|
|
86
|
+
local name="auto"
|
|
87
|
+
local project_sql name_sql goal_sql baseline_sql run_key_sql session_sql cwd_sql agent_sql trigger_sql prompt_sql
|
|
88
|
+
local oid lanes_json lanes_sql lane_count
|
|
89
|
+
project_sql=$(eagle_sql_escape "$project_raw")
|
|
90
|
+
name_sql=$(eagle_sql_escape "$name")
|
|
91
|
+
goal_sql=$(eagle_sql_escape "$(eagle_trim_text "$prompt_raw" 700)")
|
|
92
|
+
baseline_sql=$(eagle_sql_escape "$(git -C "${cwd_raw:-$(pwd)}" rev-parse --short HEAD 2>/dev/null || true)")
|
|
93
|
+
run_key_sql=$(eagle_sql_escape "auto-$(printf '%s' "$project_raw|$prompt_raw" | eagle_sha256_stream | cut -c1-12)")
|
|
94
|
+
session_sql=$(eagle_sql_escape "$session_id_raw")
|
|
95
|
+
cwd_sql=$(eagle_sql_escape "$cwd_raw")
|
|
96
|
+
agent_sql=$(eagle_sql_escape "$(eagle_auto_orchestration_agent "$agent_raw")")
|
|
97
|
+
trigger_sql=$(eagle_sql_escape "$trigger_raw")
|
|
98
|
+
prompt_sql=$(eagle_sql_escape "$(eagle_trim_text "$prompt_raw" 300)")
|
|
99
|
+
|
|
100
|
+
eagle_db_pipe <<SQL >/dev/null
|
|
101
|
+
INSERT INTO orchestrations (project, name, goal, status, baseline_ref, run_key)
|
|
102
|
+
VALUES ('$project_sql', '$name_sql', '$goal_sql', 'active', '$baseline_sql', '$run_key_sql')
|
|
103
|
+
ON CONFLICT(project, name) DO UPDATE SET
|
|
104
|
+
goal = excluded.goal,
|
|
105
|
+
status = 'active',
|
|
106
|
+
baseline_ref = COALESCE(NULLIF(excluded.baseline_ref, ''), orchestrations.baseline_ref),
|
|
107
|
+
run_key = CASE
|
|
108
|
+
WHEN orchestrations.status IN ('completed', 'cancelled')
|
|
109
|
+
OR orchestrations.run_key IS NULL
|
|
110
|
+
OR orchestrations.run_key = ''
|
|
111
|
+
THEN excluded.run_key
|
|
112
|
+
ELSE orchestrations.run_key
|
|
113
|
+
END,
|
|
114
|
+
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now');
|
|
115
|
+
SQL
|
|
116
|
+
|
|
117
|
+
oid=$(eagle_db "SELECT id FROM orchestrations WHERE project = '$project_sql' AND name = '$name_sql' LIMIT 1;" 2>/dev/null || true)
|
|
118
|
+
[ -n "$oid" ] || return 0
|
|
119
|
+
|
|
120
|
+
eagle_db_pipe <<SQL >/dev/null
|
|
121
|
+
BEGIN;
|
|
122
|
+
$(eagle_auto_orchestration_lane_sql "$oid" "$project_raw" "$name" "scope" "Scope broad request" "Map requirements, affected files, risks, and existing docs before implementation." "$agent_raw" "Document scope and affected surfaces")
|
|
123
|
+
$(eagle_auto_orchestration_lane_sql "$oid" "$project_raw" "$name" "implementation" "Implement scoped changes" "Make the smallest safe changes needed for the broad request, preserving existing behavior." "$agent_raw" "Run targeted implementation checks")
|
|
124
|
+
$(eagle_auto_orchestration_lane_sql "$oid" "$project_raw" "$name" "verification" "Verify and report" "Run targeted tests, smoke checks, and update backlog or handoff notes after implementation." "$agent_raw" "Run relevant tests and smoke checks")
|
|
125
|
+
UPDATE orchestrations SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = $oid;
|
|
126
|
+
COMMIT;
|
|
127
|
+
SQL
|
|
128
|
+
|
|
129
|
+
lanes_json=$(eagle_db_json "SELECT lane_key, title, agent, status
|
|
130
|
+
FROM orchestration_lanes
|
|
131
|
+
WHERE orchestration_id = $oid
|
|
132
|
+
ORDER BY CASE lane_key WHEN 'scope' THEN 0 WHEN 'implementation' THEN 1 WHEN 'verification' THEN 2 ELSE 3 END, lane_key;" 2>/dev/null || printf '[]')
|
|
133
|
+
[ -n "$lanes_json" ] || lanes_json="[]"
|
|
134
|
+
lanes_sql=$(eagle_sql_escape "$lanes_json")
|
|
135
|
+
lane_count=$(printf '%s' "$lanes_json" | jq 'length' 2>/dev/null || printf '0')
|
|
136
|
+
|
|
137
|
+
if [ -n "$(eagle_db "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'orchestration_auto_events' LIMIT 1;" 2>/dev/null || true)" ]; then
|
|
138
|
+
eagle_db "INSERT INTO orchestration_auto_events (
|
|
139
|
+
session_id, project, cwd, agent, prompt_snippet, trigger,
|
|
140
|
+
orchestration_id, orchestration_name, lanes, status
|
|
141
|
+
)
|
|
142
|
+
VALUES (
|
|
143
|
+
'$session_sql', '$project_sql', '$cwd_sql', '$agent_sql', '$prompt_sql', '$trigger_sql',
|
|
144
|
+
$oid, '$name_sql', '$lanes_sql', 'created'
|
|
145
|
+
);" >/dev/null 2>&1 || true
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
printf '%s|%s|%s|%s\n' "$name" "$oid" "${lane_count:-0}" "$lanes_json"
|
|
149
|
+
}
|
package/lib/db.sh
CHANGED
|
@@ -9,11 +9,13 @@ _EAGLE_DB_LOADED=1
|
|
|
9
9
|
_eagle_db_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
10
|
|
|
11
11
|
. "$_eagle_db_dir/db-core.sh"
|
|
12
|
+
. "$_eagle_db_dir/db-events.sh"
|
|
12
13
|
. "$_eagle_db_dir/db-sessions.sh"
|
|
13
14
|
. "$_eagle_db_dir/db-observations.sh"
|
|
14
15
|
. "$_eagle_db_dir/db-summaries.sh"
|
|
15
16
|
. "$_eagle_db_dir/db-mirrors.sh"
|
|
16
17
|
. "$_eagle_db_dir/db-features.sh"
|
|
18
|
+
. "$_eagle_db_dir/db-orchestration.sh"
|
|
17
19
|
. "$_eagle_db_dir/db-hints.sh"
|
|
18
20
|
. "$_eagle_db_dir/db-backfill.sh"
|
|
19
21
|
. "$_eagle_db_dir/db-guardrails.sh"
|
package/lib/hooks.sh
CHANGED
|
@@ -42,15 +42,20 @@ eagle_patch_hook() {
|
|
|
42
42
|
|
|
43
43
|
# Check both command AND matcher to avoid skipping entries with different matchers
|
|
44
44
|
# (e.g. PreToolUse with "Bash" vs "Read" matcher using the same script)
|
|
45
|
-
local match_query
|
|
46
45
|
if [ -n "$matcher" ]; then
|
|
47
|
-
|
|
46
|
+
if jq -e --arg m "$matcher" --arg c "$command" \
|
|
47
|
+
".hooks.${event}[]? | select(.matcher == \$m and (.hooks[]?.command == \$c))" \
|
|
48
|
+
"$settings" &>/dev/null; then
|
|
49
|
+
[ -n "$description" ] && eagle_ok "$description ${DIM}(already registered)${RESET}"
|
|
50
|
+
return 0
|
|
51
|
+
fi
|
|
48
52
|
else
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
if jq -e --arg c "$command" \
|
|
54
|
+
".hooks.${event}[]? | select(.matcher == null and (.hooks[]?.command == \$c))" \
|
|
55
|
+
"$settings" &>/dev/null; then
|
|
56
|
+
[ -n "$description" ] && eagle_ok "$description ${DIM}(already registered)${RESET}"
|
|
57
|
+
return 0
|
|
58
|
+
fi
|
|
54
59
|
fi
|
|
55
60
|
|
|
56
61
|
local entry
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ═══════════════════════════════════════════════════════════
|
|
3
|
+
# Eagle Mem — OpenCode plugin registration helpers
|
|
4
|
+
# Shared by install.sh, update.sh, uninstall.sh, and doctor.sh
|
|
5
|
+
# ═══════════════════════════════════════════════════════════
|
|
6
|
+
[ -n "${_EAGLE_OPENCODE_HOOKS_LOADED:-}" ] && return 0
|
|
7
|
+
_EAGLE_OPENCODE_HOOKS_LOADED=1
|
|
8
|
+
|
|
9
|
+
eagle_opencode_detected() {
|
|
10
|
+
[ -d "$EAGLE_OPENCODE_DIR" ] && return 0
|
|
11
|
+
command -v opencode >/dev/null 2>&1
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
eagle_opencode_plugin_source() {
|
|
15
|
+
local package_dir="${1:-.}"
|
|
16
|
+
printf '%s\n' "$package_dir/integrations/opencode_eagle_mem_plugin.js"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
eagle_install_opencode_plugin() {
|
|
20
|
+
local package_dir="${1:-.}" dry_run="${2:-0}"
|
|
21
|
+
local src
|
|
22
|
+
src=$(eagle_opencode_plugin_source "$package_dir")
|
|
23
|
+
|
|
24
|
+
[ -f "$src" ] || {
|
|
25
|
+
eagle_warn "OpenCode plugin source missing: $src"
|
|
26
|
+
return 1
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if [ "$dry_run" = "1" ]; then
|
|
30
|
+
eagle_info "Would install OpenCode plugin: $EAGLE_OPENCODE_PLUGIN"
|
|
31
|
+
return 0
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
mkdir -p "$EAGLE_OPENCODE_PLUGINS_DIR"
|
|
35
|
+
if [ -f "$EAGLE_OPENCODE_PLUGIN" ] && ! grep -q "EAGLE_MEM_OPENCODE_PLUGIN" "$EAGLE_OPENCODE_PLUGIN" 2>/dev/null; then
|
|
36
|
+
eagle_warn "OpenCode plugin path already exists and is not Eagle Mem-owned: $EAGLE_OPENCODE_PLUGIN"
|
|
37
|
+
return 1
|
|
38
|
+
fi
|
|
39
|
+
cp "$src" "$EAGLE_OPENCODE_PLUGIN"
|
|
40
|
+
chmod 600 "$EAGLE_OPENCODE_PLUGIN" 2>/dev/null || true
|
|
41
|
+
eagle_ok "OpenCode plugin ${DIM}(global local plugin)${RESET}"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
eagle_install_opencode_skills() {
|
|
45
|
+
local package_dir="${1:-.}" dry_run="${2:-0}"
|
|
46
|
+
[ -d "$package_dir/skills" ] || return 0
|
|
47
|
+
|
|
48
|
+
if [ "$dry_run" = "1" ]; then
|
|
49
|
+
eagle_info "Would symlink OpenCode skills to: $EAGLE_OPENCODE_SKILLS_DIR"
|
|
50
|
+
return 0
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
mkdir -p "$EAGLE_OPENCODE_SKILLS_DIR"
|
|
54
|
+
find "$EAGLE_OPENCODE_SKILLS_DIR" -maxdepth 1 -name "eagle-mem-*" -type l 2>/dev/null | while read -r existing; do
|
|
55
|
+
skill_name=$(basename "$existing")
|
|
56
|
+
if [ ! -d "$package_dir/skills/$skill_name" ]; then
|
|
57
|
+
rm "$existing"
|
|
58
|
+
eagle_ok "Removed stale OpenCode skill: $skill_name"
|
|
59
|
+
fi
|
|
60
|
+
done
|
|
61
|
+
for skill_dir in "$package_dir"/skills/*/; do
|
|
62
|
+
[ -d "$skill_dir" ] || continue
|
|
63
|
+
skill_name=$(basename "$skill_dir")
|
|
64
|
+
dst="$EAGLE_OPENCODE_SKILLS_DIR/$skill_name"
|
|
65
|
+
[ -L "$dst" ] && rm "$dst"
|
|
66
|
+
ln -sf "$skill_dir" "$dst"
|
|
67
|
+
done
|
|
68
|
+
eagle_ok "OpenCode skills updated"
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
eagle_remove_opencode_plugin() {
|
|
72
|
+
[ -f "$EAGLE_OPENCODE_PLUGIN" ] || return 1
|
|
73
|
+
|
|
74
|
+
if grep -q "EAGLE_MEM_OPENCODE_PLUGIN" "$EAGLE_OPENCODE_PLUGIN" 2>/dev/null; then
|
|
75
|
+
rm -f "$EAGLE_OPENCODE_PLUGIN"
|
|
76
|
+
return 0
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
return 1
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
eagle_remove_opencode_skills() {
|
|
83
|
+
[ -d "$EAGLE_OPENCODE_SKILLS_DIR" ] || return 1
|
|
84
|
+
|
|
85
|
+
local removed=1
|
|
86
|
+
for target in "$EAGLE_OPENCODE_SKILLS_DIR"/eagle-mem-*; do
|
|
87
|
+
[ -L "$target" ] || continue
|
|
88
|
+
rm -rf "$target"
|
|
89
|
+
eagle_ok "OpenCode skill removed: $(basename "$target")"
|
|
90
|
+
removed=0
|
|
91
|
+
done
|
|
92
|
+
return "$removed"
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
eagle_opencode_plugin_state() {
|
|
96
|
+
if ! eagle_opencode_detected; then
|
|
97
|
+
printf '%s\n' "not found"
|
|
98
|
+
elif [ -f "$EAGLE_OPENCODE_PLUGIN" ] && grep -q "EAGLE_MEM_OPENCODE_PLUGIN" "$EAGLE_OPENCODE_PLUGIN" 2>/dev/null; then
|
|
99
|
+
printf '%s\n' "registered"
|
|
100
|
+
elif [ -f "$EAGLE_OPENCODE_PLUGIN" ]; then
|
|
101
|
+
printf '%s\n' "custom"
|
|
102
|
+
else
|
|
103
|
+
printf '%s\n' "not registered"
|
|
104
|
+
fi
|
|
105
|
+
}
|
package/lib/provider.sh
CHANGED
|
@@ -513,7 +513,7 @@ _eagle_call_codex_cli() {
|
|
|
513
513
|
--ephemeral \
|
|
514
514
|
--skip-git-repo-check \
|
|
515
515
|
--ignore-rules \
|
|
516
|
-
-c features.
|
|
516
|
+
-c features.hooks=false \
|
|
517
517
|
--sandbox read-only \
|
|
518
518
|
--cd "${EAGLE_AGENT_CWD:-$(pwd)}" \
|
|
519
519
|
--model "$model" \
|
|
@@ -525,7 +525,7 @@ _eagle_call_codex_cli() {
|
|
|
525
525
|
--ephemeral \
|
|
526
526
|
--skip-git-repo-check \
|
|
527
527
|
--ignore-rules \
|
|
528
|
-
-c features.
|
|
528
|
+
-c features.hooks=false \
|
|
529
529
|
--sandbox read-only \
|
|
530
530
|
--cd "${EAGLE_AGENT_CWD:-$(pwd)}" \
|
|
531
531
|
--output-last-message "$out_file" \
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eagle-mem",
|
|
3
|
-
"version": "4.
|
|
4
|
-
"description": "Shared memory, release guardrails, RTK token protection, and worker lanes for Claude Code, Codex, Grok, and Google Antigravity",
|
|
3
|
+
"version": "4.11.0",
|
|
4
|
+
"description": "Shared memory, release guardrails, RTK token protection, and worker lanes for Claude Code, Codex, OpenCode, Grok, and Google Antigravity",
|
|
5
5
|
"bin": {
|
|
6
6
|
"eagle-mem": "bin/eagle-mem"
|
|
7
7
|
},
|
|
@@ -20,11 +20,14 @@
|
|
|
20
20
|
"docs/",
|
|
21
21
|
"architecture.html",
|
|
22
22
|
"integrations/*.py",
|
|
23
|
+
"integrations/*.js",
|
|
24
|
+
"tests/fixtures/",
|
|
23
25
|
"CHANGELOG.md"
|
|
24
26
|
],
|
|
25
27
|
"keywords": [
|
|
26
28
|
"claude-code",
|
|
27
29
|
"codex",
|
|
30
|
+
"opencode",
|
|
28
31
|
"grok",
|
|
29
32
|
"memory",
|
|
30
33
|
"sqlite",
|