5 min read typescriptagents

No build step: the source is the program, the checker is a gate

Bun runs Efferent’s workspace TypeScript directly; tsc emits nothing, and the typecheck command composes compiler errors with the repo’s own zero-baseline static gates.

On this page

Efferent’s current line has no build script.

That is not a flex about startup speed and not shorthand for “the runtime ignores types.” Bun runs the .ts files directly, while the verification path is stricter than a normal TypeScript build: the full tsc --noEmit diagnostics and the repo’s zero-baseline rule suite both have to pass, in one gate command.

The useful separation is execute the source; verify the source; produce nothing. Once those are independent, a stale dist/ cannot sit between an edit and the behavior the agent observes.

Every entry point names a source file

The root scripts are literal:

{
  "scripts": {
    "smith": "bun packages/smith/src/main.ts",
    "smith:dev": "bun --hot packages/smith/src/main.ts",
    "math": "bun packages/math/src/main.ts",
    "canvas": "bun packages/canvas/src/main.ts",
    "social": "bun packages/social/src/main.ts",
    "scenarios": "bun packages/scenarios/src/main.ts"
  }
}

Development and ordinary execution differ only by hot reload. Bun’s loader strips TypeScript syntax in memory as modules load; it writes no JavaScript artifact to disk.

That removes a particularly nasty agent failure mode. In a source→dist workflow, an agent can edit the right file, run a stale artifact, observe no change, and “fix” correct code. Humans escape because they remember the missing build step. An agent has only the evidence the harness gives it. Making the edited file the executed file removes the ambiguity structurally.

Workspace packages export TypeScript

The monorepo packages point their public surface directly at src:

{
  "name": "@xandreed/engine",
  "type": "module",
  "main": "./src/index.ts",
  "exports": {
    ".": "./src/index.ts"
  }
}

When Smith imports @xandreed/engine, Bun follows the workspace link and loads that TypeScript file. There are no declarations to rebuild, no project-reference graph, and no package build order.

The root checker sees the same source as one program:

{
  "extends": "./tsconfig.base.json",
  "include": ["packages/*/src/**/*"],
  "exclude": ["packages/website/**"]
}

Change a port in engine and every consumer goes red in the same tsc invocation. The package boundary still matters for ownership and imports; it no longer has to be a compilation boundary.

tsc became an inspector

The compiler is still present. It simply has noEmit: true, so options such as strict, exactOptionalPropertyTypes, and noUncheckedIndexedAccess describe the program the checker will accept rather than JavaScript it should manufacture.

This produces a useful — and sharp — property: code with type errors can execute. During a refactor, Smith can run one focused test before the whole graph is coherent. The type checker has no veto until the workflow asks for its verdict.

That means the verification command must be a real gate, not a convention in a README. Efferent’s typecheck script is:

{
  "typecheck": "bun packages/foundry/src/main.ts check --config foundry.config.ts --baseline .foundry/baseline.json"
}

The name undersells it. There is no separate tsc process: Foundry loads the root tsconfig.json, runs the full compiler diagnostics on one shared program — semantically tsc --noEmit, through the compiler API — and folds them into the same verdict as its own gates. The one command checks three layers:

  1. TypeScript coherence;
  2. the idiom and architecture rules;
  3. package boundaries — all ratcheted against a committed baseline that holds zero entries, so any new finding anywhere fails outright.

One newly introduced let, loop statement, try/catch, nullable-return pattern, or forbidden dependency can fail the same command even when tsc is happy. The absence of a production build did not weaken the verdict; it made room to state what “valid source” actually means.

The compiler came back as a library

There is an irony in the design: removing TypeScript emit made the TypeScript compiler API more important.

Foundry’s idiom gates parse the real workspace through typescript, walk syntax trees, and in some rules consult the type checker. The tool that no longer produces runtime code now helps enforce architecture and house semantics:

  • dependency direction between packages;
  • no loop statements or mutable let in Effect code;
  • no parallel interfaces beside a schema;
  • branded id fields;
  • no nullable returns inside the domain.

This is a cleaner division of labor. Bun owns execution. tsc owns type validity. Foundry uses the compiler as an analysis engine for rules TypeScript does not express by itself.

Dependency direction needs a gate

Without per-package builds, there is no build graph to incidentally constrain imports. Efferent therefore checks dependency direction explicitly: engine and foundry import nothing internal; providers may depend on engine; the trusted surface compiler sees only the UI agent’s data contracts; each product depends only on its substrate; scenarios sits at the top — it may import the agents, and nothing imports it back.

That is stronger than relying on package references. Build references answer “what must compile first?” An architecture gate answers “what is allowed to know what?” Removing the first question does not remove the second.

Source-run is also a distribution decision

Efferent does not publish the current packages and makes no compatibility promise to a Node-only machine. The README is explicit: clone the repo, install with Bun, run the source.

That is a real cost, not an implementation detail. Bun is required for bun:sqlite, the test runner, TypeScript loading, and the scripts themselves. A future binary or npm line would have to reintroduce a packaging boundary and test the produced artifact. “No build” describes today’s product shape; it is not a moral objection to distribution.

Solid JSX is the other asterisk. OpenTUI views still require an in-memory JSX transform. No build artifact does not mean no transforms; it means transformations happen as part of loading the source and do not create a second program developers accidentally run.

Verify versus produce

Build pipelines mix two jobs:

  • verify — types, tests, lint, architecture, quality rules;
  • produce — transpiled files, declarations, bundles, binaries.

Efferent currently needs the first and not the second. So verification is a first-class gate and production is absent. The result is less machinery in the edit/run loop and more honesty in the verdict: the command named typecheck checks the properties this repository actually cares about, while the program under test remains the file you just edited.

That is the default worth considering for a source-run TypeScript tool in 2026. Start with the source as the program. Add a production step only when you have an artifact to produce — and keep it out of the path that tells a developer, or an agent, whether the last edit worked.