Scan and surfaces
The scan is the entry point. It reads your codebase, detects your stack, identifies which packages you actually deploy, and writes the configuration every agent reads.
What the scan detects
The scan reads your project and writes two files: ana.json (configuration every agent reads) and scan.json (the full inventory).
It detects your language, framework, package manager, and testing tools. It identifies external service dependencies — Stripe, Redis, Postgres — so agents know what your system talks to. It reads conventions: import style, error handling patterns, file organization. And it records findings about potential issues — missing test commands, misconfigured build scripts, unused dependencies that suggest dead code.
Everything the scan detects is stored, not recomputed. Agents read scan.json on startup — they don't re-analyze your source tree on every pipeline run.
This means the scan is a point-in-time snapshot. If you add a new framework dependency between scans, agents won't know about it until the next ana init.
Manual overrides via ana config set survive re-init — the scan updates detected values but preserves your corrections. Override the test command, change the branch prefix, add a surface the scan missed — all of it persists.
Surfaces
In a monorepo, not every package is something you deploy. Surfaces are the workspace packages that represent actual deployment targets — the ones with CLI entry points, framework configurations, or server framework dependencies.
A Next.js app with a next.config is a surface. A shared utils package imported by three other packages is not.
The scan identifies surfaces by looking for deployment indicators:
- A
binfield in package.json — CLI entry point - Framework config files like
next.config.jsorvite.config.ts - Dependencies on server frameworks like Express or Fastify
Packages that exist only to be imported — shared libraries, internal tooling, example apps, e2e test suites — are excluded. The scan looks for evidence that a package runs independently, not that it has code in it.
Each surface carries its own metadata: path, language, framework, and per-surface commands for build, test, and lint. When you run ana config set surfaces.api.commands.test "cd apps/api && pnpm test", you're telling agents how to compile and verify that specific surface.
A monorepo with three surfaces gets three independent test commands. Agents run the right one based on which surface the current work targets. If the scan misses a surface or detects a false one, you can add or remove surfaces manually — see the configurability guide.
Application shape
The scan assigns your project one of nine application shapes: web-app, api, cli, mobile, desktop, library, monorepo, static-site, or fullstack. Single-surface projects get the shape of their one surface. Multi-surface projects are classified as monorepo or fullstack depending on what the surfaces contain.
Shape is determined by framework evidence — a project with Next.js is a web-app, not "a Node project with React dependencies." Framework signals outrank dependency signals. A project with both a CLI entry point and a React frontend is fullstack, not cli.
Shape affects how the scan describes your project (the header line agents see on startup), how ana init scaffolds descriptions, and what context agents receive about your architecture.
It does not affect pipeline behavior — a CLI and a web-app go through the same five stages with the same verification guarantees. Shape is context, not control flow.
The cascade
Surfaces flow through the entire pipeline:
- Scan detects them from workspace packages with deployment indicators
- Init writes per-surface commands into
ana.json - Think scopes work targeting a specific surface
- Plan reads that surface's test command and writes it into the Build Brief
- Build runs surface-specific tests — not the project-wide suite — to verify changes in isolation
- Verify checks independently using the same surface command
- Learn labels proof chain entries by surface
A concrete example: the scan detects a surface called api at packages/api. Init writes (cd 'packages/api' && pnpm run test) as its test command. Plan references this in the spec. Build runs (cd 'packages/api' && pnpm run test) after each change — not pnpm run test at the root, which would run every surface's tests.
The proof chain entry records surface: api. Six months later, ana proof health can show quality trajectory per surface — the API improved while the dashboard regressed.
Without surfaces, a monorepo is a single blob. With them, every stage of the pipeline knows which part of the codebase it's working on — and the proof chain tracks quality at that granularity.
Even single-surface projects benefit. The surface commands in ana.json are what agents actually run. The scan writes them once; you override them once; every pipeline run uses them automatically.