#!/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-instructions.sh" ROOT="$(mktemp -d)" } teardown() { rm -rf "$ROOT" } make_package() { printf 'name: my-package\nversion: 0.1.0\ntype: instructions\ntargets:\n - claude\n' > "$ROOT/apm.yml" } # Replace every placeholder and drop the template's comments, leaving a valid file. fill() { sed -i -E \ -e '/^#/{/^# FILL IN/!d}' \ -e 's/^description: FILL IN.*/description: Python style rules/' \ -e 's/^applyTo: .*/applyTo: "**\/*.py"/' \ -e 's/^# FILL IN.*/# Python style/' \ -e 's/^- FILL IN.*/- Use type hints./' \ "$1" } need_apm() { command -v apm >/dev/null 2>&1 || skip "apm CLI not installed" } # --------------------------------------------------------------------------- # Help and arguments # --------------------------------------------------------------------------- @test "--help exits 0" { run bash "$SCRIPT" --help assert_success assert_output --partial "Usage:" } @test "missing arguments exits 1" { run bash "$SCRIPT" assert_failure assert_output --partial "name and root are required" } @test "nonexistent root exits 1" { run bash "$SCRIPT" my-rule "$ROOT/missing" assert_failure assert_output --partial "does not exist" } @test "rejects names that are not kebab-case" { make_package for bad in My-Rule my_rule -leading trailing- double--hyphen; do run bash "$SCRIPT" "$bad" "$ROOT" assert_failure assert_output --partial "lowercase letters" done assert [ ! -d "$ROOT/.apm" ] } # --------------------------------------------------------------------------- # Package resolution # --------------------------------------------------------------------------- @test "creates .instructions.md under .apm/instructions/" { make_package run bash "$SCRIPT" my-rule "$ROOT" assert_success assert [ -f "$ROOT/.apm/instructions/my-rule.instructions.md" ] } @test "walks up from a subdirectory to the package root" { make_package mkdir -p "$ROOT/deep/er" run bash "$SCRIPT" my-rule "$ROOT/deep/er" assert_success assert [ -f "$ROOT/.apm/instructions/my-rule.instructions.md" ] assert [ ! -d "$ROOT/deep/er/.apm" ] } @test "skips a type-less apm.yml and keeps walking up" { make_package mkdir -p "$ROOT/marketplace" printf 'name: catalog\nmarketplace:\n packages: []\n' > "$ROOT/marketplace/apm.yml" run bash "$SCRIPT" my-rule "$ROOT/marketplace" assert_success assert [ -f "$ROOT/.apm/instructions/my-rule.instructions.md" ] assert [ ! -d "$ROOT/marketplace/.apm" ] } @test "accepts a quoted type value" { printf 'name: p\ntype: "hybrid"\n' > "$ROOT/apm.yml" run bash "$SCRIPT" my-rule "$ROOT" assert_success assert [ -f "$ROOT/.apm/instructions/my-rule.instructions.md" ] } @test "no package: exits 1, points at apm-workflow configure, writes nothing" { mkdir -p "$ROOT/.git" run bash "$SCRIPT" my-rule "$ROOT" assert_failure assert_output --partial "apm-workflow configure" assert [ ! -d "$ROOT/.apm" ] } @test "does not walk above a .git boundary" { make_package mkdir -p "$ROOT/repo/.git" run bash "$SCRIPT" my-rule "$ROOT/repo" assert_failure assert [ ! -d "$ROOT/.apm" ] } @test "no-op when the file already exists" { make_package mkdir -p "$ROOT/.apm/instructions" echo "existing" > "$ROOT/.apm/instructions/my-rule.instructions.md" run bash "$SCRIPT" my-rule "$ROOT" assert_success run cat "$ROOT/.apm/instructions/my-rule.instructions.md" assert_output "existing" } # --------------------------------------------------------------------------- # Template contents # --------------------------------------------------------------------------- @test "scaffold carries FILL IN placeholders and a quoted applyTo" { make_package bash "$SCRIPT" my-rule "$ROOT" file="$ROOT/.apm/instructions/my-rule.instructions.md" run grep -c 'FILL IN' "$file" assert_success run grep -E '^applyTo: "' "$file" assert_success } # --------------------------------------------------------------------------- # apm behaviour the skill's gotchas rest on (verified against apm 0.28.0) # --------------------------------------------------------------------------- @test "filled scaffold compiles for the Claude target into CLAUDE.md without the description" { need_apm make_package bash "$SCRIPT" my-rule "$ROOT" fill "$ROOT/.apm/instructions/my-rule.instructions.md" cd "$ROOT" run apm compile --target claude assert_success assert [ -f "$ROOT/CLAUDE.md" ] run grep -F 'Use type hints.' "$ROOT/CLAUDE.md" assert_success run grep -F 'Python style rules' "$ROOT/CLAUDE.md" assert_failure } @test "install deploys .claude/rules with paths: and drops the description" { need_apm make_package bash "$SCRIPT" my-rule "$ROOT" fill "$ROOT/.apm/instructions/my-rule.instructions.md" cd "$ROOT" run apm install assert_success rule="$ROOT/.claude/rules/my-rule.md" assert [ -f "$rule" ] run grep -F 'paths:' "$rule" assert_success run grep -F '**/*.py' "$rule" assert_success run grep -F 'Python style rules' "$rule" assert_failure } @test "once rules are installed, compile --target claude writes no CLAUDE.md and exits 0" { need_apm make_package bash "$SCRIPT" my-rule "$ROOT" fill "$ROOT/.apm/instructions/my-rule.instructions.md" cd "$ROOT" apm install run apm compile --target claude assert_success assert [ ! -f "$ROOT/CLAUDE.md" ] } @test "an unquoted applyTo still installs, as a rule with no paths:" { need_apm make_package mkdir -p "$ROOT/.apm/instructions" printf -- '---\ndescription: x\napplyTo: **/*.py\n---\n# T\n\n- a\n' > "$ROOT/.apm/instructions/bad.instructions.md" cd "$ROOT" run apm install assert_success assert [ -f "$ROOT/.claude/rules/bad.md" ] run grep -F 'paths:' "$ROOT/.claude/rules/bad.md" assert_failure } @test "compile --validate exits 0 and hides the warnings a real compile prints" { need_apm make_package mkdir -p "$ROOT/.apm/instructions" printf -- '---\napplyTo: "**/*.py"\n---\n' > "$ROOT/.apm/instructions/bare.instructions.md" cd "$ROOT" run apm compile --validate assert_success refute_output --partial "Missing 'description'" run apm compile --dry-run --target claude assert_success assert_output --partial "Missing 'description'" assert_output --partial "Empty content" }