SKILL: pipeline-runner
SkillAI & modelsUniversal dispatcher: run a named taffy workflow (.claude/taffy/<name>.yaml), a skill (/skill-name), or a sub-agent (@agent-name). Falls back to skills.sh registry when a skill is not found locally. Tracks all runs in .claude/state/maple.json so the maple TUI shows live progress.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the SKILL: pipeline-runner skill
What this skill tells your AI
The instructions your AI receives, as published by kinncj/heimdall in .claude/skills/pipeline-runner/SKILL.md and read by ahel’s review.
What It Does
Dispatches any named workflow, skill, or agent from a single entry point. Resolution order:
- Taffy workflow — look for
.claude/taffy/<name>.yaml; if found, execute each stage in order - Skill (local) — look for
.claude/skills/<name>/; if found, invoke the skill - Agent — look for
.claude/agents/<name>.md; if found, delegate to@<name> - Skill (registry) — if not found locally, try to install from skills.sh, then retry step 2
Pipeline state is written to .claude/state/maple.json at every transition.
Usage
/pipeline-runner <name>
Examples:
/pipeline-runner new-ui-feature
/pipeline-runner api-endpoint
/pipeline-runner implement-stories
/pipeline-runner tdd-workflow
/pipeline-runner orchestrator
List available taffy workflows:
ls .claude/taffy/*.yaml | grep -v schema
List available skills:
ls .claude/skills/
Dispatch Protocol
Step 1: Resolve the target
# Check taffy first
[ -f ".claude/taffy/<name>.yaml" ] && dispatch=taffy
# Then local skill
[ -d ".claude/skills/<name>" ] && dispatch=skill
# Then agent
[ -f ".claude/agents/<name>.md" ] && dispatch=agent
# Fallback: fetch from skills.sh registry, then retry
if [ -z "$dispatch" ] && command -v npx &>/dev/null; then
echo "pipeline-runner: '<name>' not found locally — checking skills.sh…"
npx --yes skills add kinncj/maple@<name> -a claude-code -y 2>/dev/null \
|| npx --yes skills add <name> -a claude-code -y 2>/dev/null \
|| true
[ -d ".claude/skills/<name>" ] && dispatch=skill
fi
If nothing matches after the registry fallback, report:
pipeline-runner: no taffy workflow, skill, or agent named '<name>' (also checked skills.sh registry)
Step 2: Initialise state
Write to .claude/state/maple.json (merge — do not overwrite unowned fields):
{
"taffy": "<name>",
"stage": "<first-stage or skill-name>",
"status": "RUNNING",
"awaiting_approval": null,
"started_at": "<iso8601>",
"updated_at": "<iso8601>"
}
Step 2b: Runtime policy enforcement (mandatory)
Before any stage execution, read and enforce:
- Harness-specific root markdown:
- Claude harness →
CLAUDE.md - OpenCode harness →
OPENCODE.md - Cursor harness →
CURSOR.md - Copilot harness →
COPILOT.md
- Claude harness →
AGENTS.md.github/copilot-instructions.md.github/instructions/stories.instructions.md(when touching story files)
When the launch prompt contains a <maple-gherkin-handoff> block:
- Treat it as hard scope: implement only listed story paths / IDs.
- Do not run Spec-Kit or regenerate stories.
- Preserve the repository's current Cucumber stack:
- If generated stories include
cucumber/*_steps.py, use Python behave-style steps. - Do not introduce TypeScript
@cucumber/cucumberunless the repository already uses it as the active standard.
- If generated stories include
- Keep BusinessRepo structure and phase gates exactly as defined by instruction files.
- Treat test layout as mandatory:
- Gherkin feature files must live under
/tests/features/. - Step definitions must use the repository's active Cucumber stack and live under
/tests. - Do not place acceptance tests under
/appor story directories.
- Gherkin feature files must live under
- Enforce module boundaries independent of language:
- Runtime/source files must not import from
docs/,.github/, or.claude/. - Copying/adapting approved artifact content into app/test source is allowed.
- Design/spec artifacts are references, not runtime code dependencies.
- Runtime/source files must not import from
Step 3a: Taffy workflow execution
Load .claude/taffy/<name>.yaml, parse stages:, resolve depends_on order.
If the workflow defines an orchestrator-kickoff stage, execute it first before all other stages. This stage must publish the initial plan and heartbeat cadence.
For each stage:
when: guard:
when: ui:true— skip if story hasui: false, unless.claude/state/maple.jsonhasforce_ui: truewithlaunch_source: maple-x(MAPLE[x]quick-launch override).when: ui:false— skip if story hasui: truewhen: always— always run- UI-related scope includes web, mobile, desktop, and TUI stories. Any such run must include design-review human-approval stages before implementation phases.
depends_on: all listed stages must be DONE before this one starts.
Dispatch:
agent: <name>→ delegate to@<name>with current story contextskill: <name>→ invoke the skillpipeline: standard→ run the full 8-phase orchestrator pipeline
After each stage: update maple.json with current stage + RUNNING.
Progress heartbeats (mandatory):
- Send an immediate kickoff status before the first long-running tool/agent call.
- While a taffy run is active, send a concise progress update at least every 60-120 seconds.
- On each heartbeat, refresh
maple.jsonupdated_atand currentstage. - Every heartbeat must include concrete progress evidence:
- changed files/artifacts since last update (explicit paths), or
- a specific blocker that prevented changes.
- Use this status format:
- Progress:
<stage / phase> - Done since last update:
<brief> - Current action:
<brief> - Blockers:
<none or blocker> - Next update:
<ETA>
- Progress:
- Do not send heartbeat-only timestamp churn with no artifact/blocker details.
- If a stage requires writing artifacts and write access/tools are unavailable, set
maple.jsontoFAILEDwith an explicit error and stop. - If blocked/waiting, report what is pending and continue heartbeats until unblocked.
Completion artifact gate (mandatory):
- Before marking
DONE, verify the run produced concrete story-linked artifacts under the BusinessRepo layout. - Required for implementation runs:
- application changes in
/app(or existing domain folders), - tests in
/tests(unit/integration/e2e as applicable), - Gherkin assets in
/tests/featuresplus matching step implementations.
- application changes in
- Boundary check:
- fail the run if generated runtime code imports paths under
docs/,.github/, or.claude/.
- fail the run if generated runtime code imports paths under
- If required test/gherkin artifacts are missing, set
maple.jsontoFAILEDand report missing paths explicitly.
Step 3b: Skill invocation
Invoke the skill directly. Update maple.json on start and completion.
Step 3c: Agent delegation
Delegate to @<name>. Update maple.json on start and completion.
Step 4: Human-approval gates (taffy only)
When a stage has gate: human-approval:
- Complete stage work (produce artifact).
- For design review stages (
wireframe,visual-identity,design-tokens,ui-mockup-builder,design-refresh), artifact production is mandatory:- create at least one previewable artifact (
.excalidraw,.html,.svg,.png,.jpg,.jpeg,.webp, or.md) under docs/design (or approved artifact dirs), and - the required formats depend on
design.target(project.config.yaml, defaultweb): web =.md+.html+.excalidraw; tui =.md+.excalidraw(no.html). A run that produces fewer than the required formats is incomplete, and - update
.claude/state/design-artifacts.jsonwith current stage artifact paths so the review portal can update live.
- create at least one previewable artifact (
- Path + completeness gate for
wireframestage (mandatory before PAUSING):
If the gate fails, produce the missing files before re-running the gate check.# Verify wireframes are in the canonical location and the required formats exist. # Required formats depend on design.target (web: md+html+excalidraw; tui: md+excalidraw). TARGET=$(sed -n 's/^[[:space:]]*target:[[:space:]]*\([a-z]*\).*/\1/p' project.config.yaml 2>/dev/null | head -1) [ -z "$TARGET" ] && TARGET=web CANONICAL=$(find docs/design/wireframes -name "*.wireframe.*" 2>/dev/null | wc -l | tr -d ' ') MISPLACED=$(find docs -name "*.wireframe.*" -not -path "*/docs/design/wireframes/*" 2>/dev/null) MISSING_HTML="" if [ "$TARGET" != "tui" ]; then MISSING_HTML=$(find docs/design/wireframes -name "*.wireframe.md" 2>/dev/null | while read md; do base="${md%.wireframe.md}" [ ! -f "${base}.wireframe.html" ] && echo "${base}.wireframe.html" done) fi MISSING_EXCALIDRAW=$(find docs/design/wireframes -name "*.wireframe.md" 2>/dev/null | while read md; do base="${md%.wireframe.md}" [ ! -f "${base}.wireframe.excalidraw" ] && echo "${base}.wireframe.excalidraw" done) if [ "$CANONICAL" -eq 0 ]; then if [ -n "$MISPLACED" ]; then echo "PIPELINE GATE FAILED: wireframes found at wrong path(s): $MISPLACED" echo "Canonical path is docs/design/wireframes/ — move files there before this stage can complete." else echo "PIPELINE GATE FAILED: no wireframe artifacts in docs/design/wireframes/" fi # set maple.json FAILED and stop fi if [ -n "$MISSING_HTML" ]; then echo "PIPELINE GATE FAILED: missing .wireframe.html for: $MISSING_HTML — generate it now." # set maple.json FAILED and stop fi if [ -n "$MISSING_EXCALIDRAW" ]; then echo "PIPELINE GATE FAILED: missing .wireframe.excalidraw for: $MISSING_EXCALIDRAW — generate it now." # set maple.json FAILED and stop fi - If no reviewable artifact exists for a design gate, set
maple.jsontoFAILEDand stop.
- For design review stages (
- Write PAUSED state:
{ "stage": "<name>", "status": "PAUSED", "awaiting_approval": "<name>", "updated_at": "<iso8601>" }
- Write stage name to
.claude/state/approval-pending.txt. - Output:
TAFFY PAUSED — awaiting human approval
Stage: <stage-name>
Artifact: <artifact path or description>
Approve via the maple TUI ([P] pipeline → [a] approve) or reply "approved" / "continue".
I will not advance to the next stage until approval is confirmed.
- Poll:
timeout 540 bash -c 'until [ ! -f .claude/state/approval-pending.txt ]; do sleep 2; done'- On timeout (exit 124), re-run the same poll. The Bash tool caps at 10 min per call; re-polling across calls lets approval delays exceed that bound.
- Also accept an explicit "approved" / "continue" reply in chat.
- When the user approves via the maple TUI ([P] → [a]), the TUI deletes the pending file and sends a "continue" keystroke to the agent's pane via the active multiplexer (outer tmux/zellij, or a detached
tmux new-sessionwrapper). Either signal is sufficient to resume.
- On resume: update to
RUNNING, advance to next stage. - While paused, monitor
.claude/state/design-feedback.json:status: requested_changesorstatus: rejectedmeans apply the requested updates before advancing.- Treat
attachmentsas required review inputs (uploaded files such as.excalidraw, images, HTML, text), typically underdocs/design/review-input/. - Summarize how each feedback item and attachment was addressed before continuing.
Step 5: Completion
{ "taffy": "<name>", "stage": "DONE", "status": "DONE", "awaiting_approval": null, "updated_at": "<iso8601>" }
Output:
TAFFY COMPLETE — <name>
Stages run: N
Duration: <elapsed>
Failure Handling
After 3 consecutive failures on any stage:
{ "stage": "<name>", "status": "FAILED", "error": "<summary>", "updated_at": "<iso8601>" }
Stop. Report failed stage and error to human. Do not proceed.
Rate-limit Handling
When a usage limit, rate limit, HTTP 429, or "resets at " message stops you continuing, before stopping:
- Merge-write
.claude/state/maple.json(keeptaffy/stage):
{ "status": "RATE_LIMITED", "resume_at": "<iso8601 reset time, else now+1h>", "rate_limit_reason": "<one line>", "updated_at": "<iso8601>" }
- Write a one-line breadcrumb to
.claude/state/rate-limit.txt:<resume_at>|<reason>. One cheap write — do it even if nothing else is possible. - Stop. Do not hammer the API. MAPLE resumes the pipeline when the window clears (manual
[r], or auto when armed).
Session Context
On startup, read .claude/state/sessions.json if it exists:
{ "claude": "<uuid>", "opencode": "<id>", "copilot": "<id>" }
Use the matching session ID when resuming work within an existing agent session.
State File Reference
All state in .claude/state/. TUI and skill share these files.
.claude/state/maple.json
| Field | Owner | Values |
|---|---|---|
taffy | skill | workflow/skill/agent name |
stage | skill | current stage name |
status | skill | RUNNING, PAUSED, DONE, FAILED |
awaiting_approval | skill | local MAPLE stage name (e.g., spec-kit = Specification Knowledge & Integration Toolkit) or null — not an external package reference |
pipeline | skill | standard if running 8-phase |
started_at | skill | ISO 8601 |
updated_at | skill | ISO 8601 |
resume_at | skill/TUI | ISO 8601 — when the rate-limit window clears |
rate_limit_reason | skill/TUI | one-line cause |
harness | TUI | which harness ran this pipeline (for resume) |
auto_resume | TUI | true when auto-resume is armed |
state | TUI | running or exited |
ts | TUI | ISO 8601 |
Merge-not-overwrite: read existing file, update only owned fields, re-write.
.claude/state/approval-pending.txt
Skill writes stage name. TUI deletes when user presses [a].
.claude/state/rate-limit.txt
Skill/agent writes <resume_at>|<reason>. TUI reads, promotes maple.json to RATE_LIMITED, and deletes the file.
.claude/state/sessions.json
TUI writes harness→session-ID map. Skill reads for session resume.
Skip Conditions
spike/*andchore/*branches: skip Spec-Kit stages, run implementation stages.- Stage
when: ui:trueon aui: falsestory: skip silently, log[pipeline-runner] SKIP stage=<name> reason=ui:falseunless quick-launch override is active (force_ui=true,launch_source=maple-x).
Signals
- GitHub stars
- 66
- Forks
- 4
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
pipeline-runner- Source
- github.com/kinncj/heimdall