batch_run ended with a bare wait, which blocks on every background job the calling shell has, not the ones it started. Harmless for all three current callers, but a future caller that backgrounds anything of its own would have batch_run block on it or consume its status. It now records each $! and reaps exactly those PIDs. The `wait "$pid" || true` there is load-bearing: unlike a bare wait, wait <pid> returns the job's status, so without it a single failing job would abort the set -e caller at the call site -- before run-tests.sh or sync-plugin-content.sh could read their .status files and print a summary. Status semantics stay in those files, exactly as before. check-vale-style-sync.sh's glob probe discarded vale's exit code and output and decided purely on a grep, so a failed exec, an OOM-killed vale or a full TMPDIR was indistinguishable from a real glob defect -- both printed "its glob sections do not cover a path" with no evidence. A flake seen once in this probe could not be diagnosed afterwards for that reason. The probe now attaches vale's rc and output: a genuine glob defect reads "vale exited 0 ... in 0 files", a killed vale reads "vale exited 137; output: <empty>". That flake was investigated and not reproduced -- 1680 probes across three contention setups including an offline namespace, all clean -- so nothing is changed speculatively. The misattribution is worth recording: it was reported against tests/test-vale-wrap.sh, which never invokes this script; the assertion belongs to check-vale-style-sync.sh and reaches a log through a different suite. Also drops the last stale field roster from agent-author's scaffolder. Its next-steps hint enumerated "(name, description, model, body only)" -- omitting disallowedTools, and never accurate anyway, since the template marks only description and the body FILL IN. Its --help carried the inverted form, already missing six forbidden fields. Both now state the shape rule and point at field-inventory.md, and a bats case enforces all-or-nothing: name every allowlisted field or name none, since a partial roster is the shape that goes stale silently. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X7GvKuJfy2WrdBmUttV4DT
scripts/
new-agent.sh
Scaffolds agent definition file(s) from a single root directory input.
Usage: new-agent.sh <agent-name> <root>
Resolves scope by walking up from <root>: a type:-bearing apm.yml found at or above <root> → plugin/APM scope (single file at <package-root>/.apm/agents/<name>.agent.md; an apm.yml without type: is a marketplace-only manifest and is skipped); <root> exactly ~ → user scope (~/.claude/agents/ + ~/.copilot/agents/); otherwise project scope (<root>/.claude/agents/ + <root>/.github/agents/). Each file is a no-op if it already exists. See --help for full usage.
Tests: tests/new-agent.bats (requires bats-support and bats-assert) — source-only. scripts/sync-plugin-content.sh strips <category>/<name>/tests from the generated mirror (ADR-0017), so this file exists in a repo checkout of .apm/skills/agent-author/ and not in an installed plugin.