NOTE: This is the canonical provider-independent contract. Both provider wrappers must load it in full.
- Resolve supporting assets and relative script paths against
.claude/skills/goblin-do/; the canonical prose lives here while implementation assets remain with the compatibility launcher. - A named skill call means the host's skill-dispatch primitive. An Agent, Explore agent, workflow, or sub-agent means an isolated task/action invocation with fresh context and the requested capability tier. Provider wrappers map these roles to their native APIs.
- Model tier names in inherited procedures describe capability roles. Resolve them through
config/model-tier-map.json; an unavailable required capability returnsrequired_tool_missingrather than weakening a gate. - Never expose hidden chain-of-thought. Preserve only decisions, evidence, commands, structured artifacts, and operator-facing rationale required by this contract.
Take ONE chosen goblin atom (a suggestion produced by /goblin-suggest, or a UAT task
produced by /goblin-suggest --uat, persisted under brain/suggestions/) and act on it.
goblin do is the single front door: it resolves the atom, reads its kind + mode, and
mode-dispatches:
- a
smallsuggestion or a safeuattask → EXECUTED: goblin leases the existing workspace/build-steprail against the TARGET project inside agoblin/do/<slug>scratch branch, then either auto-ships a genuinely-clean result (fast-forwards it into the target's default branch) or parks anything borderline on the scratch branch with a one-linegoblin do --land <id>to ship later. - a
bigsuggestion or a not-safeuattask → HANDED OFF: prints the/plan-featureseed (or a UAT handoff block) + the build-rail next step. A big idea or a not-safe task needs real planning / human work — auto-executing it would be wrong, sogoblin dodeliberately stops at the handoff.
This collapses /goblin-handoff for the small path: where you previously ran
/goblin-handoff <id> then hand-built the result, the small/safe path is now one command.
The big/not-safe path still emits exactly the same seed /goblin-handoff did.
- A suggestion or UAT atom exists. Run
/goblin-suggest <project>(or/goblin-suggest <project> --uat) first; it persists atoms underbrain/suggestions/(sugg-<project>-<slug>.mdoruat-<project>-<slug>.md). Pick one. uv synchas been run in theb2_project_goblindirectory (installs thegoblinconsole script).- For EXECUTION (a
small/safe-uatatom, NOT--dry-run):CLAUDE_CODE_OAUTH_TOKENis set (subscription OAuth — NOT an API key). The session path runs/build-stepvia a Workflowagent()call, which stays on subscription.ghis authenticated (gh auth status). An issue-anchored atom re-checks the issue is still OPEN viaghbefore acting.- The target project is a clean git repo —
goblin dorefuses a dirty target working tree (it will not create a scratch branch on uncommitted changes).
- For a
--dry-runpreview, none of the execution prerequisites are needed (it touches nothing). For thebig/not-safe handoff path, no OAuth/gh/clean-tree is needed either — it only reads the atom and renders text.
Session path (recommended — runs /build-step via Workflow agent(), stays on subscription OAuth):
// From a Claude Code session:
Workflow({
scriptPath: "skills/goblin-do/goblin_do.workflow.js",
args: { id: "sugg-toybox-add-a-smoke-test" }
})Or via the /goblin-do skill shorthand when invoked interactively.
CLI fallback path (non-session / offline — shells to claude -p for the build-step dispatch):
From the b2_project_goblin directory:
uv run goblin do <id-or-text>The argument is a suggestion/UAT atom id (the filename stem, e.g.
sugg-toybox-add-a-smoke-test or uat-toybox-run-the-emulator-smoke) OR free text that
fuzzy-matches one atom. Examples:
uv run goblin do sugg-toybox-add-a-smoke-test
uv run goblin do "add a smoke test"
uv run goblin do sugg-void-furnace-tighten-plan-doc --brain-dir ./brainPreview the dispatch without creating a branch or running anything (CLI or session):
uv run goblin do sugg-toybox-add-a-smoke-test --dry-runShip a previously-parked item after you have glanced at its branch (CLI path):
uv run goblin do --land sugg-toybox-add-a-smoke-testFlags:
--dry-run— preview the build-step dispatch (the resolved target, thegoblin/do/<slug>scratch branch, and the EXACT/build-stepcommand that WOULD be dispatched) without creating a branch or dispatching anything.--land <id>— ship a previously-parked item via fast-forward (FF-only; never--force).--brain-dir <path>— override the atom store (default: the goblin repo'sbrain/).--land --dry-runis refused (there is no meaningful preview of a land — landing does a real fast-forward + a real target test gate; the refusal points you atgit log/git diffon the parked branch for an actual preview).
goblin do resolves the atom once across both kinds, then dispatches on kind + mode:
| Atom | Outcome |
|---|---|
Suggestion mode: small |
EXECUTE via /build-step (auto-ship-clean / park-rest) |
Suggestion mode: big |
HAND OFF — print the /plan-feature seed + build-rail next step |
| UatTask, safe subset | EXECUTE via /build-step (the executor re-applies the safe gate) |
| UatTask, not safe | HAND OFF — print the UAT handoff block + build-rail next step |
A UAT task is in the safe-auto-execute subset when it is ai_doable, has an empty
clarifying_question, and has no human_residual_steps (the uat analogue of a
suggestion's small mode). Anything else is handed off.
For a small suggestion or a safe uat task, goblin:
- Re-checks the anchor is still LIVE (an issue-ref anchor must be OPEN per
gh; a file-path anchor must still be tracked in the target). A stale anchor refuses loudly. - Resolves the target, refuses a dirty target tree, computes the default branch + its SHA.
- Creates the
goblin/do/<slug>scratch branch and leases/build-stepagainst the target inside a worktree/build-stepcreates (its developer agent does ALL source edits — goblin's own process never writes the target's source). - Decides auto-ship vs park against a four-part floor — it auto-ships ONLY when ALL of:
/build-stepreported a clean PASS (not BLOCKED), AND- the target's default-branch SHA is byte-unchanged (no stray merge to default happened during the build), AND
- the produced diff is confined to the anchored file(s) + its related test, AND
- the anchor is live and the text resolved to exactly one atom. Any miss → park (the scratch branch + its commits are kept; the default is untouched).
- On an auto-ship: fast-forwards the confined diff into the target's default branch and deletes the scratch branch.
- On a PASS, flips the originating atom
status: proposed → acceptedand appends agoblin-do:<branch>provenance entry — goblin's ONLY authored write, intobrain/only.
When a result parks (e.g. /build-step left a borderline change, or the diff sprawled
beyond the anchor), goblin prints the parked branch + a one-line goblin do --land <id>.
The flow is:
uv run goblin do <id>→ result PARKED ongoblin/do/<slug>.- Glance at the parked work:
git -C <target> log --oneline <default>..goblin/do/<slug> git -C <target> diff <default>..goblin/do/<slug>
- If it looks good, ship it (FF-only; re-runs the target's own test gate first):
uv run goblin do --land <id>
--land asserts a clean fast-forward (the default tip must be an ancestor of the branch
tip — a diverged branch is refused, never force-merged), re-runs the target's own tests as
the last guard, FF-merges, deletes the branch, and appends a goblin-land:<branch>
provenance entry.
--dry-run resolves the target + scratch branch and prints the EXACT /build-step command
that WOULD be dispatched — but creates no branch, dispatches nothing, and writes nothing to
brain/. Use it to confirm the target + command before spending the real build.
The CLI prints a labelled block keyed to the outcome (action):
dry-run— a PREVIEW:Project,Target, aMode: DRY RUN (preview only ...)line, theDefault branch, theAnchorstatus (<file> — live (tracked)or refused), theScratch branch (would be created), theWould dispatch /build-step:command, and theReason. Nothing was created or dispatched.shipped—/build-stepbuilt it and the result auto-shipped:Project,Target,Default branch (fast-forwarded),Scratch branch (built + merged, then deleted), theChanged files, theReason, and theAtomfield (<id> → status: accepted).parked(two sub-cases, same label):- Built but parked (failed a gate):
Project,Target,Default branch (unchanged),Scratch branch (kept — its commits survive),Changed files,Reason(names every gate that missed),Atom(<id> → status: accepted), and — prominently —PARKED — to ship it after a glance, run:with the one-linegoblin do --land <id>command. - Big or not-safe handoff (no build dispatched):
Project,Target, and — prominently —PARKED (handoff) — to plan this, paste this seed into /plan-feature:followed by the/plan-featureseed text and the build-rail next step (/plan-review → /repo-sync → /build-phase). No scratch branch; no atom status change.
- Built but parked (failed a gate):
landed— a previously-parked item shipped via--land:Project,Target,Default branch (fast-forwarded),Scratch branch (landed, then deleted), theChanged files, and theReason.
A bad/ambiguous/unknown id, a bad target, or a guardrail refusal (mode/safe gate, stale
anchor, dirty tree, branch slip) prints goblin do failed: <reason> to stderr and exits
nonzero.
Every EXECUTE runs a full /build-step (a worktree dependency rebuild + up to 3 build
iterations + 4 reviewers) via a Workflow agent() call (session path) or claude -p
subprocess (CLI fallback). Expect this to take minutes and real token cost per fix —
goblin do is not free the way /goblin-suggest reading-and-ranking is. Use --dry-run
first if you want to confirm the target + command before paying for the build. The session
path stays on subscription OAuth; the CLI fallback uses claude -p.
goblin do collapses /goblin-handoff: the small/safe path that previously needed a
separate handoff + hand-built result is now this one command, and the big/not-safe path
emits the exact same /plan-feature (or UAT handoff) seed /goblin-handoff did. The
goblin handoff CLI subcommand still works as a deprecated alias (it prints a
deprecation note to stderr, then the seed) so nothing breaks for an existing caller — but
prefer goblin do <id>.
This big/not-safe handoff is the codified template the routing web's re-route contract
was generalized from — the do→plan edge (the atom needs real planning), and likewise
do→bug when the thing being improved turns out to be broken (the correct-rail seed is
then a /user-debug --symptom line rather than a /plan-feature seed). The full
contract — the standard emit-line format and the write-back to the intake ledger when
one exists — is owned by
skill-pipeline.md § Re-route contract;
cite it there, never restate it.
For a handed-off big/not-safe atom, paste the emitted seed into /plan-feature, then run
the normal build rail: /plan-review → /repo-sync → /build-phase.