LangENKO

For Agents

Every Agent Prompt the Mandu docs ship, indexed in one place — fetch this URL and you have the whole site.

On this page

For Agents

Every Agent Prompt the Mandu docs ship, indexed in one place. AI agents can fetch this single URL instead of crawling the docs tree.

Currently aggregating 63 prompts across 63 pages.

Auto-generated. Edit scripts/build-for-agents.ts or the source pages' frontmatter / Agent Prompt blocks to change this output.

AI Integration

mandu ai chat

Interactive REPL with streaming responses and slash commands. Works offline with the local echo provider, or hit Claude / OpenAI / Gemini / Ollama / LM-Studio.

ai-hintmandu ai chat is a streaming REPL. Slash commands: /help /reset /save /load /preset /system /provider /model /quit. History scrollback bounded to 100 turns. Default provider local works without API keys.

Invariants:

  • Default provider is local — works offline with deterministic echo
  • In-memory scrollback bounded to 100 turns — older entries drop when exceeded
  • History JSON schema v1 — /save / /load are portable across sessions
  • Ctrl+C during streaming aborts the turn cleanly — failed turn is dropped from history
  • Slash-command args are escaped: /preset ../etc rejected by strict alphanumeric allow-list
🤖 `mandu ai chat` · `mandu ai chat`
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/ai/chat to my project.

Summary of the page:
`mandu ai chat` is a streaming REPL. Slash commands: /help /reset /save /load /preset /system /provider /model /quit. History scrollback bounded to 100 turns. Default provider `local` works without API keys.

Required invariants — must hold after your changes:
- Default provider is `local` — works offline with deterministic echo
- In-memory scrollback bounded to 100 turns — older entries drop when exceeded
- History JSON schema v1 — `/save` / `/load` are portable across sessions
- Ctrl+C during streaming aborts the turn cleanly — failed turn is dropped from history
- Slash-command args are escaped: `/preset ../etc` rejected by strict alphanumeric allow-list

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

mandu ai eval

Non-interactive prompt evaluator — runs one prompt across one or more providers and prints JSON to stdout. Exit 0 if every provider succeeded; exit 1 if any errored. Ideal for CI diff tests.

ai-hintmandu ai eval is the batch / non-interactive counterpart to mandu ai chat. Accepts --providers=a,b,c to fan out, --prompt=... or stdin, streams to JSON on stdout. Use with jq or snapshot tests in CI. Exit 0 = all providers OK, exit 1 = any provider failed.

Invariants:

  • Output is JSON to stdout — jq friendly, snapshot-friendly
  • Exit 0 if every provider in --providers succeeded; exit 1 if any returned an error
  • Prompts can be passed via --prompt OR piped over stdin
  • Each provider call honors MANDU_AI_TIMEOUT_MS — slow ones fail the whole eval
  • No history — every invocation is a fresh context
🤖 `mandu ai eval` · `mandu ai eval`
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/ai/eval to my project.

Summary of the page:
`mandu ai eval` is the batch / non-interactive counterpart to `mandu ai chat`. Accepts `--providers=a,b,c` to fan out, `--prompt=...` or stdin, streams to JSON on stdout. Use with `jq` or snapshot tests in CI. Exit 0 = all providers OK, exit 1 = any provider failed.

Required invariants — must hold after your changes:
- Output is JSON to stdout — `jq` friendly, snapshot-friendly
- Exit 0 if every provider in `--providers` succeeded; exit 1 if any returned an error
- Prompts can be passed via `--prompt` OR piped over stdin
- Each provider call honors `MANDU_AI_TIMEOUT_MS` — slow ones fail the whole eval
- No history — every invocation is a fresh context

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

AI

mandu ai is a terminal playground for streaming chat with Claude, OpenAI, Gemini, or a local Ollama/LM-Studio. Plus the prompt template system, 4 new MCP tools, loop closure framework, and auto skills generator.

ai-hint — AI category hub. Four providers: claude, openai, gemini, local (echo + Ollama/LM-Studio). Two CLI commands: mandu ai chat (REPL) + mandu ai eval (non-interactive). Phase 14 ships: 4 new MCP tools (run.tests, deploy.preview, ai.brief, loop.close), prompt templates, loop closure framework.

Invariants:

  • Four providers: claude, openai, gemini, local (Ollama/LM-Studio compatible)
  • API keys NEVER logged — errors mask them as sk-***
  • Local provider echo responder works offline with no API key — deterministic, CI-safe
  • History JSON schema v1 — /save produces portable exports
  • MCP tool mandu.loop.close is pure — no I/O, no spawn, advisory text only
🤖 AI · AI
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/ai/index to my project.

Summary of the page:
AI category hub. Four providers: claude, openai, gemini, local (echo + Ollama/LM-Studio). Two CLI commands: `mandu ai chat` (REPL) + `mandu ai eval` (non-interactive). Phase 14 ships: 4 new MCP tools (run.tests, deploy.preview, ai.brief, loop.close), prompt templates, loop closure framework.

Required invariants — must hold after your changes:
- Four providers: `claude`, `openai`, `gemini`, `local` (Ollama/LM-Studio compatible)
- API keys NEVER logged — errors mask them as `sk-***`
- Local provider echo responder works offline with no API key — deterministic, CI-safe
- History JSON schema v1 — `/save` produces portable exports
- MCP tool `mandu.loop.close` is pure — no I/O, no spawn, advisory text only

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Loop closure

Pure detector + emitter framework that recognizes stall patterns in agent output (stdout / stderr + exit code) and composes an advisory nextPrompt. No I/O. Deterministic. Safe to call on untrusted output.

ai-hint — Library: @mandujs/skills/loop-closurecloseLoop(). MCP tool: mandu.loop.close. 10 detectors in priority order: typecheck-error > test-failure > missing-module > syntax-error > not-implemented > unhandled-rejection > incomplete-function > todo-marker > fixme-marker > stack-trace. All pure functions — never fs/spawn/fetch/Math.random/timestamps.

Invariants:

  • Every detector is a pure function: (DetectorInput) => Evidence[]
  • Emitter is pure: (Evidence[], exitCode) => LoopClosureReport
  • closeLoop() performs NO I/O — never fs, spawn, fetch, Math.random, or time-dependent logic
  • Output is deterministic — identical inputs yield identical reports
  • Oversized streams are truncated to the last 1,000,000 characters (tails kept — banners live at the end)
  • Detectors err on LOW false-positive — well-formed Mandu source text produces zero matches
🤖 Loop closure · Loop closure
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/ai/loop-closure to my project.

Summary of the page:
Library: `@mandujs/skills/loop-closure` → `closeLoop()`. MCP tool: `mandu.loop.close`. 10 detectors in priority order: typecheck-error > test-failure > missing-module > syntax-error > not-implemented > unhandled-rejection > incomplete-function > todo-marker > fixme-marker > stack-trace. All pure functions — never `fs`/`spawn`/`fetch`/`Math.random`/timestamps.

Required invariants — must hold after your changes:
- Every detector is a pure function: `(DetectorInput) => Evidence[]`
- Emitter is pure: `(Evidence[], exitCode) => LoopClosureReport`
- `closeLoop()` performs NO I/O — never `fs`, `spawn`, `fetch`, `Math.random`, or time-dependent logic
- Output is deterministic — identical inputs yield identical reports
- Oversized streams are truncated to the last 1,000,000 characters (tails kept — banners live at the end)
- Detectors err on LOW false-positive — well-formed Mandu source text produces zero matches

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

MCP tools (Phase 14.3)

Four new tools added to @mandujs/mcp in Phase 14.3 — mandu.run.tests, mandu.deploy.preview, mandu.ai.brief, and mandu.loop.close. Each closes the loop between an agent's actions and its orchestrator.

ai-hint — Phase 14.3 Agent G adds 4 MCP tools: run.tests (runs mandu test, returns structured summary), deploy.preview (mandu deploy --dry-run adapter), ai.brief (project briefing for new agents), loop.close (pure stall-pattern detector → nextPrompt). All four in TOOL_MODULES in packages/mcp/src/tools/index.ts.

Invariants:

  • All four tools ship in the default full MCP profile — appear in list_tools
  • mandu.loop.close is pure (readOnlyHint: true) — no I/O, no spawn
  • mandu.deploy.preview always passes --dry-run — cannot trigger real deployments
  • mandu.run.tests child process timeout: 10 minutes
  • mandu.ai.brief is read-only — git log is cap'd at 10s, failure → empty recent_changes
🤖 MCP tools (Phase 14.3) · MCP tools (Phase 14.3)
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/ai/mcp-tools to my project.

Summary of the page:
Phase 14.3 Agent G adds 4 MCP tools: run.tests (runs mandu test, returns structured summary), deploy.preview (mandu deploy --dry-run adapter), ai.brief (project briefing for new agents), loop.close (pure stall-pattern detector → nextPrompt). All four in TOOL_MODULES in packages/mcp/src/tools/index.ts.

Required invariants — must hold after your changes:
- All four tools ship in the default `full` MCP profile — appear in `list_tools`
- `mandu.loop.close` is pure (readOnlyHint: true) — no I/O, no spawn
- `mandu.deploy.preview` always passes `--dry-run` — cannot trigger real deployments
- `mandu.run.tests` child process timeout: 10 minutes
- `mandu.ai.brief` is read-only — git log is cap'd at 10s, failure → empty recent_changes

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Prompt templates

Mandu's prompt template system — docs/prompts/*.md files loaded via --preset or /preset, plus the provider-specific adapter architecture behind mandu ai.

ai-hint — Prompt presets live at docs/prompts/<name>.md. Loaded by mandu ai chat --preset=<name> / mandu ai eval --preset=<name> / /preset <name> slash command. Name is allow-listed to [a-zA-Z0-9_-]+ — path traversal blocked. Adapter layer: each provider maps Mandu-normalized messages → provider-specific SSE format.

Invariants:

  • Preset names are allow-listed to [a-zA-Z0-9_-]+ — slashes, dots, tilde rejected at parse
  • Preset lookup path is exactly docs/prompts/<name>.md relative to the project root
  • Frontmatter is optional but recommended — name, version, audience, last_verified
  • Adapter layer normalizes messages into a provider-agnostic shape before streaming
  • System prompt is separate from user messages — never concatenated into the first user turn
🤖 Prompt templates · Prompt templates
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/ai/prompts to my project.

Summary of the page:
Prompt presets live at `docs/prompts/<name>.md`. Loaded by `mandu ai chat --preset=<name>` / `mandu ai eval --preset=<name>` / `/preset <name>` slash command. Name is allow-listed to `[a-zA-Z0-9_-]+` — path traversal blocked. Adapter layer: each provider maps Mandu-normalized messages → provider-specific SSE format.

Required invariants — must hold after your changes:
- Preset names are allow-listed to `[a-zA-Z0-9_-]+` — slashes, dots, tilde rejected at parse
- Preset lookup path is exactly `docs/prompts/<name>.md` relative to the project root
- Frontmatter is optional but recommended — `name`, `version`, `audience`, `last_verified`
- Adapter layer normalizes messages into a provider-agnostic shape before streaming
- System prompt is separate from user messages — never concatenated into the first user turn

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Skills generator

@mandujs/skills auto-generates a per-project skill manifest — the list of domain-specific actions an agent can perform in this repo. Pure, deterministic, safe to run in CI.

ai-hint — Skills generator: scans project source for exported functions matching skill conventions, builds a manifest at .mandu/skills/manifest.json with { id, source: 'generated'|'static', path?, description? }. Static skills are human-written under skills/. Both surfaced via mandu.ai.brief MCP tool. Generator is pure — no network, no spawn.

Invariants:

  • Static skills live under skills/<id>/index.ts (human-written, versioned)
  • Generated skills live under .mandu/skills/generated/<id>.ts (machine-emitted, regenerable)
  • Manifest is deterministic — identical source tree produces byte-identical .mandu/skills/manifest.json
  • Generator is pure — no network, no spawn, only fs reads under project root
  • Skills surface in mandu.ai.brief MCP tool output under skills: Array<{ id, source, path? }>
🤖 Skills generator · Skills generator
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/ai/skills-generator to my project.

Summary of the page:
Skills generator: scans project source for exported functions matching skill conventions, builds a manifest at `.mandu/skills/manifest.json` with { id, source: 'generated'|'static', path?, description? }. Static skills are human-written under `skills/`. Both surfaced via `mandu.ai.brief` MCP tool. Generator is pure — no network, no spawn.

Required invariants — must hold after your changes:
- Static skills live under `skills/<id>/index.ts` (human-written, versioned)
- Generated skills live under `.mandu/skills/generated/<id>.ts` (machine-emitted, regenerable)
- Manifest is deterministic — identical source tree produces byte-identical `.mandu/skills/manifest.json`
- Generator is pure — no network, no spawn, only fs reads under project root
- Skills surface in `mandu.ai.brief` MCP tool output under `skills: Array<{ id, source, path? }>`

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Architecture

Client Rendering

"use client" pages, *.island.tsx islands, and partials — when to pick which.

ai-hint — Mandu has three client-render mechanisms: 'use client' page (whole page client-rendered), .island.tsx (partial hydration of a component inside a server page), and partial (even smaller than island, for single buttons). Prefer islands for most interactive UI.

Invariants:

  • *.island.tsx files must live next to a page.tsx and include \"use client\"
  • Page-level \"use client\" opts the whole route out of prerender
  • Partials render server-side for initial HTML then hydrate on demand
🤖 Client Rendering · Client Rendering
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/architect/client-rendering to my project.

Summary of the page:
Mandu has three client-render mechanisms: 'use client' page (whole page client-rendered), .island.tsx (partial hydration of a component inside a server page), and partial (even smaller than island, for single buttons). Prefer islands for most interactive UI.

Required invariants — must hold after your changes:
- `*.island.tsx` files must live next to a `page.tsx` and include `\"use client\"`
- Page-level `\"use client\"` opts the whole route out of prerender
- Partials render server-side for initial HTML then hydrate on demand

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Content Layer

Astro-style build-time content collections with Zod validation and digest caching.

ai-hint — Mandu Content Layer loads Markdown/JSON/YAML/API content at build time into typed collections. Define collections in content.config.ts using glob/file/api loaders + Zod schemas. Query with getCollection() and getEntry().

🤖 Content Layer · Content Layer
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/architect/content-layer to my project.

Summary of the page:
Mandu Content Layer loads Markdown/JSON/YAML/API content at build time into typed collections. Define collections in content.config.ts using glob/file/api loaders + Zod schemas. Query with getCollection() and getEntry().

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Decision Memory (ADR)

Persistent architectural memory for agents — save, search, and enforce decisions.

ai-hint — Mandu Decision Memory stores ADRs (Architecture Decision Records) per project. Agents use searchDecisions() to avoid re-relitigating, saveDecision() to record choices, and checkConsistency() to verify implementation matches intent.

🤖 Decision Memory (ADR) · Decision Memory (ADR)
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/architect/decision-memory to my project.

Summary of the page:
Mandu Decision Memory stores ADRs (Architecture Decision Records) per project. Agents use searchDecisions() to avoid re-relitigating, saveDecision() to record choices, and checkConsistency() to verify implementation matches intent.

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Accessing generated content

The official pattern for reading .mandu/generated/ artifacts from user code — runtime registry, not direct imports.

ai-hint — Direct imports from .mandu/generated/ or any /generated/ path are forbidden by the guard rule INVALID_GENERATED_IMPORT. The official access path is the runtime registry: getGenerated(key) / getManifest() / getRouteById(id) / tryGetGenerated(key) exported from @mandujs/core/runtime. The registry is seeded during server boot by registerManifestHandlers() from @mandujs/cli. User code that wants a new generated artifact extends the GeneratedRegistry interface via module augmentation.

Invariants:

  • Files under app/, src/, and packages/ cannot import from any path containing /generated/
  • The runtime registry is the single source of truth for generated artifacts at runtime
  • getGenerated() throws a helpful error with docs link if called before manifest registration
  • registerManifestHandlers() (from @mandujs/cli) is the only glue that seeds the routes manifest; third-party artifacts need their own glue
  • Barrel re-export workarounds through src/shared/**/index.ts bypass the guard but are not endorsed
🤖 Accessing generated content · Accessing generated content
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/architect/generated-access to my project.

Summary of the page:
Direct imports from .mandu/generated/ or any /generated/ path are forbidden by the guard rule INVALID_GENERATED_IMPORT. The official access path is the runtime registry: getGenerated(key) / getManifest() / getRouteById(id) / tryGetGenerated(key) exported from @mandujs/core/runtime. The registry is seeded during server boot by registerManifestHandlers() from @mandujs/cli. User code that wants a new generated artifact extends the GeneratedRegistry interface via module augmentation.

Required invariants — must hold after your changes:
- Files under app/, src/, and packages/ cannot import from any path containing /generated/
- The runtime registry is the single source of truth for generated artifacts at runtime
- getGenerated() throws a helpful error with docs link if called before manifest registration
- registerManifestHandlers() (from @mandujs/cli) is the only glue that seeds the routes manifest; third-party artifacts need their own glue
- Barrel re-export workarounds through src/shared/**/index.ts bypass the guard but are not endorsed

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Guard

The invariant layer — every rule Mandu's static analyzer enforces, what each one means, and how to fix the violation.

ai-hint — Guard is a build-time static analyzer. It enforces six invariant families: layer-violation, circular-dependency, cross-slice, deep-nesting, file-type, invalid-shared-segment. It also runs FS-routes rules (page/layout/route import whitelists) and contract rules (slot/contract exports). Severity is configurable; 'error' fails the build. Each violation carries filePath, line, column, ruleName, ruleDescription, suggestions.

Invariants:

  • Any .js or .jsx file in a scanned layer is a file-type violation
  • src/shared is restricted to {contracts, schema, types, utils/client, utils/server, env}; other segments are invalid-shared-segment
  • Client-layer files cannot import shared/env (server-only)
  • page.tsx cannot import another page.tsx (noPageToPage rule)
  • Layer imports must appear in the importing layer's canImport list
  • Cross-slice imports within the same layer are rejected unless via a public API
🤖 Guard · Guard
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/architect/guard to my project.

Summary of the page:
Guard is a build-time static analyzer. It enforces six invariant families: layer-violation, circular-dependency, cross-slice, deep-nesting, file-type, invalid-shared-segment. It also runs FS-routes rules (page/layout/route import whitelists) and contract rules (slot/contract exports). Severity is configurable; 'error' fails the build. Each violation carries filePath, line, column, ruleName, ruleDescription, suggestions.

Required invariants — must hold after your changes:
- Any .js or .jsx file in a scanned layer is a file-type violation
- src/shared is restricted to {contracts, schema, types, utils/client, utils/server, env}; other segments are invalid-shared-segment
- Client-layer files cannot import shared/env (server-only)
- page.tsx cannot import another page.tsx (noPageToPage rule)
- Layer imports must appear in the importing layer's canImport list
- Cross-slice imports within the same layer are rejected unless via a public API

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Architect

The three pillars — Prerender, Island, Guard — that shape every Mandu app.

ai-hint — Architect category hub. Three pillars: Prerender (default), Island (opt-in), Guard (invariants).

🤖 Architect · Architect
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/architect/index to my project.

Summary of the page:
Architect category hub. Three pillars: Prerender (default), Island (opt-in), Guard (invariants).

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Island

The .island.tsx contract — the single signal Mandu uses to decide what ships to the browser, and the mismatch guards that keep hydration honest.

ai-hint — Islands are the opt-in client boundary. The .island.tsx suffix and 'use client' directive together form the contract. The FS scanner picks the first island co-located with a page as that route's clientModule; otherwise a page with 'use client' is its own clientModule. A known SSR-shell pattern triggers a hydration-shell-mismatch error at scan time.

Invariants:

  • Any file named *.island.tsx is a client-bundled module
  • An island must start with the 'use client' directive
  • For a page route, the co-located island (if any) becomes clientModule; otherwise a page with 'use client' is its own clientModule
  • A page that imports an island and also renders {typeof X !== 'undefined' && null} against it is rejected at scan time as a hydration-shell-mismatch-risk
🤖 Island · Island
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/architect/island to my project.

Summary of the page:
Islands are the opt-in client boundary. The .island.tsx suffix and 'use client' directive together form the contract. The FS scanner picks the first island co-located with a page as that route's clientModule; otherwise a page with 'use client' is its own clientModule. A known SSR-shell pattern triggers a hydration-shell-mismatch error at scan time.

Required invariants — must hold after your changes:
- Any file named *.island.tsx is a client-bundled module
- An island must start with the 'use client' directive
- For a page route, the co-located island (if any) becomes clientModule; otherwise a page with 'use client' is its own clientModule
- A page that imports an island and also renders {typeof X !== 'undefined' && null} against it is rejected at scan time as a hydration-shell-mismatch-risk

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Architecture Overview

The three pillars that keep Mandu apps — and the agents writing them — from drifting.

ai-hint — Mandu rests on three pillars — Prerender, Island, Guard. Prerender is the default render mode, Island is the opt-in client boundary, Guard is the machine-checkable invariant layer.

Invariants:

  • Every route is prerendered unless it explicitly opts into dynamic rendering
  • Interactivity is opt-in via .island.tsx files; nothing ships to the client by default
  • Guard rules are enforced at build time and fail the build on violation
  • src/server/** never crosses the client boundary
🤖 Architecture Overview · Architecture Overview
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/architect/overview to my project.

Summary of the page:
Mandu rests on three pillars — Prerender, Island, Guard. Prerender is the default render mode, Island is the opt-in client boundary, Guard is the machine-checkable invariant layer.

Required invariants — must hold after your changes:
- Every route is prerendered unless it explicitly opts into dynamic rendering
- Interactivity is opt-in via .island.tsx files; nothing ships to the client by default
- Guard rules are enforced at build time and fail the build on violation
- src/server/** never crosses the client boundary

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Prerender

Static-by-default rendering — how Mandu turns your routes into HTML at build time, and the tiny lever that opts out.

ai-hint — Prerender is the default render mode. Every page route without a dynamic segment is rendered to HTML at build time into .mandu/static/**/index.html. Dynamic routes participate via generateStaticParams(). Crawl mode follows internal links to discover unlisted paths.

Invariants:

  • A page route with no dynamic segment is always prerendered
  • A dynamic page route is prerendered only for params returned by generateStaticParams()
  • Output lives under .mandu/static//index.html (clean URLs)
  • generateStaticParams() must return an array; non-array returns are warned and skipped
🤖 Prerender · Prerender
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/architect/prerender to my project.

Summary of the page:
Prerender is the default render mode. Every page route without a dynamic segment is rendered to HTML at build time into .mandu/static/**/index.html. Dynamic routes participate via generateStaticParams(). Crawl mode follows internal links to discover unlisted paths.

Required invariants — must hold after your changes:
- A page route with no dynamic segment is always prerendered
- A dynamic page route is prerendered only for params returned by generateStaticParams()
- Output lives under .mandu/static/<pathname>/index.html (clean URLs)
- generateStaticParams() must return an array; non-array returns are warned and skipped

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Rendering Modes

Mandu blends eight rendering strategies — here's when to use each.

ai-hint — Mandu supports 8 rendering modes. Pages default to prerender + island hydration. Use SSR for user-specific data, streaming SSR for slow pages with Suspense, 'use client' for fully interactive pages, partials for fine-grained hydration.

🤖 Rendering Modes · Rendering Modes
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/architect/rendering-modes to my project.

Summary of the page:
Mandu supports 8 rendering modes. Pages default to prerender + island hydration. Use SSR for user-specific data, streaming SSR for slow pages with Suspense, 'use client' for fully interactive pages, partials for fine-grained hydration.

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Smooth Navigation

CSS View Transitions + hover-based link prefetch, auto-injected into every SSR response. Closes the perceived-latency gap against Next.js / Astro / SvelteKit without requiring a client-side SPA runtime.

ai-hint — Smooth navigation adds two things to every SSR response: (1) <style>@view-transition{navigation:auto}</style> for supported browsers, and (2) a ~500-byte inline hover-prefetch IIFE that injects <link rel=prefetch as=document> on same-origin anchor hover. Opt out globally via transitions: false / prefetch: false in mandu.config.ts; per-link via data-no-prefetch.

Invariants:

  • Both transitions and prefetch default to enabled (true)
  • View Transitions is a CSS at-rule — zero JS runtime cost, ~70 bytes HTML overhead
  • Hover prefetch is a ~500-byte inline IIFE in <head> — CSP unsafe-inline or a future nonce required for strict CSP
  • Prefetch helper only acts on same-origin anchors (href^='/') that are not <a download>, not target=_blank, and not marked data-no-prefetch
  • Prefetch ≠ SPA navigation — click still triggers a full document reload; use <Link> from @mandujs/core/client for SPA-style routing
🤖 Smooth Navigation · Smooth Navigation
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/architect/smooth-navigation to my project.

Summary of the page:
Smooth navigation adds two things to every SSR response: (1) `<style>@view-transition{navigation:auto}</style>` for supported browsers, and (2) a ~500-byte inline hover-prefetch IIFE that injects `<link rel=prefetch as=document>` on same-origin anchor hover. Opt out globally via `transitions: false` / `prefetch: false` in `mandu.config.ts`; per-link via `data-no-prefetch`.

Required invariants — must hold after your changes:
- Both `transitions` and `prefetch` default to enabled (`true`)
- View Transitions is a CSS at-rule — zero JS runtime cost, ~70 bytes HTML overhead
- Hover prefetch is a ~500-byte inline IIFE in `<head>` — CSP `unsafe-inline` or a future nonce required for strict CSP
- Prefetch helper only acts on same-origin anchors (`href^='/'`) that are not `<a download>`, not `target=_blank`, and not marked `data-no-prefetch`
- Prefetch ≠ SPA navigation — click still triggers a full document reload; use `<Link>` from `@mandujs/core/client` for SPA-style routing

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

SSR & Streaming SSR

Render on the server per request — including chunked Suspense streams.

ai-hint — Mandu SSR renders React components per request via renderToStream. Use for auth'd pages, user-specific content, or data that can't be known at build time. Streaming SSR uses React Suspense to ship HTML as it resolves.

🤖 SSR &amp; Streaming SSR · SSR & Streaming SSR
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/architect/ssr to my project.

Summary of the page:
Mandu SSR renders React components per request via renderToStream. Use for auth'd pages, user-specific content, or data that can't be known at build time. Streaming SSR uses React Suspense to ship HTML as it resolves.

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Transactions & Rollback

Atomic code changes — beginChange, commitChange, rollbackChange.

ai-hint — Mandu transactions (beginChange/commitChange/rollbackChange) wrap agent edits in atomic snapshots. Begin takes a snapshot, edits happen, commit locks it in or rollback restores. Pairs with ATE/Guard for safe LLM-driven changes.

🤖 Transactions &amp; Rollback · Transactions & Rollback
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/architect/transaction to my project.

Summary of the page:
Mandu transactions (beginChange/commitChange/rollbackChange) wrap agent edits in atomic snapshots. Begin takes a snapshot, edits happen, commit locks it in or rollback restores. Pairs with ATE/Guard for safe LLM-driven changes.

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

benchmarks

Mandu vs Hono

When the API layer is everything — Hono's router-as-library vs Mandu's full fullstack framework with built-in agent surface.

ai-hint — Hono is a tiny edge-first router; Mandu is a fullstack framework that includes routing, React UI, build tooling, runtime guard, and 100+ MCP tools. Pick Hono if you only need an API; pick Mandu if you also want UI, contracts, and agent-driven scaffolding.

🤖 Port Hono to Mandu · Mandu vs Hono
I have a Hono API service and I'm evaluating moving it under Mandu (https://mandujs.com) so I can add a React UI in the same repo.
1. Pick 3 representative routes from my Hono app.
2. For each, show me the Mandu equivalent — contract file (`spec/contracts/<NAME>.contract.ts`) + handler (`app/api/<NAME>/route.ts` using `Mandu.handler(contract, ...)`).
3. Map Hono middleware to Mandu's filling chain `.use(...)` middleware.
4. Identify any Hono-specific features I lose (specific edge adapters, bundler tricks).
5. Estimate the percentage of routes that port cleanly vs need rewriting.

Mandu vs Next.js

An honest, agent-first comparison — file-system routing, type-safe APIs, runtime guard, and AI-editor workflows on Bun + React.

ai-hint — Mandu replaces Next.js for projects where AI agents drive most of the code. Same file-system route convention (app/**/page.tsx) but Mandu adds runtime architecture guard, contract-first APIs (one zod schema → types + validation + OpenAPI), 100+ MCP tools for direct agent control, and Bun-native runtime. Use Next.js if you want maximum ecosystem; use Mandu if you want agent-safe code at scale.

🤖 Compare a Next route against Mandu · Mandu vs Next.js
I have a Next.js app and I'm evaluating Mandu (https://mandujs.com).
1. Pick a representative route (an API + a page) from my codebase.
2. Show me what the same route would look like as Mandu — `Mandu.filling()` chain or `Mandu.contract({...})` + `Mandu.handler(contract, ...)` — using the exact paths and types my Next route already uses.
3. Highlight which lines disappear (CSRF, manual zod parse, status code branching) because Mandu derives them from the contract.
4. Estimate how much of the codebase would migrate cleanly vs need rewriting (give me a percentage with reasoning).
5. List the top 3 risks of switching for my project specifically.

Mandu vs Remix

Two routing-first React frameworks compared — loaders/actions vs filling chain, web standards vs MCP-driven agents.

ai-hint — Mandu and Remix both lean on web standards and file-system routing, but Mandu adds runtime architecture guard, contract-first APIs, and 100+ MCP tools so AI editors can drive the codebase. Remix focuses on progressive enhancement and tight loader/action coupling; Mandu focuses on agent-safe scaffolding and explicit contracts.

🤖 Compare a Remix route against Mandu · Mandu vs Remix
I have a Remix v2 app and I'm evaluating Mandu (https://mandujs.com).
1. Pick one of my routes that has a loader + action + UI.
2. Show me the Mandu shape — contract file (`spec/contracts/<NAME>.contract.ts`), API route (`app/api/<NAME>/route.ts` using Mandu.handler), and UI page that consumes the typed `client(contract)` wrapper.
3. Identify which Remix-specific patterns disappear (loader return types, action `_action` discriminator, fetcher data) and which require manual replacement (progressive enhancement, useFetcher state).
4. Estimate the percentage of routes that would port cleanly vs need rewriting.
5. List the top 3 risks of switching for my project.

Build with Agents

ATE — Agent Test Engine

Extract → Generate → Run → Report → Heal. A six-stage pipeline that turns the interaction graph of your app into runnable Playwright specs and suggests diffs when they fail.

ai-hint — ATE is @mandujs/ate. Pipeline: ateExtract → ateGenerate → ateRun → ateReport → ateHeal with ateImpact for subset selection. CLI: bunx mandu test:auto. MCP: mandu.ate.* tools plus mandu.ate.auto_pipeline. Oracle levels L0-L3 gate assertion depth.

Invariants:

  • Every stage writes to .mandu/ate or .mandu/reports and can be resumed by runId
  • ate.heal produces diff suggestions only; it never auto-commits code
  • ate.apply_heal always creates a snapshot via the change system before writing
  • oracleLevel determines assertion depth: L0 smoke, L1 HTTP, L2 contract, L3 behavioral
🤖 ATE — Agent Test Engine · ATE — Agent Test Engine
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/build-with-agents/ate to my project.

Summary of the page:
ATE is @mandujs/ate. Pipeline: ateExtract → ateGenerate → ateRun → ateReport → ateHeal with ateImpact for subset selection. CLI: bunx mandu test:auto. MCP: mandu.ate.* tools plus mandu.ate.auto_pipeline. Oracle levels L0-L3 gate assertion depth.

Required invariants — must hold after your changes:
- Every stage writes to .mandu/ate or .mandu/reports and can be resumed by runId
- ate.heal produces diff suggestions only; it never auto-commits code
- ate.apply_heal always creates a snapshot via the change system before writing
- oracleLevel determines assertion depth: L0 smoke, L1 HTTP, L2 contract, L3 behavioral

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Change — snapshot transactions

begin → commit or rollback. Atomic, snapshot-backed edits so agents can experiment without leaving the repo in a half-written state.

ai-hint — The Change system is in @mandujs/core/change. CLI: bunx mandu change begin|commit|rollback|status|list|prune. MCP: mandu.tx.begin|commit|rollback|status. State lives under .mandu/history with snapshots, changes.json, and a single active.json lock.

Invariants:

  • Only one transaction can be active at a time per project (enforced via .mandu/history/active.json)
  • Every begin takes a full snapshot of the manifest and spec/slots before any write
  • rollback restores files from the captured snapshot; commit only marks the record committed
  • Change IDs use YYYYMMDD-HHmmss-xxx and never collide with snapshot IDs
🤖 Change — snapshot transactions · Change — snapshot transactions
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/build-with-agents/change to my project.

Summary of the page:
The Change system is in @mandujs/core/change. CLI: bunx mandu change begin|commit|rollback|status|list|prune. MCP: mandu.tx.begin|commit|rollback|status. State lives under .mandu/history with snapshots, changes.json, and a single active.json lock.

Required invariants — must hold after your changes:
- Only one transaction can be active at a time per project (enforced via .mandu/history/active.json)
- Every begin takes a full snapshot of the manifest and spec/slots before any write
- rollback restores files from the captured snapshot; commit only marks the record committed
- Change IDs use YYYYMMDD-HHmmss-xxx and never collide with snapshot IDs

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Build with Agents

Guards, MCP, and ATE — the tools that keep LLM-written code honest.

ai-hint — Build-with-agents hub. Covers MCP setup, Guard authoring, ATE agent test engine.

🤖 Build with Agents · Build with Agents
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/build-with-agents/index to my project.

Summary of the page:
Build-with-agents hub. Covers MCP setup, Guard authoring, ATE agent test engine.

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

MCP Setup

Connect Claude Code, Cursor, and Gemini CLI to the Mandu MCP server so agents can negotiate, scaffold, and guard without ad-hoc shell commands.

ai-hint — The Mandu MCP server ships as the bin mandu-mcp from @mandujs/mcp. Client JSON config points at command bunx, args [mandu-mcp]. Tools are namespaced mandu.. and filtered by the MANDU_MCP_PROFILE env (minimal|standard|full).

Invariants:

  • The MCP server is launched as bunx mandu-mcp, never via a Python shim named mcp
  • Tool names use dot-notation under the mandu prefix (for example mandu.negotiate, mandu.generate, mandu.guard.check)
  • MANDU_MCP_PROFILE gates which tool categories are registered at startup
  • mandu init writes .mcp.json, .claude.json, and .gemini/settings.json atomically with backups
🤖 MCP Setup · MCP Setup
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/build-with-agents/mcp-setup to my project.

Summary of the page:
The Mandu MCP server ships as the bin mandu-mcp from @mandujs/mcp. Client JSON config points at command bunx, args [mandu-mcp]. Tools are namespaced mandu.<category>.<verb> and filtered by the MANDU_MCP_PROFILE env (minimal|standard|full).

Required invariants — must hold after your changes:
- The MCP server is launched as bunx mandu-mcp, never via a Python shim named mcp
- Tool names use dot-notation under the mandu prefix (for example mandu.negotiate, mandu.generate, mandu.guard.check)
- MANDU_MCP_PROFILE gates which tool categories are registered at startup
- mandu init writes .mcp.json, .claude.json, and .gemini/settings.json atomically with backups

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Architecture Negotiation

Agents propose structure first, get approval, then scaffold — no cowboy coding.

ai-hint — Mandu Negotiation is a pre-implementation dialog. Call mandu.negotiate with {intent, requirements, constraints} → framework returns a structured plan {files, layers, decisions} → if approved, mandu.negotiate.scaffold materializes it. RFC-001.

Invariants:

  • Every new feature an agent adds must pass mandu.negotiate before any files are written
  • The scaffold step respects the Guard preset (layers, naming, imports)
  • A rejected plan is NEVER partially applied
🤖 Architecture Negotiation · Architecture Negotiation
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/build-with-agents/negotiation to my project.

Summary of the page:
Mandu Negotiation is a pre-implementation dialog. Call mandu.negotiate with {intent, requirements, constraints} → framework returns a structured plan {files, layers, decisions} → if approved, mandu.negotiate.scaffold materializes it. RFC-001.

Required invariants — must hold after your changes:
- Every new feature an agent adds must pass `mandu.negotiate` before any files are written
- The scaffold step respects the Guard preset (layers, naming, imports)
- A rejected plan is NEVER partially applied

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

CLI

mandu db seed

Replay deterministic fixture data against the live database. Declarative or imperative seed files, parameter-bound SQL with quoteIdent() injection defense, idempotent upsert + tamper detection.

ai-hintmandu db seed runs spec/seeds/*.seed.ts in lexicographic order. Declarative = { resource, key, data }; imperative = async function seed(ctx) { ... }. SQL injection blocked via quoteIdent() + parameter binding. Prod deploys require MANDU_DB_SEED_PROD_CONFIRM=yes.

Invariants:

  • Every seed identifier flows through quoteIdent() — SQL injection blocked at the driver layer
  • Every value is parameter-bound via Bun.SQL — never concatenated
  • Rows validated against the resource's Zod schema BEFORE any INSERT
  • Each seed file runs inside a transaction — any failure rolls the whole file back
  • Tamper detection: if a previously-applied file's bytes change, the runner refuses to replay until --reset
  • --env=prod requires MANDU_DB_SEED_PROD_CONFIRM=yes in the environment
🤖 `mandu db seed` · `mandu db seed`
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/cli/db-seed to my project.

Summary of the page:
`mandu db seed` runs `spec/seeds/*.seed.ts` in lexicographic order. Declarative = `{ resource, key, data }`; imperative = `async function seed(ctx) { ... }`. SQL injection blocked via quoteIdent() + parameter binding. Prod deploys require MANDU_DB_SEED_PROD_CONFIRM=yes.

Required invariants — must hold after your changes:
- Every seed identifier flows through `quoteIdent()` — SQL injection blocked at the driver layer
- Every value is parameter-bound via `Bun.SQL` — never concatenated
- Rows validated against the resource's Zod schema BEFORE any INSERT
- Each seed file runs inside a transaction — any failure rolls the whole file back
- Tamper detection: if a previously-applied file's bytes change, the runner refuses to replay until `--reset`
- `--env=prod` requires `MANDU_DB_SEED_PROD_CONFIRM=yes` in the environment

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

mandu mcp register

Automatic IDE integration — writes a Mandu MCP server entry into Claude Code, Cursor, Continue, or Aider without touching sibling configuration. Merge-safe, tolerant of JSON-C, preserves YAML preamble.

ai-hintmandu mcp register merges mcpServers.mandu into ~/.claude/mcp.json (Claude Code), ~/.cursor/mcp.json (Cursor), ~/.continue/config.json (Continue), /.aider.conf.yml (Aider). Never overwrites sibling keys. Stashes .bak. before every write. Token defaults to ${localEnv:MANDU_MCP_TOKEN} placeholder.

Invariants:

  • Four IDEs: claude, cursor, continue, aider — --ide=all covers them all
  • JSON-C comments (Claude/Cursor/Continue) are tolerated on read; stripped on write with a warning
  • YAML (Aider) merge preserves preamble and trailing keys verbatim
  • Every modified file gets a .bak.<unix-ms> sibling — atomic rename semantics
  • Tokens default to ${localEnv:MANDU_MCP_TOKEN} placeholder — never embedded inline unless explicitly requested
🤖 `mandu mcp register` · `mandu mcp register`
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/cli/mcp-register to my project.

Summary of the page:
`mandu mcp register` merges `mcpServers.mandu` into ~/.claude/mcp.json (Claude Code), ~/.cursor/mcp.json (Cursor), ~/.continue/config.json (Continue), <cwd>/.aider.conf.yml (Aider). Never overwrites sibling keys. Stashes <config>.bak.<ms> before every write. Token defaults to ${localEnv:MANDU_MCP_TOKEN} placeholder.

Required invariants — must hold after your changes:
- Four IDEs: claude, cursor, continue, aider — `--ide=all` covers them all
- JSON-C comments (Claude/Cursor/Continue) are tolerated on read; stripped on write with a warning
- YAML (Aider) merge preserves preamble and trailing keys verbatim
- Every modified file gets a `.bak.<unix-ms>` sibling — atomic rename semantics
- Tokens default to `${localEnv:MANDU_MCP_TOKEN}` placeholder — never embedded inline unless explicitly requested

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

mandu upgrade

Self-updater for both the npm-installed @mandujs/* packages and the standalone compile-binary builds. SHA-256 verified, atomic swap, --rollback capable.

ai-hintmandu upgrade auto-detects package vs binary mode via process.execPath. Binary mode: downloads, verifies SHA256 from SHA256SUMS.txt, atomically swaps (rename on POSIX; rename-then-replace on Windows). Stashes the old binary in ~/.mandu/bin/previous/ so --rollback works. SLSA L2 provenance attached.

Invariants:

  • Package mode (bun run mandu): runs bun update @mandujs/*
  • Binary mode (mandu.exe / mandu-bun-linux-x64): downloads + verifies + swaps
  • SHA-256 verified against SHA256SUMS.txt released alongside each binary
  • Windows: rename running .exe to .old.<pid>.exe first (allowed while loaded), then move new binary into place
  • Previous binary stashed in ~/.mandu/bin/previous/--rollback restores it
  • SLSA Build L2 provenance is attached via actions/attest-build-provenance
🤖 `mandu upgrade` · `mandu upgrade`
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/cli/upgrade to my project.

Summary of the page:
`mandu upgrade` auto-detects package vs binary mode via process.execPath. Binary mode: downloads, verifies SHA256 from SHA256SUMS.txt, atomically swaps (rename on POSIX; rename-then-replace on Windows). Stashes the old binary in ~/.mandu/bin/previous/ so --rollback works. SLSA L2 provenance attached.

Required invariants — must hold after your changes:
- Package mode (`bun run mandu`): runs `bun update @mandujs/*`
- Binary mode (`mandu.exe` / `mandu-bun-linux-x64`): downloads + verifies + swaps
- SHA-256 verified against `SHA256SUMS.txt` released alongside each binary
- Windows: rename running `.exe` to `.old.<pid>.exe` first (allowed while loaded), then move new binary into place
- Previous binary stashed in `~/.mandu/bin/previous/` — `--rollback` restores it
- SLSA Build L2 provenance is attached via `actions/attest-build-provenance`

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Deploy

Cloudflare Pages

mandu deploy --target=cf-pages emits a wrangler.toml + a functions/_middleware.ts glue file. Artifact-only in Phase 13 — runtime compatibility via the Phase 15 Workers adapter.

ai-hint — cf-pages is the Pages+Functions variant (as distinct from @mandujs/edge/workers which is the dedicated Workers adapter). Emits wrangler.toml + functions/_middleware.ts routing every request to the Worker handler. Runtime compat via Phase 15's Bun.CookieMap → LegacyCookieCodec polyfills.

Invariants:

  • Distinct from @mandujs/edge/workers — cf-pages uses Pages Functions (per-project), Workers adapter uses standalone Worker
  • Required secret: CLOUDFLARE_API_TOKEN — stored in Bun.secrets
  • Minimum wrangler version: 3.0.0
  • App bindings (KV, R2, D1) are declared in wrangler.toml — secret values go via wrangler pages secret put
  • Runtime compatibility lands in Phase 15 via @mandujs/edge — some Bun APIs currently shim slowly
🤖 Cloudflare Pages · Cloudflare Pages
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/deploy/cf-pages to my project.

Summary of the page:
cf-pages is the Pages+Functions variant (as distinct from `@mandujs/edge/workers` which is the dedicated Workers adapter). Emits wrangler.toml + functions/_middleware.ts routing every request to the Worker handler. Runtime compat via Phase 15's Bun.CookieMap → LegacyCookieCodec polyfills.

Required invariants — must hold after your changes:
- Distinct from `@mandujs/edge/workers` — cf-pages uses Pages Functions (per-project), Workers adapter uses standalone Worker
- Required secret: `CLOUDFLARE_API_TOKEN` — stored in Bun.secrets
- Minimum wrangler version: 3.0.0
- App bindings (KV, R2, D1) are declared in wrangler.toml — secret values go via `wrangler pages secret put`
- Runtime compatibility lands in Phase 15 via @mandujs/edge — some Bun APIs currently shim slowly

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Docker

Emit a multi-stage Dockerfile + .dockerignore tuned for Bun + Mandu. Artifact-only — users run docker build / docker push themselves.

ai-hintmandu deploy --target=docker emits a Dockerfile using oven/bun as the build and runtime base. Multi-stage: deps → build → runtime. Runtime stage uses USER bun (non-root) and exposes port 3333. .dockerignore excludes .mandu/, node_modules/, coverage/.

Invariants:

  • mandu deploy --target=docker NEVER invokes docker build — users run it themselves
  • Runtime stage runs as USER bun (non-root) — avoid running as root
  • Default port is 3333 — override with PORT env at runtime
  • Build cache is optimized via layered COPY: package.json + bun.lock first, source last
🤖 Docker · Docker
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/deploy/docker to my project.

Summary of the page:
`mandu deploy --target=docker` emits a Dockerfile using `oven/bun` as the build and runtime base. Multi-stage: deps → build → runtime. Runtime stage uses `USER bun` (non-root) and exposes port 3333. `.dockerignore` excludes `.mandu/`, `node_modules/`, `coverage/`.

Required invariants — must hold after your changes:
- `mandu deploy --target=docker` NEVER invokes `docker build` — users run it themselves
- Runtime stage runs as `USER bun` (non-root) — avoid running as root
- Default port is 3333 — override with `PORT` env at runtime
- Build cache is optimized via layered COPY: `package.json` + `bun.lock` first, source last

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Docker Compose

Multi-service scaffold — Mandu app + Postgres sidecar + .env.example. Ideal for local reproducible dev environments and single-host self-hosting.

ai-hintmandu deploy --target=docker-compose emits Dockerfile + docker-compose.yml + .env.example. The compose file scaffolds a Postgres service by default (Phase 4c DB integration). Runtime service uses the same non-root Dockerfile as --target=docker.

Invariants:

  • Postgres sidecar is scaffolded by default; disable with --no-db
  • Services communicate over a private bridge network (internal by default)
  • .env.example is committed; .env is gitignored (compose reads .env automatically)
  • No volumes for app code — only for database data (postgres_data:/var/lib/postgresql/data)
🤖 Docker Compose · Docker Compose
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/deploy/docker-compose to my project.

Summary of the page:
`mandu deploy --target=docker-compose` emits Dockerfile + docker-compose.yml + .env.example. The compose file scaffolds a Postgres service by default (Phase 4c DB integration). Runtime service uses the same non-root Dockerfile as --target=docker.

Required invariants — must hold after your changes:
- Postgres sidecar is scaffolded by default; disable with `--no-db`
- Services communicate over a private bridge network (`internal` by default)
- `.env.example` is committed; `.env` is gitignored (compose reads `.env` automatically)
- No volumes for app code — only for database data (`postgres_data:/var/lib/postgresql/data`)

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Fly.io

mandu deploy --target=fly emits a fly.toml + Dockerfile pair. With --execute + flyctl ≥ 0.1.0 installed, Mandu invokes fly deploy on your behalf.

ai-hint — Fly adapter: uses the same non-root Dockerfile as Docker adapter, plus a fly.toml tuned for Mandu (port 3333, auto-stop, auto-start). Secret: FLY_API_TOKEN — read from OS keychain, never passed on argv. --execute requires flyctl.

Invariants:

  • Uses the same multi-stage Dockerfile as --target=docker
  • Required secret: FLY_API_TOKEN — stored in Bun.secrets / OS keychain
  • Minimum flyctl version: 0.1.0
  • internal_port = 3333, auto_stop_machines = true, auto_start_machines = true by default
  • Multi-region: default primary region from flyctl login — override with primary_region
🤖 Fly.io · Fly.io
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/deploy/fly to my project.

Summary of the page:
Fly adapter: uses the same non-root Dockerfile as Docker adapter, plus a fly.toml tuned for Mandu (port 3333, auto-stop, auto-start). Secret: `FLY_API_TOKEN` — read from OS keychain, never passed on argv. `--execute` requires flyctl.

Required invariants — must hold after your changes:
- Uses the same multi-stage Dockerfile as `--target=docker`
- Required secret: `FLY_API_TOKEN` — stored in Bun.secrets / OS keychain
- Minimum flyctl version: 0.1.0
- `internal_port = 3333`, `auto_stop_machines = true`, `auto_start_machines = true` by default
- Multi-region: default primary region from flyctl login — override with `primary_region`

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Deploy

mandu deploy turns a built Mandu project into the config files + pipeline each hosting provider expects. Seven adapters, one CLI surface, zero secrets-in-artifacts.

ai-hintmandu deploy --target=<adapter> runs validate → guard → build → check → prepare → (optional) deploy. Secrets are stored in Bun.secrets (OS keychain), never written to artifact files. 7 adapters: docker, docker-compose, fly, vercel, railway, netlify, cf-pages.

Invariants:

  • --target=<name> is required — unsupported values return CLI_E200
  • Secrets NEVER touch artifact files — writeArtifact throws SecretLeakError (CLI_E210) if a value appears verbatim
  • Secrets NEVER get logged — all formatters route through maskSecret() → constant \"****\"
  • --execute is required for adapter.deploy() to run; without it, prepare-only
  • Adapter check() only probes provider CLIs when --execute is passed — clean CI runners still get through --dry-run
🤖 Deploy · Deploy
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/deploy/index to my project.

Summary of the page:
`mandu deploy --target=<adapter>` runs validate → guard → build → check → prepare → (optional) deploy. Secrets are stored in `Bun.secrets` (OS keychain), never written to artifact files. 7 adapters: docker, docker-compose, fly, vercel, railway, netlify, cf-pages.

Required invariants — must hold after your changes:
- `--target=<name>` is required — unsupported values return CLI_E200
- Secrets NEVER touch artifact files — writeArtifact throws SecretLeakError (CLI_E210) if a value appears verbatim
- Secrets NEVER get logged — all formatters route through `maskSecret()` → constant `\"****\"`
- `--execute` is required for adapter.deploy() to run; without it, prepare-only
- Adapter `check()` only probes provider CLIs when `--execute` is passed — clean CI runners still get through `--dry-run`

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Netlify

mandu deploy --target=netlify emits a netlify.toml + a Node SSR function at netlify/functions/ssr.ts. With --execute and netlify ≥ 17.0.0 installed, Mandu triggers a deploy.

ai-hint — Netlify adapter: emits netlify.toml with a redirect to Node Functions at netlify/functions/ssr.ts. Static assets from public/; every other request → SSR function. Edge Functions adapter deferred to Phase 15.3. Secret: NETLIFY_AUTH_TOKEN.

Invariants:

  • Runtime is Node.js Functions (nodejs20.x) — not Edge Functions in Phase 13.1
  • Static routes served from public/; dynamic requests go through netlify/functions/ssr.ts
  • Required secret: NETLIFY_AUTH_TOKEN — stored in Bun.secrets
  • Minimum netlify CLI version: 17.0.0
  • Environment variables are set via netlify env:set — the emitted netlify.toml never contains secret values
🤖 Netlify · Netlify
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/deploy/netlify to my project.

Summary of the page:
Netlify adapter: emits netlify.toml with a redirect to Node Functions at netlify/functions/ssr.ts. Static assets from `public/`; every other request → SSR function. Edge Functions adapter deferred to Phase 15.3. Secret: `NETLIFY_AUTH_TOKEN`.

Required invariants — must hold after your changes:
- Runtime is Node.js Functions (nodejs20.x) — not Edge Functions in Phase 13.1
- Static routes served from `public/`; dynamic requests go through `netlify/functions/ssr.ts`
- Required secret: `NETLIFY_AUTH_TOKEN` — stored in Bun.secrets
- Minimum netlify CLI version: 17.0.0
- Environment variables are set via `netlify env:set` — the emitted netlify.toml never contains secret values

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Railway

mandu deploy --target=railway emits a railway.json + nixpacks.toml pair tuned for Bun + Mandu. With --execute and railway ≥ 3.0.0 installed, Mandu triggers a deploy.

ai-hint — Railway adapter emits railway.json (metadata) + nixpacks.toml (build recipe). Nixpacks installs Bun, runs bun install --frozen-lockfile, then bun run build. Start command is bun run start. Secret: RAILWAY_TOKEN.

Invariants:

  • Uses Nixpacks (Railway's default builder) — not a custom Dockerfile
  • Nixpacks installs Bun during the build step — no pre-installed runtime assumed
  • Required secret: RAILWAY_TOKEN — stored in Bun.secrets
  • Minimum railway CLI version: 3.0.0
  • App env vars are set via railway variables set, not --set-secret
🤖 Railway · Railway
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/deploy/railway to my project.

Summary of the page:
Railway adapter emits railway.json (metadata) + nixpacks.toml (build recipe). Nixpacks installs Bun, runs `bun install --frozen-lockfile`, then `bun run build`. Start command is `bun run start`. Secret: `RAILWAY_TOKEN`.

Required invariants — must hold after your changes:
- Uses Nixpacks (Railway's default builder) — not a custom Dockerfile
- Nixpacks installs Bun during the build step — no pre-installed runtime assumed
- Required secret: `RAILWAY_TOKEN` — stored in Bun.secrets
- Minimum railway CLI version: 3.0.0
- App env vars are set via `railway variables set`, not `--set-secret`

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Vercel

mandu deploy --target=vercel emits a vercel.json + a Node SSR function at api/_mandu.ts. With --execute and vercel ≥ 28.0.0 installed, Mandu triggers a deploy.

ai-hint — Vercel adapter: emits vercel.json with a rewrite to the Node SSR function at api/_mandu.ts. Runtime: Node 20 (Vercel's Bun support is not GA yet in Phase 13.1). Static routes served from public/; dynamic routes through the SSR function. VERCEL_TOKEN is the Mandu-side secret.

Invariants:

  • Runtime is Node.js 20 — Bun-specific APIs that are Node-incompatible will fail at runtime (see edge adapter for Workers path)
  • Static routes served from public/; every dynamic request rewrites to /api/_mandu
  • Required secret: VERCEL_TOKEN — stored in Bun.secrets
  • Minimum vercel CLI version: 28.0.0
  • Environment variables must be set via vercel env add — the emitted vercel.json never contains secret values
🤖 Vercel · Vercel
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/deploy/vercel to my project.

Summary of the page:
Vercel adapter: emits vercel.json with a rewrite to the Node SSR function at api/_mandu.ts. Runtime: Node 20 (Vercel's Bun support is not GA yet in Phase 13.1). Static routes served from `public/`; dynamic routes through the SSR function. `VERCEL_TOKEN` is the Mandu-side secret.

Required invariants — must hold after your changes:
- Runtime is Node.js 20 — Bun-specific APIs that are Node-incompatible will fail at runtime (see edge adapter for Workers path)
- Static routes served from `public/`; every dynamic request rewrites to `/api/_mandu`
- Required secret: `VERCEL_TOKEN` — stored in Bun.secrets
- Minimum vercel CLI version: 28.0.0
- Environment variables must be set via `vercel env add` — the emitted vercel.json never contains secret values

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Edge

Cloudflare Workers

Ship a Mandu app to Cloudflare Workers via @mandujs/edge/workers. mandu build --target=workers emits a bundled Worker + wrangler.toml. Bindings (KV, R2, D1) via AsyncLocalStorage-backed accessors.

ai-hint — Cloudflare Workers adapter: mandu build --target=workers emits .mandu/workers/worker.js + wrangler.toml. Bindings accessed via getWorkersEnv() / getWorkersCtx() — AsyncLocalStorage (requires nodejs_als compat flag) with per-Request WeakMap fallback. Uncaught 500s return structured JSON with correlationId, runtime: 'workers'.

Invariants:

  • Workers runtime is V8 isolates — 1–5ms cold start, 10ms CPU cap on free tier
  • mandu build --target=workers emits .mandu/workers/worker.js (bundled) + wrangler.toml (only if absent)
  • Bindings require compatibility_flags = [\"nodejs_als\"] in wrangler.toml for cross-waitUntil access
  • Without nodejs_als, getWorkersEnv() / getWorkersCtx() fall back to per-Request WeakMap (fetch-time only)
  • Uncaught 500s return JSON with correlationId, runtime: 'workers', generic message in production
  • Stack traces / cause are NEVER in the HTTP body — logged via console.error for Logpush
🤖 Cloudflare Workers · Cloudflare Workers
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/edge/cloudflare-workers to my project.

Summary of the page:
Cloudflare Workers adapter: `mandu build --target=workers` emits `.mandu/workers/worker.js` + `wrangler.toml`. Bindings accessed via `getWorkersEnv()` / `getWorkersCtx()` — AsyncLocalStorage (requires `nodejs_als` compat flag) with per-Request WeakMap fallback. Uncaught 500s return structured JSON with `correlationId`, `runtime: 'workers'`.

Required invariants — must hold after your changes:
- Workers runtime is V8 isolates — 1–5ms cold start, 10ms CPU cap on free tier
- `mandu build --target=workers` emits `.mandu/workers/worker.js` (bundled) + `wrangler.toml` (only if absent)
- Bindings require `compatibility_flags = [\"nodejs_als\"]` in wrangler.toml for cross-`waitUntil` access
- Without nodejs_als, `getWorkersEnv()` / `getWorkersCtx()` fall back to per-Request WeakMap (fetch-time only)
- Uncaught 500s return JSON with `correlationId`, `runtime: 'workers'`, generic message in production
- Stack traces / `cause` are NEVER in the HTTP body — logged via `console.error` for Logpush

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Deno / Vercel Edge / Netlify Edge

Roadmap for Phase 15.2+ adapters in @mandujs/edge. Deno Deploy, Vercel Edge Functions, and Netlify Edge Functions are scaffolded stubs — file structure in place, implementations coming.

ai-hint — Deno/Vercel/Netlify Edge adapters: currently scaffolded stubs under packages/edge/src/{deno,vercel,netlify}/. Phase 15.2 will complete Deno and Vercel Edge; Phase 15.3 Netlify Edge. API shape mirrors workers adapter: createXHandler(manifest, opts) + getXEnv()/getXCtx() accessors. Polyfill mapping identical — WebCrypto + web-standard fetch.

Invariants:

  • All three adapters (deno, vercel-edge, netlify-edge) currently export stub factories — calling them returns CLI_E214
  • API surface will mirror @mandujs/edge/workerscreateXHandler(manifest, opts) + getXEnv() / getXCtx() accessors
  • Polyfill mapping is identical to Workers — WebCrypto, web-standard fetch, no Bun APIs at runtime
  • Phase 15.2: Deno Deploy + Vercel Edge complete
  • Phase 15.3: Netlify Edge complete
🤖 Deno / Vercel Edge / Netlify Edge · Deno / Vercel Edge / Netlify Edge
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/edge/deno-vercel-netlify to my project.

Summary of the page:
Deno/Vercel/Netlify Edge adapters: currently scaffolded stubs under `packages/edge/src/{deno,vercel,netlify}/`. Phase 15.2 will complete Deno and Vercel Edge; Phase 15.3 Netlify Edge. API shape mirrors workers adapter: createXHandler(manifest, opts) + getXEnv()/getXCtx() accessors. Polyfill mapping identical — WebCrypto + web-standard fetch.

Required invariants — must hold after your changes:
- All three adapters (deno, vercel-edge, netlify-edge) currently export stub factories — calling them returns CLI_E214
- API surface will mirror `@mandujs/edge/workers` — `createXHandler(manifest, opts)` + `getXEnv() / getXCtx()` accessors
- Polyfill mapping is identical to Workers — WebCrypto, web-standard fetch, no Bun APIs at runtime
- Phase 15.2: Deno Deploy + Vercel Edge complete
- Phase 15.3: Netlify Edge complete

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Edge

@mandujs/edge ships edge-runtime adapters for Cloudflare Workers (Phase 15.1, shipped). Deno Deploy, Vercel Edge, and Netlify Edge adapters are scaffolded and land in Phase 15.2+.

ai-hint@mandujs/edge is a separate package from core — install with bun add @mandujs/edge. Polyfill mapping: Bun.CookieMap → LegacyCookieCodec (WebCrypto); Bun.CSRF → crypto.subtle HMAC-SHA256; Bun.serve → export default { fetch }. Unsupported APIs return BunApiUnsupportedOnEdge 500 JSON. Cloudflare Workers shipped; Deno/Vercel/Netlify coming.

Invariants:

  • @mandujs/edge is a separate package — not bundled with @mandujs/core
  • Cloudflare Workers adapter is shipped (Phase 15.1); Deno/Vercel/Netlify Edge are stubs
  • Calling an unsupported Bun API inside a Worker returns a structured 500 with BunApiUnsupportedOnEdge payload
  • Wrangler is a peer dependency — install with bun add -D wrangler
  • All edge adapters use Mandu's same manifest.json + registered handlers — zero app-code changes required
🤖 Edge · Edge
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/edge/index to my project.

Summary of the page:
`@mandujs/edge` is a separate package from core — install with `bun add @mandujs/edge`. Polyfill mapping: Bun.CookieMap → LegacyCookieCodec (WebCrypto); Bun.CSRF → crypto.subtle HMAC-SHA256; Bun.serve → `export default { fetch }`. Unsupported APIs return `BunApiUnsupportedOnEdge` 500 JSON. Cloudflare Workers shipped; Deno/Vercel/Netlify coming.

Required invariants — must hold after your changes:
- `@mandujs/edge` is a separate package — not bundled with `@mandujs/core`
- Cloudflare Workers adapter is shipped (Phase 15.1); Deno/Vercel/Netlify Edge are stubs
- Calling an unsupported Bun API inside a Worker returns a structured 500 with `BunApiUnsupportedOnEdge` payload
- Wrangler is a peer dependency — install with `bun add -D wrangler`
- All edge adapters use Mandu's same `manifest.json` + registered handlers — zero app-code changes required

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Migrate

Migrate

Move an existing Next.js, Vite, or Remix app to Mandu.

ai-hint — Migrate hub. Porting guides from Next.js, Vite, Remix.

🤖 Migrate · Migrate
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/migrate/index to my project.

Summary of the page:
Migrate hub. Porting guides from Next.js, Vite, Remix.

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Recipes

Authentication

Sessions, JWT, OAuth, and CSRF — wired into Mandu's filling chain via middleware.

ai-hint — Use withSession() and withCsrf() from @mandujs/core/auth as .use(...) middleware on filling chains. Use loginUser(ctx, userId) to start a session and ctx.get("user") to read it. Short-circuit unauthenticated requests with ctx.unauthorized() inside .guard(...). Never roll your own session cookie.

Invariants:

  • Session middleware (withSession()) must be .use(...)-d before any handler that reads ctx.get("user")
  • CSRF middleware (withCsrf()) is required for any POST / PUT / PATCH / DELETE that mutates user data
  • Never construct Set-Cookie headers directly — use loginUser(ctx, id) / logoutUser(ctx) so the framework can rotate session ids
  • Password verification uses argon2id via verifyPassword(plain, hash); never bcrypt or sha256
  • For OAuth, register the provider in mandu.config.ts auth.providers and route the callback through Mandu.filling().get(...) at app/api/auth/[provider]/callback/route.ts
🤖 Wire authentication · Authentication
Add authentication to my Mandu app:
1. For email/password login: create `app/api/login/route.ts` as `Mandu.filling().use(withSession()).use(withCsrf()).post(...)`. Verify the password with `verifyPassword(plain, hash)` from `@mandujs/core/auth`, then call `await loginUser(ctx, user.id)` and redirect.
2. For protected routes: chain `.use(withSession())` then `.guard((ctx) => { if (!ctx.get('user')) return ctx.unauthorized('Login required'); })`.
3. For OAuth: register the provider in `mandu.config.ts` under `auth.providers`, then create `app/api/auth/<provider>/callback/route.ts` that calls `handleOAuthCallback(ctx, '<provider>')` and `loginUser(ctx, user.id)`.
Required invariants:
- withSession() must be .use()-d before any handler that reads ctx.get('user')
- withCsrf() is required for any mutating method (POST/PUT/PATCH/DELETE)
- Never construct Set-Cookie directly; use loginUser() / logoutUser() so the framework rotates session ids
- Passwords use argon2id (verifyPassword), never bcrypt/sha256
After writing the route, run `bun run guard` and `bun run check`.

Build an API endpoint

Two patterns for an API route — a quick filling chain, or a type-safe contract. One prompt covers both.

ai-hint — For quick endpoints, use Mandu.filling().get(...).post(...) chain on a default export at app/api/<route>/route.ts. For type-safe contracts, define Mandu.contract({ request: { GET, POST }, response: { 200, 400 } }) and pair it with Mandu.handler(contract, { GET, POST }). Use ctx.body<T>(), ctx.query, ctx.ok(), ctx.created(), ctx.unauthorized() helpers — never read the raw Request directly.

Invariants:

  • Handler files live at app/api/<route>/route.ts and export a default (file-system routing)
  • Filling chain shape — Mandu.filling().use(...).guard(...).get(...).post(...); never instantiate new Mandu() or write a raw Request handler
  • Contract shape — Mandu.contract({ request: { GET, POST, ... }, response: { 200, 400, ... } }) with zod schemas keyed by HTTP method / status code
  • Use ctx.body<T>(), ctx.query, and the response helpers (ctx.ok, ctx.created, ctx.unauthorized, ctx.redirect) — Mandu Guard rejects raw Response construction in API handlers
  • Middleware runs before handlers — chain with .use(withSession()), not inside the body
🤖 Add an API endpoint · Build an API endpoint
Add a Mandu API route at `app/api/<NAME>/route.ts` for the resource `<NAME>` (e.g. users, posts, signup).
Pick the pattern based on what I need:
- If I want it quick or just prototyping, use Pattern A (filling chain): `export default Mandu.filling().get(...).post(...)` with `ctx.body<T>()` and `ctx.ok()` / `ctx.created()` / `ctx.unauthorized()`. Add middleware via `.use(withSession())` / `.use(withCsrf())` and short-circuit auth via `.guard((ctx) => ...)`.
- If I want type-safe + OpenAPI, use Pattern B (contract): create `spec/contracts/<NAME>.contract.ts` with `export const <name>Contract = Mandu.contract({ request: { GET, POST }, response: { 200, 400 } })` using zod schemas keyed by method / status. Then in the route file: `export default Mandu.handler(<name>Contract, { GET: async (ctx) => ..., POST: async (ctx) => ... })`.
Required invariants:
- File path: app/api/<NAME>/route.ts (default export only)
- Use ctx helpers (ctx.body<T>, ctx.query, ctx.ok, ctx.created, ctx.unauthorized, ctx.redirect) — never construct a raw Response or read raw Request.
- Middleware chains with .use(...); never inline auth/CSRF logic in the handler body.
- For Pattern B, contract path is spec/contracts/<NAME>.contract.ts with a named export ending in `Contract`.
After writing the files, run `bun run guard` and `bun run check` and report any violations.

Deploy

Ship a Mandu app — Vercel, Cloudflare Pages, Fly, Railway, Netlify, Docker, or a plain VPS. One command per target.

ai-hint — Use mandu deploy --to=<target> once per environment. Mandu generates vercel.json, wrangler.toml, fly.toml, Dockerfile etc. on first deploy. The static build target writes to dist/ — never edit dist files by hand.

Invariants:

  • Use mandu deploy --to=<target> (not npx vercel / flyctl deploy directly) so the generated config matches the runtime expectations
  • Generated config files (vercel.json, wrangler.toml, fly.toml, Dockerfile) must be committed; they are the deploy contract
  • The static build output lives at dist/; the production server entry is dist/server/main.js for container targets
  • Edge targets (Cloudflare Workers, Vercel Edge) have stricter API surface — see edge/ for adapter notes before deploying
🤖 Deploy · Deploy
Deploy my Mandu app to <TARGET> (one of: vercel, cloudflare, fly, railway, netlify, docker, docker-compose).
Steps:
1. Run `mandu deploy --to=<TARGET>`. This generates the platform's config file (vercel.json, wrangler.toml, fly.toml, Dockerfile etc.) on first run, then triggers the deploy.
2. Commit the generated config — it is the deploy contract.
3. For edge targets (Cloudflare Workers, Vercel Edge, Deno Deploy, Netlify Edge), confirm the route's API surface is edge-safe by checking the relevant adapter page under /docs/edge.
Required invariants:
- Don't bypass `mandu deploy` with `npx vercel` / `flyctl deploy` directly; the wrappers know which build artefacts each target needs.
- Never edit files in `dist/` by hand — they're regenerated by the static build.
- The container entry is `dist/server/main.js` for docker/fly/railway.
After deploy, fetch the live URL and confirm `/`, `/docs`, and any auth flow render. Report any 5xx in the deploy log.

Internationalisation (i18n)

Add a [lang] segment, translation modules, and a locale switcher — the same pattern this docs site uses across 23 languages.

ai-hint — Wrap localised pages under app/[lang]/ and pass params.lang to a getTranslation(lang) helper that returns a typed Translation object. Validate lang against locales and fall back to defaultLocale. Set dir="rtl" for ar/he/ur. Use named export generateStaticParams to prerender every locale.

Invariants:

  • Localised pages live under app/[lang]/...params.lang is the only source of truth for locale
  • Validate params.lang with isValidLocale(params.lang) and fall back to defaultLocale for unknown values
  • Translation modules export a typed Translation object; never import locale strings inline
  • dir="rtl" must be set on the layout's root element when isRtl(lang) is true (ar, he, ur, fa)
  • generateStaticParams must return every locale so the static build emits one HTML per language
Add i18n to my Mandu app:
1. Create `src/shared/utils/client/i18n/types.ts` with a `Translation` interface, then `translations/<locale>.ts` per locale exporting a typed Translation. Add an `index.ts` that re-exports `locales` (string[]), `defaultLocale`, `getTranslation(lang)`, `isValidLocale(lang)`, `isRtl(lang)` (true for ar/he/ur/fa).
2. Move pages that need localisation under `app/[lang]/...`. In each page: validate via `isValidLocale(params.lang)`; if invalid, render a 404 fallback. Read copy as `getTranslation(params.lang)`.
3. Add `export function generateStaticParams() { return locales.map((lang) => ({ lang })); }` so the static build emits every locale.
4. In `app/[lang]/layout.tsx`, set `dir={isRtl(lang) ? 'rtl' : 'ltr'}` on the root wrapper.
Required invariants:
- params.lang is the only source of truth for locale.
- Never import a locale module directly in a page; always go through getTranslation(lang).
- generateStaticParams must enumerate every entry of `locales` so the static build emits one HTML per language.
- dir='rtl' must be applied for ar, he, ur, fa.
After writing the files, run `bun run guard`, `bun run check`, and verify a couple of locale URLs in the dev server.

Recipes

Copy-paste patterns for common tasks — auth, forms, data, deploy.

ai-hint — Recipes hub. Task-oriented copy-paste snippets.

🤖 Recipes · Recipes
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/recipes/index to my project.

Summary of the page:
Recipes hub. Task-oriented copy-paste snippets.

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Streaming responses

SSE and chunked streams for AI chat, live progress, and large payloads — without breaking the filling chain.

ai-hint — For Server-Sent Events, return ctx.sse((emit) => { ... emit({ data }); ... }) from a .get() handler. For incremental chunked text, use ctx.stream(async function* () { yield "..."; }). Both keep the filling chain (.use(), .guard()) intact and live in app/api/<route>/route.ts.

Invariants:

  • SSE handlers must return ctx.sse(emitter); never new Response(stream, { ... }) manually
  • Chunked text/JSON streams use ctx.stream(asyncGenerator) so the runtime sets Transfer-Encoding: chunked and flushes correctly
  • Don't mix .post(...) and ctx.sse(...) on the same route — SSE clients always GET
  • When piping LLM output, await the model's stream and emit({ data: token }) per token; never accumulate before emitting
  • Always send a done event before closing so clients can stop reconnecting
🤖 Add a streaming endpoint · Streaming responses
Add a streaming endpoint to my Mandu app at `app/api/<NAME>/route.ts`.
Pick the kind:
- For SSE (live LLM tokens, progress, push updates): return `ctx.sse(async (emit) => { ... emit({ data }); ... emit({ event: 'done', data: '' }); })` from a `.get()` handler.
- For chunked text/JSON (CSV export, NDJSON, large payloads): return `ctx.stream(async function* () { yield '...'; }, { contentType: '...' })`.
Required invariants:
- Stay inside the filling chain — `.use(withSession())` etc. still applies.
- Never construct a `new Response(stream, ...)` directly; the framework wraps it.
- For SSE: emit a `done` event before the generator returns so EventSource clients can close cleanly.
- For LLM streaming: await the model and `emit({ data: token })` per token; do not accumulate before emitting.
- SSE handlers must use `.get(...)`, never `.post(...)` (browsers can only EventSource GET).
After writing the route, test with `curl -N` for SSE or `curl --no-buffer` for chunked, and run `bun run guard`.

Testing

Unit, integration, and E2E — all through Bun's test runner with Mandu's ATE auto-generating contract tests for you.

ai-hint — Use bun test for unit + integration. Mandu's ATE (Automated Test Engineer) auto-generates contract tests via bun run mandu ate generate. E2E runs through Playwright via bun run test:e2e. Tests live next to the file (*.test.ts) for unit, in tests/ for integration, and in tests/e2e/ for browser tests.

Invariants:

  • Unit tests live alongside the source as *.test.ts; integration tests live in tests/; E2E in tests/e2e/
  • Test runner is Bun (bun test); never vitest or jest
  • Generated contract tests live under tests/_generated/ and must NOT be hand-edited (ATE regenerates them)
  • For DB-backed tests, use tests/setup-db.ts to provision an in-memory SQLite instance — never share DB state across tests
  • E2E tests require bun run dev running on port 3333 (or bun run test:e2e:server to manage automatically)
🤖 Add tests · Testing
Add tests to my Mandu project:
1. For pure functions (utils, domain logic): create `<file>.test.ts` next to the source. Use `import { describe, expect, test } from 'bun:test'`.
2. For API contracts: run `bun run mandu ate generate` and review the generated tests under `tests/_generated/`. Add the missing edge cases to a sibling `<contract>.cases.test.ts` (don't edit the generated file).
3. For E2E flows: create `tests/e2e/<flow>.spec.ts` using `@playwright/test`. Assume the dev server is on http://localhost:3333.
Required invariants:
- Use `bun:test`, NEVER `vitest` or `jest`.
- Unit tests live next to the source as `*.test.ts`; integration in `tests/`; E2E in `tests/e2e/`.
- Generated contract tests under `tests/_generated/` are read-only; ATE regenerates them on demand.
- For DB-backed tests, use `tests/setup-db.ts` so each test gets a fresh in-memory SQLite — never share state.
After writing, run `bun test` (and `bun run test:e2e:server` if E2E was added) and report any failures.

Reference

CLI Reference

Exhaustive reference for every mandu subcommand: flags, environment variables, exit codes.

ai-hint — Canonical list of all mandu CLI subcommands registered in @mandujs/cli. Use this to know which commands exist, which flags are accepted, and which exit codes to expect. Do not invent commands not listed here.

Invariants:

  • The runnable command set is whatever @mandujs/cli registers in commands/registry.ts — this list reflects v0.22/v0.23
  • Unknown commands print an error and exit with code 1; unknown subcommands do the same
  • CLI entrypoint is the bun shebang (#!/usr/bin/env bun) — do not invoke with node
  • Short flags are fixed: -h=--help, -q=--quiet, -v=--verify, -d=--diff, -y=--yes
  • Subcommands that only make sense with a target (add, middleware, session, auth, collection, scaffold) exit with code 1 when called without their required subcommand
🤖 CLI Reference · CLI Reference
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/reference/cli to my project.

Summary of the page:
Canonical list of all mandu CLI subcommands registered in @mandujs/cli. Use this to know which commands exist, which flags are accepted, and which exit codes to expect. Do not invent commands not listed here.

Required invariants — must hold after your changes:
- The runnable command set is whatever @mandujs/cli registers in commands/registry.ts — this list reflects v0.22/v0.23
- Unknown commands print an error and exit with code 1; unknown subcommands do the same
- CLI entrypoint is the bun shebang (#!/usr/bin/env bun) — do not invoke with node
- Short flags are fixed: -h=--help, -q=--quiet, -v=--verify, -d=--diff, -y=--yes
- Subcommands that only make sense with a target (add, middleware, session, auth, collection, scaffold) exit with code 1 when called without their required subcommand

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Client Router

useRouter, useParams, Link, NavLink — the SPA navigation hooks.

ai-hint — Mandu's client router (from @mandujs/core/client) mirrors React Router: useRouter for imperative navigation, useParams for route params, Link for declarative nav, NavLink for active-state links. Use inside islands or 'use client' pages.

🤖 Client Router · Client Router
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/reference/client-router to my project.

Summary of the page:
Mandu's client router (from @mandujs/core/client) mirrors React Router: useRouter for imperative navigation, useParams for route params, Link for declarative nav, NavLink for active-state links. Use inside islands or 'use client' pages.

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

mandu.config.ts Reference

Full schema for ManduConfig — every top-level field and sub-field with types, defaults, and examples.

ai-hint — ManduConfig schema from @mandujs/core/src/config/mandu.ts. Use this to know which fields are allowed in mandu.config.ts, what types they take, and which files Mandu will load. Do not invent fields not listed here.

Invariants:

  • All top-level fields on ManduConfig are optional — a config file may be empty
  • Mandu searches, in order: mandu.config.ts, mandu.config.js, mandu.config.json, .mandu/guard.json
  • .mandu/guard.json may contain either the full config or a guard-only object; in the guard-only case it is coerced to { guard: ... }
  • When a config file exists but fails to load or parse, loadManduConfig returns {} — invalid configs do not throw
  • guard.preset must be one of: mandu, fsd, clean, hexagonal, atomic, cqrs
  • GuardRuleSeverity is limited to: error, warn, warning, off
🤖 mandu.config.ts Reference · mandu.config.ts Reference
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/reference/config to my project.

Summary of the page:
ManduConfig schema from @mandujs/core/src/config/mandu.ts. Use this to know which fields are allowed in mandu.config.ts, what types they take, and which files Mandu will load. Do not invent fields not listed here.

Required invariants — must hold after your changes:
- All top-level fields on ManduConfig are optional — a config file may be empty
- Mandu searches, in order: mandu.config.ts, mandu.config.js, mandu.config.json, .mandu/guard.json
- .mandu/guard.json may contain either the full config or a guard-only object; in the guard-only case it is coerced to { guard: ... }
- When a config file exists but fails to load or parse, loadManduConfig returns {} — invalid configs do not throw
- guard.preset must be one of: mandu, fsd, clean, hexagonal, atomic, cqrs
- GuardRuleSeverity is limited to: error, warn, warning, off

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Reference

Auto-generated API surface, CLI flags, and config reference.

ai-hint — Reference hub. API surface from TypeDoc, CLI command reference, mandu.config options.

🤖 Reference · Reference
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/reference/index to my project.

Summary of the page:
Reference hub. API surface from TypeDoc, CLI command reference, mandu.config options.

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Getting Started

Start

Install Mandu, scaffold an app, and run it locally in 5 minutes.

ai-hint — Start category hub. Entry point: quickstart. Covers install, scaffold, dev server.

🤖 Start · Start
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/start/index to my project.

Summary of the page:
Start category hub. Entry point: quickstart. Covers install, scaffold, dev server.

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Quickstart

Ship a Mandu app in 5 minutes — install, scaffold, dev. Two install paths, one prompt your agent can run end-to-end.

ai-hint — Install via the standalone binary (curl/iwr) or bun install -g @mandujs/cli, scaffold with mandu create my-app, then bun run dev on port 3333. Routes live under app/ as page.tsx. Bun >= 1.3.12 is required for the npm path.

Invariants:

  • Bun >= 1.3.12 is required for the npm/Bun package path; the standalone binary embeds Bun and has no prerequisite
  • Routes are recognised only as app/**/page.tsx (no .jsx, no index.tsx)
  • Interactive components must end with .island.tsx and start with "use client"
  • The dev server defaults to port 3333; configure via mandu.config.ts
🤖 Bootstrap a new project · Quickstart
Bootstrap a new Mandu project. Walk me through:
1. Install Mandu — prefer `curl -fsSL https://raw.githubusercontent.com/konamgil/mandu/main/install.sh | sh` on Unix, `iwr https://raw.githubusercontent.com/konamgil/mandu/main/install.ps1 -useb | iex` on Windows. (Or `bun install -g @mandujs/cli` if Bun is already installed.)
2. `mandu create my-app` (template: `starter`).
3. `cd my-app && bun run dev`.
4. Open http://localhost:3333 and confirm the welcome page renders.
Diagnose using these invariants: routes live at app/**/page.tsx (.tsx only); .island.tsx with `use client` is required for hydration; dev port is 3333; the npm path requires Bun >= 1.3.12 (the binary embeds Bun).
Once dev is running, show me the project tree (app/, src/client, src/server, src/shared, spec/, content/, mandu.config.ts).

Testing

Coverage

mandu test --coverage turns on Bun's native line coverage and Playwright V8 coverage, then merges them into the canonical .mandu/coverage/lcov.info.

ai-hint--coverage merges LCOV v2 from Bun (coverage/lcov.info) + Playwright (coverage/e2e.lcov) → .mandu/coverage/lcov.info. Thresholds configured in mandu.config.ts::test.coverage fail CI with CLI_E065 when unmet.

Invariants:

  • Merged LCOV always lands at .mandu/coverage/lcov.info
  • Missing input files are silently skipped — single-leg coverage still produces the merged output
  • Thresholds are evaluated on the MERGED LCOV — unit + E2E hits combine before the check
  • Exit code 1 on threshold failure, with CLI_E065 and actual% < expected%
  • Round-trip: parse(serialize(parse(x))) === parse(x) — merge is byte-stable for CI diffs
🤖 Coverage · Coverage
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/testing/coverage to my project.

Summary of the page:
`--coverage` merges LCOV v2 from Bun (`coverage/lcov.info`) + Playwright (`coverage/e2e.lcov`) → `.mandu/coverage/lcov.info`. Thresholds configured in `mandu.config.ts::test.coverage` fail CI with CLI_E065 when unmet.

Required invariants — must hold after your changes:
- Merged LCOV always lands at `.mandu/coverage/lcov.info`
- Missing input files are silently skipped — single-leg coverage still produces the merged output
- Thresholds are evaluated on the MERGED LCOV — unit + E2E hits combine before the check
- Exit code 1 on threshold failure, with CLI_E065 and `actual% < expected%`
- Round-trip: `parse(serialize(parse(x))) === parse(x)` — merge is byte-stable for CI diffs

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

E2E Tests

mandu test --e2e wraps the ATE (Automation Test Engine) pipeline to extract the interaction graph, generate Playwright specs, and run them — with optional heal-on-failure suggestions.

ai-hintmandu test --e2e runs extract → generate → spawn. Specs land under tests/e2e/auto/. Requires @playwright/test >= 1.40.0 as a peer dep. Use --heal to emit non-destructive diff suggestions after a failure.

Invariants:

  • @playwright/test >= 1.40.0 is a peer dependency — missing it returns CLI_E063
  • Playwright config must live at tests/e2e/playwright.config.ts (override via test.e2e.configPath)
  • Generated specs land under tests/e2e/auto/<routeId>.spec.ts (regenerated each run)
  • Interaction graph is cached at .mandu/interaction-graph.json
  • --heal NEVER auto-commits — it emits diffs for human review
🤖 E2E Tests · E2E Tests
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/testing/e2e to my project.

Summary of the page:
`mandu test --e2e` runs extract → generate → spawn. Specs land under `tests/e2e/auto/`. Requires `@playwright/test >= 1.40.0` as a peer dep. Use `--heal` to emit non-destructive diff suggestions after a failure.

Required invariants — must hold after your changes:
- `@playwright/test >= 1.40.0` is a peer dependency — missing it returns CLI_E063
- Playwright config must live at `tests/e2e/playwright.config.ts` (override via `test.e2e.configPath`)
- Generated specs land under `tests/e2e/auto/<routeId>.spec.ts` (regenerated each run)
- Interaction graph is cached at `.mandu/interaction-graph.json`
- `--heal` NEVER auto-commits — it emits diffs for human review

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Testing

Mandu ships its own batteries-included test runner — unit, integration, E2E, coverage, watch — all driven by a single mandu test command.

ai-hint — Testing category hub. mandu test wraps bun:test for unit + integration + E2E (ATE/Playwright) with coverage merge, watch mode, and snapshot testing. Zero external deps required for unit/integration.

Invariants:

  • mandu test requires Bun >= 1.3.12 — the runner wraps bun test directly
  • Test configuration lives under the test block of mandu.config.ts and is .strict() — unknown keys throw at load
  • Exit code 0 = success, 1 = test failure, 2 = infra error, 4 = config error
  • --e2e requires @playwright/test as a peer dependency (bun add -d @playwright/test)
  • Merged LCOV always lands at .mandu/coverage/lcov.info (Phase 12.3)
🤖 Testing · Testing
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/testing/index to my project.

Summary of the page:
Testing category hub. `mandu test` wraps bun:test for unit + integration + E2E (ATE/Playwright) with coverage merge, watch mode, and snapshot testing. Zero external deps required for unit/integration.

Required invariants — must hold after your changes:
- `mandu test` requires Bun >= 1.3.12 — the runner wraps `bun test` directly
- Test configuration lives under the `test` block of `mandu.config.ts` and is `.strict()` — unknown keys throw at load
- Exit code 0 = success, 1 = test failure, 2 = infra error, 4 = config error
- `--e2e` requires `@playwright/test` as a peer dependency (`bun add -d @playwright/test`)
- Merged LCOV always lands at `.mandu/coverage/lcov.info` (Phase 12.3)

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Integration Tests

Exercise the full HTTP stack with createTestServer + createTestSession + createTestDb — from @mandujs/core/testing. Each fixture implements Symbol.asyncDispose so await using handles teardown.

ai-hint — Integration tests boot a real Bun.serve listener on an ephemeral port. Use createTestSession() to bypass login. Use createTestDb({schema, seed}) for an isolated sqlite instance. Every async fixture supports await using — no manual .close() needed.

Invariants:

  • Default include glob: tests/integration/**/*.test.ts
  • createTestServer(manifest, opts) binds to port 0 (ephemeral) — tests run in parallel safely
  • Every async fixture implements Symbol.asyncDisposeawait using cleans up in reverse order
  • The default dbUrl is sqlite::memory: — per-test isolation unless you point at a shared URL
  • Integration test timeout defaults to 60_000 ms (override via test.integration.timeout)
🤖 Integration Tests · Integration Tests
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/testing/integration to my project.

Summary of the page:
Integration tests boot a real `Bun.serve` listener on an ephemeral port. Use `createTestSession()` to bypass login. Use `createTestDb({schema, seed})` for an isolated sqlite instance. Every async fixture supports `await using` — no manual `.close()` needed.

Required invariants — must hold after your changes:
- Default include glob: `tests/integration/**/*.test.ts`
- `createTestServer(manifest, opts)` binds to port 0 (ephemeral) — tests run in parallel safely
- Every async fixture implements `Symbol.asyncDispose` — `await using` cleans up in reverse order
- The default `dbUrl` is `sqlite::memory:` — per-test isolation unless you point at a shared URL
- Integration test timeout defaults to 60_000 ms (override via `test.integration.timeout`)

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Snapshot testing

Use bun:test's toMatchSnapshot() helper for golden-file tests, plus --update-snapshots to regenerate after intentional UI or contract changes.

ai-hint — Snapshot testing uses expect(x).toMatchSnapshot() from bun:test. Snapshots live in __snapshots__/<file>.snap next to the test. Run --update-snapshots after an intentional change. Keep snapshots deterministic — strip timestamps/IDs first.

Invariants:

  • Snapshot files live at __snapshots__/<test-file>.snap next to the test
  • --update-snapshots regenerates snapshots in place — always review the diff before committing
  • toMatchSnapshot() is provided by bun:test; no separate dep is required
  • Snapshots must be deterministic — serialize timestamps/IDs through a helper before the assertion
🤖 Snapshot testing · Snapshot testing
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/testing/snapshot to my project.

Summary of the page:
Snapshot testing uses `expect(x).toMatchSnapshot()` from bun:test. Snapshots live in `__snapshots__/<file>.snap` next to the test. Run `--update-snapshots` after an intentional change. Keep snapshots deterministic — strip timestamps/IDs first.

Required invariants — must hold after your changes:
- Snapshot files live at `__snapshots__/<test-file>.snap` next to the test
- `--update-snapshots` regenerates snapshots in place — always review the diff before committing
- `toMatchSnapshot()` is provided by `bun:test`; no separate dep is required
- Snapshots must be deterministic — serialize timestamps/IDs through a helper before the assertion

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Unit Tests

Run fast, pure-function tests against fillings and handlers without booting a server — testFilling, createTestRequest, createTestContext via @mandujs/core/testing.

ai-hintmandu test unit wraps bun:test and picks up **/*.test.ts + **/*.test.tsx by default. Use testFilling(fn, opts) to invoke a filling without a server. Every async fixture implements Symbol.asyncDispose for await using.

Invariants:

  • Default include glob: **/*.test.ts, **/*.test.tsx; exclude: node_modules/**, .mandu/**
  • testFilling is a pure function call — no HTTP stack, no Bun.serve
  • Unit test timeout defaults to 30_000 ms (override via test.unit.timeout)
  • Mocks (mockMail, mockStorage) implement Symbol.dispose so using clears them on scope exit
🤖 Unit Tests · Unit Tests
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/testing/unit to my project.

Summary of the page:
`mandu test unit` wraps `bun:test` and picks up `**/*.test.ts` + `**/*.test.tsx` by default. Use `testFilling(fn, opts)` to invoke a filling without a server. Every async fixture implements `Symbol.asyncDispose` for `await using`.

Required invariants — must hold after your changes:
- Default include glob: `**/*.test.ts`, `**/*.test.tsx`; exclude: `node_modules/**`, `.mandu/**`
- `testFilling` is a pure function call — no HTTP stack, no Bun.serve
- Unit test timeout defaults to 30_000 ms (override via `test.unit.timeout`)
- Mocks (`mockMail`, `mockStorage`) implement `Symbol.dispose` so `using` clears them on scope exit

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

Watch mode

mandu test --watch keeps a persistent chokidar watcher over app/, src/, and packages/ and re-runs only the tests affected by each change — a ~500ms red-green-refactor cycle.

ai-hint--watch uses a 200ms debounced chokidar watcher. Change → compute affected tests (direct match OR substring import scan) → bun test <narrowed>. Concurrency guarded: no pile-ups. For transitive-impact precision use mandu test:watch instead.

Invariants:

  • Watch dirs: app/, src/, packages/ — ignores node_modules/, .git/, .mandu/, dist/
  • Debounce window: 200ms — bursts of saves coalesce into a single re-run
  • Concurrency guarded — re-runs already in flight absorb new changes into a follow-up batch
  • Affected-test heuristic: direct file match OR substring import scan (NOT full dep-graph)
  • --e2e is incompatible with --watch — use mandu test:watch for E2E watch
🤖 Watch mode · Watch mode
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/testing/watch to my project.

Summary of the page:
`--watch` uses a 200ms debounced chokidar watcher. Change → compute affected tests (direct match OR substring import scan) → `bun test <narrowed>`. Concurrency guarded: no pile-ups. For transitive-impact precision use `mandu test:watch` instead.

Required invariants — must hold after your changes:
- Watch dirs: `app/`, `src/`, `packages/` — ignores `node_modules/`, `.git/`, `.mandu/`, `dist/`
- Debounce window: 200ms — bursts of saves coalesce into a single re-run
- Concurrency guarded — re-runs already in flight absorb new changes into a follow-up batch
- Affected-test heuristic: direct file match OR substring import scan (NOT full dep-graph)
- `--e2e` is incompatible with `--watch` — use `mandu test:watch` for E2E watch

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

For Agents

AI hint

This page aggregates every Agent Prompt from the Mandu docs. Agents can fetch this single URL instead of crawling individual docs pages. Auto-generated by scripts/build-for-agents.ts.