---
title: How Spyglass reads a codebase
description: 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](/spyglass/reference/output); 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`:

1. **Topology.** The import graph among the package's own source files.
2. **Symbol locality.** For each symbol used inside the package, where it is
   declared against where its consumers are.
3. **Responsibilities.** Regions of files that rule-fired edges bind
   together, and how the regions relate. The viewer draws these as
   districts.
4. **Primitive conventions.** Symbol roles, consumer scope, and recurring
   placement conventions.
5. **Rewiring scenarios.** For each candidate, the current arrangement, a
   target, its simulated effects, and what it preserves.
6. **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.
