write-contract — Primitive Decision Record author
Current forge state (NO_STATE means the pipeline has not started in this project):
!cat .forge/state.json 2>/dev/null || echo NO_STATE
If the line above rendered as [shell command execution disabled by policy], read
.forge/state.json with the Read tool as the very first action and apply the guard below to
its contents (this is the documented disableSkillShellExecution fallback — see
references/cross-primitive-traps.md).
Phase guard — apply before any other work
| Injected state | Action |
|---|---|
NO_STATE |
REFUSE. Say exactly: "No forge pipeline in this project. Run /plugin-forge:forge to start it." Stop. |
phase: "interview" |
REFUSE. Say exactly: "The interview is not finished. Run /plugin-forge:forge to complete it; it will hand off here." Stop. |
phase: "contract" |
Proceed (normal mode). |
phase: "evals" | "armed" | "building" | "smoke" | "verify" | "ship" — arguments do NOT contain --revise |
REFUSE. Say exactly: "The contract is approved and hash-frozen. To change it run /plugin-forge:write-contract --revise (this regresses the pipeline and invalidates the armed suite). To continue the current phase, run /plugin-forge:forge." Stop. |
phase past contract AND arguments contain --revise |
Proceed in revise mode (see "Revise mode" below). |
This skill deliberately uses default invocation, NOT disable-model-invocation: true, so the
forge conductor can chain it through the Skill tool; the guard above — not invocation control —
carries the safety. That decision is itself the worked example of invocation-control reasoning
in references/pdr-template.md (CAP-0).
Mission
Turn design/INTENT.md into design/CONTRACT.md — the Primitive Decision Record (PDR), the
five-steps-ahead artifact the entire pipeline enforces. The governing rule is
unverifiable → unbuildable: build-evals derives its whole task list from column C5, so a
capability no grader can observe is redesigned or rejected here, never discovered broken after
generation. Skipping the lookahead leaves the eval builder with literally nothing to generate.
This is design work that deserves the deepest reasoning available: ultrathink through every consequence chain before committing a row. A wrong composition written here gets sha256-frozen, transposed into the eval suite, and mechanically enforced by hooks for the rest of the build.
Procedure
Step 1 — Load the knowledge layer
- Invoke the
plugin-forge:primitive-catalogskill via the Skill tool. It routes to one card per primitive family; each card ends with Consequence prompts, Reject when, and Eval hooks sections. Do not select any primitive from memory when a card covers it. - Read
${CLAUDE_SKILL_DIR}/references/pdr-template.md— the complete CONTRACT.md template. - Read
${CLAUDE_SKILL_DIR}/references/slot-ledger.md— one decision rule + proving check per manifest slot. - Read
${CLAUDE_SKILL_DIR}/references/cross-primitive-traps.md— the trap table consulted while filling C3, C4, and C6.
Step 2 — Read the inputs
Read design/INTENT.md. If it is missing, REFUSE with: "design/INTENT.md not found — run
/plugin-forge:forge to redo the interview." The intent fixes runtime environments, external
systems (live vs mock), side-effect inventory, and org-policy constraints; the PDR must not
contradict it. Where INTENT.md is silent on a fact a chain column needs, ask the user now —
never invent runtime facts.
Step 3 — Offer domain grounding (scientific/business content)
When the plugin will embed domain claims — assay protocols, statistical methods, regulatory or
financial rules, literature-domain conventions — offer to run the bundled /plugin-forge:skill-forge
research pipeline on those topics before writing capability rows, so domain content is
research-grounded rather than recalled. If the user accepts, run it, then cite the produced
research artifacts in PDR section 0. If /plugin-forge:skill-forge is unavailable in this environment, say
so and record "domain grounding: declined/unavailable" in section 0 — never silently skip.
Step 4 — Decompose intent into capabilities
Write each capability as verb + object + quality bar (e.g. "generate a cited literature review in reviews/.md with ≥N key papers cited"). Number them CAP-1… Keep the set minimal and testable; merge capabilities that share a grader and split ones that need different enforcement.
Step 5 — Enumerate candidates and consult the cards
For EVERY capability, enumerate 2–3 candidate primitive compositions — never a single default. For each candidate, load the matching catalog card and answer its Consequence prompts in writing, inside the PDR (the "Card consequence prompts answered" block of the capability entry). A candidate that hits a card's Reject when criterion goes straight to section 3 (rejected alternatives) with the killing chain step named.
Step 6 — Fill the six-step consequence chain (C1–C6) for the chosen composition
Fill every column per references/pdr-template.md. Summary of the columns:
- C1 Trigger — invoker (user / model / hook / schedule), trigger phrases, reliability under listing pressure (1%-of-context listing budget, 1,536-char description cap), and the trigger task IDs that will measure it.
- C2 Context economics — always-on chars, on-invoke tokens, compaction survival plan (critical guidance in the first 5,000 tokens; 25,000-token combined re-attach budget), fork vs inline.
- C3 Enforcement — what mechanically guarantees the behavior: hook event + output shape, permission rule, allowed-tools grant, or sandbox. "Prose" is not enforcement and requires an explicit written justification.
- C4 Failure & degradation — what silently breaks (background tool filter, plugin-agent
frontmatter stripping,
disableSkillShellExecution, stdio crash, org model allowlist, headless-pconstraints, cloud sessions) and the designed fallback, plus version floors. - C5 Testability — HARD GATE — grader type (
workspace|transcript|state_check|judge|trigger) + the observable signal + ≥1 planned eval task ID. A row missing any of the three is INVALID: redesign the capability until observable, or reject it. "Manual QA" is not a grader type. - C6 Distribution & lifecycle — install level, version floor, update semantics (SHA-versioned during dev, semver + tag at release), migration.
Assign task IDs (T01, T02, …) unique across the contract; build-evals generates exactly these. Every auto-invoked component must plan both should-trigger AND should-not-trigger task IDs.
Step 7 — Rejected alternatives (mandatory)
Record at least one rejected alternative per capability, each naming the chain step (C1–C6) that killed it and a one-line reason. "No alternative existed" is unacceptable — Step 5 guarantees candidates. Thin rejections ("seemed worse") block critic approval.
Step 8 — Slot ledger
Fill section 4 with one row per manifest slot — all fourteen, per
references/slot-ledger.md: skills, commands (legacy), agents, hooks, mcpServers, lspServers,
workflows, monitors, bin, outputStyles, themes, settings.json (agent /
subagentStatusLine), channels, userConfig. Every row is USE or NOT-USED with the cited
decision rule; every USE row names the smoke/eval check that proves it. NOT-USED rows are
mandatory, not optional politeness — they are how checkbox-driven primitive selection dies.
Step 9 — Runtime profile and cost sheet
Complete PDR sections 1 and 5. The runtime profile fixes the minimum Claude Code version (name
the feature that sets the floor), install level, distribution channel, and the permission story
(the permissions.allow rules the generated plugin will ship). The cost sheet estimates
always-on chars and on-invoke tokens per component, records the model/effort routing table, and
sets the --max-cost-usd value for eval runs; after scaffold, claude plugin details output
becomes the regression baseline for these numbers.
Step 10 — Self-audit
Before dispatching the critic, walk references/cross-primitive-traps.md top to bottom and
verify every applicable trap appears in the affected capability's C3/C4/C6 cell with its
workaround. Then verify the hard rules (bottom of this file).
Step 11 — spec-critic loop
Dispatch the plugin-forge:spec-critic agent (read-only; three lenses: context economics,
headless/enforcement, distribution/maintenance) against the draft. Run it foreground and wait —
do not proceed on a pending verdict. It writes .forge/critic-report.md with APPROVED or
BLOCKED + findings. On BLOCKED: log each finding in PDR section 6, fix the contract (not the
finding's wording), and re-dispatch. Loop until APPROVED. Never edit .forge/critic-report.md.
Step 12 — User approval, hash freeze, phase advance
- Present a summary: capability index, chosen compositions, rejected-alternative count, slot ledger USE rows, cost totals, task-ID coverage. Ask the user to approve via AskUserQuestion (options: approve / request changes).
- On approval, set the Status line to
USER-APPROVEDand fill the approval block (approver, ISO-8601 date). The sha256 is NOT embedded in the file — embedding the hash would change the file it hashes; it lives only in.forge/state.json(the mirror the guards compare against). - Compute the hash and advance the phase with exactly:
python3 - <<'EOF'
import json, hashlib, datetime, pathlib
state_path = pathlib.Path(".forge/state.json")
state = json.loads(state_path.read_text())
state["phase"] = "evals"
state["contract_sha256"] = hashlib.sha256(pathlib.Path("design/CONTRACT.md").read_bytes()).hexdigest()
state["updated"] = datetime.datetime.now(datetime.timezone.utc).isoformat()
state_path.write_text(json.dumps(state, indent=2) + "\n")
print("phase=evals contract_sha256=" + state["contract_sha256"])
EOF
- Tell the user the next step:
/plugin-forge:build-evals(or/plugin-forge:forgeto let the conductor route). State plainly: the contract is now frozen — any edit todesign/CONTRACT.mdbreaks the hash and the pipeline regresses tocontractuntil re-approval.
Revise mode (--revise)
Reopening an approved contract invalidates downstream work. Before touching the file:
- Warn the user explicitly: the frozen eval suite was built against the old contract; after revision, arm-evals must re-arm (it bumps the suite version), and any build loop in flight must be stopped. Get explicit confirmation.
- Regress the state:
python3 - <<'EOF'
import json, datetime, pathlib
state_path = pathlib.Path(".forge/state.json")
state = json.loads(state_path.read_text())
state["phase"] = "contract"
state["contract_sha256"] = ""
state["updated"] = datetime.datetime.now(datetime.timezone.utc).isoformat()
state_path.write_text(json.dumps(state, indent=2) + "\n")
EOF
- Edit under the normal procedure (Steps 4–10 for affected capabilities), append the revision rationale to PDR section 6, then re-run Steps 11–12 in full: critic re-approval, user re-approval, new hash. Shortcutting the critic on "small" revisions is forbidden — the sha256 re-review trigger exists precisely because small edits change enforcement semantics.
Hard rules (enforced by spec-critic and the forge gates)
- C5 is a hard gate: grader type + observable signal + ≥1 task ID, or the row is INVALID.
- ≥1 rejected alternative per capability, each naming the killing chain step.
- "Prose" in C3 requires written justification; prefer a hook, permission rule, or sandbox.
- All fourteen slot-ledger rows filled; every USE names its proving check.
- Card Consequence prompts answered in the PDR for every candidate considered.
- Never hand-edit
design/CONTRACT.mdafter approval; use--revise. - Never invent runtime facts absent from INTENT.md — ask the user.
- Scoped names everywhere: MCP tools as
mcp__plugin_<plugin>_<server>__<tool>, servers asplugin:<plugin>:<server>, agents as<plugin>:<agent>(seereferences/cross-primitive-traps.mdfor the full freshness-guard list).