run
Skill2 starsChanged 34 days agoMITView on GitHub
Run the craft feature-delivery workflow on a backlog id, a spec/PRD file, or a free-text feature description. Triggers - "apply the workflow", "use my default workflow", "craft this".
48 other projects use a version of this.
Adopted by
Most recently taken up by
- XORwell/archeflowtheir own version, first seen Nov 15, 2026
- zecod/claude-queuetheir own version, first seen Nov 15, 2026
- stunt-double/stuntdouble-mcptheir own version, first seen Nov 8, 2026
- chryzxc/cpipetheir own version, first seen Nov 8, 2026
- jorgenschaefer/skillstheir own version, first seen Nov 1, 2026
- ccomkhj/product-demotheir own version, first seen Nov 1, 2026
Used with
Projects with it also use report, doctor and 5 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
MicrosoftSkillreport
Turn an exploration (threads, findings, charts) into a single Markdown report — note, blog post, executive summary, KPI dashboard, slide brief, or multi-section analytical report, with embedded charts.
Also in 3 projects that use this
MicrosoftSkilldoctor
Diagnose an Amplifier CLI environment through the CLI doctor agent. USE WHEN a user requests CLI runtime, settings or missing-capability inspection. DO NOT USE for project bugs or other hosts.
Also in 3 projects that use this
BlockSkillplan
Draft a TDD, PRD, or lightweight plan in the conversation without committing it.
Also in 4 projects that use this
VercelSkillsetup
Set up Vercel CLI and project configuration. Use when the user says "set up Vercel", "configure Vercel", "link to Vercel", or "vercel init".
Also in 5 projects that use this
AnthropicSkillstatus
Case status summary by audience — client-facing (plain language), internal (for the professor), or court-ready (formal caption format per local rules). Same facts, different framing and depth. Use when a student needs to update the client, brief the professor, or prepare a court status report.
Also in 3 projects that use this
Hugging FaceSkillimplement
Make tests pass. Invoke after /write-tests produces failing tests.
Also in 4 projects that use this
Other versions48 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 48
- perber/leafwiki★ 1.2ksame name
- NVIDIA/nvcf★ 261same name
- jjanczur/tyran★ 81same name
- Decentralized-LM/delm-agent-plugin★ 47same name
- microsoft/Employee-Self-Service-Agent-Developer-Kit★ 29same name
- phnx-labs/agi-cli★ 26same name
- doem97/cvsm★ 23same name
- JinukMoon/oh-my-mlip★ 20same name
- Hassaan146/claude-skills★ 18same name
- Aloim/phaneslightplugin★ 5same name
38 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository20 other files it gives its agents.
- architectureSkill
- decisionsSkill
- designSkill
- documentationSkill
- impact-triageSkill
- implementationSkill
- initSkill
- integrateSkill
- lessonsSkill
- metricsSkill
- planningSkill
- promote-configSkill
- proposeSkill
- pruneSkill
- refactoringSkill
- requirementsSkill
- reviewSkill
- tuneSkill
- validationSkill
- workspaceSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
31 rules. 0 are common, 8 are distinctive, and 23 are very specific to this project.
0
Common
Most of the 44 edited versions we read keep these.
8
Distinctive
This author's own choices. Worth a look.
23
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 56
When --config is absent: use .claude/workflow.md as <manifest-path>.
- Very specific: line 58
Run "${CRAFT_ROOT:-${CLAUDE_PLUGIN_ROOT}}/scripts/manifest-lint.sh" <manifest-path> (passing the resolved path from step 0b).
- Distinctive: line 60
Read the manifest (frontmatter = config, body = policy rationale).
- Distinctive: line 77
STOP; surface stderr to the user.
- Very specific: line 78
If effective[] is empty: STOP.
- Very specific: line 123
Use waivers[] for the gate-release decision (each proposeGateReleased: true releases that phase's propose-gate).
- Very specific: line 163
Never silence open's stdout: the run-key it prints into this session's transcript is what binds the compaction hooks to this run.
- Very specific: line 181
Never run close without the user choosing option 2.
- Distinctive: line 184
Flush-per-line: Every ledger line is appended in the tool call that produces it or in the orchestrator's very next tool call — never held …
- Very specific: line 203
Every ledger line opens with a fixed, greppable token.
- Very specific: line 227
Necessity probe (auto-skip): If phase.autoSkipEligible is true, evaluate against the live change whether the phase has any work *before …
- Very specific: line 242
After an implementation no-op (the run record holds NO-OP(implementation):), the review, refactoring, validation and architecture signals …
- Very specific: line 248
When provably empty: (a) append the fixed token auto-skip: <phase> — evaluated unnecessary (<signal>) to the run record (e.g.
- Very specific: line 266
If the skill or plugin a procedure names is not installed (no skills/<id>/ dir for a craft-native procedure.
- Very specific: line 311
Memory hint (advisory): Before assembling the block, slice the in-session MemoryView for this phase's concern(s) (see per-phase notes in …
- Very specific: line 363
inline: run the phase body in-thread — no Task spawn. - Very specific: line 394
NOT start until every id in the in-session awaitingHarnesses set has landed and its gate is green
- Very specific: line 400
If codeProducing: true: apply gate-cadence invariant (targeted gate per fix commit.
- Very specific: line 402
If codeProducing: false and gate non-empty: run gate once at phase boundary.
- Distinctive: line 403
If gate is empty string: no gate check.
- Very specific: line 472
If an executing-harness was waived (skipped via pipeline.skip), its gate is released — the waiver is in Resolution.waivers[] and …
- Distinctive: line 520
Every other finding is folded.
- Distinctive: line 561
Artifact is the handoff: every delegated agent's contribution must be in a committed artifact before the phase closes.
- Distinctive: line 571
Agent spawns: every role-agent spawn is structured as:
- Distinctive: line 613
Driven stop: when the run is about to end its turn at a terminal stop it cannot continue from — any STOP in §0 or in the walk error paths, …
- Very specific: line 626
Every §0 stop uses phase id resolve.
- Very specific: line 631
Policy consult: before any phase performs a nameable outward action (a member of POLICY_ACTIONS — see docs/contributing/specs/policy.md), …
- Very specific: line 643
Provenance: no phase/ADR/backlog references inside source or test code, ever.
- Very specific: line 669
When locate prints nothing, the run is closed (close already ran and removed the ledger) or was never opened.
- Very specific: line 768
Metrics ledger (separate, append-only): Call bash "${CRAFT_ROOT:-${CLAUDE_PLUGIN_ROOT}}/scripts/emit-metrics.sh" --run <run-id> --session …
- Very specific: line 772
When this session compacted, it then prints one advisory line, Compactions: <n> (main <m>, sub-agent <s>) — estimated summary-call cost …
“Common” is measured: kept by at least 6 in 10 of the 44 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
craft — orchestrator
You are running the craft workflow. The SESSION is the orchestrator: it resolves the input, talks to the user (ADRs, escalations, merge confirmation), verifies every delegated artifact, applies review fixes, runs phase-boundary gates, and owns all synthesis (run record, backlog follow-ups, PR body). Heavy work runs in the craft role agents per each phase skill's instructions.
Input: $ARGUMENTS
0 — Resolve
0-driver. Driver probe. Run
"${CRAFT_ROOT:-${CLAUDE_PLUGIN_ROOT}}/scripts/driver-state.sh" active via Bash. Exit 0 =
this run is driven: an outside driver set CRAFT_DRIVER_STATE to a state dir and waits
for the run's status file there (docs/contributing/specs/driver.md). Exit 1 = not driven;
nothing below changes. Exit 2 = the variable is set but unusable: surface its stderr and
continue undriven — a broken env never stops a run. Nothing is held in session state: every
later use re-runs driver-state.sh active, so a compaction or a resume loses nothing.
0a. Parse craft flags from the input first: strip any --profile <name>,
--skip <id,…>, repeatable --harness <phase>.<knob>=<value>, and repeatable
--policy <action>=<verdict> tokens (they may appear anywhere — lead or trail;
comma-split the skip ids; --harness and --policy may each be repeated for multiple
knobs/actions). Also strip --config <name> when present (at most one occurrence);
hold the name for manifest-path resolution below.
--config is distinct from --profile: --config selects which manifest file
is read; --profile sets the execution map inside that manifest. The two compose —
both may be present in the same invocation. Hold --profile/--skip/--harness/--policy
for step 1b. The
non-flag remainder is the input brief consumed at step 2 — a flags-only
input (e.g. --profile lean) leaves an empty brief, and step 2 STOPs as
ambiguous exactly as a zero-argument invocation does. These are per-invocation
overrides: they win over the manifest's pipeline.profile/pipeline.skip/phases.<id>.harness/policy:
(the bin merges them at highest precedence).
0b. Resolve the manifest path. When --config <name> was parsed in step 0a:
run node "${CRAFT_ROOT:-${CLAUDE_PLUGIN_ROOT}}/engine/bin/config-resolve.js" <name> via Bash. This
resolves <name> across BOTH scopes — local ./.claude/craft-<name>.md (always wins)
then user ~/.claude/craft-<name>.md — so there is no separate existence check.
- On exit 0: stdout is the ABSOLUTE winning path — hold it as
<manifest-path>. Surface any stderr scope/shadow note into the run record (advisory; the config still resolved). Steps 1 (manifest-lint.sh <manifest-path>) and 1b (pipeline-resolve … [manifest-path]) pass it through UNCHANGED — both already accept an absolute path. - On non-zero exit: STOP; surface stderr verbatim — either the two-scope
neither-found diagnostic (names both
./.claude/craft-<name>.mdand~/.claude/craft-<name>.md) or a bad-name/traversal diagnostic. Never silently fall back to.claude/workflow.md. When--configis absent: use.claude/workflow.mdas<manifest-path>.
- Run
"${CRAFT_ROOT:-${CLAUDE_PLUGIN_ROOT}}/scripts/manifest-lint.sh" <manifest-path>(passing the resolved path from step 0b). It must pass — on INVALID, STOP and surface the errors. Read the manifest (frontmatter = config, body = policy rationale). No manifest = pure defaults via each phase's capability probe.
1b. Run node "${CRAFT_ROOT:-${CLAUDE_PLUGIN_ROOT}}/engine/bin/pipeline-resolve.js" \
"${CRAFT_ROOT:-${CLAUDE_PLUGIN_ROOT}}/pipeline/default.yml" [manifest-path] \
[--profile <name>] [--skip <id,…>] [--harness <phase>.<knob>=<value>]… \
[--policy <action>=<verdict>]… via Bash,
capturing stdout. The manifest path argument (the resolved path from step 0b) is
included only when a manifest file was found in step 1; the --profile/--skip flags
are appended only when parsed in step 0a; each --harness occurrence is forwarded as a
separate --harness <phase>.<knob>=<value> argument in the order parsed; each --policy
occurrence is forwarded as a separate --policy <action>=<verdict> argument in the order
parsed. The bin folds all three over the manifest at highest precedence — CLI --harness
and --policy values win over their manifest counterparts (a bad knob value or unknown
phase or unknown action exits non-zero; stderr names the violation).
- On non-zero exit (this includes resolver ok: false — a rejected role: or a
stranded consumer — whose errors[] are written to stderr, never as stdout JSON):
STOP; surface stderr to the user; refuse to proceed.
- If effective[] is empty: STOP; surface "no enabled phases in resolution".
Parse the JSON as Resolution.
1c. Seed the run record (step 4) with every entry in Resolution.record[] as
its initial lines. Subsequent phase outcomes, skip reasons, no-op
justifications, probe results, and forced actions are appended.
1c-mem. Load memory store (once per run). Resolve the store path from the
manifest's memory.ref (default .claude/craft-memory.md, ADR-118/121), rooted
at the repo ROOT (the worktree/checkout root — NEVER ${CLAUDE_PLUGIN_ROOT}, hard
constraint). Call load(repoRoot, deps) — see docs/contributing/specs/memory.md Claude
binding. Hold the single MemoryView in-session beside the run record for the
duration of this run. A cold, absent, or malformed store yields an empty view and
records a load no-op — the run proceeds without hints (advisory-only, never a
blocker; ADR-116/120). load is called once per run, not per phase.
1c-int. Load intention view (once per run). Build an in-session IntentionView via
the intention port's consult — see docs/contributing/specs/intention.md file adapter
procedure. With no intention: manifest key, probe the zero-config corpus — the living pages
(docs/contributing/specs/*.md, docs/contributing/prd/DESIGN-*.md, docs/contributing/DOD.md, docs/guides/customizing.md)
plus the resolved ADR directory for the governing lane — where
scripts/governing-corpus.sh emits only the decision records that carry a line-1
frontmatter fence, so the lane's READ is bounded to the records that opted in rather
than the whole corpus, and an empty governing lane is normal, not an error;
hold the single IntentionView in-session beside the run record for the duration of
this run. A cold or absent corpus yields an empty view and records a load no-op —
never a blocker (advisory). This view is not carried in the MemoryView — a
genuine parallel mechanism, loaded once here exactly like the memory store above but
kept in its own in-session slot. consult is called once per run, not per phase.
The lessons lane is the exception: walk step 4's Lessons hint consults it at every phase
entry, re-read from disk, so it holds no in-session state.
1d. Resolution.gateDecisions is an ARRAY of { phaseId, gate, codeProducing }
(the propose entry also carries awaitingHarnesses[]). Find the entry whose
phaseId === "propose" and store its awaitingHarnesses[] in-session as the
executing-harness ids that must land before propose starts pr create
(a missing entry or absent field = the empty set); also emit
AWAITING(propose): <ids comma-joined, or none> (appended at step 4).
1e. Surface Resolution.waivers[] in the run record. The engine pre-formats a
WAIVER: … line into record[] only for executing-harness skips
(proposeGateReleased: true — e.g. validation/architecture); those arrive
via step 1c. For every OTHER waiver (a skipped review/refactoring,
proposeGateReleased: false — the engine writes no record line), append your own
loud waiver notice to the run record so every skip is visible (ADR-005).
Use waivers[] for the gate-release decision (each proposeGateReleased: true
releases that phase's propose-gate).
- Classify the input (the non-flag remainder from step 0a): backlog id (matches the
repo's backlog convention — only if the manifest declares
backlog:; look the entry up there) | file path (read it) | free-text brief (use verbatim). Empty or ambiguous → STOP and ask. For the per-sourceresolvemechanism seedocs/contributing/specs/backlog.md: forsource: file, classify by the repo's backlog convention (prose-judgment) and resolve by reading the entry; forsource: custom, the script owns the id-form and resolve runsrefwith argv["resolve", id]—idis untrusted, passed as a discrete argument (never spliced into a shell string) and validated against the source's id-form before invoking (see the spec's safe-invocation note). Id-not-found, an id failing the id-form, or an unreachablecustomsource is a runtime blocker (never a guessed brief). - Derive a kebab-case topic slug (≤6 words). Print:
Resolved → topic: <slug>, brief: <one line>for user confirmation. Open the run record — an append-only on-disk ledger at
<git-common-dir>/craft-runs/<run-id>.md, the directoryrun-ledger.sh dirprints: inside the git common dir, where no commit can write, outside every working tree and never under${CLAUDE_PLUGIN_ROOT}— seedocs/contributing/specs/run-record.md. Below,run-ledger.shmeans that shimmed path, assigned once as$ledgerin the code block below; shell variables do not survive between Bash calls, so the orchestrator expands the shim itself in every call that follows — never a bare${CLAUDE_PLUGIN_ROOT}path.Open the ledger by running
run-ledger.sh open <run-id>— spelled below as"$ledger" open— and append the seeded §0 lines in the same Bash call:ledger="${CRAFT_ROOT:-${CLAUDE_PLUGIN_ROOT}}/scripts/run-ledger.sh" "$ledger" open <run-id> && "$ledger" append <run-id> resolve <<'EOF' RESOLVE: <craft flags verbatim, or none> EFFECTIVE: <Resolution.effective[].id comma-joined> <Resolution.record[] lines> <config/load notes from steps 0b and 1c-mem/1c-int> <waivers from step 1e> AWAITING(propose): <ids comma-joined, or none> EOFNever silence
open's stdout: the run-key it prints into this session's transcript is what binds the compaction hooks to this run.openalso creates the run's discovered-impacts store<run-id>.impacts.mdbeside the ledger, written only throughrun-ledger.sh deferandrun-ledger.sh verdict(see the invariant Impacts: fold or defer at discovery).A refused
openis a STOP.openexits 3 when the run-id is already open: another run of this topic is live, or crashed withoutclose. This is not a failed append: continuing would append this run's lines to that run's ledger under the same run-id, and the&&above already keeps the seeded lines out of it. STOP with the blocker{ resolve, reason: "run-id <slug> already open since <iso>", options }:- Stop here. The run is live: continue it in its own session.
- Discard it. The run is dead: run
run-ledger.sh close <run-id>, then re-run this step's call unchanged. A leftover../<repo>-<slug>worktree or*/<slug>branch still hitscraft:workspace's collision STOP. - Re-slug. Re-run step 3 with a different topic slug.
Never run
closewithout the user choosing option 2. Until then, append nothing for this run and never continue the walk.Flush-per-line. Every ledger line is appended in the tool call that produces it or in the orchestrator's very next tool call — never held longer than that.
One ledger, one place, the whole run. The ledger stays at
craft-runs/<run-id>.mdfromopentoclose, whatever the workspace strategy:workspacenever moves it and teardown never removes it.The §0 lines produced before the run-id exists — steps 0b–1e, which all run before step 3 derives the topic slug — are the only in-session hold: bounded to §0 itself, and re-derivable by simply re-running §0.
Each line is one run-id-prefixed record. Every subsequent phase outcome, skip reason, no-op justification, probe result, and forced action is appended the same way. Only the orchestrator ever appends to this file — through its own tool calls, foreground or background; the compaction hooks only read. No role agent writes it, in any phase, including phases that run in parallel. The final summary and the PR body take only the lines whose run-id prefix is this run's.
Run-record tokens
Every ledger line opens with a fixed, greppable token. Besides the walk's own
(PHASE-START, PHASE-DONE, NO-OP(<phase>):, GATE(<phase>):, auto-skip:, WAIVER:,
POLICY(...), AWAITING(propose):, EFFECTIVE:, MODEL-RETRY(<role>):, MODEL-DEGRADED(<tier>):),
these are emitted elsewhere — full definitions in
docs/contributing/specs/run-record.md Token vocabulary:
| Token | Emitted by |
INTENTION-DRIFT(<page>): <changed-path>, INTENTION-WAIVE(<page>): <reason> | the validation phase's assert-fresh walk (docs/contributing/specs/intention.md) |
STUB-FOUND(<file>): <marker>@L<n>, STUB-WAIVE(<file>): <reason>, SLOP-FOUND(<file>): <entry>, SLOP-WAIVE(<file>): <reason> | the ci.sh hygiene cadence |
DECISION-REVERSAL(ADR-NNN): <what changed> -> ADR-MMM | the decisions phase, one line per supersedes entry authored that run |
DECISION-CITE-WAIVE(<file>): <reason> | the adr-lint C3 waiver, collected from the same --waiver-source files as STUB-WAIVE/INTENTION-WAIVE |
MEMORY-RETRACT(<concern>): <merge-key> | the phase that owns a concern's write surface, on a mechanical re-check that disproves a stored entry |
FOLDED(<file>): <finding> -> <sha> | the phase that folds, under the invariant Impacts: fold or defer at discovery, one line per locus file |
DEFERRED(<file>): I<n> <title> | run-ledger.sh defer, phase column = the capturing phase, one line per entry |
TRIAGED(I<n>): <verdict> — <rationale> | run-ledger.sh verdict, phase impact-triage |
FILED(<file>): I<n> <title> — not folded: <reason> | the impact-triage phase step 4, after the BACKLOG commit, one line per filed entry |
LESSONS: <path> (<outcome>)[, …], NO-OP(lessons): <reason> | the lessons phase (craft:lessons step 6, or its standalone preamble) |
LESSON-REVERIFY(<lesson>): <changed-path> | the validation phase's assert-fresh lessons rows (step 5), resolved by the lessons phase |
Phase walk (driven by Resolution.effective[])
Walk each phase descriptor in Resolution.effective[] order. For each phase:
Necessity probe (auto-skip). If
phase.autoSkipEligibleistrue, evaluate against the live change whether the phase has any work before assembling the contract. The per-phase signal (auto-skip only when provably empty; any doubt runs the phase):Eligible phase Provably-empty signal ⇒ auto-skip decisionsthe design doc's Decision-candidates section is empty (no load-bearing choice to put to the user) — the up-front form of the runtime decisions no-op; session-owned, so auto-skip avoids an empty user conversation rather than an agent spawn reviewno reviewable source diff in scope since implementation(e.g. a docs/config-only change)refactoringno source change in scope to motivate a structural pass (same signal as review); when source was touched, it runs and may recordNO-OP(refactoring):documentationno design/changecontent maps to any documentation surfacelessonsevaluated only after the cross-phase wait; the ledger holds no lesson-shaped event ( craft:lessonsstep 1 table) and noLESSON-REVERIFY(line and, whenvalidationdid not run its step 5, theassert-freshlessons lane over<base>...HEADis emptyimpact-triageevaluated only after the cross-phase wait; the ledger holds no DEFERRED(line for this run →auto-skip: impact-triage — evaluated unnecessary (no deferred findings)validationno mutable code changed in scope — the technique no-op signal, evaluated before spawning the run architectureno dependency-graph-affecting change (no import/module-boundary edits) since implementationAfter an implementation no-op (the run record holds
NO-OP(implementation):), thereview,refactoring,validationandarchitecturesignals are evaluated over<base>...HEADinstead of "sinceimplementation", so source that an earlier phase committed is still probed.<base>is the default branch the workspace phase probed (origin/HEAD→main→master). A run whose only commits are the design doc, its ADRs and their citation sweeps still auto-skips all four.When provably empty: (a) append the fixed token
auto-skip: <phase> — evaluated unnecessary (<signal>)to the run record (e.g.auto-skip: review — evaluated unnecessary (no source diff in scope)) — distinct fromWAIVER:andNO-OP(<phase>):; (b) if the phase is an executing-harness, release itsawaitingHarnessesentry (see Cross-phase invariants); (c) continue WITHOUT running the phase — no contract assembled, no agent spawned, no commit. When the probe is non-empty, or the phase is notautoSkipEligible, the phase runs (steps 2–7). When emptiness cannot be proven, RUN the phase (doubt runs; never auto-skip on an unprovable judgment).- Resolve the skill — invoke
phase.procedureverbatim (the descriptor'sprocedure:field — e.g.craft:design, or an inserted phase'scraft:bench/acme:bench). For every craft-native phase the procedure iscraft:<phase.id>and the skill dir name equalsphase.id, so default phases are unaffected. An inserted phase carries its ownprocedure:and may name a craft-local skill or a namespaced one — the walk dispatches the string as-is, across plugins too; the derived-plugin registration surface (craft.extends:) registers the phases/agents the walk dispatches — a registered or inserted phase resolves, carries its contract bundle, and executes under the engine-owned contract. If the skill or plugin aprocedurenames is not installed (noskills/<id>/dir for a craft-native procedure; no installed plugin for a namespaced one) → the loud STOP "procedure<phase.procedure>resolves to no installed skill" — the intended guard, not a silent skip. Swap-fidelity (G5) — a swap changes who runs a phase or which skill orchestrates it, never what invariants bind it. A manifest may swap a default phase's worker (phases.<id>.role) or its orchestrating skill (phases.<id>.procedure); either way the descriptoridis unchanged, so the injected contract (step 4) — assembled from thatid— is identical, and the swapped worker (agent or inline) always runs inside the same engine-owned contract. Arole:- orprocedure:-swapped phase can never drop the invariant core. A swapped default-phaseprocedure:is dispatched verbatim exactly like an inserted one (above); a procedure that resolves to no installed skill STOPs via the same "resolves to no installed skill" guard. A swappedrole:the resolver'sroleExistsprobe rejects makespipeline-resolveexit non-zero at §0 (resolverok: false) — the role case of the existing non-zero-exit error-path row — before the walk dispatches anything, uniformly for agent and inline. The bin wires a live install-probe for craft-nativecraft:<role>refs (a typo'd role fails closed); externalmy:/acme:refs fail closed unless the ref is registered viaextends(extends.agents∪ therole:of every registered/inserted phase). Inserted/registered-phase contract execution: the walk passes the resolved descriptor tocontract-assemblevia--descriptor-json(step 4), so a novel/registeredidEXECUTEs under the engine-owned contract — the same core + declared bundles that wrap any default phase. - Resolve execution — use
phase.execution(agent|inline) from the Resolution. Apply manifest override (phases.<id>.override,phases.<id>.context) as before. Assemble the injected block — at phase entry run:
node "${CRAFT_ROOT:-${CLAUDE_PLUGIN_ROOT}}/engine/bin/contract-assemble.js" \ --descriptor-id <phase.id> \ [--manifest <manifest-path>] \ [--inline] # only when phase.execution is "inline" [--contracts-dir "${CRAFT_ROOT:-${CLAUDE_PLUGIN_ROOT}}/contracts"]For an inserted or registered phase (any phase whose
idis not among the engine defaults), append--descriptor-json <path>where<path>is a temp file the walk writes the resolved descriptor JSON to (single object or array written viawriteFileSync), or--descriptor-json -to pipe the JSON on stdin. The flag value is a file path (or the literal-for stdin) — the bin reads the content withreadFileSync(path)/ reads stdin; passing JSON text inline as the arg value does not work. The bin resolvesphase.idagainst the loaded descriptor set so the registered id EXECUTEs; the default-phase path passes no flag. Via Bash, capturing stdout as the injected contract block. On non-zero exit: STOP; surface stderr; refuse to proceed. On agent execution the block is PREPENDED to the Task spawn prompt. On inline execution the block is loaded into the session at phase entry and the session follows it.Memory hint (advisory). Before assembling the block, slice the in-session
MemoryViewfor this phase's concern(s) (see per-phase notes in each phase skill's Preamble). If the slice is non-empty, prepend it into the injected contract block as part of the pre-chewed context — this is slot 1 of the Agent spawns invariant below (the same slot whether the phase is agent-spawned or inline; no second injection surface is added). A hint that failed validate-on-read was already dropped atload— if the slice is empty, the phase runs its own probes. This read is purely advisory and never gates. Seedocs/contributing/specs/memory.mdClaude binding.Intention hint (advisory). For the
design,decisionsandplanningphases only, slice the in-sessionIntentionViewfor this phase's change scope: theentrieswhose subjects intersect the phase's touched set. If the slice is non-empty, prepend it into the SAME slot-1 prepend, alongside the memory hint — no second injection surface. An empty slice means the phase runs its own probes. Seedocs/contributing/specs/intention.md.Lessons hint (advisory). At every phase entry, build the hint with one call (
<lessons-dir>=paths.lessons, elsedocs/lessons/), the phase touched set on stdin:git diff --name-only <base>...HEAD | node "${CRAFT_ROOT:-${CLAUDE_PLUGIN_ROOT}}/engine/bin/lessons-hint.js" --phase <phase.id> --max <lessons.max_injected, default 3> --dir "<lessons-dir>"Append the repo paths the phase's input artifact names to stdin: the brief for
design, the design doc forplanning, the plan parts'### Contextpaths forimplementation. Prepend its stdout verbatim into the SAME slot-1 prepend, after the memory and intention hints; empty stdout adds nothing. The bin reads lessons only from the committed base revision, so only lessons merged into the default branch are injected: drafts on the run's branch wait for ratification. The orchestrator never reads lesson files itself, and the hint never gates. The frontmatter is never injected. The bin prints one index line, then the How to apply section verbatim, per lesson:Lessons (advisory — open the path for context and evidence): - <title> — when <trigger> → <path> <How to apply, verbatim, at most 6 lines>Phase-start marker. At phase entry, in the same Bash call as the contract-assemble invocation above, append
PHASE-START(<phase.id>):viarun-ledger.sh append <run-id> <phase.id>, with no time:appendstamps the line with its own UTC clock and replaces any time written after the token. Skip this append when the phase is already in flight — a resumed phase keeps its firstPHASE-START, so a later--sincederivation stays right.Execute via the resolved execution mode (
phase.execution).agent(default): spawncraft:<role>(or the manifest-swapped role) as a Task, structured per the Agent spawns invariant below — the step-4 injected block PREPENDED to the spawn prompt, then working dir, task dynamics, artifact paths. Await the commit; verify on return.inline: run the phase body in-thread — no Task spawn. The step-4 block was assembled with--inline; load it as the governing constraint for this phase. Ifphase.roleis present AND resolves to a local agent def (agents/<name>.md,<name>= the role ref minus anycraft:namespace), also load that agent body (sans frontmatter) right after the block and follow it as self-directed craft — the same two artifacts a spawn carries, in the same order; the spawn-only "final message to the parent" line is moot (no parent). A role that resolves to no local def (e.g.acme:planner) runs on the block alone. The session is both orchestrator and worker for this phase. Verify, gate, record, and handoff are identical toagent— the block already carries the inline carve-outs (the commit is the handoff; the session model). Role-less phases (workspace/decisions/lessons/propose/integrate) are session-owned regardless of mode.Session-owned responsibilities by archetype:
setup: workspace preparation and setupspecification: verify artifact; conversation if norolefield (decisions)construction: verify each part; run phase gate per gate-cadence invariantharness(harness-read ∈ contract): apply ALL findings; converge per thephase.harnessknobs (dimensions/passes/max_cycles/convergence —craft:reviewreads them)refinement: judgment (scan + scoping); apply ALL findingsharness(harness-exec ∈ contract): start background run with thephase.harnesstool/scope/incremental (craft:validationreads them); gateproposeon triage completion (see invariants below)delivery(documentation): synthesis (fold in-scope findings into this PR, defer the rest to impact triage, backlog guard) — may parallel a running executing-harnessdelivery(lessons): harvest percraft:lessons; starts only onceawaitingHarnessesis empty; never gatesproposebeyond its own draft lintdelivery(impact-triage): triage percraft:impact-triage; starts only onceawaitingHarnessesis empty; spawnscraft:impact-triager; never itself awaiteddelivery(propose): pre-propose gate; body; PR creation per policy — does NOT start until every id in the in-sessionawaitingHarnessesset has landed and its gate is greendelivery(integrate): user confirms; cleanup
- Gate — read the gate string (
.gate) from theResolution.gateDecisionsentry whosephaseId === phase.id. IfcodeProducing: true: apply gate-cadence invariant (targeted gate per fix commit; phase gate once per round; never commit on known-red). IfcodeProducing: falseand gate non-empty: run gate once at phase boundary. If gate is empty string: no gate check. - Record outcome in the run record (appended to the seeded entries): append
PHASE-DONE(<phase.id>): <one-line outcome>for every phase that ran (NO-OP phases included), together with the existingGATE/NO-OP/inline:lines, in onerun-ledger.sh appendcall tocraft-runs/<run-id>.md. That call must still fall within the flush-per-line window: the call that produced the line, or the orchestrator's very next tool call. A gate'sGATEline may instead land in the gate's own call. An inline-executed phase is noted:inline: <phase.id> — ran in-session. At each phase boundary where a gate ran, append the fixed greppable tokenGATE(<phase.id>): greenorGATE(<phase.id>): redto the run record — one line per phase gate result. An auto-skipped or waived executing-harness records noGATE(...)line (the phase did not produce a recorded gate result); an auto-skipped phase records only itsauto-skip:line (step 1) — noPHASE-START/PHASE-DONE. A judgment phase (decisions/refactoring) that records aNO-OP(<phase>):line — e.g.NO-OP(decisions): no user-judgment decisions — …orNO-OP(refactoring): nothing cleared the bar — …— has produced its outcome; it is NOT a missing artifact and never re-runs or escalates as a gap. Thevalidationphase's DoD sub-concern records aNO-OP(verify): no DoD declared — …line under the same convention: it has produced its outcome; it is NOT a missing artifact and never re-runs or escalates as a gap.planningandimplementationrecordNO-OP(planning): no part remains — …andNO-OP(implementation): no plan parts — planning recorded NO-OP(planning)under the same convention when an earlier phase already landed every deliverable. Each has produced its outcome, is NOT a missing artifact and never re-runs or escalates as a gap.NO-OP(planning):is appended in one call with itsPHASE-DONEline;NO-OP(implementation):is appended with itsPHASE-DONEline only on a green gate — on red it lands withGATE(implementation): red, andPHASE-DONEfollows the green re-run (implementation Preamble 2). - On blocker: escalate
{ phase/part, reason, ≤3 candidate options }. Never spin, never silently abandon. - On model-down (not a task blocker): an overload retries once on the same
tier (
MODEL-RETRY); otherwise, or on a second overload of that spawn, mark the tier degraded and re-resolve to fallback (MODEL-DEGRADED); respawn from artifact.
design and review are already concern-named (no alias); every other craft-native phase
id maps to a skills/<id>/ dir of the same name, so its procedure is
craft:<phase.id> and the walk dispatches it with no translation table. Inserted phases
bring their own procedure, dispatched verbatim (step 2).
Walk error paths
| Condition | Behavior |
Non-zero exit from pipeline-resolve (incl. resolver ok: false — a swapped role: the roleExists probe rejects, or a stranded consumer — whose errors[] are written to stderr, never stdout JSON) | Stop; surface stderr; refuse to proceed |
effective[] is empty | Stop; surface "no enabled phases in resolution" |
A phase's procedure resolves to no installed skill (no skills/<id>/ dir for a craft-native procedure; no installed plugin for a namespaced one; incl. a swapped default-phase procedure:) | Stop; surface "procedure <phase.procedure> resolves to no installed skill" |
awaitingHarnesses on propose is empty | Propose is not gated on any harness; proceed normally |
waivers[] is non-empty | Executing-harness waivers are pre-formatted in record[]; surface every other waiver (review/refactoring) to the run record yourself per §1e; continue |
| A skip strands a consumer | ok: false already; covered by the stop-on-error path |
| manifest-lint exits 2 (invalid) | Stop; surface errors |
Cross-phase invariants (non-overridable)
- Executing-harness triage gates
propose: a phase is an executing-harness whenarchetype: harnessandharness-exec ∈ contract.proposedoes not startpr createuntil every phase id in the in-sessionawaitingHarnessesset (theawaitingHarnesses[]of theResolution.gateDecisionsentry whosephaseId === "propose", captured in §0 step 1d) has landed its run and its gate is green.documentation(archetype:delivery) may parallel a background executing-harness;proposemay not.lessonsmay not parallel a background executing-harness either — it waits for the same awaited set — and is never itself awaited. On the ledger, that awaited set is theAWAITING(propose):set minus recorded releases —auto-skip: <id>, exactNO-OP(<id>):, or the lastGATE(<id>)green — the release rules below, stated as ledger facts. If an executing-harness was waived (skipped viapipeline.skip), its gate is released — the waiver is inResolution.waivers[]and pre-formatted inResolution.record[]— andproposemay proceed without waiting for it. Likewise, if an awaited executing-harness records a runtime no-op — it is enabled and ineffective[](so it carries no engine waiver and IS inawaitingHarnesses), but at runtime its tool-agnostic probe finds nothing and the phase ends with a note, never landing a run — itsawaitingHarnessesentry is released, symmetric to the skip-waiver above. This release is NOT an engine waiver (the engine emits waivers for skip/disable only, when the phase is absent fromeffective[]); it is the orchestrator treating a recorded no-op as a release at gate-check time, soproposemay proceed without waiting for the no-op'd harness. Likewise, a recordedauto-skip:of an awaited executing-harness (it never lands a run, by the same up-front necessity probe) releases itsawaitingHarnessesentry the same way — the orchestrator treats a recordedauto-skip:as a release at gate-check time, exactly as it treats a recorded no-op; this is NOT an engine waiver (the phase is ineffective[]). Soproposeis never left waiting on a harness that auto-skipped. Clarification forvalidation: the entry may carry two recorded sub-outcomes — the technique note AND aNO-OP(verify):line (DoD sub-concern); the entry is released only when no technique run lands, and is satisfied by a landed and triaged technique run regardless of anyNO-OP(verify):line — the verify no-op never independently blocks or releases the single gate entry. - Scope expansion re-enters the harness-read phase: any feature behavior added
after a phase with
archetype: harnessandharness-read ∈ contracthas run — during phases of archetypeconstructionorrefinement— that is NOT a fix to an existing finding gets its own feature-scoped review before the executing-harness gate closes. Therefinementphase's re-review is scoped to the refinement diff only and does not substitute. A scope expansion that reaches the executing-harness gate unreviewed is a workflow breach. Impacts: fold or defer at discovery: an impact is a finding outside the current phase's own fix set — an
IMPACT(<file>):line or a deferred observation in a handback, a refinement's reported-not-executed refactor, a review or harness finding whose locus is outside the change's scope, or the session's own observation. The orchestrator gives each impact one verdict, folded or deferred, in the phase that received it, before that phase closes. It is never folded or deferred silently.- A finding the brief or the design lists as out of scope is deferred, whatever the boundary says.
- It is in scope when its locus file is in
git diff --name-only <base>...HEAD, or when it concerns a claim or behaviour the diff edits, or a statement that contradicts what the diff now makes true, in at most one file outside the diff. The run's own design, plan and ADR docs and BACKLOG.md are in the diff. A finding out of scope is deferred. - An in-scope finding whose fix needs a new decision goes to the user through the
blocker protocol
{ unit, reason, ≤3 options }: fold it with an ADR authored through the decisions flow, or defer it with reason needs its own decision. - An in-scope finding whose fix needs a paid or live probe, waits on an external trigger, or adds feature behaviour is deferred.
- Every other finding is folded.
Fold: the session makes the fix itself, in prose, or in source and tests that add no feature behaviour, never while a role agent is working in the tree. One commit per fold, typed by what it changes (
docs(<scope>):,fix(<scope>):,test(<scope>):). A fold beyond prose commits only on a greengates.part. A fold that turns red has its uncommitted edit discarded and is deferred with reason the fix did not pass its gate, itsexpected:being the first failing line. After each fold commit, appendFOLDED(<file>): <finding> -> <sha>, one line per locus file. A finding with no locus file of its own keys on the run's design doc path. A source fold made after the executing-harness started ends its line with(outside the executing-harness run), which is how the PR trailer names it. A fold adds no feature behaviour, so it never triggers the scope-expansion rule above.Defer: through
run-ledger.sh defer <run-id> <phase>, one entry on stdin:title: install hook leaves stale symlink on re-run locus: scripts/worktree-setup.sh:L88 reason: out of scope proof: bash scripts/worktree-setup.sh "$tmp" && bash scripts/worktree-setup.sh "$tmp"; ls -l "$tmp/.claude" expected: .claude -> <stale target>Its
reason:line gives the reason: out of scope, needs its own decision, needs a probe, waits on an external trigger, adds feature behaviour, or the fix did not pass its gate. No proof, no entry:deferrefuses (exit 2, nothing written) an entry with noproof:body, noexpected:line, a locus that is absolute, home-rooted, holds whitespace or climbs out with a..step, a control character, or another reason. It appends the entry to the store andDEFERRED(<file>): I<n> <title>to the ledger, and printsI<n>. Only the orchestrator defers; a role agent reports anIMPACT(line and never acts on it. With no open run (a standalone phase)deferrefuses and the impacts are listed in the phase's final output. Theimpact-triagephase judges every deferred entry beforepropose.The
<finding>,<title>and<reason>text follows the same discipline as<file>: paths repo-relative, an out-of-repo location named by placeholder (<worktree>,<main-repo-dir>), and no secret or env assignment copied from a handback.- Artifact is the handoff: every delegated agent's contribution must be in a committed artifact before the phase closes. A dead agent = fresh respawn fed from the artifact, never a continuation.
- Gates: each fix commit gates on the TARGETED check (
gates.partover the touched files +gates.review-batchif declared); the full phase-boundary gate runs ONCE per round — after each code-producing phase, after eachharness/refinementfix round, and before push — not after every intra-round commit. Nothing is ever committed on a known-red gate. - Agent spawns: every role-agent spawn is structured as:
- Injected contract block (from step 4 above, includes the assembled core +
bundle invariants + derived retrieval note + manifest
context:appended by the assembler; plus the memory hint prepended when the MemoryView slice for this phase is non-empty — see step 4 memory-hint clause anddocs/contributing/specs/memory.mdClaude binding; plus the lessons hint in the same prepend when the phase's lessons slice is non-empty — see step 4 Lessons hint). Do NOT separately re-injectcontext:— the assembler already appends it; double-injection is a breach. - Working directory and task dynamics (phase id, part text, gate string).
- Artifact paths: committed artifacts passed by PATH — the agent reads them in-place. The prompt embeds the pre-chewed context and the load-bearing deltas, not a second verbatim copy. A respawn from a partial artifact points at the on-disk state rather than re-transcribing it. The pre-chew mandate forbids making an agent re-explore the codebase — reading a committed plan is not re-exploring. (This block is the Execution port's spawn verb — the Claude binding. See docs/contributing/specs/execution.md.)
- Injected contract block (from step 4 above, includes the assembled core +
bundle invariants + derived retrieval note + manifest
- Model resolution & fallback: resolve each spawn's model as manifest
models.<role>→ the descriptor'smodel:field (the canonical per-role tier; the agent-def frontmatter pin is the Claude binding of that same tier) →models.fallback→ engine defaultsonnet→ session model (pass the resolved tier as the spawn's model param). If a spawn dies on a model-availability error (NOT a task blocker, which uses the blocker protocol), classify it. A transient overload (529as an HTTP status or API error code, never a bare substring, oroverloaded_error) is retried ONCE on the same tier, immediately (the harness already backed off), respawned from the artifact, recordingMODEL-RETRY(<role>): <tier> overload. When the text also matches a permanent error (e.g. a "high load … switch to Sonnet" off-switch), the permanent class wins. Any other availability error (model unknown, no access, switched off), an unclassifiable one, or an overload on that retry marks the model degraded for the rest of the run: recordMODEL-DEGRADED(<tier>): fallback=<tier> cause=<overload|unavailable>, re-resolve tomodels.fallbackif declared, else the engine defaultsonnet, else the session's own model (guaranteed available), then respawn from the artifact. The retry allowance is per spawn. Once a tier is known degraded this run, later spawns and retries skip straight to the fallback (a retry already in flight finishes on its tier) — never pay the same dead spawn twice. (This is the Model port's select/isAvailable — the Claude binding. See docs/contributing/specs/model.md.) - Blockers escalate to the user as
{ phase/part, reason, ≤3 candidate options }— never spin, never silently abandon. - Driven stop: when the run is about to end its turn at a terminal stop it cannot
continue from — any STOP in §0 or in the walk error paths, the workspace collision STOP,
the workspace in-place background STOP, an escalation the user declined or left with no
way forward, a
degrade-to-blockerpolicy surface — its last tool call is one Bash call:
The reason travels on stdin through the quoted heredoc, so text from stderr, a branch name or a backlog entry is never expanded by the shell. In the Bash call, the closingdriver="${CRAFT_ROOT:-${CLAUDE_PLUGIN_ROOT}}/scripts/driver-state.sh" "$driver" active >/dev/null && "$driver" blocked <phase-id> - <<'REASON' <reason> REASONREASONline starts at column 0: an indented terminator does not close the heredoc. Every §0 stop uses phase idresolve. An escalation is still asked first; only a stop the run cannot continue from writesBLOCKED. The ledger stays open, so the run resumes as after any STOP. Undriven,activeexits 1 and nothing is written; a non-zero exit fromblockedis surfaced to the user, never a blocker. - Policy consult: before any phase performs a nameable outward action (a member of
POLICY_ACTIONS— seedocs/contributing/specs/policy.md), the session callsPolicy.consult(action, { effectivePolicy: Resolution.policy, binding })over the heldResolution.policyand the active binding, and obeys the returned surface:proceed(execute silently),ask-then-proceed(raiseAskUserQuestionthen execute on approval),refuse(do not execute; no-op or block per reversibility), ordegrade-to-blocker(headlessaskwith no pre-approval — block and record). OnePOLICY(...)token is appended to the run record per consult (seedocs/contributing/specs/policy.mdgreppable record tokens). The three engine floors (never-commit-on-red,validation-triage-gates-propose,artifact-handoff) are not inPOLICY_ACTIONSand are never consulted — they remain absolute. - Provenance: no phase/ADR/backlog references inside source or test code, ever. Design docs and the PR body carry provenance.
Maintainer smokes — not CI-gated
On-demand release checks — inline fidelity, the model-class matrix, registered-phase
dispatch, second-instantiation, the behavioural eval suite — live in
docs/contributing/maintainer-smokes.md. They are not part of a run; run one only when a
maintainer asks for it by name.
Review cadence — engine vs working-style
The engine cadence is the single review phase over the whole change (per-dimension
convergence, owned by craft:review). The "4-dimension review after every code part"
is a session working-style — a discipline the orchestrator may apply, not an engine
invariant.
Rebuild after compaction
A compaction can land mid-walk; this is how the orchestrator rebuilds from the ledger instead of trusting the summary it just produced.
- The ledger outranks the summary. Locate the ledger from the reorient block's
Ledger:line. Without a block (non-Claude harness, the hook absent), runrun-ledger.sh locate --run <run-id>, where the run-id is the topic slug re-derivable from the/craft:runmessage the summary keeps verbatim. Whenlocateprints nothing, the run is closed (closealready ran and removed the ledger) or was never opened. Skip steps 2–6 and follow Closed run below. Never re-openthe run. - Re-derive the Resolution. Re-run §0 steps 0b, 1 and 1b with the flags on the
ledger's
RESOLVE:line; re-load()the memory store (1c-mem) and re-consult()the intention view (1c-int), and re-rundriver-state.sh active(no ledger token records it: the env is the source of truth). - Pipe that Resolution into
run-state:
Exit 1 (annode "${CRAFT_ROOT:-${CLAUDE_PLUGIN_ROOT}}/engine/bin/run-state.js" <ledger-path> --run <run-id>AWAITING(propose):mismatch — the manifest or flags changed mid-run) or exit 2 → blocker{ rebuild, reason, ≤3 options }, never a guess. On exit 0, restorecompleted/inFlight/next/awaitingHarnesses/parts/findings/backgroundfrom the printed state, and surface itswarnings. - Only a background run survives. A foreground spawn blocks the orchestrator, so a
compaction can never land mid-spawn; only a background Bash run — recorded as
HARNESS-BG— can outlive an orchestrator compaction. Resume. Re-enter each
inFlightphase at walk steps 2–4 (idempotent; step 4 skips a secondPHASE-START), then resume per:In-flight phase Resume from any agent phase except review the phase's committed artifact (design doc, plan, ADRs, commits). A dead or lost spawn is a fresh respawn from the artifact (existing invariant). workspace../<repo>-<slug>exists on<type>/<slug>but setup may have failed part-way: re-runworktree-setup.sh <abs-worktree-path> [manifest scripts.post-setup]; escalate if it fails again. Never re-create the worktree, because the collision rule would STOP on the run's own tree.decisionsADRs are committed one at a time; the user's answers survive as verbatim user messages. implementationparts[]+git logagainst the plan. A landed commit without aPARTline is verified, then gets its line (withsize=?if unknown). Continue at the first part with neither. No plan and the ledger holdsNO-OP(planning):: appendNO-OP(implementation): no plan parts — planning recorded NO-OP(planning)if absent, rungates.phase(fixing a red one first), and close the phase only on a green gate (GATE(implementation): greenandPHASE-DONEin one append).reviewreload findings[]for the current cycle;git logfix commits;RULED-OUTlines. A dimension with noFINDINGSline for the cycle is re-spawned, because a compaction right after the fan-out returns can drop reviewer output before it is persisted.validation/architecturebackground[]: pid alive (kill -0) → wait. Dead with non-emptyout→ triage. Dead with emptyout→ the existing empty-output blocker.lessonsa LESSONS:orNO-OP(lessons):line means done; otherwise re-run the harvest from step 1 (step 3 dedupes drafts already committed).impact-triageevery DEFERREDid has aTRIAGEDline and aFILEDline exists for eachbacklog:*verdict (or theNO-OP(impact-triage):line, or this phase'sPOLICY(line refusingbacklog-write) → step 5. SomeTRIAGEDlines missing → re-spawn over the missing ids only. All triaged but thedocs(<slug>): file triaged impactscommit absent fromgit log→ step 4. The store is on disk beside the ledger, so a rebuild never loses a deferral.propose/integratequery the PR's state (VCS port) before pr create, and CI and merge state before merging.integrate(after teardown)check git worktree listfrom<main-repo-dir>: when the worktree is gone, teardown already ran — skip step 3 and continue at step 4. The ledger outlives the worktree;Donederives the delta and the--sinceisos from it, never from a summary.- Walk from
next.
Closed run. The ledger is gone, so the summary is all that is left; never guess the
outcome from it. Run driver-state.sh active. Exit 1 (undriven): tell the user no open
ledger exists for <run-id> (closed, or never opened) and stop, writing nothing. Driven
(exit 0): list, through the VCS port, every same-repository PR (never one from a fork) in
any state whose head is <type>/<run-id>, for <type> in feat, fix, chore, as the
resume table queries PR state for propose/integrate. The VCS host outranks the summary,
as the ledger does on the normal path.
- Exactly one such PR, merged — run the election of §Done's Driver hand-off (rules in
Election (driven runs only)) from
<main-repo-dir>, since the worktree went atintegrate: writenext.mdandnext.promptin a freshmktemp -ddir outside every working tree, then callfinished <pr-url>with both. No election, or exit 3 →finished <pr-url>alone; on exit 3 the final message says the election was withheld and why. - No such PR, or exactly one not merged →
blocked integrate -withnot merged <pr-or-branch>on stdin. - More than one such PR — a reused slug leaves an earlier run's PR on the same head, so
the run cannot tell which is its own →
blocked integrate -withambiguous PR history <branch>on stdin; neverfinished.
driver="${CRAFT_ROOT:-${CLAUDE_PLUGIN_ROOT}}/scripts/driver-state.sh"
"$driver" active
"$driver" finished <pr-url> --election <tmp-dir>/next.md --prompt <tmp-dir>/next.prompt
"$driver" finished <pr-url>
"$driver" blocked integrate - <<'REASON'
not merged <pr-or-branch>
REASONEach Bash call starts with the driver= line. The status write is the last tool call. Exit 4
(a status already exists: a restarted session with its transcript intact already wrote it)
or any other non-zero exit is surfaced to the user, never a blocker.
Done
The ledger at craft-runs/<run-id>.md outlives integrate's worktree-teardown.sh:
appends continue after teardown until close ends the run. Every call after teardown —
run-ledger.sh, emit-metrics.sh — runs from <main-repo-dir>, since the worktree it
would otherwise run from is gone.
Memory save (once per run). delta is derived from the ledger's lines carrying this
run's run-id, as concern-keyed facts — the store's per-concern schema and its
leak guardrails still bind (docs/contributing/specs/memory.md): paths repo-RELATIVE
never absolute, gate commands stored BARE with any env/secret assignment prefix
stripped. The ledger is run-local, but the store it feeds is committed, so the scrub
happens here on the way in. Derive the delta here, from the ledger file itself — it
outlives the worktree (docs/contributing/specs/run-record.md) — never from a summary.
A MEMORY-RETRACT(<concern>): <merge-key> line derives to { concern, payload, retract:
true } rather than to a plain observation; for the findings concern the <merge-key>
is the payload split on the first run of whitespace, first field file, remainder
pattern (see the spec's Token vocabulary). Resolve the store path from memory.ref (default
.claude/craft-memory.md) rooted at the repo ROOT, same as load (the engine joins
ref under the repo root and refuses a path that escapes it). Call
save(repoRoot, view, delta, deps) once, atomically — view is the run-start
MemoryView (so non-re-observed entries decay rather than vanish) and deps carries
writeStore/validators/caps/run/ref — the same validate-on-read map load took, so
an observation that breaks the store's schema (e.g. an upper-case finding severity) is dropped
rather than written. A failed save is a recorded
warning in the run record — never a blocker (ADR-120); no locking (last-flush-wins).
Writes are buffered all run and flushed once here, so a phase that blocked mid-run leaves
the store unchanged (atomicity; Req 6).
A failed ledger append (anywhere in the walk) is surfaced to the user in-session and the
run continues; it is not recorded into the ledger itself (that would be circular) —
same posture as a failed save above.
Metrics ledger (separate, append-only). Call
bash "${CRAFT_ROOT:-${CLAUDE_PLUGIN_ROOT}}/scripts/emit-metrics.sh" --run <run-id> --session "$CLAUDE_CODE_SESSION_ID" once
from <main-repo-dir>; it groups this session's sub-agent transcripts by phase, appends one
row per agent-spawned phase to .claude/craft-metrics.md, and prints what it appended.
When this session compacted, it then prints one advisory line,
Compactions: <n> (main <m>, sub-agent <s>) — estimated summary-call cost <lo>k–<hi>k equiv (estimate; not in totals)
— stdout only, never a ledger row. A phase that ran twice in
one session (a revision round, or validation and architecture sharing one role) needs its
own call with --phase <phase-id> --since <iso8601> --session "$CLAUDE_CODE_SESSION_ID", where --since is the iso run-ledger.sh stamped on that
phase's latest PHASE-START(<phase>): line in the ledger, or its
row re-counts the first run of that phase; a --phase call never prints the Compactions:
line. A phase with no transcript records
transcript=na. Never hand-assemble a row; never write metrics into the learnings store
.claude/craft-memory.md.
run-ledger.sh close <run-id> — the last ledger action, once the run record has been read for
the final message: it removes the pointer, the ledger and the impacts store. On an already-closed run it is a
silent exit 0, so a session restarted after its process died re-runs it and loses nothing.
Driver hand-off. Once close has run, run driver-state.sh active; exit 1 skips this
paragraph. Driven (exit 0):
- PR merged — elect the next item (below), write the rationale to
next.mdand the ready-to-paste prompt tonext.promptin a freshmktemp -ddir outside the working tree, then calldriver-state.sh finishedwith both. No election →finished <pr-url>alone. Exit 3 (the prompt still held a token that reads as--policy— the bare flag, or a variant with invisible characters, Unicode dashes, homoglyphs, another case or glued punctuation) → re-runfinished <pr-url>alone and say in the final message that the election was withheld and why. - No merge (
integrateresolved tonever, the merge ask declined,integrateskipped, or no remote) →driver-state.sh blocked integrate -withnot merged <pr-or-branch>on stdin, no election.
driver="${CRAFT_ROOT:-${CLAUDE_PLUGIN_ROOT}}/scripts/driver-state.sh"
"$driver" active
"$driver" finished <pr-url> --election <tmp-dir>/next.md --prompt <tmp-dir>/next.prompt
"$driver" finished <pr-url>
"$driver" blocked integrate - <<'REASON'
not merged <pr-or-branch>
REASONEach Bash call starts with the driver= line. The status write is the run's last tool call:
nothing follows it but the final message. Exit 4 (a status already exists) or any other
non-zero exit is surfaced to the user, never a blocker.
Election (driven runs only). With backlog: { source: file }: the first open entry
top-down in the backlog file that is unblocked — no unmet dependency, no paid eval, no
interactive-only step — with the context a cold session needs (decision points, gates, known
traps). The prompt is /craft:run <entry>; the script strips any --policy flag, because
pre-approval belongs to the driver, per entry. source: custom (no list operation) or no
backlog: key: no election, the status alone. An undriven run elects nothing.
Final message: the PR URL (or branch name if no remote) + one-line summary + the run
record + the Compactions: line verbatim when the run-level emit-metrics call printed
one.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

