Configuration (windup.config.ts)

import { defineConfig } from "windupjs";

export default defineConfig({
  baseUrl: "http://localhost:3000",
  llm: {
    provider: "google",
    model: "gemini-3.1-flash-lite",
    // Several providers at once — pick per run with --llm (see "LLM providers"):
    providers: { openai: { model: "gpt-5-mini" } },
  },
  scenarios: "e2e/scenarios",
  framework: "react-router",          // detected by init; used by scan
  // browser: "chromium",             // or "firefox" / "webkit" (need: npx playwright install <name>)
  scan: {
    llmAssist: { enabled: true, maxCalls: 20 },   // hard cost cap per scan
  },
  // Project manifest: team-provided knowledge injected into the planner prompt.
  context: {
    conventions: ["every interactive element has a data-testid"],
    credentials: {
      qa: { user: "ENV:QA_USER", password: "ENV:QA_PASSWORD" },
    },
    vocabulary: { "order": "the Order entity, screen /orders" },
  },
});
  • context.credentials maps account names to ENV references. When a task mentions the account, the plan uses value_ref — manifest credentials take precedence even if the page displays values, and the planner is forbidden from inventing ENV names.
  • LLM-assist (scan layer 3) reads files the static layers couldn’t resolve (dynamically built routes, indirect components), capped by maxCalls. Results are remembered per file hash — unchanged files never cost again. Costs are recorded in the ledger and shown by windup costs.

What lives where

PathContentsCommit?
windup.config.tsConfiguration
e2e/scenarios/*.jsonYour tests, in natural language
e2e/fragments/*.jsonCurated reusable blocks
windup.credentials.jsonAccount → ENV-name mapping (no values)
.env.localCredential values❌ (auto-gitignored; CI uses secrets with the same names)
.windup/Derived state: plan cache, run ledger, site map, reports❌ (init adds it to .gitignore)