# Scripts Conventions — Per-Skill `scripts/` Directory

**Purpose**: Defines how generated skills bundle executable scripts (TypeScript via bun, or shell), how those scripts are invoked, and how Justfile recipes wrap them. Read in Stage 2 when `bundles_scripts = true`.

**Read this when**:
- Generating a Script-driven skill (canonical case)
- Generating any skill where the author indicated bundled executable scripts at Stage 1
- Authoring a Justfile recipe that wraps a per-skill script

---

## When a Skill Bundles Scripts

A generated skill ships a `scripts/` directory iff one of:

1. **Archetype = Script-driven** — canonical. The skill's primary value is bundled-script execution.
2. **Author indicated bundled scripts at Stage 1** — non-Script-driven archetype where the user's interview answers signal executable scripts (e.g., a Pipeline that orchestrates a TS validator like create-skill itself with `scripts/run-loop.ts` + `scripts/grade.ts`; a Reviewer that bundles a static-analysis script).

**Default behavior for non-Script-driven archetypes**: do NOT scaffold `scripts/`. The author must explicitly opt in via the Stage 1 follow-up. This avoids cluttering simple skills (Generator, Tool Wrapper, Reviewer, Inversion, Research) with empty directories.

---

## What Gets Scaffolded

When `bundles_scripts = true`, Stage 2 emits:

| File | Purpose |
|------|---------|
| `scripts/.gitkeep` | Empty file — preserves the directory in git for skills that ship scripts but author hasn't yet written them |

That is the **entire T-021 scaffold scope**. Concrete script files are author-supplied — `create-skill` does not generate executable code (no archetype-generic starter would be useful enough to outweigh the noise of generated TODOs). Authors write their scripts under `scripts/` post-scaffold.

**Distinction**: Existing skills (e.g., `create-skill/scripts/run-loop.ts` + `grade.ts` from T-019; `bulwark-scaffold/scripts/`) contain concrete scripts authored manually, NOT generated by `create-skill`. Those are starter PATTERNS for new authors to mimic, not outputs of the scaffolder. The T-021 scaffolder produces only `.gitkeep`; everything else is author-supplied post-scaffold.

If the author wants a starter pattern, they reference an existing skill's `scripts/` (e.g., `create-skill/scripts/run-loop.ts` for a TS-via-bun pattern; `bulwark-scaffold/scripts/` for shell-based scaffolding).

---

## Invocation Convention — `${CLAUDE_PLUGIN_ROOT}`

Per-skill scripts are referenced via the canonical Claude Code plugin root variable:

```
${CLAUDE_PLUGIN_ROOT}/skills/<skill-name>/scripts/<script-file>
```

Use this in:
- **Justfile recipes** that wrap the script
- **SKILL.md prose** that documents how to invoke the script
- **hooks/hooks.json** entries that reference the script
- **Sub-agent prompts** that instruct invocation

**Do NOT use** (latent bug source — see `MEMORY.md` Session 103 entry):
- `$CLAUDE_PLUGIN_DIR` — does NOT exist in Claude Code
- Absolute paths — break across user installs and dogfood/public asset drift
- Repo-relative paths like `skills/<name>/scripts/...` — may resolve correctly in dev but fail in plugin-installed deployments

**Why**: `${CLAUDE_PLUGIN_ROOT}` is the single canonical path variable Claude Code resolves at runtime. It works in dev (`--plugin-dir` mode) and in plugin-installed deployments (post `claude /plugin install`).

---

## Justfile Recipe Pattern

Per-skill scripts get wrapped in Justfile recipes for ergonomic invocation. Recipe naming convention: `{skill-name}-{action}` for skill-specific recipes; reserve `eval-skill`, `eval-grade` etc. for cross-skill orchestration recipes.

### Pattern: skill-specific recipe

Add to the project root `Justfile`:

```just
# Run the {skill-name} skill's <action> script
{skill-name}-{action} *args:
    bun "${CLAUDE_PLUGIN_ROOT}/skills/{skill-name}/scripts/{script-name}.ts" {{args}}
```

Example (from `create-skill` itself):

```just
# Run the eval loop on a target skill (T-019 Layer 2)
eval-skill skill_path:
    bun "${CLAUDE_PLUGIN_ROOT}/skills/create-skill/scripts/run-loop.ts" "{{skill_path}}"
```

### Pattern: shell script recipe

```just
{skill-name}-{action} *args:
    bash "${CLAUDE_PLUGIN_ROOT}/skills/{skill-name}/scripts/{script-name}.sh" {{args}}
```

### Quoting rule (SEC-008 from T-019 Code Review)

Always quote `${CLAUDE_PLUGIN_ROOT}` and any user-supplied parameters in recipes. Unquoted paths break on spaces; unquoted parameters open command-injection vectors when the recipe is invoked from automation.

---

## Runtime Prerequisite — `bun`

TypeScript scripts run via `bun` (not `tsc + node`, not `tsx`, not `ts-node`). Per memo D2: bun is the locked Bulwark TS runtime — fast cold start, single dependency, native TypeScript.

**End-user gap**: bun is NOT yet installed by Bulwark's scaffold/init flow. Users without bun on PATH will fail when invoking `just <recipe>` for any TS-via-bun script. Formal installer scoped as **P10.11** (`plans/task-briefs/P10.11-bun-runtime-installer.md` — mirrors the P10.1 just-installer idiom).

Until P10.11 ships, generated skills with `scripts/*.ts` MUST document bun as a prerequisite in their SKILL.md (mirror the `Runtime Prerequisites` section pattern from `create-skill/SKILL.md`).

Shell scripts (`*.sh`) have no equivalent runtime gap — bash is universal.

---

## Permissions Setup

Generated skills with executable scripts MUST document required tool permissions for whoever runs the recipe (the user or an orchestrating skill):

```yaml
# .claude/settings.json or settings.local.json
permissions:
  allow:
    - "Bash(bun:*)"        # if scripts/*.ts
    - "Bash(bash:*)"       # if scripts/*.sh
    - "Bash(just:*)"       # if invoked via Justfile recipe
```

This belongs in the SKILL.md "Permissions Setup" section — same pattern as bundled agents (see `agents/skill-eval-grader.md` and `agents/skill-eval-comparator.md` for reference).

---

## Sandbox Considerations

Per `feedback_sandbox_escalation.md`: Bash invocations under `/tmp/`, `/dev/`, or non-allowlisted writable paths trigger sandbox blocks. When a generated script writes outside the project root (e.g., `/tmp/<foo>/output.json`), the running orchestrator must escalate via `dangerouslyDisableSandbox: true`.

Best practice for new scripts:
- Write outputs under the project root (`logs/`, `tmp/`, or per-skill `evals/runs/`) — sandbox-friendly.
- If `/tmp/` is unavoidable (large transient artifacts, OS-specific scratch), document the sandbox-escalation expectation in the SKILL.md.

---

## Cross-References

- `references/eval-shape.md` — Layer 1 schema (run-loop.ts + grade.ts read evals.json/triggers.json/compliance.json)
- `references/eval-scaffolding.md` — Layer 1 emission guide (T-020)
- `plans/task-briefs/P10.11-bun-runtime-installer.md` — formal bun installer (will close end-user gap)
- `MEMORY.md` Session 103 — `$CLAUDE_PLUGIN_DIR` does NOT exist; `${CLAUDE_PLUGIN_ROOT}` is canonical
- Memo D2 (`docs/internal/p10.2-part-b-scope-decision.md`) — TypeScript-via-bun runtime lock
