Skip to content

Architecture notes for contributors ​

The engineering rules, in particular:

  • Keep protocol adapters independent of each other and keep UI state separate from execution state.
  • Never block the renderer. Use bounded concurrency, stream data, and never hold unbounded payloads in memory.
  • Never log secrets. Route every persisted artefact through the Redactor.
  • Label AI-generated output (source: 'ai-judge', AI-generated suggestion banners).

The map, and what keeps it ​

packages/core        the engine, host-agnostic (Node): the same code runs in the CLI, the desktop app and a server later
  model/             the types of everything saved (types.ts) and the schema version
  storage/           workspaces on disk: collections, environments, tests, trash, git files, secrets, trust, watcher; the
                     workspace export (workspace-bundle, bundle-secrets: files capped, secrets replaced) and unique node ids
  protocols/         http, graphql, grpc, websocket, socketio, mqtt, kafka, mcp: one client each, nothing shared between them
  runner/            test files, suites, the run loop, flows (edits, blocks, data runs, env matrices, webhooks, timelines), results, baselines, CI config
  eval/, load/       checks and evaluators, load tests
  ai/                LLM providers, and the AI features: the task registry and runner (tasks/), the context builder (redaction,
                     privacy levels, budgets), structured output, verifiers, proposals (apply / undo) and the AI settings;
                     the analyses of a failed run (run-evidence, triage) and of a flaky test (flaky, flaky-run), the fix
                     items they share (fix-items) and the tp.* reference read from the sandbox (script-reference);
                     the usage ledger is storage/ai-usage.ts (see Design ▸ TestPion's own AI features)
  import/            Postman, Insomnia (template tags), Bruno (.env, bru.* → tp.*), Hoppscotch, GraphQL SDL, OpenAPI, HAR,
                     cURL/fetch snippets, save-request (secrets to variables); foreign auth that never becomes inherit; notes
  security/          the one secret detector (names and value shapes, with a confidence) for exports, git check and masks
  scripts/           the tp.* sandbox (QuickJS in WebAssembly): scripts never reach Node
  git/               git through the system git, changes by meaning, the merge driver
  net/               the production guard (one rule for every host), private-network guards
  mcp-server/        the workspace as MCP tools for agents: tool.ts (a tool), tool-contract.ts (paging, response_format, confirm, output schemas) and tool-docs.ts (when / when not / example), one module per subject, testpion-mcp.ts composes them
  index.ts           the public surface: everything the CLI and the app may use
packages/shared      pure, browser-safe code for every host; output/ is the one registry of downloads: formats.ts (formats
                     and builders), filename.ts (safe names), csv-write.ts (the formula guard), surfaces.ts (every output that
                     can be saved, its formats and secrets policy; tests/unit/output-surfaces.test.ts keeps it complete)
packages/cli         commands over core (commands/*.ts); every --json is one envelope (output.ts), exit codes 0 / 1 / 2 / 3
apps/desktop
  backend/           the app's RPC handlers over core (handlers/*.ts, one file per subject); backend.ts wires them and owns long-running state;
                     production-guard.ts checks every sending or running call against production environments
  electron/          the window, menu, updater, preload (the context bridge), --mcp-server and --merge-driver modes
  src/               the renderer (React): talks to the backend only through api.ts (RPC); from core it takes types only;
                     components/output/ is the down arrow (OutputActions / OutputHeader): a new output describes itself as
                     an OutputSpec there and its id in surfaces.ts, and the backend's handlers/output.ts writes the file
  e2e/               the UI regression suite (plans/*.cjs) and the profiler plan
docs/                the published site; planning/ is internal

Three rules, kept by tests/unit/architecture.test.ts (it runs with npm test):

  1. Boundaries. Core imports no Electron, React or app code; the backend never imports the renderer; the renderer never imports Node, Electron, backend files or core at run time. The renderer is a web page that a cloud host will serve as it is.
  2. Big files do not grow. backend.ts, testpion-mcp.ts, cli/commands/data.ts, workspace.ts and types.ts have a ceiling; a new MCP tool goes into the module of its subject (git-tools.ts, workspace-edit-tools.ts, …), a new RPC handler into handlers/<subject>.ts, a new CLI command into commands/<subject>.ts. Nothing else passes 1,200 lines.
  3. One place per feature, every host. A feature is core logic + a CLI command with --json + an RPC handler + (when an agent would want it) an MCP tool, with the tool's name added to the pinned list in tests/integration/workspace-run.test.ts.

Migrations ​

Workspace format changes need a new entry in MIGRATIONS (packages/core/src/storage/workspace.ts), a bump to SCHEMA_VERSION, and a test. Each migration upgrades exactly one version, and opening a newer format is refused rather than silently broken. The SQLite metadata schema has its own versioned migrations in metastore.ts.