@onlooker-community/ecosystem 0.9.0 → 0.14.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/.claude-plugin/marketplace.json +39 -1
- package/.claude-plugin/plugin.json +2 -2
- package/.github/copilot-instructions.md +46 -0
- package/.github/workflows/coverage.yml +78 -0
- package/.github/workflows/release.yml +24 -8
- package/.github/workflows/test.yml +3 -0
- package/.markdownlintignore +3 -0
- package/.release-please-manifest.json +4 -1
- package/CHANGELOG.md +44 -0
- package/README.md +57 -13
- package/config.json +6 -1
- package/docs/adr/001-claude-code-hooks-as-integration-surface.md +43 -0
- package/docs/adr/002-centralized-jsonl-event-log.md +39 -0
- package/docs/adr/003-ulid-over-uuid.md +40 -0
- package/docs/adr/004-plugin-config-with-settings-overlay.md +34 -0
- package/docs/architecture.md +117 -0
- package/hooks/hooks.json +4 -0
- package/package.json +13 -7
- package/plugins/archivist/.claude-plugin/plugin.json +14 -0
- package/plugins/archivist/CHANGELOG.md +8 -0
- package/plugins/archivist/README.md +105 -0
- package/plugins/archivist/config.json +18 -0
- package/plugins/archivist/hooks/hooks.json +35 -0
- package/plugins/archivist/scripts/hooks/archivist-extract.sh +238 -0
- package/plugins/archivist/scripts/hooks/archivist-inject.sh +159 -0
- package/plugins/archivist/scripts/lib/archivist-config.sh +66 -0
- package/plugins/archivist/scripts/lib/archivist-project-key.sh +91 -0
- package/plugins/archivist/scripts/lib/archivist-storage.sh +215 -0
- package/plugins/archivist/scripts/lib/archivist-ulid.sh +52 -0
- package/plugins/echo/.claude-plugin/plugin.json +14 -0
- package/plugins/echo/CHANGELOG.md +24 -0
- package/plugins/echo/README.md +110 -0
- package/plugins/echo/config.json +15 -0
- package/plugins/echo/docs/adr/001-echo-as-separate-plugin.md +33 -0
- package/plugins/echo/docs/adr/002-direct-evaluation-vs-tribunal-pipeline.md +35 -0
- package/plugins/echo/docs/adr/003-stop-hook-trigger.md +40 -0
- package/plugins/echo/hooks/hooks.json +15 -0
- package/plugins/echo/scripts/hooks/echo-stop-gate.sh +366 -0
- package/plugins/echo/scripts/lib/echo-config.sh +108 -0
- package/plugins/echo/scripts/lib/echo-events.sh +74 -0
- package/plugins/echo/scripts/lib/echo-project-key.sh +81 -0
- package/plugins/echo/scripts/lib/echo-ulid.sh +46 -0
- package/plugins/tribunal/.claude-plugin/plugin.json +20 -0
- package/plugins/tribunal/CHANGELOG.md +10 -0
- package/plugins/tribunal/README.md +134 -0
- package/plugins/tribunal/agents/tribunal-actor.md +35 -0
- package/plugins/tribunal/agents/tribunal-judge-adversarial.md +51 -0
- package/plugins/tribunal/agents/tribunal-judge-security.md +47 -0
- package/plugins/tribunal/agents/tribunal-judge-standard.md +47 -0
- package/plugins/tribunal/agents/tribunal-meta-judge.md +61 -0
- package/plugins/tribunal/config.json +50 -0
- package/plugins/tribunal/docs/adr/001-actor-jury-meta-gate-loop.md +40 -0
- package/plugins/tribunal/docs/adr/002-majority-gate-policy.md +48 -0
- package/plugins/tribunal/hooks/hooks.json +15 -0
- package/plugins/tribunal/scripts/hooks/tribunal-stop-gate.sh +267 -0
- package/plugins/tribunal/scripts/lib/tribunal-aggregate.sh +65 -0
- package/plugins/tribunal/scripts/lib/tribunal-config.sh +101 -0
- package/plugins/tribunal/scripts/lib/tribunal-events.sh +97 -0
- package/plugins/tribunal/scripts/lib/tribunal-gate.sh +111 -0
- package/plugins/tribunal/scripts/lib/tribunal-jury.sh +102 -0
- package/plugins/tribunal/scripts/lib/tribunal-project-key.sh +84 -0
- package/plugins/tribunal/scripts/lib/tribunal-rubric.sh +153 -0
- package/plugins/tribunal/scripts/lib/tribunal-ulid.sh +50 -0
- package/plugins/tribunal/scripts/lib/tribunal-verdict.sh +127 -0
- package/plugins/tribunal/skills/tribunal/SKILL.md +129 -0
- package/release-please-config.json +43 -5
- package/scripts/coverage/bash-coverage.mjs +169 -0
- package/scripts/coverage/format-comment.mjs +120 -0
- package/scripts/coverage/run-coverage.mjs +151 -0
- package/scripts/hooks/agent-spawn-tracker.sh +4 -4
- package/scripts/hooks/prompt-rule-injector.sh +122 -0
- package/scripts/lib/onlooker-event.mjs +82 -10
- package/scripts/lib/portable-lock.sh +48 -0
- package/scripts/lib/prompt-rules.sh +207 -0
- package/scripts/lib/tool-history.sh +7 -8
- package/scripts/lib/validate-path.sh +4 -0
- package/scripts/lint/check-manifests.mjs +314 -0
- package/scripts/lint/check-references.mjs +311 -0
- package/skills/list-prompt-rules/SKILL.md +15 -0
- package/test/bats/archivist-config-files.bats +60 -0
- package/test/bats/archivist-config.bats +54 -0
- package/test/bats/archivist-inject.bats +73 -0
- package/test/bats/archivist-project-key.bats +75 -0
- package/test/bats/archivist-storage.bats +119 -0
- package/test/bats/archivist-ulid.bats +36 -0
- package/test/bats/config.bats +10 -10
- package/test/bats/echo-config.bats +90 -0
- package/test/bats/echo-events.bats +121 -0
- package/test/bats/echo-project-key.bats +115 -0
- package/test/bats/echo-stop-hook.bats +101 -0
- package/test/bats/echo-ulid.bats +38 -0
- package/test/bats/portable-lock.bats +62 -0
- package/test/bats/prompt-rules.bats +269 -0
- package/test/bats/read-chunk-tracking.bats +73 -0
- package/test/bats/tool-history-tracker.bats +1 -0
- package/test/bats/tribunal-aggregate.bats +77 -0
- package/test/bats/tribunal-config.bats +86 -0
- package/test/bats/tribunal-events.bats +209 -0
- package/test/bats/tribunal-gate.bats +95 -0
- package/test/bats/tribunal-jury.bats +80 -0
- package/test/bats/tribunal-rubric.bats +119 -0
- package/test/bats/tribunal-stop-hook.bats +73 -0
- package/test/bats/tribunal-verdict.bats +71 -0
- package/test/bats/validate-path.bats +1 -1
- package/test/fixtures/hook-inputs/post-tool-use-read-chunked.json +15 -0
- package/test/fixtures/hook-inputs/user-prompt-submit-rule-match.json +8 -0
- package/test/fixtures/hook-inputs/user-prompt-submit-rule-nomatch.json +8 -0
- package/test/helpers/setup.bash +9 -0
- package/test/node/check-manifests.test.mjs +173 -0
- package/test/node/check-references.test.mjs +279 -0
- package/test/node/coverage.test.mjs +143 -0
- package/test/node/schema-events.test.mjs +41 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
# Verifies the on-disk plugin manifests for archivist are wired up correctly.
|
|
3
|
+
|
|
4
|
+
setup_file() {
|
|
5
|
+
source "${BATS_TEST_DIRNAME}/../helpers/setup.bash"
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
@test "archivist plugin.json is valid JSON with name and version" {
|
|
9
|
+
run jq -e '.name == "archivist" and (.version | length > 0)' \
|
|
10
|
+
"${REPO_ROOT}/plugins/archivist/.claude-plugin/plugin.json"
|
|
11
|
+
[ "$status" -eq 0 ]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@test "archivist config.json has plugin_name and archivist.enabled defaulting to false" {
|
|
15
|
+
run jq -e '.plugin_name == "archivist" and .archivist.enabled == false' \
|
|
16
|
+
"${REPO_ROOT}/plugins/archivist/config.json"
|
|
17
|
+
[ "$status" -eq 0 ]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@test "marketplace.json lists ecosystem first and archivist second" {
|
|
21
|
+
run jq -e '.plugins[0].name == "ecosystem" and .plugins[1].name == "archivist"' \
|
|
22
|
+
"${REPO_ROOT}/.claude-plugin/marketplace.json"
|
|
23
|
+
[ "$status" -eq 0 ]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@test "marketplace.json plugin entries omit version (claude reads version from plugin.json)" {
|
|
27
|
+
# See https://code.claude.com/docs/en/plugins-reference.md#version-management:
|
|
28
|
+
# plugin.json's version is the cache key. Setting it in both locations is a
|
|
29
|
+
# documented drift hazard.
|
|
30
|
+
run jq -e 'all(.plugins[]; has("version") | not)' "${REPO_ROOT}/.claude-plugin/marketplace.json"
|
|
31
|
+
[ "$status" -eq 0 ]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@test "release-please-manifest.json tracks plugins/archivist" {
|
|
35
|
+
run jq -e '.["plugins/archivist"]' "${REPO_ROOT}/.release-please-manifest.json"
|
|
36
|
+
[ "$status" -eq 0 ]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@test "release-please-config.json declares plugins/archivist as a package" {
|
|
40
|
+
run jq -e '.packages["plugins/archivist"] | type == "object"' \
|
|
41
|
+
"${REPO_ROOT}/release-please-config.json"
|
|
42
|
+
[ "$status" -eq 0 ]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@test "archivist hooks.json wires PreCompact manual+auto and SessionStart" {
|
|
46
|
+
local f="${REPO_ROOT}/plugins/archivist/hooks/hooks.json"
|
|
47
|
+
run jq -e '
|
|
48
|
+
(.hooks.PreCompact | length) == 2 and
|
|
49
|
+
.hooks.PreCompact[0].matcher == "manual" and
|
|
50
|
+
.hooks.PreCompact[1].matcher == "auto" and
|
|
51
|
+
.hooks.SessionStart[0].matcher == "*"
|
|
52
|
+
' "$f"
|
|
53
|
+
[ "$status" -eq 0 ]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@test "archivist hook scripts are executable" {
|
|
57
|
+
for script in archivist-extract.sh archivist-inject.sh; do
|
|
58
|
+
test -x "${REPO_ROOT}/plugins/archivist/scripts/hooks/$script"
|
|
59
|
+
done
|
|
60
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
|
|
3
|
+
setup() {
|
|
4
|
+
source "${BATS_TEST_DIRNAME}/../helpers/setup.bash"
|
|
5
|
+
setup_test_env
|
|
6
|
+
|
|
7
|
+
PLUGIN_ROOT="${REPO_ROOT}/plugins/archivist"
|
|
8
|
+
export CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT"
|
|
9
|
+
# shellcheck disable=SC1091
|
|
10
|
+
source "${PLUGIN_ROOT}/scripts/lib/archivist-config.sh"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@test "config defaults from plugin config.json: disabled" {
|
|
14
|
+
archivist_config_load ""
|
|
15
|
+
run archivist_config_enabled
|
|
16
|
+
[ "$status" -ne 0 ]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@test "user-level settings.json overlay can enable archivist" {
|
|
20
|
+
mkdir -p "${HOME}/.claude"
|
|
21
|
+
printf '%s\n' '{"archivist":{"enabled":true}}' > "${HOME}/.claude/settings.json"
|
|
22
|
+
archivist_config_load ""
|
|
23
|
+
run archivist_config_enabled
|
|
24
|
+
[ "$status" -eq 0 ]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@test "repo-level settings.json overrides user-level" {
|
|
28
|
+
mkdir -p "${HOME}/.claude"
|
|
29
|
+
printf '%s\n' '{"archivist":{"enabled":true}}' > "${HOME}/.claude/settings.json"
|
|
30
|
+
local repo="${BATS_TEST_TMPDIR}/repo"
|
|
31
|
+
mkdir -p "${repo}/.claude"
|
|
32
|
+
printf '%s\n' '{"archivist":{"enabled":false}}' > "${repo}/.claude/settings.json"
|
|
33
|
+
archivist_config_load "$repo"
|
|
34
|
+
run archivist_config_enabled
|
|
35
|
+
[ "$status" -ne 0 ]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@test "injection.max_items defaults from config.json" {
|
|
39
|
+
archivist_config_load ""
|
|
40
|
+
local v
|
|
41
|
+
v=$(archivist_config_get '.archivist.injection.max_items')
|
|
42
|
+
[ "$v" = "8" ]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@test "settings overlay merges deeply (preserves unset defaults)" {
|
|
46
|
+
mkdir -p "${HOME}/.claude"
|
|
47
|
+
printf '%s\n' '{"archivist":{"injection":{"max_items":3}}}' > "${HOME}/.claude/settings.json"
|
|
48
|
+
archivist_config_load ""
|
|
49
|
+
local overridden default_model
|
|
50
|
+
overridden=$(archivist_config_get '.archivist.injection.max_items')
|
|
51
|
+
default_model=$(archivist_config_get '.archivist.extraction.model')
|
|
52
|
+
[ "$overridden" = "3" ]
|
|
53
|
+
[ -n "$default_model" ]
|
|
54
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
|
|
3
|
+
setup() {
|
|
4
|
+
source "${BATS_TEST_DIRNAME}/../helpers/setup.bash"
|
|
5
|
+
setup_test_env
|
|
6
|
+
|
|
7
|
+
PLUGIN_ROOT="${REPO_ROOT}/plugins/archivist"
|
|
8
|
+
export CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT"
|
|
9
|
+
export ONLOOKER_ECOSYSTEM_ROOT="$REPO_ROOT"
|
|
10
|
+
|
|
11
|
+
# Stand up a fake project repo so project-key resolution succeeds.
|
|
12
|
+
PROJECT_REPO="${BATS_TEST_TMPDIR}/repo"
|
|
13
|
+
mkdir -p "$PROJECT_REPO"
|
|
14
|
+
git -C "$PROJECT_REPO" init -q
|
|
15
|
+
git -C "$PROJECT_REPO" config user.email t@example.com
|
|
16
|
+
git -C "$PROJECT_REPO" config user.name "Test"
|
|
17
|
+
git -C "$PROJECT_REPO" remote add origin git@github.com:org/archivist-inject-test.git
|
|
18
|
+
|
|
19
|
+
# Compute the project key the hook will use.
|
|
20
|
+
# shellcheck disable=SC1091
|
|
21
|
+
source "${PLUGIN_ROOT}/scripts/lib/archivist-project-key.sh"
|
|
22
|
+
PROJECT_KEY=$(archivist_project_key "$PROJECT_REPO")
|
|
23
|
+
[ -n "$PROJECT_KEY" ]
|
|
24
|
+
|
|
25
|
+
# Seed an artifact on disk for this project.
|
|
26
|
+
local kind_dir="${ONLOOKER_DIR}/archivist/${PROJECT_KEY}/decisions"
|
|
27
|
+
mkdir -p "$kind_dir"
|
|
28
|
+
printf '%s\n' '{
|
|
29
|
+
"id": "01TESTTESTTESTTESTTESTTEST",
|
|
30
|
+
"kind": "decision",
|
|
31
|
+
"summary": "use git remote SHA256 as project key",
|
|
32
|
+
"detail": "remote URL is stable across machines; falls back to repo path",
|
|
33
|
+
"files": [],
|
|
34
|
+
"created_at": "2026-05-22T10:00:00Z",
|
|
35
|
+
"updated_at": "2026-05-22T10:00:00Z"
|
|
36
|
+
}' > "${kind_dir}/01TESTTESTTESTTESTTESTTEST.json"
|
|
37
|
+
|
|
38
|
+
# Project-scoped settings.json that enables archivist.
|
|
39
|
+
mkdir -p "${PROJECT_REPO}/.claude"
|
|
40
|
+
printf '%s\n' '{"archivist":{"enabled":true}}' > "${PROJECT_REPO}/.claude/settings.json"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@test "inject hook is a no-op when archivist is disabled" {
|
|
44
|
+
rm -f "${PROJECT_REPO}/.claude/settings.json"
|
|
45
|
+
local input
|
|
46
|
+
input=$(jq -n --arg cwd "$PROJECT_REPO" '{cwd: $cwd, source: "startup", session_id: "s"}')
|
|
47
|
+
run bash -c "printf '%s' '$input' | '${PLUGIN_ROOT}/scripts/hooks/archivist-inject.sh'"
|
|
48
|
+
[ "$status" -eq 0 ]
|
|
49
|
+
echo "$output" | jq -e '.hookSpecificOutput.additionalContext == ""' >/dev/null
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@test "inject hook emits seeded artifact when enabled" {
|
|
53
|
+
local input
|
|
54
|
+
input=$(jq -n --arg cwd "$PROJECT_REPO" '{cwd: $cwd, source: "startup", session_id: "s"}')
|
|
55
|
+
run bash -c "printf '%s' '$input' | '${PLUGIN_ROOT}/scripts/hooks/archivist-inject.sh'"
|
|
56
|
+
[ "$status" -eq 0 ]
|
|
57
|
+
|
|
58
|
+
echo "$output" | jq -e '.hookSpecificOutput.hookEventName == "SessionStart"' >/dev/null
|
|
59
|
+
local ctx
|
|
60
|
+
ctx=$(echo "$output" | jq -r '.hookSpecificOutput.additionalContext')
|
|
61
|
+
[[ "$ctx" == *"use git remote SHA256 as project key"* ]]
|
|
62
|
+
[[ "$ctx" == *"Archivist injected 1"* ]]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@test "inject hook skips when there is no git context" {
|
|
66
|
+
local non_git="${BATS_TEST_TMPDIR}/no-git"
|
|
67
|
+
mkdir -p "$non_git"
|
|
68
|
+
local input
|
|
69
|
+
input=$(jq -n --arg cwd "$non_git" '{cwd: $cwd, source: "startup", session_id: "s"}')
|
|
70
|
+
run bash -c "printf '%s' '$input' | '${PLUGIN_ROOT}/scripts/hooks/archivist-inject.sh'"
|
|
71
|
+
[ "$status" -eq 0 ]
|
|
72
|
+
echo "$output" | jq -e '.hookSpecificOutput.additionalContext == ""' >/dev/null
|
|
73
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
|
|
3
|
+
setup() {
|
|
4
|
+
# shellcheck source=../helpers/setup.bash
|
|
5
|
+
source "${BATS_TEST_DIRNAME}/../helpers/setup.bash"
|
|
6
|
+
setup_test_env
|
|
7
|
+
|
|
8
|
+
PLUGIN_ROOT="${REPO_ROOT}/plugins/archivist"
|
|
9
|
+
# shellcheck disable=SC1091
|
|
10
|
+
source "${PLUGIN_ROOT}/scripts/lib/archivist-project-key.sh"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@test "non-git directory returns empty key" {
|
|
14
|
+
local d="${BATS_TEST_TMPDIR}/non-git"
|
|
15
|
+
mkdir -p "$d"
|
|
16
|
+
run archivist_project_key "$d"
|
|
17
|
+
[ "$status" -eq 0 ]
|
|
18
|
+
[ -z "$output" ]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@test "git repo without remote falls back to repo-root hash" {
|
|
22
|
+
local d="${BATS_TEST_TMPDIR}/local-only-repo"
|
|
23
|
+
mkdir -p "$d"
|
|
24
|
+
git -C "$d" init -q
|
|
25
|
+
git -C "$d" config user.email t@example.com
|
|
26
|
+
git -C "$d" config user.name "Test"
|
|
27
|
+
|
|
28
|
+
local k1
|
|
29
|
+
k1=$(archivist_project_key "$d")
|
|
30
|
+
[ -n "$k1" ]
|
|
31
|
+
[ "${#k1}" -eq 12 ]
|
|
32
|
+
|
|
33
|
+
# Stability: a second call returns the same key.
|
|
34
|
+
local k2
|
|
35
|
+
k2=$(archivist_project_key "$d")
|
|
36
|
+
[ "$k1" = "$k2" ]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@test "git repo with remote uses remote hash and ignores local path" {
|
|
40
|
+
local a="${BATS_TEST_TMPDIR}/clone-a"
|
|
41
|
+
local b="${BATS_TEST_TMPDIR}/clone-b"
|
|
42
|
+
mkdir -p "$a" "$b"
|
|
43
|
+
for d in "$a" "$b"; do
|
|
44
|
+
git -C "$d" init -q
|
|
45
|
+
git -C "$d" config user.email t@example.com
|
|
46
|
+
git -C "$d" config user.name "Test"
|
|
47
|
+
git -C "$d" remote add origin git@github.com:org/proj.git
|
|
48
|
+
done
|
|
49
|
+
|
|
50
|
+
local ka kb
|
|
51
|
+
ka=$(archivist_project_key "$a")
|
|
52
|
+
kb=$(archivist_project_key "$b")
|
|
53
|
+
[ -n "$ka" ]
|
|
54
|
+
[ "$ka" = "$kb" ]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@test "different remotes yield different keys" {
|
|
58
|
+
local a="${BATS_TEST_TMPDIR}/proj-a"
|
|
59
|
+
local b="${BATS_TEST_TMPDIR}/proj-b"
|
|
60
|
+
mkdir -p "$a" "$b"
|
|
61
|
+
for d in "$a" "$b"; do
|
|
62
|
+
git -C "$d" init -q
|
|
63
|
+
git -C "$d" config user.email t@example.com
|
|
64
|
+
git -C "$d" config user.name "Test"
|
|
65
|
+
done
|
|
66
|
+
git -C "$a" remote add origin git@github.com:org/proj-a.git
|
|
67
|
+
git -C "$b" remote add origin git@github.com:org/proj-b.git
|
|
68
|
+
|
|
69
|
+
local ka kb
|
|
70
|
+
ka=$(archivist_project_key "$a")
|
|
71
|
+
kb=$(archivist_project_key "$b")
|
|
72
|
+
[ -n "$ka" ]
|
|
73
|
+
[ -n "$kb" ]
|
|
74
|
+
[ "$ka" != "$kb" ]
|
|
75
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
|
|
3
|
+
setup() {
|
|
4
|
+
# shellcheck source=../helpers/setup.bash
|
|
5
|
+
source "${BATS_TEST_DIRNAME}/../helpers/setup.bash"
|
|
6
|
+
setup_test_env
|
|
7
|
+
|
|
8
|
+
PLUGIN_ROOT="${REPO_ROOT}/plugins/archivist"
|
|
9
|
+
# shellcheck disable=SC1091
|
|
10
|
+
source "${PLUGIN_ROOT}/scripts/lib/archivist-storage.sh"
|
|
11
|
+
# shellcheck disable=SC1091
|
|
12
|
+
source "${PLUGIN_ROOT}/scripts/lib/archivist-ulid.sh"
|
|
13
|
+
|
|
14
|
+
REPO="${BATS_TEST_TMPDIR}/repo"
|
|
15
|
+
mkdir -p "$REPO/src"
|
|
16
|
+
: > "$REPO/src/known.ts"
|
|
17
|
+
: > "$REPO/README.md"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@test "validate accepts an existing repo-relative path" {
|
|
21
|
+
run archivist_validate_repo_path "$REPO" "src/known.ts"
|
|
22
|
+
[ "$status" -eq 0 ]
|
|
23
|
+
[ "$output" = "src/known.ts" ]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@test "validate accepts an absolute path inside the repo" {
|
|
27
|
+
run archivist_validate_repo_path "$REPO" "${REPO}/src/known.ts"
|
|
28
|
+
[ "$status" -eq 0 ]
|
|
29
|
+
[ "$output" = "src/known.ts" ]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@test "validate rejects a path outside the repo" {
|
|
33
|
+
local outside="${BATS_TEST_TMPDIR}/outside.ts"
|
|
34
|
+
: > "$outside"
|
|
35
|
+
run archivist_validate_repo_path "$REPO" "$outside"
|
|
36
|
+
[ "$status" -eq 0 ]
|
|
37
|
+
[ -z "$output" ]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@test "validate rejects a ../ escape" {
|
|
41
|
+
run archivist_validate_repo_path "$REPO" "../escaped.ts"
|
|
42
|
+
[ "$status" -eq 0 ]
|
|
43
|
+
[ -z "$output" ]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@test "validate rejects a path that does not exist" {
|
|
47
|
+
run archivist_validate_repo_path "$REPO" "src/missing.ts"
|
|
48
|
+
[ "$status" -eq 0 ]
|
|
49
|
+
[ -z "$output" ]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@test "validate_paths_array strips invalid entries" {
|
|
53
|
+
local input='["src/known.ts","../escape.ts","src/missing.ts","README.md"]'
|
|
54
|
+
local cleaned compact
|
|
55
|
+
cleaned=$(archivist_validate_paths_array "$REPO" "$input")
|
|
56
|
+
compact=$(printf '%s' "$cleaned" | jq -c .)
|
|
57
|
+
[ "$compact" = '["src/known.ts","README.md"]' ]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@test "storage_init creates kind directories" {
|
|
61
|
+
local key="abc123def456"
|
|
62
|
+
archivist_storage_init "$key"
|
|
63
|
+
[ -d "${ONLOOKER_DIR}/archivist/${key}/decisions" ]
|
|
64
|
+
[ -d "${ONLOOKER_DIR}/archivist/${key}/dead_ends" ]
|
|
65
|
+
[ -d "${ONLOOKER_DIR}/archivist/${key}/open_questions" ]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@test "write_artifact creates a ULID-keyed file" {
|
|
69
|
+
local key="abc123def456"
|
|
70
|
+
local id
|
|
71
|
+
id=$(archivist_ulid)
|
|
72
|
+
local json='{"id":"'"$id"'","kind":"decision","summary":"hello"}'
|
|
73
|
+
run archivist_storage_write_artifact "$key" "decisions" "$id" "$json"
|
|
74
|
+
[ "$status" -eq 0 ]
|
|
75
|
+
[ -f "${ONLOOKER_DIR}/archivist/${key}/decisions/${id}.json" ]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@test "write_artifact rejects unknown kind" {
|
|
79
|
+
local key="abc123def456"
|
|
80
|
+
run archivist_storage_write_artifact "$key" "bogus_kind" "01J" '{}'
|
|
81
|
+
[ "$status" -ne 0 ]
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@test "load_ranked sorts pinned items first" {
|
|
85
|
+
local key="abc123def456"
|
|
86
|
+
archivist_storage_init "$key"
|
|
87
|
+
|
|
88
|
+
# Write two decisions; pin the older one.
|
|
89
|
+
local older_id="01AAAAAAAAAAAAAAAAAAAAAAAA"
|
|
90
|
+
local newer_id="01ZZZZZZZZZZZZZZZZZZZZZZZZ"
|
|
91
|
+
printf '{"id":"%s","summary":"older","created_at":"2026-05-01T00:00:00Z","updated_at":"2026-05-01T00:00:00Z"}\n' "$older_id" \
|
|
92
|
+
> "${ONLOOKER_DIR}/archivist/${key}/decisions/${older_id}.json"
|
|
93
|
+
printf '{"id":"%s","summary":"newer","created_at":"2026-05-22T00:00:00Z","updated_at":"2026-05-22T00:00:00Z"}\n' "$newer_id" \
|
|
94
|
+
> "${ONLOOKER_DIR}/archivist/${key}/decisions/${newer_id}.json"
|
|
95
|
+
printf '{"ids":["%s"]}\n' "$older_id" > "${ONLOOKER_DIR}/archivist/${key}/pinned.json"
|
|
96
|
+
|
|
97
|
+
local ranked
|
|
98
|
+
ranked=$(archivist_storage_load_ranked "$key")
|
|
99
|
+
local first_id
|
|
100
|
+
first_id=$(printf '%s' "$ranked" | jq -r '.[0].id')
|
|
101
|
+
[ "$first_id" = "$older_id" ]
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
@test "load_ranked sorts non-pinned by recency desc" {
|
|
105
|
+
local key="abc123def456"
|
|
106
|
+
archivist_storage_init "$key"
|
|
107
|
+
local older_id="01AAAAAAAAAAAAAAAAAAAAAAAA"
|
|
108
|
+
local newer_id="01ZZZZZZZZZZZZZZZZZZZZZZZZ"
|
|
109
|
+
printf '{"id":"%s","summary":"older","created_at":"2026-05-01T00:00:00Z","updated_at":"2026-05-01T00:00:00Z"}\n' "$older_id" \
|
|
110
|
+
> "${ONLOOKER_DIR}/archivist/${key}/decisions/${older_id}.json"
|
|
111
|
+
printf '{"id":"%s","summary":"newer","created_at":"2026-05-22T00:00:00Z","updated_at":"2026-05-22T00:00:00Z"}\n' "$newer_id" \
|
|
112
|
+
> "${ONLOOKER_DIR}/archivist/${key}/decisions/${newer_id}.json"
|
|
113
|
+
|
|
114
|
+
local ranked
|
|
115
|
+
ranked=$(archivist_storage_load_ranked "$key")
|
|
116
|
+
local first_id
|
|
117
|
+
first_id=$(printf '%s' "$ranked" | jq -r '.[0].id')
|
|
118
|
+
[ "$first_id" = "$newer_id" ]
|
|
119
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
|
|
3
|
+
setup() {
|
|
4
|
+
source "${BATS_TEST_DIRNAME}/../helpers/setup.bash"
|
|
5
|
+
setup_test_env
|
|
6
|
+
# shellcheck disable=SC1091
|
|
7
|
+
source "${REPO_ROOT}/plugins/archivist/scripts/lib/archivist-ulid.sh"
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
@test "archivist_ulid returns a 26-char crockford base32 string" {
|
|
11
|
+
local id
|
|
12
|
+
id=$(archivist_ulid)
|
|
13
|
+
[ "${#id}" -eq 26 ]
|
|
14
|
+
[[ "$id" =~ ^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}$ ]]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@test "two ULIDs minted apart are lexicographically ordered" {
|
|
18
|
+
local a b
|
|
19
|
+
a=$(archivist_ulid)
|
|
20
|
+
sleep 0.01
|
|
21
|
+
b=$(archivist_ulid)
|
|
22
|
+
[[ "$a" < "$b" ]] || [ "$a" = "$b" ]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@test "many ULIDs are unique" {
|
|
26
|
+
local seen="${BATS_TEST_TMPDIR}/ulids.txt"
|
|
27
|
+
: > "$seen"
|
|
28
|
+
local i
|
|
29
|
+
for ((i = 0; i < 50; i++)); do
|
|
30
|
+
printf '%s\n' "$(archivist_ulid)" >> "$seen"
|
|
31
|
+
done
|
|
32
|
+
local total unique
|
|
33
|
+
total=$(wc -l < "$seen" | tr -d ' ')
|
|
34
|
+
unique=$(sort -u "$seen" | wc -l | tr -d ' ')
|
|
35
|
+
[ "$total" = "$unique" ]
|
|
36
|
+
}
|
package/test/bats/config.bats
CHANGED
|
@@ -10,17 +10,15 @@ setup_file() {
|
|
|
10
10
|
[ "$status" -eq 0 ]
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
@test "
|
|
13
|
+
@test "ecosystem plugin.json version matches package.json" {
|
|
14
14
|
local pkg_ver
|
|
15
15
|
pkg_ver=$(jq -r '.version' "${REPO_ROOT}/package.json")
|
|
16
16
|
|
|
17
|
+
# Claude Code reads version from plugin.json. marketplace.json should NOT
|
|
18
|
+
# carry plugins[].version (see plugins-reference: setting both is a drift
|
|
19
|
+
# hazard since plugin.json silently wins).
|
|
17
20
|
run jq -e --arg v "$pkg_ver" '.version == $v' "${REPO_ROOT}/.claude-plugin/plugin.json"
|
|
18
21
|
[ "$status" -eq 0 ]
|
|
19
|
-
|
|
20
|
-
run jq -e --arg v "$pkg_ver" \
|
|
21
|
-
'[.plugins[].version] | unique | length == 1 and .[0] == $v' \
|
|
22
|
-
"${REPO_ROOT}/.claude-plugin/marketplace.json"
|
|
23
|
-
[ "$status" -eq 0 ]
|
|
24
22
|
}
|
|
25
23
|
|
|
26
24
|
@test "hooks.json wildcard matcher references tool-sequence-tracker" {
|
|
@@ -97,17 +95,19 @@ setup_file() {
|
|
|
97
95
|
[[ "$hook_cmd" == *tool-history-tracker.sh ]]
|
|
98
96
|
}
|
|
99
97
|
|
|
100
|
-
@test "hooks.json UserPromptSubmit references turn
|
|
101
|
-
run jq -e '.hooks.UserPromptSubmit[0].hooks | length ==
|
|
98
|
+
@test "hooks.json UserPromptSubmit references turn, session-duration, and prompt-rule trackers" {
|
|
99
|
+
run jq -e '.hooks.UserPromptSubmit[0].hooks | length == 3' "${REPO_ROOT}/hooks/hooks.json"
|
|
102
100
|
[ "$status" -eq 0 ]
|
|
103
101
|
|
|
104
|
-
local turn_cmd duration_cmd
|
|
102
|
+
local turn_cmd duration_cmd rule_cmd
|
|
105
103
|
turn_cmd=$(jq -r '.hooks.UserPromptSubmit[0].hooks[0].command' "${REPO_ROOT}/hooks/hooks.json")
|
|
106
104
|
duration_cmd=$(jq -r '.hooks.UserPromptSubmit[0].hooks[1].command' "${REPO_ROOT}/hooks/hooks.json")
|
|
105
|
+
rule_cmd=$(jq -r '.hooks.UserPromptSubmit[0].hooks[2].command' "${REPO_ROOT}/hooks/hooks.json")
|
|
107
106
|
[[ "$turn_cmd" == *turn-tracker.sh ]]
|
|
108
107
|
[[ "$duration_cmd" == *session-duration-tracker.sh ]]
|
|
108
|
+
[[ "$rule_cmd" == *prompt-rule-injector.sh ]]
|
|
109
109
|
|
|
110
|
-
for hook_cmd in "$turn_cmd" "$duration_cmd"; do
|
|
110
|
+
for hook_cmd in "$turn_cmd" "$duration_cmd" "$rule_cmd"; do
|
|
111
111
|
local script_path="${hook_cmd//\$CLAUDE_PLUGIN_ROOT/$REPO_ROOT}"
|
|
112
112
|
script_path="${script_path//\"/}"
|
|
113
113
|
run test -x "$script_path"
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
|
|
3
|
+
setup() {
|
|
4
|
+
source "${BATS_TEST_DIRNAME}/../helpers/setup.bash"
|
|
5
|
+
setup_test_env
|
|
6
|
+
|
|
7
|
+
PLUGIN_ROOT="${REPO_ROOT}/plugins/echo"
|
|
8
|
+
export CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT"
|
|
9
|
+
# shellcheck disable=SC1091
|
|
10
|
+
source "${PLUGIN_ROOT}/scripts/lib/echo-config.sh"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@test "echo is disabled by default" {
|
|
14
|
+
CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" echo_config_load ""
|
|
15
|
+
run echo_config_enabled
|
|
16
|
+
[ "$status" -ne 0 ]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@test "settings.json echo.enabled=true enables echo" {
|
|
20
|
+
local repo="${BATS_TEST_TMPDIR}/repo"
|
|
21
|
+
mkdir -p "${repo}/.claude"
|
|
22
|
+
printf '%s\n' '{"echo":{"enabled":true}}' > "${repo}/.claude/settings.json"
|
|
23
|
+
CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" echo_config_load "$repo"
|
|
24
|
+
run echo_config_enabled
|
|
25
|
+
[ "$status" -eq 0 ]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@test "settings.json echo.enabled=false overrides plugin default" {
|
|
29
|
+
local repo="${BATS_TEST_TMPDIR}/repo"
|
|
30
|
+
mkdir -p "${repo}/.claude"
|
|
31
|
+
printf '%s\n' '{"echo":{"enabled":false}}' > "${repo}/.claude/settings.json"
|
|
32
|
+
CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" echo_config_load "$repo"
|
|
33
|
+
run echo_config_enabled
|
|
34
|
+
[ "$status" -ne 0 ]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@test "default model is claude-haiku-4-5-20251001" {
|
|
38
|
+
CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" echo_config_load ""
|
|
39
|
+
local m
|
|
40
|
+
m=$(CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" echo_config_model)
|
|
41
|
+
[ "$m" = "claude-haiku-4-5-20251001" ]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@test "default timeout is 60" {
|
|
45
|
+
CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" echo_config_load ""
|
|
46
|
+
local t
|
|
47
|
+
t=$(CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" echo_config_timeout)
|
|
48
|
+
[ "$t" = "60" ]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@test "default drift_threshold is 0.05" {
|
|
52
|
+
CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" echo_config_load ""
|
|
53
|
+
local d
|
|
54
|
+
d=$(CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" echo_config_drift_threshold)
|
|
55
|
+
[ "$d" = "0.05" ]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@test "default watch_paths includes plugins/*/agents/*.md" {
|
|
59
|
+
CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" echo_config_load ""
|
|
60
|
+
local paths
|
|
61
|
+
paths=$(CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" echo_config_watch_paths)
|
|
62
|
+
printf '%s\n' "$paths" | grep -q 'plugins/\*/agents/\*.md'
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@test "exclude_paths always includes plugins/echo/** regardless of config" {
|
|
66
|
+
CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" echo_config_load ""
|
|
67
|
+
local excl
|
|
68
|
+
excl=$(CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" echo_config_exclude_paths)
|
|
69
|
+
printf '%s\n' "$excl" | grep -q 'plugins/echo/\*\*'
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@test "settings.json model override wins over plugin default" {
|
|
73
|
+
local repo="${BATS_TEST_TMPDIR}/repo"
|
|
74
|
+
mkdir -p "${repo}/.claude"
|
|
75
|
+
printf '%s\n' '{"echo":{"evaluation":{"model":"claude-opus-4-7"}}}' > "${repo}/.claude/settings.json"
|
|
76
|
+
CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" echo_config_load "$repo"
|
|
77
|
+
local m
|
|
78
|
+
m=$(CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" echo_config_model)
|
|
79
|
+
[ "$m" = "claude-opus-4-7" ]
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@test "settings.json drift_threshold override wins" {
|
|
83
|
+
local repo="${BATS_TEST_TMPDIR}/repo"
|
|
84
|
+
mkdir -p "${repo}/.claude"
|
|
85
|
+
printf '%s\n' '{"echo":{"drift_threshold":0.1}}' > "${repo}/.claude/settings.json"
|
|
86
|
+
CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" echo_config_load "$repo"
|
|
87
|
+
local d
|
|
88
|
+
d=$(CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" echo_config_drift_threshold)
|
|
89
|
+
[ "$d" = "0.1" ]
|
|
90
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
|
|
3
|
+
# Validates every emitted echo.* event against @onlooker-community/schema.
|
|
4
|
+
|
|
5
|
+
setup() {
|
|
6
|
+
source "${BATS_TEST_DIRNAME}/../helpers/setup.bash"
|
|
7
|
+
setup_test_env
|
|
8
|
+
|
|
9
|
+
PLUGIN_ROOT="${REPO_ROOT}/plugins/echo"
|
|
10
|
+
export CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT"
|
|
11
|
+
export ONLOOKER_EVENTS_LOG="${ONLOOKER_DIR}/logs/onlooker-events.jsonl"
|
|
12
|
+
mkdir -p "$(dirname "$ONLOOKER_EVENTS_LOG")"
|
|
13
|
+
|
|
14
|
+
export _ONLOOKER_EVENT_JS="${REPO_ROOT}/scripts/lib/onlooker-event.mjs"
|
|
15
|
+
export CLAUDE_SESSION_ID="bats-session-$$"
|
|
16
|
+
|
|
17
|
+
# shellcheck disable=SC1091
|
|
18
|
+
source "${PLUGIN_ROOT}/scripts/lib/echo-events.sh"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
_validate_latest_event() {
|
|
22
|
+
local last
|
|
23
|
+
last=$(tail -n 1 "$ONLOOKER_EVENTS_LOG")
|
|
24
|
+
[ -n "$last" ] || return 1
|
|
25
|
+
printf '%s' "$last" | ONLOOKER_DIR="$ONLOOKER_DIR" \
|
|
26
|
+
node "${REPO_ROOT}/scripts/lib/onlooker-event.mjs" validate >/dev/null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
SUITE_ID="01J000000000000000000000SS"
|
|
30
|
+
TEST_ID="01J000000000000000000000TT"
|
|
31
|
+
|
|
32
|
+
@test "echo.suite.started validates" {
|
|
33
|
+
local p
|
|
34
|
+
p=$(jq -n --arg s "$SUITE_ID" '{
|
|
35
|
+
suite_id: $s,
|
|
36
|
+
test_count: 2,
|
|
37
|
+
trigger: "file_change",
|
|
38
|
+
changed_file: "plugins/tribunal/agents/tribunal-judge-standard.md"
|
|
39
|
+
}')
|
|
40
|
+
echo_emit_event "echo.suite.started" "$p"
|
|
41
|
+
run _validate_latest_event
|
|
42
|
+
[ "$status" -eq 0 ]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@test "echo.suite.complete validates without drift fields" {
|
|
46
|
+
local p
|
|
47
|
+
p=$(jq -n --arg s "$SUITE_ID" '{
|
|
48
|
+
suite_id: $s,
|
|
49
|
+
test_count: 1,
|
|
50
|
+
improved: 0,
|
|
51
|
+
degraded: 0,
|
|
52
|
+
neutral: 1,
|
|
53
|
+
merge_recommended: true,
|
|
54
|
+
duration_ms: 3200
|
|
55
|
+
}')
|
|
56
|
+
echo_emit_event "echo.suite.complete" "$p"
|
|
57
|
+
run _validate_latest_event
|
|
58
|
+
[ "$status" -eq 0 ]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@test "echo.suite.complete validates with drift fields" {
|
|
62
|
+
local p
|
|
63
|
+
p=$(jq -n --arg s "$SUITE_ID" '{
|
|
64
|
+
suite_id: $s,
|
|
65
|
+
test_count: 1,
|
|
66
|
+
improved: 1,
|
|
67
|
+
degraded: 0,
|
|
68
|
+
neutral: 0,
|
|
69
|
+
merge_recommended: true,
|
|
70
|
+
duration_ms: 3200,
|
|
71
|
+
baseline_score: 0.72,
|
|
72
|
+
score_after: 0.85,
|
|
73
|
+
drift: 0.13,
|
|
74
|
+
drift_threshold: 0.05
|
|
75
|
+
}')
|
|
76
|
+
echo_emit_event "echo.suite.complete" "$p"
|
|
77
|
+
run _validate_latest_event
|
|
78
|
+
[ "$status" -eq 0 ]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@test "echo.improvement.detected validates" {
|
|
82
|
+
local p
|
|
83
|
+
p=$(jq -n --arg s "$SUITE_ID" --arg t "$TEST_ID" '{
|
|
84
|
+
suite_id: $s,
|
|
85
|
+
test_id: $t,
|
|
86
|
+
test_name: "tribunal-judge-standard.md",
|
|
87
|
+
score_before: 0.70,
|
|
88
|
+
score_after: 0.85,
|
|
89
|
+
delta: 0.15,
|
|
90
|
+
confidence: 0.9
|
|
91
|
+
}')
|
|
92
|
+
echo_emit_event "echo.improvement.detected" "$p"
|
|
93
|
+
run _validate_latest_event
|
|
94
|
+
[ "$status" -eq 0 ]
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@test "echo.regression.detected validates" {
|
|
98
|
+
local p
|
|
99
|
+
p=$(jq -n --arg s "$SUITE_ID" --arg t "$TEST_ID" '{
|
|
100
|
+
suite_id: $s,
|
|
101
|
+
test_id: $t,
|
|
102
|
+
test_name: "tribunal-judge-standard.md",
|
|
103
|
+
score_before: 0.85,
|
|
104
|
+
score_after: 0.62,
|
|
105
|
+
delta: -0.23,
|
|
106
|
+
confidence: 0.88
|
|
107
|
+
}')
|
|
108
|
+
echo_emit_event "echo.regression.detected" "$p"
|
|
109
|
+
run _validate_latest_event
|
|
110
|
+
[ "$status" -eq 0 ]
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@test "emission fails on unknown event type" {
|
|
114
|
+
run echo_emit_event "echo.no.such.event" '{"suite_id":"x"}'
|
|
115
|
+
[ "$status" -ne 0 ]
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@test "echo_emit_event returns 1 when payload is empty" {
|
|
119
|
+
run echo_emit_event "echo.suite.started" ""
|
|
120
|
+
[ "$status" -ne 0 ]
|
|
121
|
+
}
|