Files
holocron/plugins/kyberforge/skills/agent-author/tests/new-agent.bats
Defame1297 ba68b09c87 feat(kyberforge): add agent-author skill for Claude Code and Copilot CLI agents
## Why

skill-author explicitly excludes agent definition files ("Do not use to author
agent definition files"). No factory skill existed to create or improve the
.md / .agent.md files that define Claude Code subagents and Copilot CLI agents
in a plugin, project, or user scope. This fills that gap.

## Implementation Notes

Single-root scaffold convention: new-agent.sh <name> <root> derives both
provider file paths from the root by convention — plugin scope (plugin.json
present) writes both files into <root>/agents/; non-plugin scope writes
.claude/agents/<name>.md and .github/agents/<name>.agent.md. This keeps
input minimal while always generating both provider files. See ADR-0015.

Routing is file-level (not directory-level like skill-author): neither file
exists → create flow; at least one exists → improve flow; scaffold is a
file-by-file no-op so retries are safe.

No companion agent-audit skill — inline validation in the close step covers
the simpler agent field contract. agent-audit is tracked as a follow-on.

## Impact

Closes the skill-author gap for agent definitions. Follow-ons tracked in
Gitea #11: agent-audit skill and --copilot-dest override flag for non-standard
Copilot project paths.

---
ADR: docs/adr/0015-agent-author-dual-provider-scaffold.md
Refs: #10
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 15:07:33 +00:00

185 lines
5.4 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)/new-agent.sh"
ROOT="$(mktemp -d)"
}
teardown() {
rm -rf "$ROOT"
}
# ---------------------------------------------------------------------------
# Help
# ---------------------------------------------------------------------------
@test "--help exits 0" {
run bash "$SCRIPT" --help
assert_success
assert_output --partial "Usage:"
}
# ---------------------------------------------------------------------------
# Plugin scope (plugin.json present at root)
# ---------------------------------------------------------------------------
@test "plugin scope: creates both agent files in agents/" {
touch "$ROOT/plugin.json"
run bash "$SCRIPT" my-agent "$ROOT"
assert_success
assert [ -f "$ROOT/agents/my-agent.md" ]
assert [ -f "$ROOT/agents/my-agent.agent.md" ]
}
@test "plugin scope: creates agents/ directory if missing" {
touch "$ROOT/plugin.json"
run bash "$SCRIPT" my-agent "$ROOT"
assert_success
assert [ -d "$ROOT/agents" ]
}
@test "plugin scope: creates sources.md in agents/" {
touch "$ROOT/plugin.json"
run bash "$SCRIPT" my-agent "$ROOT"
assert_success
assert [ -f "$ROOT/agents/sources.md" ]
}
@test "plugin scope: no-op if claude code file already exists" {
touch "$ROOT/plugin.json"
mkdir -p "$ROOT/agents"
echo "existing" > "$ROOT/agents/my-agent.md"
run bash "$SCRIPT" my-agent "$ROOT"
assert_success
run grep "existing" "$ROOT/agents/my-agent.md"
assert_success
}
@test "plugin scope: no-op if copilot file already exists" {
touch "$ROOT/plugin.json"
mkdir -p "$ROOT/agents"
echo "existing" > "$ROOT/agents/my-agent.agent.md"
run bash "$SCRIPT" my-agent "$ROOT"
assert_success
run grep "existing" "$ROOT/agents/my-agent.agent.md"
assert_success
}
@test "plugin scope: still creates missing file when one already exists" {
touch "$ROOT/plugin.json"
mkdir -p "$ROOT/agents"
echo "existing" > "$ROOT/agents/my-agent.md"
run bash "$SCRIPT" my-agent "$ROOT"
assert_success
assert [ -f "$ROOT/agents/my-agent.agent.md" ]
}
# ---------------------------------------------------------------------------
# Non-plugin scope (no plugin.json)
# ---------------------------------------------------------------------------
@test "non-plugin scope: creates claude code file in .claude/agents/" {
run bash "$SCRIPT" my-agent "$ROOT"
assert_success
assert [ -f "$ROOT/.claude/agents/my-agent.md" ]
}
@test "non-plugin scope: creates copilot file in .github/agents/" {
run bash "$SCRIPT" my-agent "$ROOT"
assert_success
assert [ -f "$ROOT/.github/agents/my-agent.agent.md" ]
}
@test "non-plugin scope: creates .claude/agents/ directory if missing" {
run bash "$SCRIPT" my-agent "$ROOT"
assert_success
assert [ -d "$ROOT/.claude/agents" ]
}
@test "non-plugin scope: creates .github/agents/ directory if missing" {
run bash "$SCRIPT" my-agent "$ROOT"
assert_success
assert [ -d "$ROOT/.github/agents" ]
}
@test "non-plugin scope: no sources.md created" {
run bash "$SCRIPT" my-agent "$ROOT"
assert_success
assert [ ! -f "$ROOT/.claude/agents/sources.md" ]
assert [ ! -f "$ROOT/.github/agents/sources.md" ]
}
@test "non-plugin scope: no-op if claude code file already exists" {
mkdir -p "$ROOT/.claude/agents"
echo "existing" > "$ROOT/.claude/agents/my-agent.md"
run bash "$SCRIPT" my-agent "$ROOT"
assert_success
run grep "existing" "$ROOT/.claude/agents/my-agent.md"
assert_success
}
# ---------------------------------------------------------------------------
# Name validation
# ---------------------------------------------------------------------------
@test "fails when no arguments given" {
run bash "$SCRIPT"
assert_failure
}
@test "fails when agent name contains uppercase" {
run bash "$SCRIPT" MyAgent "$ROOT"
assert_failure
}
@test "fails when agent name has consecutive hyphens" {
run bash "$SCRIPT" my--agent "$ROOT"
assert_failure
}
@test "fails when agent name has a leading hyphen" {
run bash "$SCRIPT" -my-agent "$ROOT"
assert_failure
}
@test "fails when agent name has a trailing hyphen" {
run bash "$SCRIPT" my-agent- "$ROOT"
assert_failure
}
@test "agent name with numbers is valid" {
run bash "$SCRIPT" agent-v2 "$ROOT"
assert_success
}
# ---------------------------------------------------------------------------
# Root validation
# ---------------------------------------------------------------------------
@test "fails when root directory does not exist" {
run bash "$SCRIPT" my-agent "/nonexistent/path"
assert_failure
}
# ---------------------------------------------------------------------------
# Template content
# ---------------------------------------------------------------------------
@test "plugin scope: claude code template contains AGENT_NAME substituted" {
touch "$ROOT/plugin.json"
bash "$SCRIPT" my-agent "$ROOT"
run grep "my-agent" "$ROOT/agents/my-agent.md"
assert_success
}
@test "plugin scope: copilot template contains AGENT_NAME substituted" {
touch "$ROOT/plugin.json"
bash "$SCRIPT" my-agent "$ROOT"
run grep "my-agent" "$ROOT/agents/my-agent.agent.md"
assert_success
}