Discovery and units
Discovery answers one question: what documentable things exist in this repo? It is the first step of the production loop and the thing a survey configures.
Two planes, one rule between them
Discovery splits into two planes, and the rule between them is what keeps the system maintainable:
- The scanner plane is code - small, deterministic, pluggable. A scanner answers "what candidates exist here?" through a narrow interface, and nothing more.
- The data plane is YAML - reviewable, agent-draftable, human-committed. Unit definitions group raw candidates into named, typed units.
The hard rule: scanners never absorb extraction logic, prompts, or doc shapes, and layout knowledge never migrates into code. Everything involving judgment stays in reviewable data. This is why a survey drafts YAML, not TypeScript.
Repos already declare their own layout
The key insight behind the scanner plane: a repo usually tells you its own structure. The js-workspace scanner reads pnpm-workspace.yaml globs and each member's package.json name - enumeration follows the workspace manifest, which is ground truth, not folder-name guessing. The same fact generalizes across ecosystems: .sln, settings.gradle, Cargo.toml, go.work.
An accuracy ladder handles repos that declare less:
- Ecosystem scanner - the repo declares itself (a workspace manifest, a solution file). Most accurate.
glob- paths predict units, so a glob maps directories to units.inventory- nothing predicts units, so a committedunits.yamllists them by hand, once.
Any language works on the lower rungs from day one; a dedicated scanner is an accuracy upgrade, not a prerequisite.
A unit is not a folder
This is the idea that makes Alloy fit real repos. A unit is one documentable thing, and the mapping to the filesystem is whatever your repo actually is:
- One unit can span several packages - a
servicedoc that covers seven slice packages of one service. - One unit can be keyed on two variables - an
adapternamed from a{concern}and a{provider}. - One folder can hold several units, and several folders can make one unit.
Unit definitions express this in YAML:
units:
- kind: service # one doc unit spans all slices of a service
match: backend/services/{name}/*
groupBy: name
- kind: adapter # two variables → one name
match: backend/adapters/{concern}/{provider}
nameTemplate: "{concern}-{provider}"Kinds fix shape and rule pack
A unit's kind (service, lib, adapter, ...) is a claim that every member of the kind is the same sort of thing. It fixes two downstream facts: the doc shape (which sections the gate requires) and the rule pack (which files feed the doc). Getting kinds right is why the survey tests each kind's definition against sampled members - a kind that does not partition cleanly is a bundle, and a bundle documents two things as if they were one.
Discovery reports what it could not match
Discovery is honest about its own blind spots. It reports not just the units it grouped but the count of unmatched candidates - files and directories no unit definition claimed. That count is the input to coverage accounting (see Views and coverage): an unmatched candidate becomes UNKNOWN, which forces a decision, so a new directory in your repo announces itself instead of waiting to be missed.