# Write Contract

> Author the Primitive Decision Record (design/CONTRACT.md) during the forge pipeline's contract phase: per-capability primitive compositions with six-step consequence chains (trigger, context economics, enforcement, failure & degradation, testability, distribution), at least one rejected alternative per capability, the full plugin slot ledger, a cost sheet, and the eval task IDs that feed build-evals. Use for: write the contract, draft the PDR, design the primitive composition, reopen an approved contract with --revise, or fix a BLOCKED spec-critic report.

- Skill: `aznatkoiny/write-contract` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add aznatkoiny/write-contract`
- Raw SKILL.md: https://api.skillmd.com/api/skills/aznatkoiny/write-contract/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: aznatkoiny (https://skillmd.com/u/aznatkoiny)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/aznatkoiny/write-contract

---


# 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

1. Invoke the `plugin-forge:primitive-catalog` skill 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.
2. Read `${CLAUDE_SKILL_DIR}/references/pdr-template.md` — the complete CONTRACT.md template.
3. Read `${CLAUDE_SKILL_DIR}/references/slot-ledger.md` — one decision rule + proving check per
   manifest slot.
4. 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/<topic>.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 `-p` constraints, 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

1. 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).
2. On approval, set the Status line to `USER-APPROVED` and 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).
3. Compute the hash and advance the phase with exactly:

```bash
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
```

4. Tell the user the next step: `/plugin-forge:build-evals` (or `/plugin-forge:forge` to let the
   conductor route). State plainly: the contract is now frozen — any edit to
   `design/CONTRACT.md` breaks the hash and the pipeline regresses to `contract` until
   re-approval.

## Revise mode (`--revise`)

Reopening an approved contract invalidates downstream work. Before touching the file:

1. 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.
2. Regress the state:

```bash
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
```

3. 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.md` after 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 as
  `plugin:<plugin>:<server>`, agents as `<plugin>:<agent>` (see
  `references/cross-primitive-traps.md` for the full freshness-guard list).

