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/scripts/uninstall.sh
CHANGED
|
@@ -11,6 +11,7 @@ LIB_DIR="$SCRIPTS_DIR/../lib"
|
|
|
11
11
|
. "$SCRIPTS_DIR/style.sh"
|
|
12
12
|
. "$LIB_DIR/common.sh"
|
|
13
13
|
. "$LIB_DIR/codex-hooks.sh"
|
|
14
|
+
. "$LIB_DIR/opencode-hooks.sh"
|
|
14
15
|
|
|
15
16
|
SETTINGS="$EAGLE_SETTINGS"
|
|
16
17
|
dry_run=false
|
|
@@ -66,6 +67,12 @@ else
|
|
|
66
67
|
eagle_warn "Could not patch Codex hooks.json (jq not found or file missing)"
|
|
67
68
|
fi
|
|
68
69
|
|
|
70
|
+
if eagle_remove_opencode_plugin; then
|
|
71
|
+
eagle_ok "OpenCode plugin removed"
|
|
72
|
+
else
|
|
73
|
+
eagle_info "OpenCode plugin not present or not Eagle Mem-owned"
|
|
74
|
+
fi
|
|
75
|
+
|
|
69
76
|
# ─── Remove instruction blocks and statusline integration ───
|
|
70
77
|
|
|
71
78
|
claude_md="$HOME/.claude/CLAUDE.md"
|
|
@@ -132,6 +139,8 @@ if [ -d "$EAGLE_GROK_SKILLS_DIR" ]; then
|
|
|
132
139
|
done
|
|
133
140
|
fi
|
|
134
141
|
|
|
142
|
+
eagle_remove_opencode_skills >/dev/null 2>&1 || true
|
|
143
|
+
|
|
135
144
|
# ─── Optionally wipe data ─────────────────────────────────
|
|
136
145
|
|
|
137
146
|
if [ -d "$EAGLE_MEM_DIR" ]; then
|
package/scripts/update.sh
CHANGED
|
@@ -16,11 +16,13 @@ LIB_DIR="$SCRIPTS_DIR/../lib"
|
|
|
16
16
|
. "$LIB_DIR/updater.sh"
|
|
17
17
|
. "$LIB_DIR/hooks.sh"
|
|
18
18
|
. "$LIB_DIR/codex-hooks.sh"
|
|
19
|
+
. "$LIB_DIR/opencode-hooks.sh"
|
|
19
20
|
|
|
20
21
|
SETTINGS="$EAGLE_SETTINGS"
|
|
21
22
|
claude_found=false
|
|
22
23
|
codex_found=false
|
|
23
24
|
grok_found=false
|
|
25
|
+
opencode_found=false
|
|
24
26
|
|
|
25
27
|
eagle_header "Update"
|
|
26
28
|
|
|
@@ -43,8 +45,11 @@ fi
|
|
|
43
45
|
if [ -d "$EAGLE_GROK_DIR" ]; then
|
|
44
46
|
grok_found=true
|
|
45
47
|
fi
|
|
48
|
+
if eagle_opencode_detected; then
|
|
49
|
+
opencode_found=true
|
|
50
|
+
fi
|
|
46
51
|
|
|
47
|
-
eagle_runtime_change_plan "update" "$PACKAGE_DIR" "$claude_found" "$codex_found"
|
|
52
|
+
eagle_runtime_change_plan "update" "$PACKAGE_DIR" "$claude_found" "$codex_found" "$opencode_found"
|
|
48
53
|
|
|
49
54
|
# ─── Update files ──────────────────────────────────────────
|
|
50
55
|
|
|
@@ -82,11 +87,12 @@ fi
|
|
|
82
87
|
|
|
83
88
|
if [ "$claude_found" = true ] && [ -f "$SETTINGS" ] && command -v jq &>/dev/null; then
|
|
84
89
|
# Clean old registrations before re-registering (handles matcher changes across versions)
|
|
90
|
+
eagle_clean_hook_entries "$SETTINGS" "Stop" "$EAGLE_MEM_DIR/hooks/stop.sh"
|
|
85
91
|
eagle_clean_hook_entries "$SETTINGS" "PostToolUse" "$EAGLE_MEM_DIR/hooks/post-tool-use.sh"
|
|
86
92
|
eagle_clean_hook_entries "$SETTINGS" "PreToolUse" "$EAGLE_MEM_DIR/hooks/pre-tool-use.sh"
|
|
87
93
|
|
|
88
94
|
eagle_patch_hook "$SETTINGS" "SessionStart" "" "$EAGLE_MEM_DIR/hooks/session-start.sh"
|
|
89
|
-
eagle_patch_hook "$SETTINGS" "Stop" "" "$EAGLE_MEM_DIR/hooks/stop.sh"
|
|
95
|
+
eagle_patch_hook "$SETTINGS" "Stop" "" "bash \"$EAGLE_MEM_DIR/hooks/stop.sh\""
|
|
90
96
|
eagle_patch_hook "$SETTINGS" "PostToolUse" "Read|Write|Edit|Bash|TaskUpdate" "$EAGLE_MEM_DIR/hooks/post-tool-use.sh"
|
|
91
97
|
eagle_patch_hook "$SETTINGS" "TaskCreated" "" "$EAGLE_MEM_DIR/hooks/post-tool-use.sh"
|
|
92
98
|
eagle_patch_hook "$SETTINGS" "TaskCompleted" "" "$EAGLE_MEM_DIR/hooks/post-tool-use.sh"
|
|
@@ -104,6 +110,12 @@ elif [ "$codex_found" = false ]; then
|
|
|
104
110
|
eagle_info "Codex hooks skipped ${DIM}(Codex not detected)${RESET}"
|
|
105
111
|
fi
|
|
106
112
|
|
|
113
|
+
if [ "$opencode_found" = true ]; then
|
|
114
|
+
eagle_install_opencode_plugin "$PACKAGE_DIR" "0"
|
|
115
|
+
else
|
|
116
|
+
eagle_info "OpenCode plugin skipped ${DIM}(OpenCode not detected)${RESET}"
|
|
117
|
+
fi
|
|
118
|
+
|
|
107
119
|
# ─── Update skill symlinks ────────────────────────────────
|
|
108
120
|
|
|
109
121
|
if [ "$claude_found" = true ] && [ -d "$PACKAGE_DIR/skills" ]; then
|
|
@@ -164,6 +176,10 @@ if [ "$grok_found" = true ] && [ -d "$PACKAGE_DIR/skills" ]; then
|
|
|
164
176
|
eagle_ok "Grok skills updated"
|
|
165
177
|
fi
|
|
166
178
|
|
|
179
|
+
if [ "$opencode_found" = true ] && [ -d "$PACKAGE_DIR/skills" ]; then
|
|
180
|
+
eagle_install_opencode_skills "$PACKAGE_DIR" "0"
|
|
181
|
+
fi
|
|
182
|
+
|
|
167
183
|
# ─── Refresh generated Claude statusline wrapper ───────────
|
|
168
184
|
|
|
169
185
|
statusline_wrapper="$EAGLE_MEM_DIR/scripts/statusline-wrapper.sh"
|
|
@@ -24,7 +24,7 @@ These are semantically different operations:
|
|
|
24
24
|
|
|
25
25
|
**Verify** = "I tested this exact change and it works." Fingerprint-specific — tied to the current diff hash. If the file changes again, a new pending verification appears.
|
|
26
26
|
|
|
27
|
-
**Waive** = "I accept
|
|
27
|
+
**Waive** = "I accept this current pending change without running the full smoke test." Fingerprint-specific — covers the current file fingerprint only. If that file changes again, Eagle Mem creates a fresh pending verification. Use when the current change is known-safe (e.g., comment-only edit, unrelated code path).
|
|
28
28
|
|
|
29
29
|
**Decision rule:** Did you run the smoke test or manually confirm behavior? Use `verify`. Is the change structurally irrelevant to the feature? Use `waive`.
|
|
30
30
|
|
|
@@ -72,7 +72,7 @@ Or waive a single pending record by ID:
|
|
|
72
72
|
eagle-mem feature waive <id> --reason "unrelated code path"
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
-
**Prefer waive-by-name** over waive-by-ID. IDs are ephemeral (new edits create new IDs), but names are stable. Waiving by name resolves all pending records for that feature at once.
|
|
75
|
+
**Prefer waive-by-name** over waive-by-ID. IDs are ephemeral (new edits create new IDs), but names are stable. Waiving by name resolves all current pending records for that feature at once.
|
|
76
76
|
|
|
77
77
|
A reason is always required — it's the audit trail for why verification was skipped.
|
|
78
78
|
|
|
@@ -116,7 +116,7 @@ eagle-mem feature show <name> # files, deps, smoke tests, last verified
|
|
|
116
116
|
| `feature show <name>` | Full detail: files, dependencies, smoke tests |
|
|
117
117
|
| `feature pending` | All unresolved pending verifications |
|
|
118
118
|
| `feature verify <name>` | Mark feature verified (fingerprint-specific) |
|
|
119
|
-
| `feature waive <name\|id>` | Waive verification
|
|
119
|
+
| `feature waive <name\|id>` | Waive current pending verification(s) |
|
|
120
120
|
| `feature add <name>` | Register a new feature with files/deps/tests |
|
|
121
121
|
| `--notes "text"` | Attach notes to verify/waive (audit trail) |
|
|
122
122
|
| `--reason "text"` | Required for waive — explains why safe |
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hook_event_name": "Status",
|
|
3
|
+
"session_id": "claude-status-fixture",
|
|
4
|
+
"transcript_path": "/tmp/eagle-mem/claude-status-fixture.jsonl",
|
|
5
|
+
"cwd": "/tmp/eagle-mem/project",
|
|
6
|
+
"model": {
|
|
7
|
+
"id": "claude-opus-4-1",
|
|
8
|
+
"display_name": "Opus"
|
|
9
|
+
},
|
|
10
|
+
"workspace": {
|
|
11
|
+
"current_dir": "/tmp/eagle-mem/project",
|
|
12
|
+
"project_dir": "/tmp/eagle-mem/project"
|
|
13
|
+
},
|
|
14
|
+
"version": "2.1.145",
|
|
15
|
+
"output_style": {
|
|
16
|
+
"name": "default"
|
|
17
|
+
},
|
|
18
|
+
"cost": {
|
|
19
|
+
"total_cost_usd": 0.01234,
|
|
20
|
+
"total_duration_ms": 45000,
|
|
21
|
+
"total_api_duration_ms": 2300,
|
|
22
|
+
"total_lines_added": 12,
|
|
23
|
+
"total_lines_removed": 2
|
|
24
|
+
},
|
|
25
|
+
"thinking": {
|
|
26
|
+
"enabled": true
|
|
27
|
+
},
|
|
28
|
+
"effort": {
|
|
29
|
+
"level": "xhigh"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"session_id": "claude-session-fixture",
|
|
3
|
+
"transcript_path": "/tmp/eagle-mem/claude-session-fixture.jsonl",
|
|
4
|
+
"cwd": "/tmp/eagle-mem/project",
|
|
5
|
+
"permission_mode": "default",
|
|
6
|
+
"hook_event_name": "UserPromptSubmit",
|
|
7
|
+
"prompt": "Review the OAuth memory before editing auth files"
|
|
8
|
+
}
|
|
9
|
+
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"sessionID": "opencode-session-fixture",
|
|
3
|
+
"input": {
|
|
4
|
+
"sessionID": "opencode-session-fixture",
|
|
5
|
+
"agent": "build",
|
|
6
|
+
"model": {
|
|
7
|
+
"providerID": "openai",
|
|
8
|
+
"modelID": "gpt-5"
|
|
9
|
+
},
|
|
10
|
+
"messageID": "opencode-user-message-fixture"
|
|
11
|
+
},
|
|
12
|
+
"output": {
|
|
13
|
+
"message": {
|
|
14
|
+
"id": "opencode-user-message-fixture",
|
|
15
|
+
"sessionID": "opencode-session-fixture",
|
|
16
|
+
"role": "user",
|
|
17
|
+
"time": {
|
|
18
|
+
"created": 1780372800000
|
|
19
|
+
},
|
|
20
|
+
"agent": "build",
|
|
21
|
+
"model": {
|
|
22
|
+
"providerID": "openai",
|
|
23
|
+
"modelID": "gpt-5"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"parts": [
|
|
27
|
+
{
|
|
28
|
+
"id": "opencode-text-part-fixture",
|
|
29
|
+
"sessionID": "opencode-session-fixture",
|
|
30
|
+
"messageID": "opencode-user-message-fixture",
|
|
31
|
+
"type": "text",
|
|
32
|
+
"text": "Review the OAuth memory before editing auth files"
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"input": {
|
|
3
|
+
"tool": "read",
|
|
4
|
+
"sessionID": "opencode-session-fixture",
|
|
5
|
+
"callID": "opencode-call-fixture",
|
|
6
|
+
"args": {
|
|
7
|
+
"filePath": "/tmp/eagle-mem/project/lib/auth.ts"
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"output": {
|
|
11
|
+
"title": "Read lib/auth.ts",
|
|
12
|
+
"output": "export function refreshToken() {}",
|
|
13
|
+
"metadata": {}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Guard against stale agent lifecycle assumptions when editing hook, statusline,
|
|
3
|
+
# config-installation, or instruction-file integration code.
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
7
|
+
DOC_DIR="$ROOT_DIR/docs/agent-compatibility"
|
|
8
|
+
FIXTURE_DIR="$ROOT_DIR/tests/fixtures/agent-hooks"
|
|
9
|
+
|
|
10
|
+
fail() {
|
|
11
|
+
echo "agent compatibility docs gate failed: $*" >&2
|
|
12
|
+
exit 1
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
require_file() {
|
|
16
|
+
local file="$1"
|
|
17
|
+
[ -f "$file" ] || fail "missing required file: ${file#$ROOT_DIR/}"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
require_contains() {
|
|
21
|
+
local file="$1"
|
|
22
|
+
local pattern="$2"
|
|
23
|
+
local label="$3"
|
|
24
|
+
if ! grep -Eq "$pattern" "$file"; then
|
|
25
|
+
fail "${file#$ROOT_DIR/} does not mention $label"
|
|
26
|
+
fi
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
require_json() {
|
|
30
|
+
local file="$1"
|
|
31
|
+
jq -e . "$file" >/dev/null || fail "invalid JSON fixture: ${file#$ROOT_DIR/}"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
require_file "$DOC_DIR/README.md"
|
|
35
|
+
require_file "$DOC_DIR/claude-code.md"
|
|
36
|
+
require_file "$DOC_DIR/codex.md"
|
|
37
|
+
require_file "$DOC_DIR/opencode.md"
|
|
38
|
+
require_file "$FIXTURE_DIR/claude-user-prompt-submit.json"
|
|
39
|
+
require_file "$FIXTURE_DIR/claude-statusline.json"
|
|
40
|
+
require_file "$FIXTURE_DIR/codex-user-prompt-submit.json"
|
|
41
|
+
require_file "$FIXTURE_DIR/codex-pre-tool-use.json"
|
|
42
|
+
require_file "$FIXTURE_DIR/opencode-chat-message.json"
|
|
43
|
+
require_file "$FIXTURE_DIR/opencode-tool-execute-before.json"
|
|
44
|
+
require_file "$FIXTURE_DIR/opencode-tool-execute-after.json"
|
|
45
|
+
require_file "$FIXTURE_DIR/opencode-todo-updated.json"
|
|
46
|
+
require_file "$FIXTURE_DIR/opencode-session-compacting.json"
|
|
47
|
+
|
|
48
|
+
require_json "$FIXTURE_DIR/claude-user-prompt-submit.json"
|
|
49
|
+
require_json "$FIXTURE_DIR/claude-statusline.json"
|
|
50
|
+
require_json "$FIXTURE_DIR/codex-user-prompt-submit.json"
|
|
51
|
+
require_json "$FIXTURE_DIR/codex-pre-tool-use.json"
|
|
52
|
+
require_json "$FIXTURE_DIR/opencode-chat-message.json"
|
|
53
|
+
require_json "$FIXTURE_DIR/opencode-tool-execute-before.json"
|
|
54
|
+
require_json "$FIXTURE_DIR/opencode-tool-execute-after.json"
|
|
55
|
+
require_json "$FIXTURE_DIR/opencode-todo-updated.json"
|
|
56
|
+
require_json "$FIXTURE_DIR/opencode-session-compacting.json"
|
|
57
|
+
|
|
58
|
+
require_contains "$DOC_DIR/README.md" "Before modifying" "the required pre-edit workflow"
|
|
59
|
+
require_contains "$DOC_DIR/README.md" "official documentation" "official documentation"
|
|
60
|
+
require_contains "$DOC_DIR/README.md" "fixture|golden-test" "fixture or golden-test coverage"
|
|
61
|
+
|
|
62
|
+
require_contains "$DOC_DIR/claude-code.md" "Last verified: [0-9]{4}-[0-9]{2}-[0-9]{2}" "a Last verified date"
|
|
63
|
+
require_contains "$DOC_DIR/claude-code.md" "https://code\\.claude\\.com/docs/en/hooks" "Claude Code hooks source"
|
|
64
|
+
require_contains "$DOC_DIR/claude-code.md" "https://code\\.claude\\.com/docs/en/statusline" "Claude Code statusline source"
|
|
65
|
+
require_contains "$DOC_DIR/claude-code.md" "UserPromptSubmit" "Claude UserPromptSubmit"
|
|
66
|
+
require_contains "$DOC_DIR/claude-code.md" "PreCompact" "Claude PreCompact"
|
|
67
|
+
require_contains "$DOC_DIR/claude-code.md" "PostCompact" "Claude PostCompact"
|
|
68
|
+
require_contains "$DOC_DIR/claude-code.md" "statusLine" "Claude statusLine config"
|
|
69
|
+
require_contains "$DOC_DIR/claude-code.md" "tests/fixtures/agent-hooks/claude-statusline\\.json" "Claude statusline fixture"
|
|
70
|
+
|
|
71
|
+
require_contains "$DOC_DIR/codex.md" "Last verified: [0-9]{4}-[0-9]{2}-[0-9]{2}" "a Last verified date"
|
|
72
|
+
require_contains "$DOC_DIR/codex.md" "https://developers\\.openai\\.com/codex/codex-manual\\.md" "Codex manual source"
|
|
73
|
+
require_contains "$DOC_DIR/codex.md" "https://developers\\.openai\\.com/codex/hooks\\.md" "Codex hooks source"
|
|
74
|
+
require_contains "$DOC_DIR/codex.md" "AGENTS\\.md" "Codex AGENTS.md behavior"
|
|
75
|
+
require_contains "$DOC_DIR/codex.md" "features\\.hooks" "canonical Codex hooks feature flag"
|
|
76
|
+
require_contains "$DOC_DIR/codex.md" "deprecated alias" "deprecated Codex hook alias"
|
|
77
|
+
require_contains "$DOC_DIR/codex.md" "UserPromptSubmit" "Codex UserPromptSubmit"
|
|
78
|
+
require_contains "$DOC_DIR/codex.md" "tests/fixtures/agent-hooks/codex-pre-tool-use\\.json" "Codex PreToolUse fixture"
|
|
79
|
+
|
|
80
|
+
require_contains "$DOC_DIR/opencode.md" "Last verified: [0-9]{4}-[0-9]{2}-[0-9]{2}" "an OpenCode Last verified date"
|
|
81
|
+
require_contains "$DOC_DIR/opencode.md" "https://opencode\\.ai/docs/plugins/" "OpenCode plugins source"
|
|
82
|
+
require_contains "$DOC_DIR/opencode.md" "https://opencode\\.ai/docs/config/" "OpenCode config source"
|
|
83
|
+
require_contains "$DOC_DIR/opencode.md" "https://opencode\\.ai/config\\.json" "OpenCode config schema source"
|
|
84
|
+
require_contains "$DOC_DIR/opencode.md" "~/.config/opencode/plugins/" "OpenCode global local plugin directory"
|
|
85
|
+
require_contains "$DOC_DIR/opencode.md" "chat\\.message" "OpenCode chat.message hook"
|
|
86
|
+
require_contains "$DOC_DIR/opencode.md" "tool\\.execute\\.before" "OpenCode tool.execute.before hook"
|
|
87
|
+
require_contains "$DOC_DIR/opencode.md" "tool\\.execute\\.after" "OpenCode tool.execute.after hook"
|
|
88
|
+
require_contains "$DOC_DIR/opencode.md" "todo\\.updated" "OpenCode todo.updated event"
|
|
89
|
+
require_contains "$DOC_DIR/opencode.md" "experimental\\.session\\.compacting" "OpenCode compaction hook"
|
|
90
|
+
require_contains "$DOC_DIR/opencode.md" "opencode --pure" "OpenCode pure-mode caveat"
|
|
91
|
+
require_contains "$DOC_DIR/opencode.md" "tests/fixtures/agent-hooks/opencode-tool-execute-before\\.json" "OpenCode PreToolUse fixture"
|
|
92
|
+
require_contains "$DOC_DIR/opencode.md" "tests/test_opencode_plugin_adapter\\.sh" "OpenCode adapter regression test"
|
|
93
|
+
|
|
94
|
+
if grep -R "features\\.codex_hooks" "$ROOT_DIR/lib" "$ROOT_DIR/scripts" "$ROOT_DIR/hooks" "$ROOT_DIR/bin" >/dev/null 2>&1; then
|
|
95
|
+
fail "implementation still uses deprecated Codex features.codex_hooks instead of features.hooks"
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
if grep -R "codex_hooks[[:space:]]*=[[:space:]]*true" "$ROOT_DIR/lib" "$ROOT_DIR/scripts" "$ROOT_DIR/hooks" "$ROOT_DIR/bin" >/dev/null 2>&1; then
|
|
99
|
+
fail "implementation still writes deprecated Codex codex_hooks config instead of hooks"
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
changed_sensitive=$(
|
|
103
|
+
{
|
|
104
|
+
git -C "$ROOT_DIR" diff --name-only HEAD -- 2>/dev/null || true
|
|
105
|
+
git -C "$ROOT_DIR" ls-files --others --exclude-standard 2>/dev/null || true
|
|
106
|
+
} | awk '
|
|
107
|
+
/^(hooks\/.*\.sh|lib\/hooks\.sh|lib\/codex-hooks\.sh|lib\/opencode-hooks\.sh|integrations\/opencode_eagle_mem_plugin\.js|scripts\/install\.sh|scripts\/update\.sh|scripts\/uninstall\.sh|scripts\/doctor\.sh|scripts\/statusline-em\.sh|lib\/common\.sh|AGENTS\.md|CLAUDE\.md)$/ { print }
|
|
108
|
+
' | sort -u
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
if [ -n "$changed_sensitive" ]; then
|
|
112
|
+
changed_evidence=$(
|
|
113
|
+
{
|
|
114
|
+
git -C "$ROOT_DIR" diff --name-only HEAD -- 2>/dev/null || true
|
|
115
|
+
git -C "$ROOT_DIR" ls-files --others --exclude-standard 2>/dev/null || true
|
|
116
|
+
} | awk '
|
|
117
|
+
/^(docs\/agent-compatibility\/.*\.md|tests\/fixtures\/agent-hooks\/.*\.json|tests\/test_agent_compatibility_docs_gate\.sh)$/ { print }
|
|
118
|
+
' | sort -u
|
|
119
|
+
)
|
|
120
|
+
[ -n "$changed_evidence" ] || fail "sensitive integration files changed without updated compatibility docs or fixtures: $changed_sensitive"
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
echo "agent compatibility docs gate passed"
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Broad prompts should create durable orchestration state automatically. This
|
|
3
|
+
# proves Eagle detects the need for lanes instead of relying only on agent
|
|
4
|
+
# obedience to instructions.
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
8
|
+
EAGLE_BIN="$ROOT_DIR/bin/eagle-mem"
|
|
9
|
+
|
|
10
|
+
tmp_dir=$(mktemp -d "$ROOT_DIR/.tmp-auto-orchestration.XXXXXX")
|
|
11
|
+
trap 'rm -rf "$tmp_dir"' EXIT
|
|
12
|
+
|
|
13
|
+
export HOME="$tmp_dir/home"
|
|
14
|
+
export EAGLE_MEM_DIR="$tmp_dir/eagle-mem"
|
|
15
|
+
mkdir -p "$HOME" "$EAGLE_MEM_DIR"
|
|
16
|
+
|
|
17
|
+
. "$ROOT_DIR/lib/common.sh"
|
|
18
|
+
"$ROOT_DIR/db/migrate.sh" >/dev/null
|
|
19
|
+
. "$ROOT_DIR/lib/db.sh"
|
|
20
|
+
|
|
21
|
+
assert_json() {
|
|
22
|
+
local json="$1" filter="$2" message="$3"
|
|
23
|
+
if ! printf '%s' "$json" | jq -e "$filter" >/dev/null; then
|
|
24
|
+
echo "$message" >&2
|
|
25
|
+
echo "$json" >&2
|
|
26
|
+
exit 1
|
|
27
|
+
fi
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
assert_contains() {
|
|
31
|
+
local haystack="$1" needle="$2" message="$3"
|
|
32
|
+
case "$haystack" in
|
|
33
|
+
*"$needle"*) ;;
|
|
34
|
+
*)
|
|
35
|
+
echo "$message" >&2
|
|
36
|
+
echo "Expected to find: $needle" >&2
|
|
37
|
+
echo "$haystack" >&2
|
|
38
|
+
exit 1
|
|
39
|
+
;;
|
|
40
|
+
esac
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
project="project-auto-orchestration"
|
|
44
|
+
repo="$HOME/$project"
|
|
45
|
+
mkdir -p "$repo"
|
|
46
|
+
|
|
47
|
+
hook_input=$(jq -nc \
|
|
48
|
+
--arg sid "auto-orchestration-session" \
|
|
49
|
+
--arg cwd "$repo" \
|
|
50
|
+
--arg prompt "Please plan and get started on a broad full codebase release. Split the work into lanes, coordinate workers, implement the fixes, and verify the ship path." \
|
|
51
|
+
'{session_id:$sid, cwd:$cwd, prompt:$prompt, transcript_path:"/tmp/transcript.jsonl"}')
|
|
52
|
+
|
|
53
|
+
hook_output=$(EAGLE_MEM_PROJECT="$project" EAGLE_MEM_DIR="$EAGLE_MEM_DIR" EAGLE_AGENT_SOURCE="codex" bash "$ROOT_DIR/hooks/user-prompt-submit.sh" <<< "$hook_input")
|
|
54
|
+
assert_contains "$hook_output" "created durable orchestration 'auto'" "broad prompt did not report auto orchestration creation"
|
|
55
|
+
assert_contains "$hook_output" "Continue from the durable lanes" "hook did not tell the agent to continue from durable lanes"
|
|
56
|
+
|
|
57
|
+
orchestration_json=$(cd "$repo" && EAGLE_MEM_PROJECT="$project" EAGLE_MEM_DIR="$EAGLE_MEM_DIR" "$EAGLE_BIN" orchestrate --name auto --json)
|
|
58
|
+
assert_json "$orchestration_json" '
|
|
59
|
+
length == 3
|
|
60
|
+
and ([.[].lane_key] | sort == ["implementation","scope","verification"])
|
|
61
|
+
and all(.[]; .status == "pending")
|
|
62
|
+
' "auto orchestration did not create the expected pending lanes"
|
|
63
|
+
|
|
64
|
+
task_json=$(cd "$repo" && EAGLE_MEM_PROJECT="$project" EAGLE_MEM_DIR="$EAGLE_MEM_DIR" "$EAGLE_BIN" tasks --json)
|
|
65
|
+
assert_json "$task_json" '
|
|
66
|
+
length >= 3
|
|
67
|
+
and ([.[] | select(.source_task_id | startswith("lane-auto-"))] | length == 3)
|
|
68
|
+
' "auto orchestration did not mirror lanes into durable tasks"
|
|
69
|
+
|
|
70
|
+
event_json=$(eagle_db_json "SELECT session_id, project, agent, prompt_snippet, orchestration_name, lanes, status
|
|
71
|
+
FROM orchestration_auto_events
|
|
72
|
+
WHERE project = '$project'
|
|
73
|
+
ORDER BY id DESC;")
|
|
74
|
+
assert_json "$event_json" '
|
|
75
|
+
length == 1
|
|
76
|
+
and .[0].session_id == "auto-orchestration-session"
|
|
77
|
+
and .[0].orchestration_name == "auto"
|
|
78
|
+
and .[0].status == "created"
|
|
79
|
+
and ((.[0].lanes | fromjson) | length == 3)
|
|
80
|
+
' "auto orchestration event was not recorded"
|
|
81
|
+
|
|
82
|
+
hook_output_again=$(EAGLE_MEM_PROJECT="$project" EAGLE_MEM_DIR="$EAGLE_MEM_DIR" EAGLE_AGENT_SOURCE="codex" bash "$ROOT_DIR/hooks/user-prompt-submit.sh" <<< "$hook_input")
|
|
83
|
+
assert_contains "$hook_output_again" "created durable orchestration 'auto'" "repeated broad prompt did not reuse auto orchestration"
|
|
84
|
+
|
|
85
|
+
lane_count=$(eagle_db "SELECT COUNT(*)
|
|
86
|
+
FROM orchestration_lanes l
|
|
87
|
+
JOIN orchestrations o ON o.id = l.orchestration_id
|
|
88
|
+
WHERE o.project = '$project'
|
|
89
|
+
AND o.name = 'auto';")
|
|
90
|
+
task_count=$(eagle_db "SELECT COUNT(*)
|
|
91
|
+
FROM agent_tasks
|
|
92
|
+
WHERE project = '$project'
|
|
93
|
+
AND source_task_id LIKE 'lane-auto-%';")
|
|
94
|
+
event_count=$(eagle_db "SELECT COUNT(*) FROM orchestration_auto_events WHERE project = '$project';")
|
|
95
|
+
|
|
96
|
+
[ "$lane_count" = "3" ] || { echo "expected 3 auto lanes after repeat, got $lane_count" >&2; exit 1; }
|
|
97
|
+
[ "$task_count" = "3" ] || { echo "expected 3 auto tasks after repeat, got $task_count" >&2; exit 1; }
|
|
98
|
+
[ "$event_count" = "2" ] || { echo "expected 2 auto events after repeat, got $event_count" >&2; exit 1; }
|
|
99
|
+
|
|
100
|
+
quiet_input=$(jq -nc \
|
|
101
|
+
--arg sid "quiet-session" \
|
|
102
|
+
--arg cwd "$repo" \
|
|
103
|
+
--arg prompt "Please inspect the config value." \
|
|
104
|
+
'{session_id:$sid, cwd:$cwd, prompt:$prompt}')
|
|
105
|
+
EAGLE_MEM_PROJECT="$project" EAGLE_MEM_DIR="$EAGLE_MEM_DIR" EAGLE_AGENT_SOURCE="codex" bash "$ROOT_DIR/hooks/user-prompt-submit.sh" <<< "$quiet_input" >/dev/null
|
|
106
|
+
quiet_event_count=$(eagle_db "SELECT COUNT(*) FROM orchestration_auto_events WHERE project = '$project';")
|
|
107
|
+
[ "$quiet_event_count" = "2" ] || { echo "quiet prompt should not create auto orchestration event" >&2; exit 1; }
|
|
108
|
+
|
|
109
|
+
echo "auto orchestration detection regressions passed"
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Regression coverage for Claude Stop hook registration.
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
6
|
+
tmp_dir=$(mktemp -d "$ROOT_DIR/.tmp-claude-stop.XXXXXX")
|
|
7
|
+
trap 'rm -rf "$tmp_dir"' EXIT
|
|
8
|
+
|
|
9
|
+
export HOME="$tmp_dir/home"
|
|
10
|
+
export EAGLE_MEM_DIR="$tmp_dir/eagle-mem"
|
|
11
|
+
mkdir -p "$HOME" "$EAGLE_MEM_DIR/hooks"
|
|
12
|
+
|
|
13
|
+
. "$ROOT_DIR/scripts/style.sh"
|
|
14
|
+
. "$ROOT_DIR/lib/common.sh"
|
|
15
|
+
. "$ROOT_DIR/lib/hooks.sh"
|
|
16
|
+
|
|
17
|
+
settings="$tmp_dir/settings.json"
|
|
18
|
+
old_stop="$EAGLE_MEM_DIR/hooks/stop.sh"
|
|
19
|
+
new_stop="bash \"$EAGLE_MEM_DIR/hooks/stop.sh\""
|
|
20
|
+
|
|
21
|
+
fail() {
|
|
22
|
+
echo "claude stop hook registration test failed: $*" >&2
|
|
23
|
+
exit 1
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
cat > "$settings" <<JSON
|
|
27
|
+
{
|
|
28
|
+
"hooks": {
|
|
29
|
+
"Stop": [
|
|
30
|
+
{
|
|
31
|
+
"hooks": [
|
|
32
|
+
{
|
|
33
|
+
"type": "command",
|
|
34
|
+
"command": "$old_stop"
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
JSON
|
|
42
|
+
|
|
43
|
+
eagle_clean_hook_entries "$settings" "Stop" "$old_stop"
|
|
44
|
+
eagle_patch_hook "$settings" "Stop" "" "$new_stop" >/dev/null
|
|
45
|
+
|
|
46
|
+
jq -e --arg cmd "$old_stop" 'all(.hooks.Stop[]?.hooks[]?; .command != $cmd)' "$settings" >/dev/null \
|
|
47
|
+
|| fail "path-only Stop hook registration remained"
|
|
48
|
+
|
|
49
|
+
jq -e --arg cmd "$new_stop" '.hooks.Stop[]? | select(.matcher == null and (.hooks[]?.command == $cmd))' "$settings" >/dev/null \
|
|
50
|
+
|| fail "bash-wrapped Stop hook registration missing"
|
|
51
|
+
|
|
52
|
+
eagle_patch_hook "$settings" "Stop" "" "$new_stop" >/dev/null
|
|
53
|
+
count=$(jq --arg cmd "$new_stop" '[.hooks.Stop[]?.hooks[]? | select(.command == $cmd)] | length' "$settings")
|
|
54
|
+
[ "$count" -eq 1 ] || fail "bash-wrapped Stop hook registration duplicated: $count"
|
|
55
|
+
|
|
56
|
+
echo "claude stop hook registration test passed"
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Codex hook enablement should use the current canonical features.hooks flag
|
|
3
|
+
# while cleaning up the older codex_hooks alias when encountered.
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
7
|
+
|
|
8
|
+
tmp_dir=$(mktemp -d "$ROOT_DIR/.tmp-codex-hooks-config.XXXXXX")
|
|
9
|
+
trap 'rm -rf "$tmp_dir"' EXIT
|
|
10
|
+
|
|
11
|
+
export HOME="$tmp_dir/home"
|
|
12
|
+
export EAGLE_MEM_DIR="$tmp_dir/eagle-mem"
|
|
13
|
+
export EAGLE_CODEX_DIR="$HOME/.codex"
|
|
14
|
+
export EAGLE_CODEX_CONFIG="$EAGLE_CODEX_DIR/config.toml"
|
|
15
|
+
export EAGLE_CODEX_HOOKS="$EAGLE_CODEX_DIR/hooks.json"
|
|
16
|
+
|
|
17
|
+
mkdir -p "$HOME" "$EAGLE_MEM_DIR" "$EAGLE_CODEX_DIR"
|
|
18
|
+
|
|
19
|
+
. "$ROOT_DIR/lib/common.sh"
|
|
20
|
+
. "$ROOT_DIR/lib/codex-hooks.sh"
|
|
21
|
+
|
|
22
|
+
fail() {
|
|
23
|
+
echo "codex hooks config regression failed: $*" >&2
|
|
24
|
+
exit 1
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
assert_has_hooks_true() {
|
|
28
|
+
grep -q '^hooks = true$' "$EAGLE_CODEX_CONFIG" || fail "missing canonical hooks = true"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
assert_no_legacy_write() {
|
|
32
|
+
if grep -q '^codex_hooks[[:space:]]*=' "$EAGLE_CODEX_CONFIG"; then
|
|
33
|
+
fail "deprecated codex_hooks key was left in generated config"
|
|
34
|
+
fi
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
rm -f "$EAGLE_CODEX_CONFIG"
|
|
38
|
+
eagle_enable_codex_hooks
|
|
39
|
+
assert_has_hooks_true
|
|
40
|
+
assert_no_legacy_write
|
|
41
|
+
|
|
42
|
+
cat > "$EAGLE_CODEX_CONFIG" <<'TOML'
|
|
43
|
+
[other]
|
|
44
|
+
name = "preserve-me"
|
|
45
|
+
|
|
46
|
+
[features]
|
|
47
|
+
codex_hooks = false
|
|
48
|
+
memories = true
|
|
49
|
+
|
|
50
|
+
[sandbox]
|
|
51
|
+
mode = "workspace-write"
|
|
52
|
+
TOML
|
|
53
|
+
|
|
54
|
+
eagle_enable_codex_hooks
|
|
55
|
+
assert_has_hooks_true
|
|
56
|
+
assert_no_legacy_write
|
|
57
|
+
grep -q '^memories = true$' "$EAGLE_CODEX_CONFIG" || fail "features table values were not preserved"
|
|
58
|
+
grep -q '^name = "preserve-me"$' "$EAGLE_CODEX_CONFIG" || fail "other config sections were not preserved"
|
|
59
|
+
grep -q '^mode = "workspace-write"$' "$EAGLE_CODEX_CONFIG" || fail "later config sections were not preserved"
|
|
60
|
+
|
|
61
|
+
cat > "$EAGLE_CODEX_CONFIG" <<'TOML'
|
|
62
|
+
[features]
|
|
63
|
+
hooks = false
|
|
64
|
+
TOML
|
|
65
|
+
|
|
66
|
+
eagle_enable_codex_hooks
|
|
67
|
+
assert_has_hooks_true
|
|
68
|
+
assert_no_legacy_write
|
|
69
|
+
hook_lines=$(grep -c '^hooks = true$' "$EAGLE_CODEX_CONFIG")
|
|
70
|
+
[ "$hook_lines" -eq 1 ] || fail "expected one hooks = true line, found $hook_lines"
|
|
71
|
+
|
|
72
|
+
echo "codex hooks config regressions passed"
|
|
73
|
+
|