Skip to content
ƒtsforgev0.55.0
38

Where do I change X?

5 min read

The harness is ~97k lines across 30 subsystems. Most changes touch two or three files and a registration point, and the registration point is the part that is hard to find. This page is the index.

Paths are relative to packages/core/src/. Line numbers move; the symbol names are the durable part, so grep for those if a number has drifted.

The one people get wrong, because registration is spread over four files.

StepWhere
1. Name itTOOL_NAME in agent/agent.constants.ts, also where the JSON schema the model sees lives
2. Write the handlerloop/tools/<your-tool>.ts
3. Register itHANDLERS in loop/tools/execute-tool.ts
4. Advertise ittoolsFor() in loop/turn.ts

Step 3 is typed Record<ToolName, ToolHandler>, so the compiler will not let you skip it. Add the name in step 1 and typecheck tells you exactly what is missing.

If the tool mutates the workspace it must surface mutated (and edit/create for file writes) on its event, or the gate will not re-run and a stale green can be reported as done.

A new site plugin (research on a specific site)

Section titled “A new site plugin (research on a specific site)”

Built-in only: a plugin fetches with the user’s cookies, so it ships in tsforge’s source. Reddit (site-plugins/reddit/) is the worked example.

StepWhere
1. Build the pluginsite-plugins/<site>/: URL builders (read endpoints only), guarded parsing, rendering, tool schemas, handlers, a guidance playbook, and an ISitePlugin in index.ts
2. Register itSITE_PLUGINS in site-plugins/registry.ts
3. Name its toolsTOOL_NAME + TOOL_SPECS in agent/agent.constants.ts, HANDLERS via sitePluginTool() in loop/tools/execute-tool.ts, network in policy/classify.ts, RESEARCH_TOOLS in loop/readonly-spin.ts
4. Allow its hostsFETCH_HOSTS in packages/chrome-extension/src/fetch-policy.ts (a test fails until it matches the registry)

The plugin’s tools are advertised with the browser tools, and its guidance is appended once when the browser capability turns on.

Write an async function yourMode(args: ICliArgs): Promise<number> in cli.ts, then add the dispatch branch in main(). The Promise<number> is the contract. It is the process exit code, and it is how the generated entry-point list finds your command. Document it in reference/commands.mdx.

Create loop/<your-stack>/ and implement the five seams described in Adapters and seams. Register it at a composition root, cli.ts or cli/**, never from inside the core loop, which an ESLint rule forbids from importing an adapter at all. loop/boringstack/ is the worked example.

mcp/registry.ts holds the client. Servers are declared in the user’s tsforge.config.json, parsed by config/tsforge-config.ts.

I want toGo to
Change when the loop calls a run finishedsettleGate / evaluateGate in loop/turn.ts
Change how the gate is composedgate/gate-runner.ts (the IGate contract), gate/core-gate.ts
Change the stage order for BoringStackloop/boringstack/gate-stages.ts
Change how tool output becomes errorsvalidate/parse.ts and the parsers beside it
Change what the write-time linter enforcesgate/linter.ts

Never relax a rule to get past a failure. The gate is the only signal that the work is real. Fix the thing that makes the model satisfy it.

I want toGo toThen
Add a rule to an existing packrule-packs/<pack>/rules/<rule>.tsexport it from the pack’s index.ts
Create a whole packrule-packs/<pack>/index.tsadd to RULE_PACKS in rule-packs/index.ts, then a descriptor in PACK_REGISTRY (stack-detection/packs.ts)
Add a non-AST rulemeta-rules/rules/<category>/register in META_RULES (meta-rules/registry.ts); add to PER_WRITE_META_RULES only if it must run on every write

After any of these run bun run rules:docs && bun run rules:build. CI fails if the generated catalog drifts from the source.

I want toGo to
Change the system promptloop/prompt/prompt.ts; the baseline role text is constitution/baseline.ts
Change per-call decisions (thinking budget, tool filtering)loop/model-call.ts
Change how the loop steers a stuck runbuildSteerMessage in loop/feedback/steer.ts; the rungs are EscalationRung in loop/loop.types.ts
Change rule help shown on a gate failureloop/feedback/rule-docs.ts and its siblings
Support a new model providerinference/openai-compatible.ts; per-model config is IModelEntry in models-config.ts
Change reasoning handling for a model familyinference/reasoning-profile.ts
Change what counts as degenerate outputinference/stream-guard.ts
I want toGo to
Add a CLI flagICliArgs in cli/args.ts, where parsing and recipe overlay also live, pure and unit-tested in tests/cli.test.ts
Add an environment flagconfig/flags.ts
Change tsforge.config.jsonconfig/tsforge-config.ts
Change a strictness profileconfig/profiles.ts
Change what a session storessession-store.ts; the structured event log is loop/ledger-writer.ts

bun run validate is the merge bar: typecheck, lint, format, tests, PTY end-to-end. It is the same gate the harness holds your code to.

Two house rules that catch most review comments:

  • A test is not coverage until it has failed. Break the line and watch the test go red. An assertion that cannot fail proves nothing about the code beneath it.
  • Scripts orchestrate; src/ decides. Anything it would be a bug to get wrong belongs in src/, where a test can import it. The generated map lists any import that violates this under Imports that leave src/.