Subsystems
Derived from packages/core/src: 30 subsystems, 686 files, 134k lines, 143 cross-subsystem edges.
This page is the exhaustive record: every subsystem, every cross-subsystem edge, and
a file:line witness for each claim. For the shape of the system rather than its
inventory, see the hand-drawn map on Internals.
Subsystems
Section titled “Subsystems”| Subsystem | Purpose | Tier | Files | Lines | Fan-in | Fan-out |
|---|---|---|---|---|---|---|
loop | The drive-to-green engine: turns, tools, gate settling, steering, adapters | core | 170 | 45k | 8 | 22 |
rule-packs | The ESLint rule packs the gate enforces, grouped by stack | core | 191 | 23k | 3 | 3 |
render | Terminal UI — status bar, menus, wizards, markdown, diffs, spinners | core | 45 | 11k | 7 | 7 |
cli | Argument parsing, the interactive REPL, and per-mode wiring | core | 25 | 9k | 2 | 18 |
agent | Tool schemas, the model-as-agent wrapper, and the malformed-tool-call repair ladder | core | 11 | 4k | 8 | 9 |
inference | OpenAI-compatible provider: streaming, tool calls, reasoning, token usage | core | 12 | 3k | 10 | 4 |
meta-rules | Gate rules that need no AST — config shape, CI wiring, supply chain | core | 42 | 3k | 1 | 2 |
self-harness | Lets the harness propose, trial, and keep edits to its own prompts and rules | optional | 15 | 3k | 2 | 9 |
config | tsforge.config.json, profiles, recipes, agent specs, and external plugins | core | 13 | 3k | 9 | 8 |
scaffold | Stands up a new project from an archetype and configures its gate | optional | 20 | 3k | 3 | 3 |
(root) | CLI entry, model registry, session persistence — the loose files in src/ | core | 6 | 3k | 6 | 15 |
editor | The terminal input-line editor behind the REPL prompt | core | 10 | 2k | 2 | 2 |
gate | Composes and runs the deterministic gate: linter, stages, tool paths | core | 15 | 2k | 5 | 8 |
reviewers | Independent review panel that grades a change before it is trusted | optional | 9 | 2k | 1 | 3 |
eval | Run scoring, failure classification, and the quality judge | optional | 10 | 2k | 4 | 4 |
lib | Shared primitives — fs, json, guards, scope globs, SSRF checks, clipboard | core | 18 | 2k | 24 | 0 |
files | Reading, creating, and hash-anchored editing of workspace files | core | 9 | 2k | 5 | 1 |
policy | Decides which actions are allowed in the current mode before they run | core | 5 | 2k | 5 | 3 |
architecture ⚠️ | Derives this map from source so the docs cannot drift from the code | optional | 8 | 1k | 0 | 0 |
mcp | Model Context Protocol client that exposes external servers as tools | optional | 8 | <1k | 2 | 1 |
lsp | TypeScript language service powering navigation and write-time diagnostics | optional | 3 | <1k | 5 | 0 |
browser | Headless Chromium oracle that render-checks a page as a gate stage | optional | 3 | <1k | 1 | 1 |
infer-rules | Scans a repo for its conventions and turns them into rule overrides | core | 7 | <1k | 5 | 2 |
validate | Runs the gate command and parses tool output into structured errors | core | 7 | <1k | 7 | 2 |
spec | Task and spec shapes, spec parsing, and test generation from intent | core | 6 | <1k | 6 | 6 |
stack-detection | Detects the project’s stack and picks which rule packs apply | core | 4 | <1k | 8 | 1 |
setup | Onboarding wizard that writes a project’s initial tsforge config | optional | 4 | <1k | 2 | 5 |
codebase | Structural workspace map and hub ranking used to seed prompt context | core | 6 | <1k | 2 | 4 |
proptest ⚠️ | Derives property-based test inputs from TypeScript types | optional | 3 | <1k | 0 | 0 |
constitution ⚠️ | Baseline system-role text and the reference ESLint constitution | optional | 1 | <1k | 0 | 0 |
⚠️ marks a subsystem with no imports in either direction inside src/. That does
not make it dead — it may be reached only from tests/ or scripts/ — but it does
mean the running harness never pulls it in.
Mutual dependencies
Section titled “Mutual dependencies”Pairs that import each other, with the line proving each direction. Only mutual pairs are listed: longer cycles exist in a graph this dense, and enumerating them buries the ones someone can actually go and break.
| Pair | One way | The other |
|---|---|---|
(root) ↔ cli | cli.ts:30 → ./cli/args | cli/config-menu.ts:10 → ../models-config |
(root) ↔ gate | cli.ts:32 → ./gate/gate-runner | gate/core-gate.ts:3 → ../update-check |
(root) ↔ inference | classify.ts:1 → ./inference | inference/image-gen.ts:4 → ../models-config |
(root) ↔ loop | cli.ts:13 → ./loop | loop/expert-handoff.ts:21 → ../models-config |
agent ↔ inference | agent/agent-runner.ts:18 → ../inference | inference/wire.ts:9 → ../agent |
agent ↔ loop | agent/agent-runner.ts:21 → ../loop/loop.types | loop/context-hygiene.ts:12 → ../agent/agent.constants |
agent ↔ policy | agent/agent-runner.ts:20 → ../policy | policy/classify.ts:1 → ../agent |
agent ↔ spec | agent/agent.types.ts:1 → ../spec | spec/generate-tests.ts:5 → ../agent |
cli ↔ render | cli/banner.ts:9 → ../render | render/command-menu.ts:2 → ../cli/commands |
config ↔ rule-packs | config/external-plugins.ts:5 → ../rule-packs | rule-packs/index.ts:135 → ../config/plugin-fingerprint |
editor ↔ render | editor/completion.ts:8 → ../render/file-menu | render/frame/input-seq.ts:1 → ../../editor/segments |
eval ↔ loop | eval/failure-class.ts:1 → ../loop/loop.types | loop/loop.types.ts:7 → ../eval/failure-class |
inference ↔ loop | inference/wire.ts:10 → ../loop/context-hygiene | loop/assistant-message.ts:1 → ../inference |
loop ↔ render | loop/gate/panel.ts:3 → ../../render/frame/ansi-plain | render/agent-tree.ts:8 → ../loop/loop.types |
loop ↔ self-harness | loop/feedback/rule-docs.ts:3 → ../../self-harness/overlay | self-harness/build-evidence.ts:3 → ../loop |
loop ↔ spec | loop/feedback/feedback.ts:2 → ../../spec | spec/generate-tests.ts:4 → ../loop |
spec ↔ validate | spec/generate-tests.ts:8 → ../validate | validate/accept.ts:1 → ../spec |
Adapter seams
Section titled “Adapter seams”The interfaces an adapter fills in. The core loop may not import an adapter —
@typescript-eslint/no-restricted-imports in eslint.config.js enforces it — so
everything stack-specific arrives through one of these.
| Seam | Declared | Files referencing it |
|---|---|---|
IStackAdapter | loop/planning/stack-adapter.ts:14 | 6 |
IConventionProvider | loop/conventions-provider.ts:10 | 17 |
IPlanSchema | loop/planning/plan-types.ts:45 | 8 |
IGate | gate/gate-runner.ts:25 | 22 |
IProductPlan | loop/planning/plan-types.ts:32 | 10 |
Entry points
Section titled “Entry points”Async functions returning an exit code, declared under the CLI — the commands.
| Function | Declared |
|---|---|
agentsMode | cli.ts:364 |
greenfieldMode | cli.ts:665 |
harnessDiagnoseMode | cli/harness-diagnose-mode.ts:211 |
harnessReviewMode | cli/harness-review-mode.ts:700 |
main | cli.ts:781 |
mapMode | cli.ts:500 |
recipesMode | cli.ts:519 |
repl | cli/repl.ts:1096 |
reviewMode | cli.ts:201 |
runOnce | cli.ts:103 |
runTraceCommand | cli/repl-commands.ts:160 |
scaffoldMode | cli.ts:747 |
setupMode | cli.ts:508 |
traceMode | cli.ts:570 |
Imports that leave src/
Section titled “Imports that leave src/”src/ reaching into scripts/ inverts the house rule that scripts orchestrate
and src/ decides — logic imported this way sits where tests are not looking.
| From | Import | Target |
|---|---|---|
scaffold | scaffold/io.ts:11 | scripts/boot-check.ts |
What this map does not cover
Section titled “What this map does not cover”Stated so a gap is not mistaken for a clean result:
- Dynamic
import()andcreateRequireare not followed — only static imports. tests/andscripts/are not scanned; only imports into them are noted above.- Coupling through shared global state or the filesystem is invisible here.
- Only mutual dependency pairs are reported, not longer cycles.