Big picture
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.
How it’s layered
Section titled “How it’s layered”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.
What runs on a task
Section titled “What runs on a task”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.
Why tsforge exists
Section titled “Why tsforge exists”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.
The problem it targets
Section titled “The problem it targets”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:
-
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.” -
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.
-
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).
Why TypeScript specifically
Section titled “Why TypeScript specifically”tsforge is opinionated about TypeScript because the language gives you machine-checkable truth:
tscis a hard floor. tsforge overlays a stricttsconfigso 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
tsccannot catch (component file layout, noconsole.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.
Models and deployment
Section titled “Models and deployment”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.
Doc map (plain language)
Section titled “Doc map (plain language)”| Section | What it covers |
|---|---|
| Interactive CLI | Day-to-day REPL: slash commands, flags, sessions |
| Plan mode | Explore → approve saves a project checklist → implement in-session |
| How the gate is built | What acceptance check tsforge runs and how it picks tsc + ESLint |
| When the gate fails | Repair loop, stop conditions, error feedback to the model |
| Greenfield scaffolding | Standing up a new BoringStack full-stack app (the first adapter) |
| Stack detection | Which ESLint packs turn on for your dependencies |
| Rule packs | What each pack enforces + bringing your own |
| Config & external plugins | Profiles, and plugging in your own rule-packs |
| Fix bad tool calls | Malformed tool JSON fixed before re-asking the model |
| Safer line edits | Line edits anchored to a content hash |
| Stop bad output early | Forbidden patterns cut off mid-stream |
| Spec format | YAML tasks for benchmarks and regression runs |
| A/B testing | Comparing harness settings with eval specs |
Glossary
Section titled “Glossary”| Term | Meaning |
|---|---|
| Gate | Shell command that must pass before tsforge treats work as done. Often tsc, ESLint, tests. |
| Harness | The tsforge runtime: CLI, loop, gate, guardrails, and model wiring. |
| Core | The stack-agnostic engine: planning, the drive-to-green loop, the gate, the rule-packs, the review panel. Imports no adapter. |
| Adapter | The stack-specific half behind the seams: how to scaffold, what an error means, how to verify. BoringStack is the first. |
| Seam | An injected interface the adapter fills (IStackAdapter, IConventionProvider, IPlanSchema, IGate). |
| LSP / language server | TypeScript’s semantic engine (LanguageService): go-to-definition, types, rename, diagnostics. tsforge embeds it; you do not run a separate tsserver process. |
| TTSR | Tool-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”). |
| Hashline | Edit format ¶path#HASH plus line ops. The hash proves the file has not changed since the model read it. |
| Rule pack | A bundle of ESLint rules keyed to a stack (React, Elysia, Drizzle, Three.js, …). |
| Meta-rule | Cross-cutting ESLint rules tsforge adds on top of packs (import boundaries, test file pairing). |
| Repair ladder | L0–L3 fixes for invalid tool calls: unwrap links, coerce types, then re-ask the model. |
| Spec | YAML file describing a task, files in scope, and expected gate outcome for evals. |
| Oracle | Same as the gate: the external check that decides pass or fail. |
| REPL | The interactive CLI session where you type tasks and slash commands. |
What tsforge is not
Section titled “What tsforge is not”- 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 lintbecause 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.