How Spyglass reads a codebase
The ways a scan understands a workspace, and where each result lands.
A scan understands a workspace in layers. Each layer is computed from the one below it and written to the dataset; the viewer draws from all of them. Nothing in a scan modifies the code. Where an analysis produces plans or scenarios, they are read-only descriptions of what the graph would support, never applied.
Package by package
The first pass looks at one package at a time, from its own sources only:
- Symbols and scope. What the package declares, and which declarations a declared entry point actually exposes. A symbol is package-public only if an entry point reaches it.
- Module roles. Whether a file is an aggregator that re-exports others or an internal module, from syntax alone.
- Local complexity. Per function: control flow, nesting, and exits, as raw facts without a score.
- Concept seeds. The named things a package introduces, which the later passes follow across the workspace.
- File kinds. Source, test, story, configuration, or other, from the path.
Across the workspace
The second pass builds one program over the whole workspace and relates the
packages to each other. Every row lands in packages/<name>.json unless it
says otherwise.
| Family | Analysis | What it computes |
|---|---|---|
| Structure | Dependencies | The import graph between files, sliced per package, with the edges that cross a boundary |
| Structure | Gravity | Fan-in, fan-out, transitive reach, cycle membership for each package and file |
| Structure | Architectural profile | Descriptive shape signals: foundation-like, leaf-like, shared hub, surface-heavy |
| Structure | Boundary interactions | Traffic per package boundary, from usage, imports, and module edges |
| Structure | Structural pressure | Where independent measurements converge on the same place |
| Evolution | Churn | Commits, line churn, recency, and authors per file over the last year, following renames |
| Evolution | Hotspots | Files where churn and complexity meet |
| Evolution | Change coupling | File and package pairs that change together, and whether a static edge or path explains it |
| Evolution | Change radius | How many files, packages, and static edges each commit touched |
| Evolution | Evolutionary pressure | Whether the Git log reinforces, contradicts, or leaves unsupported the structural findings |
| Concepts | Inventory and distribution | Each concept, and how it spreads across packages, files, and references |
| Concepts | Overlap | Concepts that may be the same thing: shared shape, properties, converters, or name tokens |
| Concepts | Ownership | Where a concept is declared, represented, used, and acted on, and the tensions between those centers |
| Concepts | Locality | How far apart the files and packages that share a concept sit in the graph and in the Git log |
| Concepts | Re-centering | Concepts whose declared home differs from their observed center, with alternative scenarios, their simulated impact, and a review that compares them without ranking |
| Opportunities | Reduction opportunities and plans | Gated structural operations the graph supports: internalizing an export, folding a package. Each comes with evidence, cautions, and a read-only plan |
Evolution rows need Git; without it they are empty and the rest is unaffected.
The workspace as a whole
The package reports are merged into workspace.json, and three more results
are computed on top:
- Graph. Package and module graph metrics.
- Concepts on the graph. Where each concept sits across packages, in
concepts/index.json. - Patterns. Recurring architectural patterns, in
projections/patterns.json.
From these, renderer-neutral projections are written under projections/.
The viewer lays islands out from dependency-graph.json.
Last, before the manifest, the scan runs its lenses over the dataset it has
just written. A lens derives a view, such as each package’s dependency layer and
file count by kind (lenses/package-sizes.json), and never analyzes the
code again. One lens reads the configuration: domains compares where each
place is used with the domains the workspace declares, and keeps the
observation beside its own label.
Inside a package
Before the dataset is published, each complete package’s internals are
derived from its package-local results into internals/<name>.json:
- Topology. The import graph among the package’s own source files.
- Symbol locality. For each symbol used inside the package, where it is declared against where its consumers are.
- Responsibilities. Regions of files that rule-fired edges bind together, and how the regions relate. The viewer draws these as districts.
- Primitive conventions. Symbol roles, consumer scope, and recurring placement conventions.
- Rewiring scenarios. For each candidate, the current arrangement, a target, its simulated effects, and what it preserves.
- Review. Pairwise comparison within each scenario family, with dispositions such as credible alternative, trade-off, or preservation required. Nothing is weighted, scored, or ranked.
This is what the viewer’s Evidence & alternatives panel shows.