| name | script-builder |
| description | Generate durable, re-runnable validation scripts from testing intent. Supports TypeScript, Python, Bash with auto-detection, enforces context-optimal PASS/FAIL + /tmp log output, and auto-documents scripts in CLAUDE.md. Triggers on phrases like "turn this into a script", "I want to test/validate X end-to-end", "wrap this in a re-runnable script", or whenever an agent needs durable validation instead of throwaway commands. |
script-builder
You are converting testing/validation intent into a durable, re-runnable script committed to the target project's scripts/ directory. The output is context-optimal: a single PASS/FAIL line + /tmp log path on success, full verbose output redirected to a timestamped log file, and an <important if> block in CLAUDE.md/AGENTS.md so future agents discover the script when the relevant testing intent recurs.
Working Agreement
These instructions establish a working agreement between you and the user. The key principles are:
-
AskUserQuestion is your primary communication tool - Whenever you need to ask the user anything (clarifications, preferences, decisions, confirmations), use the AskUserQuestion tool. Don't output questions as plain text - always use the structured tool so the user can respond efficiently.
-
Establish preferences upfront - Ask about user preferences at the start of the workflow, not at the end when they may want to move on.
-
Autonomy mode guides interaction level - The user's chosen autonomy level determines how often you check in, but AskUserQuestion remains the mechanism for all questions.
When to Use
This skill activates when:
- User invokes
/script-builder command
- Another skill references
**OPTIONAL SUB-SKILL:** desplega:script-builder
- A
planning, qa, or verifying flow needs a re-runnable validation script that doesn't yet exist
- The user expresses intent to "turn X into a script", "wrap this validation into something reusable", or "I want to test X end-to-end" with no existing script
Autonomy Mode
Adapt your behavior based on the autonomy mode:
| Mode | Behavior |
|---|
| Autopilot | Detect mode, draft, syntax-check, document, and (if escalation tier) run + iterate without intermediate confirmations. Pause only at hard blockers or destructive side-effects. |
| Critical (Default) | Confirm at each tier boundary (after draft, before doc edit, before run). Use AskUserQuestion for fix application during iterate loop. |
| Verbose | Confirm before each step. Show every diff. Walk through detection reasoning out loud. |
The autonomy mode is passed by the invoking command. If not specified, default to Critical.
Process Steps
Step 1: Detect Mode (Retrospective vs Forward-Declared)
Decide silently — do not ask the user "which mode?". Use the following heuristics:
- Scan recent session tool-use history (the last ~20 tool calls in the current conversation) for test/validation-shaped activity:
curl/fetch calls, bun run/python/pytest, database queries, qa-use browser actions, repeated grep/log inspection of a single endpoint or table. If ≥2 such actions targeting the same area exist → retrospective mode.
- Parse the user's invocation message for cues:
- Narrative cues → retrospective: "we just figured out", "turn this into a script", "wrap that in", "that thing we just did".
- Intent cues → forward-declared: "I want to test", "validate that", "smoke check", "check before deploy".
- Fallback: when ambiguous, default to forward-declared.
Retrospective first action: summarize the observed activity back to the user as plain text (≤6 lines: what was probed, against what, with what success signal), then go to Step 3 with a confirmation prompt instead of Q&A.
Forward first action: skip directly to Step 3's Q&A.
Step 2: Scan Existing Scripts for Overlap
Resolve the scripts directory in this order:
- Check
CLAUDE.md for a <!-- script-builder:dir=<path> --> marker. If present, use that path.
- If
scripts/ exists at the repo root, use it.
- Use AskUserQuestion with options:
scripts/ (Recommended) | custom path | skip dir persistence for this run.
Edge case — directory absent: if the resolved path doesn't exist, skip the dedup scan, print a one-line note (No existing scripts directory at <path> — proceeding to intent gathering), and remember to optionally offer to create the directory before Step 5.
When the directory exists:
- List files in the scripts directory (top-level only for v1).
- For each file, read the top of the file (header comment) and capture the file-name tokens.
- Read
CLAUDE.md/AGENTS.md for <important if="..."> blocks that point at scripts in this directory and capture the trigger phrases.
- Fuzzy-match the current intent string against (file-name tokens ∪ header keywords ∪ trigger phrases). A "plausible match" is shared keyword + same area noun (e.g.,
auth, health, webhook).
- If ≥1 plausible match → use AskUserQuestion with options:
Reuse <name> as-is | Extend <name> | Generate new anyway.
Never silently skip dedup on borderline matches — surface and let the user decide. The Extend branch appends a sub-command/flag to the existing script (do not create a new file); document the new flag in the script's header comment and the existing <important if> block.
Step 3: Gather Intent
Both modes converge on the same internal intent structure: { what, success_signal, failure_signal, inputs, env, side_effects }.
Forward mode — use AskUserQuestion (one or two questions, not five):
| Question | Options |
|---|
| "What are we validating, and what's the success signal?" | [Free text — collect what + success signal in one answer] |
| "Any required env vars, inputs, or side-effects to flag (e.g., writes to prod, costs money)?" | [Free text — optional; skip if forward intent already specified them] |
Retrospective mode — present the summary from Step 1 and use AskUserQuestion with: That's the flow | Close but fix X | Start over. On Close but fix X, ask for the correction inline; on Start over, fall through to forward-mode Q&A.
Persist the resolved intent in working memory (do not write to disk yet).
Step 4: Detect Language
Priority order — first match wins, with override paths:
- TypeScript —
package.json + tsconfig.json exist.
- Bun if
bun.lock or bunfig.toml is present.
- else tsx if
tsx appears in package.json devDependencies.
- else compiled Node (note in script header: requires
tsc build).
- Python —
pyproject.toml or uv.lock or requirements.txt exists.
- uv if
uv.lock exists or pyproject.toml has [tool.uv] configured (sets {{UV_METADATA}}).
- else vanilla python3 (
{{UV_METADATA}} substituted with empty string).
- Both TS and Python detected — count source files in
src//top-level and ask AskUserQuestion tiebreaker with the dominant one Recommended.
Cargo.toml / go.mod only → fall back to bash (note: TS/Python/Bash only in v1).
- Nothing matched → bash.
Task-driven override: if the gathered intent is clearly shell-y ("verify three docker containers respond", "tail this log file for an error"), propose bash even in a TS/Python project.
Confirm via AskUserQuestion with the detected language as the first option (Recommended). Skip in Autopilot.
Step 5: Draft the Script
- Pick the template:
cc-plugin/base/skills/script-builder/templates/{typescript.ts.tmpl|python.py.tmpl|bash.sh.tmpl}.
- Resolve substitution markers from gathered intent:
{{SCRIPT_NAME}} ← proposed file-name stem (see naming table).
{{WHAT}} / {{WHEN}} / {{ENV}} / {{EXAMPLE}} ← intent fields.
{{UV_METADATA}} ← per Step 4.
- Generate
{{TEST_BODY}} from the intent. Keep it minimal: a single concrete probe + assertion, not a battery. Re-read the templates' README (templates/README.md) for the contract — the body must respect the PASS/FAIL surface. Throw/raise/exit 1 on failure; let the template's outer try/trap convert it into the FAIL line.
- Propose a file name matching the intent shape — see the prefix table below. Use AskUserQuestion with the proposed name first (Recommended) and
custom name as the alternative.
Naming conventions (advisory — skill proposes, user overrides):
| Prefix | When | Example |
|---|
e2e-* | End-to-end flows across multiple components | e2e-auth-flow.ts |
check-* | Idempotent single-probe verifications | check-db-boundary.sh |
smoke-* | Minimal-viability post-deploy checks | smoke-prod-api.ts |
measure-* | Performance / size / token measurements | measure-tool-tokens.ts |
seed-* / generate-* | Data seeding or artifact generation (rare for validation) | seed-api-keys.sh |
If the intent doesn't match any prefix cleanly, propose a free-form name like validate-<area>.<ext>.
- Write the file to the resolved scripts directory. Make it executable (
chmod +x).
Step 6: Syntax/Type-Check
Run the appropriate checker:
| Language | Checker (in priority order) |
|---|
| TypeScript | bunx tsc --noEmit <file> if Bun present, else npx tsc --noEmit <file> |
| Python | python3 -m py_compile <file> (always) + ruff check <file> if ruff is on PATH |
| Bash | shellcheck <file> if available, else bash -n <file> |
On success: log a single line (syntax check OK) and proceed to Step 7.
On failure:
- Print the error (≤10 lines, not the full output).
- Propose a concrete one-edit fix (the exact
Edit you'd apply).
- Use AskUserQuestion with
Apply fix | Investigate | Stop.
- Apply fix → edit the script and re-run the checker (loop, no hardcoded cap).
- Investigate → drop into discussion with the user; do not auto-apply anything.
- Stop → leave script in place at its path; print the path; do not delete.
Step 7: Document the Script
Auto-edit the target project's CLAUDE.md and/or AGENTS.md so future agents discover this script when the matching intent recurs.
Block template (the literal markdown the skill emits):
<important if="[TRIGGER: e.g., you are testing the auth flow]">
## [Area] validation
Run `scripts/<name>` to [one-liner]. Requires [env/deps]. Example: `<cmd>`. Full log at `/tmp/<name>-*.log`.
Generated/maintained via `/script-builder`.
</important>
Behavior:
- Target file selection: edit
CLAUDE.md and AGENTS.md if both exist; edit only what exists. Never create either file from scratch — if neither exists, print a one-line note (No CLAUDE.md or AGENTS.md found — script generated but not documented) and skip this step.
- Placement heuristic: search the file for the first heading matching
Test|Testing|Validation|Scripts (case-insensitive). If found, append the new block within/after that section. Otherwise, append a new ## Scripts for testing & validation section near the end of the file but before any heading matching License|Acknowledg|Maintain|Contributors.
- Idempotency: if a block referencing
scripts/<name> already exists (i.e., a prior /script-builder run for the same script name), update it in place — replace the entire <important if=...>...</important> block, do not append a duplicate.
- Scripts-dir marker: if Step 2 resolved the scripts directory by user choice (not from an existing marker), insert
<!-- script-builder:dir=<path> --> near the top of CLAUDE.md (after the title) so subsequent runs are silent. Skip if the marker already exists.
- Show the diff: run the equivalent of
git diff CLAUDE.md AGENTS.md and print a 5-line summary of what changed. Never auto-stage — the user commits.
Step 8: Offer Escalation
After the doc edit, decide whether to proceed to the run-and-iterate tier.
| Autonomy | Behavior |
|---|
| Autopilot | Auto-escalate to Step 9 unless the intent flagged side-effects (writes to prod, sends real money, mutates a shared resource). On flagged side-effects, fall through to Critical behavior. |
| Critical (Default) | Use AskUserQuestion: `Run it now to confirm it works |
| Verbose | Same as Critical, plus offer the proposed run command for review before executing. |
If the user picks "I'll run it myself later" or "Just generate, don't run" → skip directly to Step 10.
Step 9: Iterate on Failures (Escalated Tier Only)
A bounded-by-the-human loop. Never auto-apply a fix outside Autopilot.
- Run the script with the proposed example invocation (no
--verbose, no --json). Capture exit code and tail -n 40 of the /tmp log.
- If exit code 0 → report PASS (echo the script's PASS line) and proceed to Step 10.
- If exit code non-zero:
- Summarize the failure in 1–3 lines: error class (timeout / 4xx / 5xx / assertion / dependency-missing / etc.) + likely cause (grep the log for known signatures:
ECONNREFUSED, Traceback, non-zero exit, command not found, etc.).
- Propose a concrete diff — the exact
Edit you would apply to the script. Show old → new.
- Use AskUserQuestion:
Apply fix | Investigate differently | Stop.
- Apply fix → edit the script, log the change, loop back to (1).
- Investigate differently → drop into discussion; do not auto-apply.
- Stop → leave the script in place, print its path; do not revert.
No hardcoded retry cap — the human is the implicit bound.
Side-effect flag: if the intent declared side-effects (Step 3) and Autopilot is active, surface a one-line WARNING: this script <does X> against <target> before the first run and require explicit confirmation via AskUserQuestion even in Autopilot mode.
Step 10: Handoff
How the skill exits depends on how it was invoked:
Invoked as a sub-skill (from planning, qa, verifying):
Return control to the parent skill with a structured summary: { script_path, status: "pass"|"fail"|"unrun", log_path?, doc_files_edited: [...] }.
Invoked directly (/script-builder):
Use AskUserQuestion: Run it with /qa | Run /verify-plan | Commit the script and doc changes | Done.
- Run with /qa → invoke
desplega:qa with the script path as the source.
- Run /verify-plan → invoke
desplega:verifying if a plan path is in current context.
- Commit → propose a commit message (
feat(scripts): add scripts/<name> for <area> validation) and stage only the generated script + the CLAUDE.md/AGENTS.md edits. Do not auto-commit; show the proposed git add and git commit commands and require user confirmation.
- Done → print the script path + log path (if escalation ran) and exit.
Abort path (Stop selected at any earlier gate): leave the generated script in place, leave any CLAUDE.md/AGENTS.md edit in place if Step 7 ran, and print a one-liner: Aborted. Script: <path>. Doc edits: <files or "none">. Re-run /script-builder or git checkout to discard. Do not git restore on the user's behalf.
Learning Capture
OPTIONAL SUB-SKILL: If significant insights, patterns, gotchas, or decisions emerged during this workflow, consider using desplega:learning to capture them via /learning capture. Focus on learnings that would help someone else in a future session.