Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/flow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 4 additions & 2 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ echo "✓ spec-prep -> $BIN_DIR/spec-prep"

if [ -d "$HOME/.claude" ]; then
mkdir -p "$HOME/.claude/skills"
ln -sfn "$HOME_DIR/skills/millify" "$HOME/.claude/skills/millify"
echo "✓ millify skill -> ~/.claude/skills/millify"
for skill in millify drive-mill; do
ln -sfn "$HOME_DIR/skills/$skill" "$HOME/.claude/skills/$skill"
echo "✓ $skill skill -> ~/.claude/skills/$skill"
done
fi

command -v conductor >/dev/null 2>&1 || cat <<'MSG'
Expand Down
11 changes: 10 additions & 1 deletion site/content/concepts/design-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: Design rules
description: The invariants that make mill runs trustworthy.
group: Concepts
order: 6
order: 7
---

Each rule here exists because its absence produced a concrete failure —
Expand All @@ -17,6 +17,15 @@ Universal invariants over existing code must carry transition rules, or
they are unsatisfiable incrementally and two honest models will spend six
rounds proving it.

What blocks is severity, not count. An adversarial reader of a real codebase
can almost always name one more edge case, so "zero findings" is an asymptote
— demanding it just trades six rounds of plan review for six of spec review.
The bar is *no blocking or high-severity defect*; medium and low findings are
recorded as accepted interpretations and carried into planning, where three
downstream review gates still cover them. The optional
[`spec-prep`](/concepts/spec-prep) pre-flight applies exactly this bar to
harden a spec to a millable state before a run starts.

## Scripts own control flow

Loop counters, gate results, and every git operation live in
Expand Down
11 changes: 8 additions & 3 deletions site/content/concepts/how-it-works.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,14 @@ doesn't have? Does it contradict itself — especially universal invariants
("every", "always", "never") that current code doesn't satisfy and that lack
a transition rule? Is it ambiguous in ways that change the work? Is it small
enough to converge (roughly a dozen chunks)? Findings escalate to a human
immediately; in `--auto` runs they abort. Fix the spec at its source and
rerun — that's cheaper than discovering the same defects through six rounds
of plan review.
immediately; in `--auto` runs they abort. Fixing the spec at its source is
cheaper than discovering the same defects through six rounds of plan review.

For a spec that needs real work first, [`spec-prep`](/concepts/spec-prep) is
an optional pre-flight that hardens it to a millable state in its own cheap
loop — resolving blocking defects against source truth and converging on "no
blocking findings" rather than the unreachable "zero findings" — so the run
below clears this gate on the first pass instead of aborting and rerunning.

## Plan, adversarially reviewed

Expand Down
2 changes: 1 addition & 1 deletion site/content/concepts/self-improvement.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: Self-improvement
description: How runs turn friction into policy.
group: Concepts
order: 7
order: 8
---

Mistakes should be made once, then become policy. The mill closes that loop
Expand Down
92 changes: 92 additions & 0 deletions site/content/concepts/spec-prep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
---
title: Hardening a spec first
description: The optional pre-flight that makes a spec millable before a run spends a token.
group: Concepts
order: 6
---

The mill validates the spec at the start of every run. A spec with real
defects fails that gate — and because the reviewer reads *source truth*, a
large spec can surface a *different* defect on each pass: you fix one, rerun,
and the next round finds the next. Fixing at the source and rerunning the
whole pipeline each time is correct but slow, and it drags a human through
one decision at a time.

`spec-prep` moves that hardening into its own cheap loop that runs to
convergence *before* the implementation mill starts. It has no worktree and
no baseline — it only reads the code and rewrites its own copy of the spec —
so looping is fast, and the mill it feeds sails straight through the spec
gate it would otherwise park at.

## The loop

![The spec-prep pre-flight loop: a spec is reviewed against source truth,
then a severity gate either passes it through as a hardened spec into the
mill (no blocking findings), sends it to a Claude hardener that resolves
blocking findings and loops back to review, or stalls when the round budget
is spent and a decision is needed.](/spec-prep.svg)

A rival model reviews the spec against the code, exactly as the mill's own
spec gate does. Then a **hardener** — a strong model with repository access —
rewrites the spec to resolve each finding, grounded in the actual files and
existing precedent, and the loop reviews again. It repeats until the review
comes back clean or a round budget is spent.

## Severity decides what blocks

The key move — and the reason the pre-flight converges where a bare rerun
loop doesn't — is that not every finding blocks:

- **blocking / high** findings must be resolved. A false claim about the
code, a genuine contradiction, an ambiguity that changes the shape of the
work — the spec can't be implemented correctly until these are fixed.
- **medium / low** findings are recorded as *accepted interpretations* and
carried into planning, not treated as blockers.

Demanding *zero* findings from an adversarial reader of a real codebase is an
asymptote it never quite reaches — there is almost always one more
serve-time edge case to name. And three more review gates sit downstream in
the mill (plan review, per-chunk review, final compliance). So the pre-flight
converges on "no blocking defects remain," records the rest, and lets the
pipeline's later gates catch residual depth.

## The hardener resolves; it doesn't guess

For a finding that source truth settles — a wrong file reference, a schema
that should follow an existing type — the hardener corrects the spec and
cites the real code. For a genuine *product* decision that the code can't
settle — expose a UI or stop at the API, add fixtures or test synthetically —
it picks the most conservative, smallest-scope reading, writes the spec to
that reading so it is unambiguous, and records the decision in
`.mill-prep/spec_decisions.json` for a human to review or override.

To decide those forks yourself up front, put authoritative answers in
`.mill-prep/spec_answers.md` before running; the hardener honors them exactly
rather than choosing. Either way, the finished spec carries a **Spec-prep
record** — every interpretation and decision made — so nothing is silently
chosen.

## Running it

```sh
spec-prep 58 # harden issue #58
spec-prep 58 --web # with the live dashboard
mill .mill-prep/spec.hardened.md --no-pr # then implement the hardened spec
```

| Flag | Effect |
| --- | --- |
| `--web` | Detach into the background with the live web dashboard. |
| `--fresh` | Discard existing `.mill-prep` state and start over. |
| `--dest=PATH` | Where to write the hardened spec (default `.mill-prep/spec.hardened.md`). |

The output is a spec file — with a title header, so a file-sourced mill run
derives a good PR title — that the mill consumes directly. If the source
issue was already sound, `spec-prep` finalizes on the first review with
nothing to change; if it can't converge within the round budget, it stalls
with the unresolved findings, which usually means a product decision is
waiting for you in `spec_answers.md`.

State lives in its own `.mill-prep/` directory (self-gitignored), so
`spec-prep` never collides with an implementation run's `.mill/` in the same
checkout. See [running concurrently](/reference/concurrency).
7 changes: 5 additions & 2 deletions site/content/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ order: 20
---

```sh
mill <issue#|spec-file> [--auto] [--web] [--no-pr] [--no-deep] [--fresh]
mill <issue#|spec-file> [--auto] [--web] [--no-pr] [--no-deep] [--fresh] [--model=M]
```

The argument is either a GitHub issue number (fetched with `gh` from the
current repository) or a path to a markdown spec file.
current repository) or a path to a markdown spec file. To harden a rough spec
before running, see [`spec-prep`](/concepts/spec-prep), whose hardened output
is a spec file you pass here.

| Flag | Effect |
| --- | --- |
Expand All @@ -19,6 +21,7 @@ current repository) or a path to a markdown spec file.
| `--no-pr` | Never push or open a PR; the branch stays local regardless of the ship decision. |
| `--no-deep` | The final gate re-runs the fast chunk gates instead of `[gates].deep`. |
| `--fresh` | Discard an existing worktree and branch for this source and start over. |
| `--model=M` | Model for the implement/fix agents (default `sonnet`); bump to `opus` for reasoning-heavy chunks like parsers and contract tests. Review models are fixed. |

## Where things live

Expand Down
6 changes: 6 additions & 0 deletions site/content/reference/concurrency.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ worktree** (same id, same repo): a live conductor already using that
worktree as its cwd blocks the launch, so you can't corrupt an in-flight
run — not even with `--fresh`.

[`spec-prep`](/concepts/spec-prep) runs in the repo root rather than a
worktree, so it keeps its state in its own `.mill-prep/` directory (never an
implementation run's `.mill/`) and refuses a second `spec-prep` in the same
checkout. A `spec-prep` and a `mill` run can execute together, and `spec-prep`
runs in different checkouts never contend.

## What the mill does NOT isolate

The mill isolates the filesystem and git. It does **not** give each run its
Expand Down
6 changes: 6 additions & 0 deletions site/public/flow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
88 changes: 88 additions & 0 deletions site/public/spec-prep.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading