agentleFS
scolladonscolladon/craft/skills/run/SKILL.md

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

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.
  • MicrosoftSkill

    report

    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

  • MicrosoftSkill

    doctor

    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

  • BlockSkill

    plan

    Draft a TDD, PRD, or lightweight plan in the conversation without committing it.

    Also in 4 projects that use this

  • VercelSkill

    setup

    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

  • AnthropicSkill

    status

    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 FaceSkill

    implement

    Make tests pass. Invoke after /write-tests produces failing tests.

    Also in 4 projects that use this

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
  1. Very specific:

    When --config is absent: use .claude/workflow.md as <manifest-path>.

    line 56
  2. Very specific:

    Run "${CRAFT_ROOT:-${CLAUDE_PLUGIN_ROOT}}/scripts/manifest-lint.sh" <manifest-path> (passing the resolved path from step 0b).

    line 58
  3. Distinctive:

    Read the manifest (frontmatter = config, body = policy rationale).

    line 60
  4. Distinctive:

    STOP; surface stderr to the user.

    line 77
  5. Very specific:

    If effective[] is empty: STOP.

    line 78
  6. Very specific:

    Use waivers[] for the gate-release decision (each proposeGateReleased: true releases that phase's propose-gate).

    line 123
  7. Very specific:

    Never silence open's stdout: the run-key it prints into this session's transcript is what binds the compaction hooks to this run.

    line 163
  8. Very specific:

    Never run close without the user choosing option 2.

    line 181
  9. Distinctive:

    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 …

    line 184
  10. Very specific:

    Every ledger line opens with a fixed, greppable token.

    line 203
  11. Very specific:

    Necessity probe (auto-skip): If phase.autoSkipEligible is true, evaluate against the live change whether the phase has any work *before …

    line 227
  12. Very specific:

    After an implementation no-op (the run record holds NO-OP(implementation):), the review, refactoring, validation and architecture signals …

    line 242
  13. Very specific:

    When provably empty: (a) append the fixed token auto-skip: <phase> — evaluated unnecessary (<signal>) to the run record (e.g.

    line 248
  14. Very specific:

    If the skill or plugin a procedure names is not installed (no skills/<id>/ dir for a craft-native procedure.

    line 266
  15. Very specific:

    Memory hint (advisory): Before assembling the block, slice the in-session MemoryView for this phase's concern(s) (see per-phase notes in …

    line 311
  16. Very specific:

    inline: run the phase body in-thread — no Task spawn.

    line 363
  17. Very specific:

    NOT start until every id in the in-session awaitingHarnesses set has landed and its gate is green

    line 394
  18. Very specific:

    If codeProducing: true: apply gate-cadence invariant (targeted gate per fix commit.

    line 400
  19. Very specific:

    If codeProducing: false and gate non-empty: run gate once at phase boundary.

    line 402
  20. Distinctive:

    If gate is empty string: no gate check.

    line 403
  21. Very specific:

    If an executing-harness was waived (skipped via pipeline.skip), its gate is released — the waiver is in Resolution.waivers[] and …

    line 472
  22. Distinctive:

    Every other finding is folded.

    line 520
  23. Distinctive:

    Artifact is the handoff: every delegated agent's contribution must be in a committed artifact before the phase closes.

    line 561
  24. Distinctive:

    Agent spawns: every role-agent spawn is structured as:

    line 571
  25. Distinctive:

    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, …

    line 613
  26. Very specific:

    Every §0 stop uses phase id resolve.

    line 626
  27. Very specific:

    Policy consult: before any phase performs a nameable outward action (a member of POLICY_ACTIONS — see docs/contributing/specs/policy.md), …

    line 631
  28. Very specific:

    Provenance: no phase/ADR/backlog references inside source or test code, ever.

    line 643
  29. Very specific:

    When locate prints nothing, the run is closed (close already ran and removed the ledger) or was never opened.

    line 669
  30. Very specific:

    Metrics ledger (separate, append-only): Call bash "${CRAFT_ROOT:-${CLAUDE_PLUGIN_ROOT}}/scripts/emit-metrics.sh" --run <run-id> --session …

    line 768
  31. Very specific:

    When this session compacted, it then prints one advisory line, Compactions: <n> (main <m>, sub-agent <s>) — estimated summary-call cost …

    line 772

“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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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>.md and ~/.claude/craft-<name>.md) or a bad-name/traversal diagnostic. Never silently fall back to .claude/workflow.md. When --config is absent: use .claude/workflow.md as <manifest-path>.
  1. 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).

  1. 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-source resolve mechanism see docs/contributing/specs/backlog.md: for source: file, classify by the repo's backlog convention (prose-judgment) and resolve by reading the entry; for source: custom, the script owns the id-form and resolve runs ref with argv ["resolve", id] — id is 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 unreachable custom source is a runtime blocker (never a guessed brief).
  2. Derive a kebab-case topic slug (≤6 words). Print: Resolved → topic: <slug>, brief: <one line> for user confirmation.
  3. Open the run record — an append-only on-disk ledger at <git-common-dir>/craft-runs/<run-id>.md, the directory run-ledger.sh dir prints: inside the git common dir, where no commit can write, outside every working tree and never under ${CLAUDE_PLUGIN_ROOT} — see docs/contributing/specs/run-record.md. Below, run-ledger.sh means that shimmed path, assigned once as $ledger in 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>
    EOF

    Never silence open's stdout: the run-key it prints into this session's transcript is what binds the compaction hooks to this run. open also creates the run's discovered-impacts store <run-id>.impacts.md beside the ledger, written only through run-ledger.sh defer and run-ledger.sh verdict (see the invariant Impacts: fold or defer at discovery).

    A refused open is a STOP. open exits 3 when the run-id is already open: another run of this topic is live, or crashed without close. 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 }:

    1. Stop here. The run is live: continue it in its own session.
    2. 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 hits craft:workspace's collision STOP.
    3. Re-slug. Re-run step 3 with a different topic slug.

    Never run close without 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>.md from open to close, whatever the workspace strategy: workspace never 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:

TokenEmitted 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-MMMthe 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:

  1. Necessity probe (auto-skip). If phase.autoSkipEligible is true, 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 phaseProvably-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 record NO-OP(refactoring):
    documentationno design/change content maps to any documentation surface
    lessonsevaluated only after the cross-phase wait; the ledger holds no lesson-shaped event (craft:lessons step 1 table) and no LESSON-REVERIFY( line and, when validation did not run its step 5, the assert-fresh lessons lane over <base>...HEAD is empty
    impact-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 implementation

    After an implementation no-op (the run record holds NO-OP(implementation):), the review, refactoring, validation and architecture signals are evaluated over <base>...HEAD instead of "since implementation", 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 from WAIVER: and NO-OP(<phase>):; (b) if the phase is an executing-harness, release its awaitingHarnesses entry (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 not autoSkipEligible, the phase runs (steps 2–7). When emptiness cannot be proven, RUN the phase (doubt runs; never auto-skip on an unprovable judgment).

  2. Resolve the skill — invoke phase.procedure verbatim (the descriptor's procedure: field — e.g. craft:design, or an inserted phase's craft:bench / acme:bench). For every craft-native phase the procedure is craft:<phase.id> and the skill dir name equals phase.id, so default phases are unaffected. An inserted phase carries its own procedure: 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 a procedure names is not installed (no skills/<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 descriptor id is unchanged, so the injected contract (step 4) — assembled from that id — is identical, and the swapped worker (agent or inline) always runs inside the same engine-owned contract. A role:- or procedure:-swapped phase can never drop the invariant core. A swapped default-phase procedure: 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 swapped role: the resolver's roleExists probe rejects makes pipeline-resolve exit non-zero at §0 (resolver ok: 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-native craft:<role> refs (a typo'd role fails closed); external my:/acme: refs fail closed unless the ref is registered via extends (extends.agents ∪ the role: of every registered/inserted phase). Inserted/registered-phase contract execution: the walk passes the resolved descriptor to contract-assemble via --descriptor-json (step 4), so a novel/registered id EXECUTEs under the engine-owned contract — the same core + declared bundles that wrap any default phase.
  3. Resolve execution — use phase.execution (agent | inline) from the Resolution. Apply manifest override (phases.<id>.override, phases.<id>.context) as before.
  4. 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 id is 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 via writeFileSync), 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 with readFileSync(path) / reads stdin; passing JSON text inline as the arg value does not work. The bin resolves phase.id against 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 MemoryView for 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 at load — if the slice is empty, the phase runs its own probes. This read is purely advisory and never gates. See docs/contributing/specs/memory.md Claude binding.

    Intention hint (advisory). For the design, decisions and planning phases only, slice the in-session IntentionView for this phase's change scope: the entries whose 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. See docs/contributing/specs/intention.md.

    Lessons hint (advisory). At every phase entry, build the hint with one call (<lessons-dir> = paths.lessons, else docs/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 for planning, the plan parts' ### Context paths for implementation. 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>): via run-ledger.sh append <run-id> <phase.id>, with no time: append stamps 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 first PHASE-START, so a later --since derivation stays right.

  5. Execute via the resolved execution mode (phase.execution).

    agent (default): spawn craft:<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. If phase.role is present AND resolves to a local agent def (agents/<name>.md, <name> = the role ref minus any craft: 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 to agent — 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 setup
    • specification: verify artifact; conversation if no role field (decisions)
    • construction: verify each part; run phase gate per gate-cadence invariant
    • harness (harness-read ∈ contract): apply ALL findings; converge per the phase.harness knobs (dimensions/passes/max_cycles/convergence — craft:review reads them)
    • refinement: judgment (scan + scoping); apply ALL findings
    • harness (harness-exec ∈ contract): start background run with the phase.harness tool/scope/incremental (craft:validation reads them); gate propose on 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-harness
    • delivery (lessons): harvest per craft:lessons; starts only once awaitingHarnesses is empty; never gates propose beyond its own draft lint
    • delivery (impact-triage): triage per craft:impact-triage; starts only once awaitingHarnesses is empty; spawns craft:impact-triager; never itself awaited
    • delivery (propose): pre-propose gate; body; PR creation per policy — does NOT start until every id in the in-session awaitingHarnesses set has landed and its gate is green
    • delivery (integrate): user confirms; cleanup
  6. Gate — read the gate string (.gate) from the Resolution.gateDecisions entry whose phaseId === phase.id. If codeProducing: true: apply gate-cadence invariant (targeted gate per fix commit; phase gate once per round; never commit on known-red). If codeProducing: false and gate non-empty: run gate once at phase boundary. If gate is empty string: no gate check.
  7. 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 existing GATE/NO-OP/inline: lines, in one run-ledger.sh append call to craft-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's GATE line 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 token GATE(<phase.id>): green or GATE(<phase.id>): red to the run record — one line per phase gate result. An auto-skipped or waived executing-harness records no GATE(...) line (the phase did not produce a recorded gate result); an auto-skipped phase records only its auto-skip: line (step 1) — no PHASE-START/PHASE-DONE. A judgment phase (decisions/refactoring) that records a NO-OP(<phase>): line — e.g. NO-OP(decisions): no user-judgment decisions — … or NO-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. The validation phase's DoD sub-concern records a NO-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. planning and implementation record NO-OP(planning): no part remains — … and NO-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 its PHASE-DONE line; NO-OP(implementation): is appended with its PHASE-DONE line only on a green gate — on red it lands with GATE(implementation): red, and PHASE-DONE follows the green re-run (implementation Preamble 2).
  8. On blocker: escalate { phase/part, reason, ≤3 candidate options }. Never spin, never silently abandon.
  9. 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

ConditionBehavior
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 emptyStop; 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 emptyPropose is not gated on any harness; proceed normally
waivers[] is non-emptyExecuting-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 consumerok: 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 when archetype: harness and harness-exec ∈ contract. propose does not start pr create until every phase id in the in-session awaitingHarnesses set (the awaitingHarnesses[] of the Resolution.gateDecisions entry whose phaseId === "propose", captured in §0 step 1d) has landed its run and its gate is green. documentation (archetype: delivery) may parallel a background executing-harness; propose may not. lessons may 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 the AWAITING(propose): set minus recorded releases — auto-skip: <id>, exact NO-OP(<id>):, or the last GATE(<id>) green — the release rules below, stated as ledger facts. If an executing-harness was waived (skipped via pipeline.skip), its gate is released — the waiver is in Resolution.waivers[] and pre-formatted in Resolution.record[] — and propose may proceed without waiting for it. Likewise, if an awaited executing-harness records a runtime no-op — it is enabled and in effective[] (so it carries no engine waiver and IS in awaitingHarnesses), but at runtime its tool-agnostic probe finds nothing and the phase ends with a note, never landing a run — its awaitingHarnesses entry 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 from effective[]); it is the orchestrator treating a recorded no-op as a release at gate-check time, so propose may proceed without waiting for the no-op'd harness. Likewise, a recorded auto-skip: of an awaited executing-harness (it never lands a run, by the same up-front necessity probe) releases its awaitingHarnesses entry the same way — the orchestrator treats a recorded auto-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 in effective[]). So propose is never left waiting on a harness that auto-skipped. Clarification for validation: the entry may carry two recorded sub-outcomes — the technique note AND a NO-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 any NO-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: harness and harness-read ∈ contract has run — during phases of archetype construction or refinement — that is NOT a fix to an existing finding gets its own feature-scoped review before the executing-harness gate closes. The refinement phase'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 green gates.part. A fold that turns red has its uncommitted edit discarded and is deferred with reason the fix did not pass its gate, its expected: being the first failing line. After each fold commit, append FOLDED(<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: defer refuses (exit 2, nothing written) an entry with no proof: body, no expected: 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 and DEFERRED(<file>): I<n> <title> to the ledger, and prints I<n>. Only the orchestrator defers; a role agent reports an IMPACT( line and never acts on it. With no open run (a standalone phase) defer refuses and the impacts are listed in the phase's final output. The impact-triage phase judges every deferred entry before propose.

    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.part over the touched files + gates.review-batch if declared); the full phase-boundary gate runs ONCE per round — after each code-producing phase, after each harness/refinement fix 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:
    1. 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 and docs/contributing/specs/memory.md Claude 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-inject context: — the assembler already appends it; double-injection is a breach.
    2. Working directory and task dynamics (phase id, part text, gate string).
    3. 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.)
  • Model resolution & fallback: resolve each spawn's model as manifest models.<role> → the descriptor's model: field (the canonical per-role tier; the agent-def frontmatter pin is the Claude binding of that same tier) → models.fallback → engine default sonnet → 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 (529 as an HTTP status or API error code, never a bare substring, or overloaded_error) is retried ONCE on the same tier, immediately (the harness already backed off), respawned from the artifact, recording MODEL-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: record MODEL-DEGRADED(<tier>): fallback=<tier> cause=<overload|unavailable>, re-resolve to models.fallback if declared, else the engine default sonnet, 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-blocker policy surface — its last tool call is one Bash call:
    driver="${CRAFT_ROOT:-${CLAUDE_PLUGIN_ROOT}}/scripts/driver-state.sh"
    "$driver" active >/dev/null && "$driver" blocked <phase-id> - <<'REASON'
    <reason>
    REASON
    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 closing REASON line starts at column 0: an indented terminator does not close the heredoc. Every §0 stop uses phase id resolve. An escalation is still asked first; only a stop the run cannot continue from writes BLOCKED. The ledger stays open, so the run resumes as after any STOP. Undriven, active exits 1 and nothing is written; a non-zero exit from blocked is surfaced to the user, never a blocker.
  • Policy consult: before any phase performs a nameable outward action (a member of POLICY_ACTIONS — see docs/contributing/specs/policy.md), the session calls Policy.consult(action, { effectivePolicy: Resolution.policy, binding }) over the held Resolution.policy and the active binding, and obeys the returned surface: proceed (execute silently), ask-then-proceed (raise AskUserQuestion then execute on approval), refuse (do not execute; no-op or block per reversibility), or degrade-to-blocker (headless ask with no pre-approval — block and record). One POLICY(...) token is appended to the run record per consult (see docs/contributing/specs/policy.md greppable record tokens). The three engine floors (never-commit-on-red, validation-triage-gates-propose, artifact-handoff) are not in POLICY_ACTIONS and 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.

  1. The ledger outranks the summary. Locate the ledger from the reorient block's Ledger: line. Without a block (non-Claude harness, the hook absent), run run-ledger.sh locate --run <run-id>, where the run-id is the topic slug re-derivable from the /craft:run message the summary keeps verbatim. When locate prints nothing, the run is closed (close already ran and removed the ledger) or was never opened. Skip steps 2–6 and follow Closed run below. Never re-open the run.
  2. 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-run driver-state.sh active (no ledger token records it: the env is the source of truth).
  3. Pipe that Resolution into run-state:
    node "${CRAFT_ROOT:-${CLAUDE_PLUGIN_ROOT}}/engine/bin/run-state.js" <ledger-path> --run <run-id>
    Exit 1 (an AWAITING(propose): mismatch — the manifest or flags changed mid-run) or exit 2 → blocker { rebuild, reason, ≤3 options }, never a guess. On exit 0, restore completed/inFlight/next/awaitingHarnesses/parts/findings/background from the printed state, and surface its warnings.
  4. 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.
  5. Resume. Re-enter each inFlight phase at walk steps 2–4 (idempotent; step 4 skips a second PHASE-START), then resume per:

    In-flight phaseResume from
    any agent phase except reviewthe 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-run worktree-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 log against the plan. A landed commit without a PART line is verified, then gets its line (with size=? if unknown). Continue at the first part with neither. No plan and the ledger holds NO-OP(planning):: append NO-OP(implementation): no plan parts — planning recorded NO-OP(planning) if absent, run gates.phase (fixing a red one first), and close the phase only on a green gate (GATE(implementation): green and PHASE-DONE in one append).
    reviewreload findings[] for the current cycle; git log fix commits; RULED-OUT lines. A dimension with no FINDINGS line 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-empty out → triage. Dead with empty out → the existing empty-output blocker.
    lessonsa LESSONS: or NO-OP(lessons): line means done; otherwise re-run the harvest from step 1 (step 3 dedupes drafts already committed).
    impact-triageevery DEFERRED id has a TRIAGED line and a FILED line exists for each backlog:* verdict (or the NO-OP(impact-triage): line, or this phase's POLICY( line refusing backlog-write) → step 5. Some TRIAGED lines missing → re-spawn over the missing ids only. All triaged but the docs(<slug>): file triaged impacts commit absent from git 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 list from <main-repo-dir>: when the worktree is gone, teardown already ran — skip step 3 and continue at step 4. The ledger outlives the worktree; Done derives the delta and the --since isos from it, never from a summary.
  6. 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 at integrate: write next.md and next.prompt in a fresh mktemp -d dir outside every working tree, then call finished <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 - with not 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 - with ambiguous PR history <branch> on stdin; never finished.
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>
REASON

Each 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.md and the ready-to-paste prompt to next.prompt in a fresh mktemp -d dir outside the working tree, then call driver-state.sh finished with 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-run finished <pr-url> alone and say in the final message that the election was withheld and why.
  • No merge (integrate resolved to never, the merge ask declined, integrate skipped, or no remote) → driver-state.sh blocked integrate - with not 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>
REASON

Each 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.

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.