Contents

CodeScanner

CodeScanner reads an existing directory and writes code_review.json: what is in the repository, in two layers. The scan layer is measured by code and never touched by the model — the inventory, the import graph and its load-bearing modules, public signatures, every manifest's declared dependencies, git state and activity, what the previous round planned, and how the tree has drifted from it. The review layer is the model's judgment — the project's architecture, stack, commands, entry points, conventions, and what is built — each as a fact with the file it came from, drawn from a bounded evidence sample the scan ranks. Every later agent plans against that file rather than against what it would guess.

It is the first agent of any round on existing code, and the first agent of every round after the first.

Reads

The repository, locally. Before any call is made, Spec4 walks the tree, measures it, and chooses the evidence the model receives from that measurement rather than from walk order, within a budget of 24,000 tokens (scan_context_tokens in the model settings overrides it). The evidence is assembled in four tiers, each with its own share of the budget:

Tier What's included Limit
A — File tree Every directory holding a file, with its file count and top three extensions, then the list of every non-test source file Directories never capped; 400 source files
A — README The root README, if there is one 80 lines
A — Manifests pyproject.toml, package.json, Cargo.toml, go.mod, pom.xml, Makefile, tsconfig.json, linter and type-checker configs, and the like 3,000 characters per file, 10,000 in total
A — Deployment and CI signals Dockerfile, docker-compose.yml, Procfile, fly.toml, render.yaml, Caddyfile, nginx.conf, deploy.sh, any *.service unit, .github/workflows/, Terraform directories, and the like Five files each, 1,500 characters per file
A — Project conventions .env.example (or .env.sample, .env.template), CLAUDE.md, AGENTS.md, CONTRIBUTING.md; LICENSE and CLA.md named as present, never pasted 1,500 characters; 80 lines each
A — Prior round On a re-scan: the previous round's phases by number and title, the feature ids each declared, and the planned stack; then the changes since that round — commits, authors, coding-agent trailers, touched directories, the most-touched files, files added and removed, and the load-bearing candidates that entered or left 50 ids; 25 files; 10 added and 10 removed
B — Signatures Every load-bearing candidate and entry-point candidate as a signature block: one line per public symbol with the first line of its docstring 6,000 tokens
C — Bodies The entry-point candidates (main, app, index, server, cli, __main__), the top three candidates by fan-in, and the files most touched since the last round — whole when under 4,000 tokens, else the first 150 lines plus the file's signatures 12,000 tokens, plus what B left unspent
D — Other packages One signature block per product package no B or C file sits in, from its highest fan-in member 3,000 tokens

Within each tier, files reached from an entry point go first. A block that doesn't fit is skipped, so a smaller one later in the order can still use the room; an __init__.py under 20 lines never takes a slot. Test files are outside the graph and outside the samples. On Spec4's own tree this is 4 bodies, 17 signature files, and every src/ module named, at about 17,400 tokens of evidence.

Skipped: .git, node_modules, .venv, __pycache__, dist, build, target, .next, .spec4, and the other cache and vendor directories.

Before the call, the chat shows how many files were indexed and roughly how many tokens are about to be sent, and to which model. Nothing else leaves the machine. What was shown is recorded in scan.coverage, by the code that pastes it, so the review says what the model saw.

Writes

code_review.json, as an envelope with schema_version: 2 and two objects. The model emits a bare review block, validated on its own; the envelope is assembled and validated again at commit, and a review that fails validation is written to .spec4/failures/ and re-asked once.

The scan layer

Computed from the same walk the model is shown, and held until the model's block is committed. Each block is optional and closed, so a review from an earlier 2.x is still read:

Block What it records
inventory File and line counts per extension, the first 2,000 paths, and every directory the walk pruned — so a consumer can tell "not there" from "not looked at".
coverage What the model was shown: the README, manifests, CI, deployment, and convention files pasted, the signature files and bodies sampled, the source total they were drawn from, and the evidence size.
git Head, branch, modified and untracked counts (.spec4/ excluded); the commits since the last round — after the latest IMPLEMENTED marker — with their date range, author names (never emails), per-directory touch counts, the 25 most-touched files, and whether any commit carries a coding-agent trailer; the last commit per top-level directory. Present when the project root is itself a repository. Any failure — no binary, a timeout, an empty repository — stores available: false and never fails the scan.
module_graph Import edges between the project's own source files — Python through ast, JavaScript and TypeScript by relative specifier, Go through go.mod — stored as per-node fan-in and fan-out, the top ten modules by fan-in with at least two importers as load_bearing_candidates with their consumers, the external packages with what imports them, and how much of the graph the entry points reach.
signatures For the candidates and the entry points: public top-level definitions and module-level variables with the first line of their docstring. 40 symbols per file, 32 KB in all, with truncated flags at both levels.
dependencies What each pyproject.toml (including PEP 735 dependency groups), package.json, and go.mod declares, by manifest path. Nothing else in a manifest is read.
prior_round The latest implemented round as an extract, never a pass-through: each phase's title, feature and capability declarations, and dependencies; the stack's languages, libraries, deployment targets, persistence, providers, and integrations; the vision's feature ids; the AI catalog reduced to ids and tiers. About 15 KB on BWS4, where the round's artifacts run to hundreds.
plan_drift The prior plan's dependencies against the tree, in five buckets: planned_in_manifests; planned_only_imported (never declared, but imported — a smell); planned_tooling (declared by no manifest, but witnessed by a file in the tree — uv.lock, a systemd unit, a Caddyfile, a Dockerfile, a CI workflow, a tool's own config — or named by a manifest without being depended on, like requires-python or packageManager); planned_unmatched; and declared_not_planned. A presence test, never a version check. Which bucket means drift is the consumer's judgment.
delta This scan against the prior one: files added, removed, and modified; dependencies added, removed, and bumped per manifest; candidates that entered or left; public symbols added, removed, or changed. And carried — how many prior entries the commit put back into the review, and against which baseline (below).
producer The Spec4 version and schema that measured the scan, so a reader of delta can tell "measured by an older extractor" from "the tree changed".

The review layer

Later agents don't receive the file as pasted JSON. Brainstormer, StackAdvisor, and Phaser each get a rendered view of exactly the blocks they read, each block under one line of guidance, with values verbatim and their source or inferred_from in brackets; a field the renderer has no special knowledge of still reaches them. The views carry measured facts beside the model's judgments: Phaser reads load_bearing, implemented, the reconciliation, the unplanned changes, and a coverage line; StackAdvisor reads integrations, divergences, the drift's planned_unmatched and declared_not_planned, and the dependency delta; Brainstormer reads implemented and the unplanned changes. Deployer reads integrations and is told to plan against the committed tree when the scan found it dirty; Agentifier's Scout reads through a view of its own. No raw review JSON reaches a downstream model, and none of them receives scan whole.

A probe, evals/code_scanner/review_reach.py, showed why the views exist: with the raw paste, fields a prompt named reached the plan and fields it didn't mostly didn't, and StackAdvisor recorded its prompt example's ESLint and Prettier in eight rounds out of eight for a codebase whose review said oxlint.

Excerpts of both layers are on the Artifacts page.

Asks

Nothing, by default. The scan and the draft are one call. After the review is drafted you can reply in the chat to correct or extend it — CodeScanner re-emits the review block — or press Modify later to reopen it. Re-scan Project, in the action row under a finished review, walks the tree again.

Web search, if you've given it a key, is available to CodeScanner for the standards and protocols it names.

A review block that fails schema validation is written to .spec4/failures/code_scanner_review_<n>.txt — the validator's errors first, then the raw reply — before the re-ask, and again if the re-ask fails too. Nothing in the chat is lost; the file is there so a failure can be read rather than remembered.

Buttons

Button When
Start The directory has code and no review has been written this round.
Continue This session has an unfinished conversation with CodeScanner.
Modify code_review.json exists for this round.
Required The previous round is implemented and the next hasn't started — only CodeScanner shows this, because every round after the first begins with a re-scan, even if the previous round was greenfield. Or the round's code_review.json was written by an earlier Spec4 and predates the current review format: a notice names the round's folder, nothing else in the round is lost, and every other agent is Not Ready until the re-scan.

CodeScanner has no upstream artifact, so it is never Needs Update or Not Ready. A directory you answered New project to skips it and starts with Brainstormer.

Across rounds

Every round after the first begins with a re-scan, and the re-scan reads the code as it stands: what the coding agent built, what you changed by hand, and what the previous round's plan never mentioned. That is what makes the next round a plan against the code rather than against the last plan. Rounds

The review is not rewritten from scratch. The scan finds the previous round's review, measures the delta from it, and hands the model that review with the exact list of paths that changed since it. An entry whose source path isn't on the list is carried verbatim; the model re-derives only where the list names the path, adds for paths it adds, and drops for paths it removes, and it reuses the id of a capability the prior review already names. What it drops anyway from an untouched file — an environment variable, a dependency, an endpoint — is put back at commit, keyed by name and in the prior's order, and scan.delta.carried records how many were put back and against which baseline. On BWS4's first scan after its v9 round, under 2.9.2, the model dropped 10 of 22 environment variables and 21 of 46 dependencies whose source files hadn't changed; the carry restores both lists. The committed v10 review, scanned under 2.11.1 with the carry rule in the seed, records carried: {env_vars: 0, dependencies: 0, api_surface: 0} — the model kept every untouched entry itself. With no prior review, no delta, or a truncated file list, the carry is suspended and the seed says so.

The drift is measured, not recalled. The scan compares what the last round planned with what the manifests now declare and what files in the tree witness, and lists what changed as files, signatures, manifests, and load-bearing modules, so the agents read what changed as a list rather than as the model's recollection of it.

A review written under an older schema isn't read as current. The gate above fires, and the re-scan starts fresh — the fresh seed with the old review appended as context — rather than asking for a merge into a shape the current version doesn't read.