feat(skills): add /work-decompose and /work-dispatch orchestration skills

In-session skill counterparts to `pnpm work decompose --execute` and
`pnpm work dispatch --execute`. They dispatch sub-agents — a decomposer
for /work-decompose; separate worktree-isolated implementer and
read-only reviewer sub-agents for /work-dispatch's implement-review loop.

Single source of truth: the skills read `.sandcastle/*.prompt.md` at
dispatch time and never copy them — the prompt files stay authoritative
for both Sandcastle and the skills, so the role definitions cannot
drift. `.sandcastle/` and `pnpm work` are untouched; this is additive.
This commit is contained in:
2026-05-22 09:17:46 +02:00
parent f3182537c1
commit c33e0b2669
2 changed files with 126 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
---
name: work-decompose
description: Use when an approved PRD must be broken into an epic with story and task files under docs/work/. Triggers — the user asks to decompose a PRD, invokes /work-decompose, or wants a PRD turned into the work tree.
---
# work-decompose
Decompose an `approved` PRD into an epic + story files under `docs/work/epics/`, by dispatching a **decomposer sub-agent** whose role is defined by the existing Sandcastle prompt.
This is the in-session, skill form of `pnpm work decompose --execute`. It adds a path; it changes nothing about `.sandcastle/` or `pnpm work`.
## Single source of truth — do not copy the prompt
The decomposer's role is defined in **`.sandcastle/decomposer.prompt.md`** — the same file `pnpm work decompose --execute` consumes. This skill **reads that file at dispatch time and passes it verbatim**. Never paraphrase, summarise, or inline it here. If this skill and the prompt ever disagree, **the prompt wins** — fix this skill, not the prompt.
## Process
1. **Resolve the PRD.** The user names a PRD (slug or path); otherwise list `docs/work/prds/*.prd.md` and ask which. Read the file.
2. **Refuse drafts.** If the PRD's frontmatter `status:` is not `approved`, **stop** — tell the user to flip it after review. The decomposer refuses drafts; catch it early.
3. **Build the prompt.** Read `.sandcastle/decomposer.prompt.md`. Substitute its `{{PRD_FILE_CONTENT}}` placeholder with the full PRD file contents.
4. **Dispatch the decomposer sub-agent.** Use the Agent tool, `general-purpose`. Its instructions are the substituted prompt, followed by this environment-adaptation note (the note adapts the environment — it is not a prompt edit):
> **Environment:** you are a Claude Code sub-agent, not running inside Sandcastle. Ignore the `<promise>COMPLETE</promise>` marker instruction — there is no iteration loop; just return your final summary. Write the epic + story files to `docs/work/epics/`. **Do not commit** — leave the files for the human to review and commit, per your own "offer them a chance to review + edit" step.
5. **Report.** Relay the epic folder path the sub-agent created. Remind the user to review/edit the stories, then commit, and that `pnpm work rebuild-state` (or the pre-commit hook) refreshes `_state.json`. The next step in the pipeline is `/work-dispatch`.
## Why a sub-agent
Decomposition is a self-contained, read-heavy job — the whole PRD, the slice-rule reasoning, the file-writing. Running it in a sub-agent keeps all of that out of the main session; you get back only the epic path.
## Quick reference
| | |
| ----------------- | ------------------------------------------------------------------ |
| Input | an `approved` PRD in `docs/work/prds/` |
| Role prompt | `.sandcastle/decomposer.prompt.md` — read, never copied |
| Sub-agent | one `general-purpose` agent |
| Output | `docs/work/epics/<epic-id>/``_epic.md` + `NN-<story>/_story.md` |
| Upstream | `to-prd` / `grill-with-docs` produce the PRD |
| Downstream | `/work-dispatch` runs the tasks |
| Sandcastle parity | mirrors `pnpm work decompose --execute` |
## Common mistakes
- **Copying the prompt into this skill.** `.sandcastle/decomposer.prompt.md` is the source of truth — read it at dispatch time, every time.
- **Decomposing a `draft` PRD.** Check `status: approved` first.
- **Letting the sub-agent commit.** It writes files; the human reviews and commits.

View File

@@ -0,0 +1,76 @@
---
name: work-dispatch
description: Use when a task in the docs/work/ tree should be implemented and reviewed. Triggers — the user asks to dispatch a task, run the next task, run the implement-review loop, or invokes /work-dispatch.
---
# work-dispatch
Run one work-tree task through the **implement → review loop**, using two separate sub-agents whose roles are defined by the existing Sandcastle prompts.
This is the in-session, skill form of `pnpm work dispatch --execute`. It adds a path; it changes nothing about `.sandcastle/` or `pnpm work`.
## Single source of truth — do not copy the prompts
Two role definitions, two files, read at dispatch time — **never copied**:
- implementer role → **`.sandcastle/implementer.prompt.md`**
- reviewer role → **`.sandcastle/reviewer.prompt.md`**
`pnpm work dispatch --execute` (Sandcastle) and this skill consume the **same** files. Never paraphrase or inline them. If a prompt and this skill disagree, **the prompt wins**. This skill is only the _wiring_: pick the task, substitute placeholders, dispatch the sub-agents, run the loop.
## Separate sub-agents — non-negotiable
The implementer and the reviewer are **distinct sub-agents**, dispatched separately:
- The **implementer** writes code. Dispatch a `general-purpose` Agent with `isolation: "worktree"` — it works on an isolated git worktree + branch, so bad output never touches your main tree.
- The **reviewer** must be a _different_ agent and must _not_ write. Dispatch an `Explore` Agent (read-only by construction) — it cannot Edit/Write the repo even by accident; it only verifies the diff.
Never collapse the two into one agent. An agent that writes code and then grades its own work has not been reviewed.
## Process
1. **Resolve the task.** Default: run `pnpm work next`, then read the first unchecked task file of that story (`docs/work/epics/<epic>/<story>/NN-<slug>.task.md`). Or the user names a task id. Read the task file; note its `max-attempts` frontmatter (default 3).
2. **Dispatch the implementer.** Read `.sandcastle/implementer.prompt.md`, substitute `{{TASK_FILE_CONTENT}}` with the task file. Dispatch a `general-purpose` Agent with `isolation: "worktree"`; instructions = the substituted prompt + this environment-adaptation note:
> **Environment:** a Claude Code sub-agent in a fresh, isolated git worktree — NOT a Sandcastle Docker sandbox. Run `pnpm install` as your first step (the worktree has no `node_modules`). Commit your slice on a branch. **Report that branch name** in the `notes` field of your output JSON. Ignore the `<promise>COMPLETE</promise>` marker — there is no iteration loop; just return the structured JSON as your final message.
Read the returned JSON: `status`, `commit_sha`, `files_changed`, `notes` (which carries the branch name).
3. **Handle the implementer's status.** `complete` → step 4. `blocked` or `needs-clarification` → surface the `notes` to the user and stop; do not proceed to review.
4. **Compute the diff.** `git diff main...<task-branch>` from the main tree — git worktrees share `.git`, so the implementer's branch is visible.
5. **Dispatch the reviewer.** Read `.sandcastle/reviewer.prompt.md`, substitute `{{TASK_FILE_CONTENT}}` and `{{DIFF}}`. Dispatch an `Explore` Agent (read-only); instructions = the substituted prompt + this environment-adaptation note:
> **Environment:** a Claude Code sub-agent reviewing a LOCAL branch — there is no pull request or CI run yet. Where the prompt says to trust Sandcastle's CI step: the implementer has already run and reported the five conformance gates + coverage as its commit precondition — verify AC coverage, out-of-scope discipline, and slice discipline by **reading the diff**, as your checks describe. Run the library-trace check directly (`node scripts/library-decisions/check.mjs`). The Socket and CodeQL steps require a CI run — note them as "deferred to CI", do not block on them. Ignore the `<promise>COMPLETE</promise>` marker; return the JSON decision.
Read the returned JSON: `decision`, `scope_violations`, `notes`.
6. **Run the loop.**
- **`approve`** → merge `<task-branch>` into `main`; remove the worktree. Then run `pnpm typecheck && pnpm lint && pnpm test && pnpm conformance` once on `main` as a post-merge safety check. Print the suggested task-checkbox / `_state.json` mutation for the user to apply — **do not write state yourself.**
- **`reject`** → re-dispatch the implementer (step 2) with the reviewer's `notes` appended to its instructions. Repeat until `approve`, or until the task's `max-attempts` is reached — then stop and surface the last reviewer notes.
## Why this shape
State mutation stays manual (the skill suggests, the human applies) — exactly as Sandcastle's v1 orchestrator does, and consistent with the implementer prompt's "the orchestrator handles state writes." Worktree isolation gives the implementer a clean room without Docker. The read-only reviewer makes "the reviewer does not modify the repo" a property of the tool, not a promise.
## Quick reference
| | |
| ----------------- | ------------------------------------------------------------------------------------- |
| Implementer | `.sandcastle/implementer.prompt.md` · `general-purpose` · `isolation: "worktree"` |
| Reviewer | `.sandcastle/reviewer.prompt.md` · `Explore` (read-only) |
| Placeholders | implementer: `{{TASK_FILE_CONTENT}}` · reviewer: `{{TASK_FILE_CONTENT}}` + `{{DIFF}}` |
| Loop cap | the task's `max-attempts` frontmatter (default 3) |
| State writes | suggested to the user, never applied by the skill |
| Upstream | `/work-decompose` produces the tasks |
| Sandcastle parity | mirrors `pnpm work dispatch --execute` |
## Common mistakes
- **Copying a prompt into this skill.** The `.sandcastle/*.prompt.md` files are the source of truth — read them at dispatch time.
- **One agent for both roles.** Implementer and reviewer are separate sub-agents; the reviewer is `Explore` (read-only).
- **Writing `_state.json` or ticking the checkbox.** The skill prints the suggested mutation; the human applies it.
- **Skipping the environment-adaptation note.** Without it the sub-agent follows Sandcastle-only instructions — the `<promise>` marker, "trust CI" — that do not apply in-session.
- **Reviewing before the implementer says `complete`.** A `blocked` status stops the loop; do not review a partial slice.