Skip to content
ƒtsforgev0.52.0
19

Greenfield builds

4 min read

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.

Terminal window
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.

  1. 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/.
  2. 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.
  3. 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:

FilePurpose
.tsforge/greenfield/spec.mdThe planner’s high-level spec
.tsforge/greenfield/features.jsonThe checklist ({id, desc, passes, attempts}). The source of truth.
.tsforge/greenfield/progress.mdHuman-readable status

Because features.json is the source of truth, re-running the same command resumes from the last verified feature.

Each feature passes only if every stage agrees, checked cheapest-first and short-circuiting on the first failure:

  1. Gate: the deterministic build gate (--accept: tsc / eslint / tests / build). The authority for “done”.
  2. 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.
  3. 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.

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, then db:push to 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.

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"
}
Terminal window
tsforge run kanban "build a kanban board"

Greenfield runs are long and headless-friendly. There’s no built-in scheduler; wire one with your OS:

Terminal window
# nightly build attempt, pinging you when it finishes or gets stuck
0 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.