Skip to content
ƒtsforgev0.52.0
19

Big picture

8 min read

tsforge is a TypeScript coding harness. It sits between you and a model, runs edits in your repo, and keeps asking the model to fix things until your project passes a real check. It is not an editor plugin. You point it at a folder, give it a task, and it drives file changes plus validation until the gate is green or the loop stops.

If you already ran the Quickstart, this page explains what you just turned on.

How to read the docs: start here, then How the gate is built when “what counts as done?” is unclear. Use the glossary below when a term looks unfamiliar. Eval and spec pages are for benchmark contributors, not required for day-to-day use.

tsforge is built as one general core plus adapters. The engine (write, check, steer, repeat) is the same for any TypeScript project. Everything stack-specific (how to scaffold a new app, what a given error means, how to verify a feature works) lives in an adapter that plugs into a fixed set of seams. The core imports nothing from an adapter; a lint rule fails the build if it tries.

flowchart TB
  subgraph core["Universal core · stack-agnostic"]
    direction LR
    A["planning"] --- B["drive-to-green loop"] --- C["gate · ESLint rule-packs"] --- D["tools"] --- E["review panel"]
  end
  subgraph seam["The seam · adapters plug in here"]
    direction LR
    S1["IStackAdapter"] --- S2["IConventionProvider"] --- S3["IPlanSchema<TUi>"] --- S4["IGate"] --- S5["external-plugins"]
  end
  subgraph adapters["Adapters · stack-specific"]
    direction LR
    BS["BoringStack (built)"] --- PH["Phaser (built)"] --- DD["your own rules (via external-plugins)"]
  end
  core --> seam --> adapters

An adapter is exactly: fill those interfaces. IStackAdapter detects a repo and scaffolds it; IConventionProvider supplies the “here’s how to write it right” guidance; IPlanSchema describes the plan/UI shape; IGate says how to run and verify. BoringStack is the first adapter (a full-stack Bun + Elysia + Drizzle + React stack); Phaser is the second (a Phaser 4 TypeScript game). You can also bring your own ESLint rule-packs without writing an adapter. See external plugins.

The diagram above is composition: the subsystems that exist. This is flow: what happens when you give it a task:

flowchart TD
  you(["You type a task or product description"]) --> repl["CLI · REPL"]
  repl --> detect{"Stack adapter<br/>detects the repo?"}
  detect -->|"greenfield"| plan["Plan the product<br/>model proposes feature slices"]
  detect -->|"existing repo"| loop
  plan --> approve{"You approve<br/>the plan?"}
  approve -->|"revise"| plan
  approve -->|"yes"| loop

  subgraph loop["Drive-to-green loop · repeats per slice"]
    direction TB
    write["Model writes / edits code<br/>conventions front-loaded"] --> gate["Gate<br/>tsc + ESLint rule-packs + tests"]
    gate --> green{"Green?"}
    green -->|"errors"| steer["Feedback · steer ladder<br/>exact errors + rule help"]
    steer --> write
    green -->|"near-green regression"| ckpt["Checkpoint / rollback"]
    ckpt --> write
  end

  green -->|"green"| acc["Acceptance<br/>reachability · testids · judge"]
  acc --> panel["4-model review panel"]
  panel -->|"pass"| done(["Done · ready to merge"])
  panel -->|"block"| write

The model agent is the loop driver: it calls tools, applies edits, runs the repair ladder when a tool call is malformed, and re-prompts on gate failure. The gate is the oracle: if it fails, the harness feeds the error list back and the model gets another turn. A single high crash-guard (1000 turns) plus no-progress guards stop runaway churn without cutting off a productive build. See When the gate fails for details.

tsforge started as a necessity experiment: could a small model running on local hardware produce TypeScript you’d actually merge, if the harness enforced tsc, stack rules, and stream-level corrections? It could. That proof shaped every subsystem here.

The idea was first tested against a 27B local model; today the default local stack is DeepSeek‑V4‑Flash served on-box via a custom vLLM setup. The same loop works with larger or hosted models. They usually need fewer retries and still benefit from an external oracle and stack-aware rules. Guardrails matter most when the model is constrained; they still help when it is strong.

Models in a chat window can write TypeScript fast. They also skip types, paste as any, forget your stack conventions, and stop before tests pass. Nothing in the chat UI says “this edit failed tsc, try again with these exact errors.”

tsforge closes that gap with three ideas:

  1. A gate you can trust. Work is not done until a shell command exits 0. For TypeScript repos that usually means tsc --noEmit, ESLint from tsforge’s own rule-packs, and often tests or format checks. The model sees structured errors, not a vague “something broke.”

  2. Feedback while the model still types. Stream rules (TTSR) can cut off bad tool arguments mid-generation. Hashline edits tie replacements to a file hash so line numbers do not drift. The TypeScript language service can flag a bad write before the next turn.

  3. Stack-aware rules without hand-wiring. tsforge reads package.json, turns on ESLint packs for React, Drizzle, Elysia, Three.js, and the rest, and runs them on every validation. You get project-specific guardrails without maintaining a separate lint setup, and you can layer your own packs on top (external plugins).

tsforge is opinionated about TypeScript because the language gives you machine-checkable truth:

  • tsc is a hard floor. tsforge overlays a strict tsconfig so the gate does not inherit a loose upstream config. See How the gate is built.
  • The language server (same engine as VS Code’s TypeScript support) runs in-process for symbol search, rename, and diagnostics on every write. See TypeScript language server.
  • ESLint packs encode patterns tsc cannot catch (component file layout, no console.log, Drizzle query shape, etc.). See Rule packs.

For greenfield web apps, the BoringStack adapter stands up a full stack (Bun + Elysia + Drizzle API and a Vite/React UI) and builds it feature-by-feature against its own gate. See Greenfield scaffolding.

tsforge talks to any OpenAI-compatible HTTP API. Configure endpoints in ~/.tsforge/models.json or override with environment variables. See Model adapter.

The default endpoint (http://localhost:8000/v1) is a convenience for a local inference server; point TSFORGE_BASE_URL at a hosted API when you want. The gate, packs, and stream rules do not change.

SectionWhat it covers
Interactive CLIDay-to-day REPL: slash commands, flags, sessions
Plan modeExplore → approve saves a project checklist → implement in-session
How the gate is builtWhat acceptance check tsforge runs and how it picks tsc + ESLint
When the gate failsRepair loop, stop conditions, error feedback to the model
Greenfield scaffoldingStanding up a new BoringStack full-stack app (the first adapter)
Stack detectionWhich ESLint packs turn on for your dependencies
Rule packsWhat each pack enforces + bringing your own
Config & external pluginsProfiles, and plugging in your own rule-packs
Fix bad tool callsMalformed tool JSON fixed before re-asking the model
Safer line editsLine edits anchored to a content hash
Stop bad output earlyForbidden patterns cut off mid-stream
Spec formatYAML tasks for benchmarks and regression runs
A/B testingComparing harness settings with eval specs
TermMeaning
GateShell command that must pass before tsforge treats work as done. Often tsc, ESLint, tests.
HarnessThe tsforge runtime: CLI, loop, gate, guardrails, and model wiring.
CoreThe stack-agnostic engine: planning, the drive-to-green loop, the gate, the rule-packs, the review panel. Imports no adapter.
AdapterThe stack-specific half behind the seams: how to scaffold, what an error means, how to verify. BoringStack is the first.
SeamAn injected interface the adapter fills (IStackAdapter, IConventionProvider, IPlanSchema, IGate).
LSP / language serverTypeScript’s semantic engine (LanguageService): go-to-definition, types, rename, diagnostics. tsforge embeds it; you do not run a separate tsserver process.
TTSRTool-text stream rules. Regex watchers on streaming model output. On match, tsforge aborts the stream and retries with short guidance (for example “no as any”).
HashlineEdit format ¶path#HASH plus line ops. The hash proves the file has not changed since the model read it.
Rule packA bundle of ESLint rules keyed to a stack (React, Elysia, Drizzle, Three.js, …).
Meta-ruleCross-cutting ESLint rules tsforge adds on top of packs (import boundaries, test file pairing).
Repair ladderL0–L3 fixes for invalid tool calls: unwrap links, coerce types, then re-ask the model.
SpecYAML file describing a task, files in scope, and expected gate outcome for evals.
OracleSame as the gate: the external check that decides pass or fail.
REPLThe interactive CLI session where you type tasks and slash commands.
  • Not locked to localhost. The default endpoint is local; any OpenAI-compatible URL works via Model adapter.
  • Not a replacement for your project’s own CI. It can run stricter checks than your repo’s npm run lint because it ships its own ESLint floor.
  • Not magic on non-TypeScript trees. Without tsconfig, the gate falls back to ESLint-only.

Ready to run? Quickstart. Ready to drive the session? Interactive CLI.