feat(kyberforge): add instructions-author skill for .apm/instructions files
Scaffolds and revises apm instructions files, with a throwaway-package verification recipe because `apm compile --validate` always exits 0 and Claude Code drops `description`. Routed from forge and linked from apm-workflow's compile reference. Bumps kyberforge to 2.1.0 and the catalog to 0.5.2. Fixes #148 Co-Authored-By: Claude Code <[email protected]> Claude-Session: https://claude.ai/code/session_01KkT7RSDwDbmrM9T34b6sTi
This commit is contained in:
1 parent
529ed31cef
commit
c52e351954
20 files changed
+719
-16
No files matched your search
@@ -0,0 +1,15 @@
|
||||
# tests/
|
||||
|
||||
- **`new-instructions.bats`** — covers `scripts/new-instructions.sh` (name validation, package walk-up, no-package refusal, no-op on an existing file, template placeholders) and the apm behaviour the skill's gotchas rest on, run against throwaway packages: a filled scaffold compiles into `CLAUDE.md` and installs into `.claude/rules/` with `description` dropped, compile writes nothing once rules are installed, an unquoted `applyTo` installs unscoped, and `--validate` hides the warnings a real compile prints.
|
||||
|
||||
## Dependencies
|
||||
|
||||
The test file loads `bats-support` and `bats-assert` from the repo root's `tests/test_helper/`, and runs on the repo's bats submodule at `tests/bats/`. The first `bash tests/run-bats.sh` initialises the submodules.
|
||||
|
||||
The apm tests need the `apm` CLI on `PATH` and skip when it is absent. They assert apm 0.28.0 behaviour, so a failure after an apm upgrade is a finding about the skill's gotchas, not a flaky test.
|
||||
|
||||
From the repo root:
|
||||
|
||||
```bash
|
||||
tests/bats/bin/bats plugins/kyberforge/.apm/skills/instructions-author/tests/new-instructions.bats
|
||||
```
|
||||
@@ -0,0 +1,219 @@
|
||||
#!/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 <name>.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"
|
||||
}
|
||||
Reference in new issue
Block a user