Greenfield builds
Most of tsforge drives one change to green. Greenfield mode drives a whole build to green, one feature at a time, keeping its state on disk so a long run can be interrupted and resumed without losing the plot.
tsforge --greenfield "build a kanban board" --accept "bun run build"This is the autonomous / headless checklist loop (scaffold and app builds). Interactive sessions use a different path: plan mode → approve → .tsforge/worklist/plans/<planId>.json (session-bound) → implement in the same REPL (--continue). Do not confuse the two folders.
How it works
Section titled “How it works”- Plan: a planner model turns your one-line goal into a high-level spec and a flat feature checklist (sprints, not file-level steps). Written to
.tsforge/greenfield/. - Loop: for the first unfinished feature, the work model implements it, then the layered evaluator verifies it. On success the feature is ticked; the loop moves on.
- Stop: when every feature is verified (
done), or a single feature exhausts its attempts (stuck) so a non-converging feature can’t wedge the whole build.
The state lives in the repo, not the model’s context:
| File | Purpose |
|---|---|
.tsforge/greenfield/spec.md | The planner’s high-level spec |
.tsforge/greenfield/features.json | The checklist ({id, desc, passes, attempts}). The source of truth. |
.tsforge/greenfield/progress.md | Human-readable status |
Because features.json is the source of truth, re-running the same command resumes from the last verified feature.
The layered evaluator
Section titled “The layered evaluator”Each feature passes only if every stage agrees, checked cheapest-first and short-circuiting on the first failure:
- Gate: the deterministic build gate (
--accept: tsc / eslint / tests / build). The authority for “done”. - Reachability: for a web build (BoringStack), the feature’s route must actually be served (an HTTP fetch), and its required test ids must be present in the code (a static contract check). No headless browser is run.
- Judge: a harsh, reject-by-default quality review that sees only the built code, never how it was produced.
That last point is a deliberate rule: the evaluator never sees the generator’s reasoning or tool trace, so it judges the result, not the persuasion behind it.
Full-stack builds (BoringStack)
Section titled “Full-stack builds (BoringStack)”A Phaser game scaffolded with tsforge scaffold --archetype phaser
uses the same plan-then-build flow with a game view-intent schema (scene / feature /
content), not this resource/OpenAPI loop.
A web app scaffolded with tsforge scaffold rides this same
loop, with one resource (entity) per feature and a clear division of labor:
- The harness runs the generators + wiring:
bun run new:resource/new:feature, the deterministic edits that wire the resource into the route tree, app, swagger, and the test-helper’s schema re-export, thendb:pushto sync the schema. - The model fills the domain: real fields, service logic, and the required test siblings, scoped to just that resource’s files and frozen once it passes.
The gate is BoringStack’s own validate, run baseline-aware and differential with a
prettier + eslint --fix auto-fix pass first. See
How tsforge builds the gate.
Role-based model routing
Section titled “Role-based model routing”Each role can run on its own model (names from your models.json); any role left unset falls back to the active model, so a single-endpoint setup still works:
{ "id": "kanban", "mode": "greenfield", "gate": "bun run build", "plannerModel": "big-thinker", "workModel": "fast-coder", "evaluatorModel": "harsh-judge"}tsforge run kanban "build a kanban board"Unattended runs & scheduling
Section titled “Unattended runs & scheduling”Greenfield runs are long and headless-friendly. There’s no built-in scheduler; wire one with your OS:
# nightly build attempt, pinging you when it finishes or gets stuck0 2 * * * tsforge run kanban "build a kanban board" \ --log --notify 'curl -s "$WEBHOOK?status=$TSFORGE_STATUS"'--notify <cmd> runs a shell command on completion with the outcome in $TSFORGE_STATUS (e.g. greenfield done 7/7 or greenfield stuck 3/7). It’s best-effort: a failing notifier never changes the run’s exit code. Pair it with --log and tsforge trace to inspect what happened.