Skip to content
4 changes: 4 additions & 0 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ v2 is a rethink of how coder operates: from an MCP tool that Claude calls, to a

---

## Documentation

- [Prompt Contracts](docs/prompt-contracts.md) — cross-prompt artifact section registry

## Components

### 1. Daemon
Expand Down
43 changes: 43 additions & 0 deletions docs/prompt-contracts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Prompt Contracts

## Why

Machine prompts produce and consume structured artifacts (PLANREVIEW.md, PLAN.md, ISSUE.md, etc.). When section names are hardcoded in multiple files, they drift — a section added to a producer but missing from the consumer silently breaks downstream logic. Hard parsers like `parsePlanVerdict` also key off specific headings and fail silently when names change.

## The Registry

`src/machines/prompt-contracts.js` exports a `CONTRACTS` object keyed by artifact name. Each entry declares:

- `producedBy` — file path of the producing machine
- `consumers` — file paths of consuming machines
- `format` — `"markdown"` or `"json"`
- `sections` — canonical section/field names
- `sectionDescriptions` — optional per-section prose appended by `renderRequiredSections`
- `findingFields` / `findingFieldExamples` — used by `REVIEW_FINDINGS.md`

Render helpers convert entries into prompt-ready strings:

- `renderRequiredSections(key)` — numbered list for heading-level output; appends `sectionDescriptions` after an em-dash when present
- `renderCritiqueSectionList(key)` — comma-joined actionable names (strips parentheticals, excludes Verdict) for inline use
- `renderFindingExample(key)` — bulleted `- **Field**: example` lines built from `findingFields` + `findingFieldExamples`
- `getSections(key)` — raw array for programmatic access

## Adding a Section to an Existing Artifact

1. Add the section name to the `sections` array in the corresponding `CONTRACTS` entry.
2. Run `node --test test/prompt-contracts.test.js` to verify the registry is consistent.
3. The producer and consumer prompts will pick up the change automatically via render helpers.

## Adding a New Artifact

1. Add a new key to `CONTRACTS` with `producedBy`, `consumers`, `format`, and `sections`.
2. Add corresponding tests in `test/prompt-contracts.test.js`.
3. Update producer/consumer machine files to import from the registry.

## Test Enforcement

`test/prompt-contracts.test.js` validates:

- All producer/consumer files exist on disk
- Synthetic artifacts built from registry headings roundtrip through `parsePlanVerdict` and `parseReviewVerdict`
- Render helpers produce output containing all declared sections
24 changes: 12 additions & 12 deletions src/machines/develop/implementation.machine.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
saveState,
} from "../../state/workflow-state.js";
import { defineMachine } from "../_base.js";
import { renderCritiqueSectionList } from "../prompt-contracts.js";
import { makeClaudeSessionId } from "./_session.js";
import {
artifactPaths,
Expand Down Expand Up @@ -131,18 +132,17 @@ Build upon existing correct work. Do not duplicate or revert it.

## Step 1: Address Critique
Update ${paths.plan} to resolve every finding in the critique —
Critical Issues, Over-Engineering Concerns, Data Structure Review,
Concerns, and Questions. For each Question: answer it only when the
repo or ISSUE.md gives you an explicit, verifiable answer. When you
can't:
- **Blocking question** (affects required behavior, acceptance
criteria, or API/data-shape the implementation must match): STOP.
Record the question under an \`## Open Questions (BLOCKING)\`
section in ${paths.plan} and do NOT produce an implementation diff.
Let the workflow surface the blocker.
- **Non-blocking question** (style, naming, minor polish): record
the question and the working assumption under \`## Open Questions\`
in ${paths.plan}, then proceed. Make the assumption obvious so
${renderCritiqueSectionList("PLANREVIEW.md")}. Answer each open
question only when the repo or ISSUE.md gives you an explicit,
verifiable answer. When you can't:
- **Blocking** (affects required behavior, acceptance criteria, or
API/data-shape the implementation must match): STOP. Record the
open item under an \`## Open Questions (BLOCKING)\` section in
${paths.plan} and do NOT produce an implementation diff. Let the
workflow surface the blocker.
- **Non-blocking** (style, naming, minor polish): record the open
item and the working assumption under \`## Open Questions\` in
${paths.plan}, then proceed. Make the assumption obvious so
reviewers can catch it.

If the critique says REJECT, revise significantly before proceeding.
Expand Down
23 changes: 13 additions & 10 deletions src/machines/develop/issue-draft.machine.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ScratchpadPersistence } from "../../state/persistence.js";
import { loadState, saveState } from "../../state/workflow-state.js";
import { buildIssueBranchName } from "../../worktrees.js";
import { defineMachine } from "../_base.js";
import { renderSectionsWithDescriptions } from "../prompt-contracts.js";
import {
artifactPaths,
checkArtifactCollisions,
Expand Down Expand Up @@ -362,9 +363,10 @@ preamble, no commentary). If you wrote it to disk via a tool, also
output its contents to stdout.

## Required Sections (in order)
1. **Metadata** — Source, Issue ID, Repo Root (relative), Difficulty (1-5).
2. **Problem** — what's wrong or missing, with specific file/function refs.
3. **Requirements** — behavioral requirements in EARS syntax. Follow
${renderSectionsWithDescriptions("ISSUE.md", {
Metadata: "Source, Issue ID, Repo Root (relative), Difficulty (1-5).",
Problem: "what's wrong or missing, with specific file/function refs.",
Requirements: `behavioral requirements in EARS syntax. Follow
these sentence forms, substituting concrete project-specific text
for the \`<...>\` placeholders. **Do NOT emit the angle-bracket
placeholders or backticks verbatim** — every requirement in the
Expand All @@ -373,9 +375,9 @@ output its contents to stdout.
- Event-driven: \`WHEN <trigger>, the <system> shall <behavior>.\`
- State-driven: \`WHILE <state>, the <system> shall <behavior>.\`
- Unwanted Behavior: \`IF <trigger>, THEN the <system> shall <behavior>.\`
- Optional Feature: \`WHERE <feature is present>, the <system> shall <behavior>.\`
4. **Changes** — exactly which files change and how.
5. **Testing Strategy** — search the codebase for existing test files and
- Optional Feature: \`WHERE <feature is present>, the <system> shall <behavior>.\``,
Changes: "exactly which files change and how.",
"Testing Strategy": `search the codebase for existing test files and
patterns first, then list: existing tests that cover related behavior
(paths + what they test), the repo's test framework and conventions,
and concrete new test cases (inputs, outputs, edge cases).${
Expand All @@ -385,11 +387,12 @@ output its contents to stdout.
implementation agent should write BEFORE coding (test name, assertion,
expected failure reason — e.g. missing function, wrong return value).`
: ` For this low-complexity issue, test-after is acceptable if a failing-test-first approach isn't practical.`
}
6. **Verification** — a concrete shell command or test that proves the
}`,
Verification: `a concrete shell command or test that proves the
fix works (e.g. \`npm test\`, \`node -e "..."\`, \`curl ...\`).
Downstream agents use this to close the feedback loop.
7. **Out of Scope** — what this does NOT include.
Downstream agents use this to close the feedback loop.`,
"Out of Scope": "what this does NOT include.",
})}
`;

const res = await agent.execute(issuePrompt, {
Expand Down
24 changes: 3 additions & 21 deletions src/machines/develop/plan-review.machine.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from "../../helpers.js";
import { loadState, saveState } from "../../state/workflow-state.js";
import { defineMachine } from "../_base.js";
import { renderRequiredSections } from "../prompt-contracts.js";
import { withSessionResume } from "./_session.js";
import {
artifactPaths,
Expand Down Expand Up @@ -88,17 +89,7 @@ function buildCritiqueRetryPrompt(planPath, critiquePath, round) {
`You are resuming plan review in a **fresh session**. The prior attempt exited successfully but did not create ${critiquePath} and produced no capturable critique in output.\n\n` +
`1. Read the implementation plan at **${planPath}** in full.\n` +
`2. Write a critical plan critique as markdown to **${critiquePath}**.${roundNote}\n\n` +
`Required sections (in order):\n` +
`1. Critical Issues (Must Fix)\n` +
`2. Over-Engineering Concerns — flag speculative optimizations (caches,\n` +
` memoization, fancy algorithms) that lack a measurement, and flag fancy\n` +
` data structures where plain arrays/objects would work for the expected n\n` +
`3. Data Structure Review — is the plan carrying the right data shapes?\n` +
` Would different data make the algorithm self-evident? Are core types\n` +
` reused from elsewhere in the repo?\n` +
`4. Concerns (Should Address)\n` +
`5. Questions (Need Clarification)\n` +
`6. Verdict (REJECT | REVISE | PROCEED WITH CAUTION | APPROVED)\n\n` +
`Required sections (in order):\n${renderRequiredSections("PLANREVIEW.md")}\n\n` +
`Constraints:\n` +
`- Do not modify tracked files.\n` +
`- Keep critique concrete with file-level references when possible.\n` +
Expand Down Expand Up @@ -253,16 +244,7 @@ export default defineMachine({
const basePromptBody = `Review ${paths.plan} and write a critical plan critique to ${paths.critique}.${roundNote}

Required sections (in order):
1. Critical Issues (Must Fix)
2. Over-Engineering Concerns — flag speculative optimizations (caches,
memoization, fancy algorithms) that lack a measurement, and flag fancy
data structures where plain arrays/objects would work for the expected n
3. Data Structure Review — is the plan carrying the right data shapes?
Would different data make the algorithm self-evident? Are core types
reused from elsewhere in the repo?
4. Concerns (Should Address)
5. Questions (Need Clarification)
6. Verdict (REJECT | REVISE | PROCEED WITH CAUTION | APPROVED)
${renderRequiredSections("PLANREVIEW.md")}

Constraints:
- Do not modify tracked files.
Expand Down
23 changes: 13 additions & 10 deletions src/machines/develop/planning.machine.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { z } from "zod";
import { stripAgentNoise } from "../../helpers.js";
import { loadState, saveState } from "../../state/workflow-state.js";
import { defineMachine } from "../_base.js";
import { renderSectionsWithDescriptions } from "../prompt-contracts.js";
import {
executeWithSessionAuthRetry,
makeClaudeSessionId,
Expand Down Expand Up @@ -232,21 +233,23 @@ code is buggier and n is usually small.
## Phase 3: Write ${paths.plan}

Required sections:
1. **Summary** — one paragraph.
2. **Approach** — which approach and why, referencing existing patterns.
3. **Files to Modify** — list each existing file with the specific
changes it needs.
4. **Files to Create** — only when absolutely necessary; prefer
modifying existing files. Justify each new file.
5. **Dependencies** — versions + justification.
6. **Testing Strategy** — reference ISSUE.md's strategy, list existing
${renderSectionsWithDescriptions("PLAN.md", {
Summary: "one paragraph.",
Approach: "which approach and why, referencing existing patterns.",
"Files to Modify": `list each existing file with the specific
changes it needs.`,
"Files to Create": `only when absolutely necessary; prefer
modifying existing files. Justify each new file.`,
Dependencies: "versions + justification.",
"Testing Strategy": `reference ISSUE.md's strategy, list existing
related tests, describe new test cases (inputs, outputs, edges), and
specify the test command. For difficulty ≥ 3, add a Red/Green TDD
subsection listing the failing assertions to write BEFORE code (test
name + expected failure reason — e.g. "ReferenceError:
parseConfig is not defined"). For difficulty < 3, test-after is
acceptable only when a failing-test-first approach isn't practical.
7. **Out of Scope** — what this change does NOT include.
acceptable only when a failing-test-first approach isn't practical.`,
"Out of Scope": "what this change does NOT include.",
})}

## House Rules
- Small scope: 1-3 files, existing utilities over new abstractions,
Expand Down
12 changes: 2 additions & 10 deletions src/machines/develop/quality-review.machine.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from "../../helpers.js";
import { loadState, saveState } from "../../state/workflow-state.js";
import { defineMachine } from "../_base.js";
import { renderReviewFindingsTemplate } from "../prompt-contracts.js";
import {
executeWithSessionAuthRetry,
makeClaudeSessionId,
Expand Down Expand Up @@ -147,16 +148,7 @@ Write findings to ${paths.reviewFindings} as markdown. Each finding is a
minor), **File**, **Lines**, **Issue**, **Suggestion** — like this:

\`\`\`markdown
# Review Findings — Round ${round}

## Finding 1
- **Severity**: major
- **File**: src/foo.js
- **Lines**: 42-58
- **Issue**: <what's wrong>
- **Suggestion**: <how to fix>

## VERDICT: REVISE
${renderReviewFindingsTemplate("REVIEW_FINDINGS.md", round)}
\`\`\`

End the file with \`## VERDICT: APPROVED\` or \`## VERDICT: REVISE\` as
Expand Down
Loading
Loading