## Why agent-author produces paired agent definition files (Claude Code .md + Copilot .agent.md) but had no companion audit skill to validate them. agent-audit fills that gap, giving the same structured PASS/FAIL report that skill-audit provides for SKILL.md files. ## Implementation Notes - validate.sh uses scope detection (walk up for plugin.json / .git) to locate the counterpart file and determine whether plugin-silently-ignored fields (hooks, mcpServers, permissionMode) should be flagged - CC-only and silently-ignored field lists are read from references/field-inventory.md at runtime rather than hardcoded — provenance back to the research corpus; see ADR-0019 - Single-file invocation (pass either file, counterpart derived) chosen over directory or name+root — see ADR-0018 - 12 bats tests cover provider detection, scope detection, all FAIL paths, and clean-pair pass ## Impact - kyberforge bumped to v1.1.2 - agent-author close step should be updated to reference agent-audit (#11) - Provenance/sources chain check deferred to #60 ADR: docs/adr/0018-agent-audit-single-file-invocation.md ADR: docs/adr/0019-agent-audit-field-inventory-reference.md Refs: #11 Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0147vXtL5sP6vorDdqXGJJU9
219 lines
5.2 KiB
Bash
219 lines
5.2 KiB
Bash
#!/usr/bin/env bats
|
|
|
|
setup() {
|
|
REPO_ROOT="$(cd "$BATS_TEST_DIRNAME/../../../../../" && pwd)"
|
|
load "$REPO_ROOT/tests/test_helper/bats-support/load"
|
|
load "$REPO_ROOT/tests/test_helper/bats-assert/load"
|
|
|
|
SCRIPT="$(cd "$BATS_TEST_DIRNAME/../scripts" && pwd)/validate.sh"
|
|
TMPDIR="$(mktemp -d)"
|
|
|
|
# Helper: create a plugin-scope pair in <dir> with given <name>
|
|
make_plugin_pair() {
|
|
local dir="$1"
|
|
local name="$2"
|
|
mkdir -p "$dir"
|
|
echo '{}' > "$dir/plugin.json"
|
|
cat > "$dir/${name}.md" <<EOF
|
|
---
|
|
name: ${name}
|
|
description: A valid agent description.
|
|
---
|
|
|
|
You are a test agent. When invoked, do the thing.
|
|
EOF
|
|
cat > "$dir/${name}.agent.md" <<EOF
|
|
---
|
|
name: ${name}
|
|
description: A valid agent description.
|
|
---
|
|
|
|
You are a test agent. When invoked, do the thing.
|
|
EOF
|
|
}
|
|
}
|
|
|
|
teardown() {
|
|
rm -rf "$TMPDIR"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Passing cases
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@test "passes on a clean plugin-scope pair (CC file as input)" {
|
|
local dir="$TMPDIR/agents"
|
|
make_plugin_pair "$dir" "my-agent"
|
|
run bash "$SCRIPT" "$dir/my-agent.md"
|
|
assert_success
|
|
refute_output --partial "FAIL"
|
|
}
|
|
|
|
@test "passes on a clean project-scope pair (CC file as input)" {
|
|
local root="$TMPDIR/project"
|
|
mkdir -p "$root/.git" "$root/.claude/agents" "$root/.github/agents"
|
|
cat > "$root/.claude/agents/my-agent.md" <<EOF
|
|
---
|
|
name: my-agent
|
|
description: A valid agent description.
|
|
---
|
|
|
|
You are a test agent. When invoked, do the thing.
|
|
EOF
|
|
cat > "$root/.github/agents/my-agent.agent.md" <<EOF
|
|
---
|
|
name: my-agent
|
|
description: A valid agent description.
|
|
---
|
|
|
|
You are a test agent. When invoked, do the thing.
|
|
EOF
|
|
run bash "$SCRIPT" "$root/.claude/agents/my-agent.md"
|
|
assert_success
|
|
refute_output --partial "FAIL"
|
|
}
|
|
|
|
@test "--help exits 0 and shows Usage:" {
|
|
run bash "$SCRIPT" --help
|
|
assert_success
|
|
assert_output --partial "Usage:"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Failing cases
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@test "fails when Copilot counterpart is missing" {
|
|
local dir="$TMPDIR/agents"
|
|
make_plugin_pair "$dir" "my-agent"
|
|
rm "$dir/my-agent.agent.md"
|
|
run bash "$SCRIPT" "$dir/my-agent.md"
|
|
assert_failure
|
|
assert_output --partial "counterpart"
|
|
}
|
|
|
|
@test "fails when CC-only field 'maxTurns' is in Copilot file" {
|
|
local dir="$TMPDIR/agents"
|
|
make_plugin_pair "$dir" "my-agent"
|
|
cat > "$dir/my-agent.agent.md" <<EOF
|
|
---
|
|
name: my-agent
|
|
description: A valid agent description.
|
|
maxTurns: 10
|
|
---
|
|
|
|
You are a test agent. When invoked, do the thing.
|
|
EOF
|
|
run bash "$SCRIPT" "$dir/my-agent.md"
|
|
assert_failure
|
|
assert_output --partial "maxTurns"
|
|
}
|
|
|
|
@test "fails when plugin-silently-ignored field 'hooks' is in plugin-scope CC file" {
|
|
local dir="$TMPDIR/agents"
|
|
make_plugin_pair "$dir" "my-agent"
|
|
cat > "$dir/my-agent.md" <<EOF
|
|
---
|
|
name: my-agent
|
|
description: A valid agent description.
|
|
hooks:
|
|
PostToolUse:
|
|
- match: ".*"
|
|
command: "echo done"
|
|
---
|
|
|
|
You are a test agent. When invoked, do the thing.
|
|
EOF
|
|
run bash "$SCRIPT" "$dir/my-agent.md"
|
|
assert_failure
|
|
assert_output --partial "hooks"
|
|
}
|
|
|
|
@test "fails when CC file name is not kebab-case" {
|
|
local dir="$TMPDIR/agents"
|
|
mkdir -p "$dir"
|
|
echo '{}' > "$dir/plugin.json"
|
|
cat > "$dir/my-agent.md" <<EOF
|
|
---
|
|
name: MyAgent
|
|
description: A valid agent description.
|
|
---
|
|
|
|
You are a test agent. When invoked, do the thing.
|
|
EOF
|
|
cat > "$dir/my-agent.agent.md" <<EOF
|
|
---
|
|
name: MyAgent
|
|
description: A valid agent description.
|
|
---
|
|
|
|
You are a test agent. When invoked, do the thing.
|
|
EOF
|
|
run bash "$SCRIPT" "$dir/my-agent.md"
|
|
assert_failure
|
|
assert_output --partial "kebab"
|
|
}
|
|
|
|
@test "fails when 'name' field is missing from CC file" {
|
|
local dir="$TMPDIR/agents"
|
|
make_plugin_pair "$dir" "my-agent"
|
|
cat > "$dir/my-agent.md" <<EOF
|
|
---
|
|
description: A valid agent description.
|
|
---
|
|
|
|
You are a test agent. When invoked, do the thing.
|
|
EOF
|
|
run bash "$SCRIPT" "$dir/my-agent.md"
|
|
assert_failure
|
|
}
|
|
|
|
@test "fails when 'description' field is missing from CC file" {
|
|
local dir="$TMPDIR/agents"
|
|
make_plugin_pair "$dir" "my-agent"
|
|
cat > "$dir/my-agent.md" <<EOF
|
|
---
|
|
name: my-agent
|
|
---
|
|
|
|
You are a test agent. When invoked, do the thing.
|
|
EOF
|
|
run bash "$SCRIPT" "$dir/my-agent.md"
|
|
assert_failure
|
|
}
|
|
|
|
@test "fails when body contains unfilled FILL IN: placeholder" {
|
|
local dir="$TMPDIR/agents"
|
|
make_plugin_pair "$dir" "my-agent"
|
|
cat > "$dir/my-agent.md" <<EOF
|
|
---
|
|
name: my-agent
|
|
description: A valid agent description.
|
|
---
|
|
|
|
FILL IN: replace this with your system prompt.
|
|
EOF
|
|
run bash "$SCRIPT" "$dir/my-agent.md"
|
|
assert_failure
|
|
}
|
|
|
|
@test "fails when frontmatter name does not match filename stem" {
|
|
local dir="$TMPDIR/agents"
|
|
make_plugin_pair "$dir" "my-agent"
|
|
cat > "$dir/my-agent.md" <<EOF
|
|
---
|
|
name: wrong-name
|
|
description: A valid agent description.
|
|
---
|
|
|
|
You are a test agent. When invoked, do the thing.
|
|
EOF
|
|
run bash "$SCRIPT" "$dir/my-agent.md"
|
|
assert_failure
|
|
}
|
|
|
|
@test "fails when no arguments are given" {
|
|
run bash "$SCRIPT"
|
|
assert_failure
|
|
}
|