Produce a reading order for the target codebase: a map of how data flows through
it, a breadth-first pass that teaches its structure and interfaces, then
optional depth-first dives per module, each one skippable. Read the code. Don't
change anything.
Target
jj file list | head -n 500
jj show --stat
Arguments: $ARGUMENTS
Produce the reading order for the target named in the arguments if given.
Otherwise produce it for the whole repository listed above. If the arguments
name the current commit, scope the order to the files it changes plus whatever a
reader must read first to understand them.
The listing above is truncated: a starting point, not the enumeration.
Principles
- The order teaches, it doesn't inventory. Every file earns its place by what it
lets the reader understand next. A file nothing depends on and nothing
explains doesn't belong in the order
- Breadth before depth, at every level. A level's pass covers the shape of the
whole thing: what the parts are, how they interact, where control enters. Only
then does the reader descend into one part
- Each dive is skippable. After the breadth-first pass, a reader who skips every
dive should still understand what the system does and how its pieces fit.
Nothing in a later group may be a prerequisite for an earlier one
- Interfaces before implementations. Type definitions, schemas, public exports,
route tables, and config define the vocabulary. The code that implements them
assumes it
- Recurse only where complexity requires it. A module of three files gets a flat
list. A module with its own submodules gets its own breadth-first pass, then
its own dives
- Say why, briefly. One line per file or group: what the reader gets from it.
Without that the order is unusable for deciding what to skip
These aren't exhaustive. Reason from first principles when none fits cleanly.
Workflow
- Enumerate the files in scope mechanically (
jj file list, a glob, the
commit's changed files), not from memory. Exclude generated output, vendored
code, lockfiles, and snapshots, and note that you did
- Read the orientation files first: README, CLAUDE.md, package manifests,
workspace config, build config, and the entry points they name. These
determine the top-level decomposition. Don't guess it from directory names
alone
- Partition the files into modules (see "Finding module boundaries"). Each
in-scope file goes to one module, to the breadth-first pass, or to the
leftovers
- Build the breadth-first pass: the smallest set of files that conveys the
whole system's structure, flow, and interfaces. See "What goes in a breadth
pass". Order it entry point first, then the interfaces control flows through
- Trace the data flow (see "Tracing the data flow") so the reader has a map of
what moves through the system before reading the files that move it
- Order the modules by dependence, foundations first, so a dive never assumes a
later one
- For each module, build its own reading order. If small, list its files in
dependency order. If it has submodules or more than ten files, recurse: a
breadth pass for that module, then its own skippable dives
- Verify the order (see "Verifying the order") and fix what fails
- Report as in "Reporting". Don't edit any files
Finding module boundaries
A module is a set of files that is conceptually one unit. Evidence, strongest
first:
- A directory with its own index, entry point, or public exports, and a name
that describes it
- A cluster whose files import each other freely but reach the rest of the
codebase through a few named symbols
- A package or workspace member with its own manifest
- Files sharing a naming prefix or suffix that marks a role (
*.route.ts,
handlers/)
- A test file's scope: what one test file covers is one unit
- A coherent part of the domain vocabulary, even when the files are scattered
Directory layout is evidence, not proof. When imports contradict the tree, trust
the imports and say so in the report. Files that fit no module go to the
leftovers.
What goes in a breadth pass
At the top level, and again inside any module big enough to recurse:
- The entry point or points:
main, the server bootstrap, the CLI root, the
exported index
- The public interface: exported types, schemas, protocol or API definitions,
the database schema
- The wiring that shows how parts connect: dependency injection setup, the
router, the plugin registry, the module index
- One representative end-to-end path through the system, named as a path: the
request that comes in, the handler it reaches, the store it writes
- Configuration and build files only where they change how the code is read
Keep it small. Ten to twenty files at the top level of a large repo, enough to
answer "what are the pieces and how do they interact", not "how does any one
piece work". Prefer citing a specific region of a long file over the whole file.
Tracing the data flow
The reading order lists what to read. The data flow lists what moves: where data
enters, what shape it takes at each hop, what transforms it, and where it comes
to rest or leaves.
- Start at the boundaries: request bodies, CLI arguments, file reads, message
queues, environment. Name the input as the codebase does
- Follow each input to its resting place: a database write, a response, a file,
a rendered view. Stop there
- Name the shape at each hop, using the codebase's own type or table names, and
the file that does the transforming
- Mark where the shape changes and where the same data is merely passed through.
A hop that changes nothing can be collapsed
- Distinguish data flow from control flow. Which function calls which is control
flow, and the breadth pass already covers it. Trace what the calls pass
- Cover the flows that explain the system, not every flow. One primary path,
plus the ones that differ in kind: a background job, a write path against a
read path, a stream against a request
- Name what crosses a trust or process boundary: unvalidated input, data that
leaves for another service, secrets. Where validation or encoding happens is
part of the shape
Verifying the order
- Follow the order as a reader would: at each file, is every concept it uses
either defined earlier or deferrable to a later dive? Move what fails
- Cut every dive and check the breadth pass still stands on its own
- Check every hop in the data flow names a real file and a real shape, both
read, not inferred from a name. A hop you couldn't follow is a gap to report,
not one to guess at
- Check the leftovers: each is either a file the reader can skip (say why) or a
boundary you missed
- Circular dependencies between modules mean no order can be foundations-first.
Pick the direction that reads better, and say which edge the reader must
accept before its definition
Reporting
A nested list, groups labelled so any dive can be skipped:
- Open with the shape: how many files in scope, how many modules, and what you
excluded
- Data flow: each traced flow as a chain of hops,
shape → shape, with
the file that performs each transform. One line of prose per flow saying what
it accomplishes. Use a fenced mermaid flowchart instead when a flow
branches, merges, or fans out enough that a chain misrepresents it. Put this
before Pass 1: it is the map the file list is read against
- Pass 1, structure and interfaces: the breadth-first files in order, each
with a one-line reason. Line or symbol ranges where a whole file is too much
- Dive:
<module> for each module, in dependency order. Head each with one
sentence on what the module does and what the reader gets from it, so they can
skip it on that sentence alone. Then its files in order, each with a one-line
reason. Nest a sub-pass and sub-dives inside a module that needed recursion
- Close with the leftovers, the circular dependencies you had to break, and
anything you could not place
1---2name: orient3description: Produce a reading order for a codebase: a map of how data flows through it, a breadth-first pass over structure and interfaces, then optional depth-first dives per module.4---56Produce a reading order for the target codebase: a map of how data flows through7it, a breadth-first pass that teaches its structure and interfaces, then8optional depth-first dives per module, each one skippable. Read the code. Don't9change anything.1011# Target1213```!14jj file list | head -n 50015```1617```!18jj show --stat19```2021Arguments: $ARGUMENTS2223Produce the reading order for the target named in the arguments if given.24Otherwise produce it for the whole repository listed above. If the arguments25name the current commit, scope the order to the files it changes plus whatever a26reader must read first to understand them.2728The listing above is truncated: a starting point, not the enumeration.2930# Principles3132- The order teaches, it doesn't inventory. Every file earns its place by what it33 lets the reader understand next. A file nothing depends on and nothing34 explains doesn't belong in the order35- Breadth before depth, at every level. A level's pass covers the shape of the36 whole thing: what the parts are, how they interact, where control enters. Only37 then does the reader descend into one part38- Each dive is skippable. After the breadth-first pass, a reader who skips every39 dive should still understand what the system does and how its pieces fit.40 Nothing in a later group may be a prerequisite for an earlier one41- Interfaces before implementations. Type definitions, schemas, public exports,42 route tables, and config define the vocabulary. The code that implements them43 assumes it44- Recurse only where complexity requires it. A module of three files gets a flat45 list. A module with its own submodules gets its own breadth-first pass, then46 its own dives47- Say why, briefly. One line per file or group: what the reader gets from it.48 Without that the order is unusable for deciding what to skip4950These aren't exhaustive. Reason from first principles when none fits cleanly.5152# Workflow53541. Enumerate the files in scope mechanically (`jj file list`, a glob, the55 commit's changed files), not from memory. Exclude generated output, vendored56 code, lockfiles, and snapshots, and note that you did572. Read the orientation files first: README, CLAUDE.md, package manifests,58 workspace config, build config, and the entry points they name. These59 determine the top-level decomposition. Don't guess it from directory names60 alone613. Partition the files into modules (see "Finding module boundaries"). Each62 in-scope file goes to one module, to the breadth-first pass, or to the63 leftovers644. Build the breadth-first pass: the smallest set of files that conveys the65 whole system's structure, flow, and interfaces. See "What goes in a breadth66 pass". Order it entry point first, then the interfaces control flows through675. Trace the data flow (see "Tracing the data flow") so the reader has a map of68 what moves through the system before reading the files that move it696. Order the modules by dependence, foundations first, so a dive never assumes a70 later one717. For each module, build its own reading order. If small, list its files in72 dependency order. If it has submodules or more than ten files, recurse: a73 breadth pass for that module, then its own skippable dives748. Verify the order (see "Verifying the order") and fix what fails759. Report as in "Reporting". Don't edit any files7677# Finding module boundaries7879A module is a set of files that is conceptually one unit. Evidence, strongest80first:8182- A directory with its own index, entry point, or public exports, and a name83 that describes it84- A cluster whose files import each other freely but reach the rest of the85 codebase through a few named symbols86- A package or workspace member with its own manifest87- Files sharing a naming prefix or suffix that marks a role (`*.route.ts`,88 `handlers/`)89- A test file's scope: what one test file covers is one unit90- A coherent part of the domain vocabulary, even when the files are scattered9192Directory layout is evidence, not proof. When imports contradict the tree, trust93the imports and say so in the report. Files that fit no module go to the94leftovers.9596# What goes in a breadth pass9798At the top level, and again inside any module big enough to recurse:99100- The entry point or points: `main`, the server bootstrap, the CLI root, the101 exported index102- The public interface: exported types, schemas, protocol or API definitions,103 the database schema104- The wiring that shows how parts connect: dependency injection setup, the105 router, the plugin registry, the module index106- One representative end-to-end path through the system, named as a path: the107 request that comes in, the handler it reaches, the store it writes108- Configuration and build files only where they change how the code is read109110Keep it small. Ten to twenty files at the top level of a large repo, enough to111answer "what are the pieces and how do they interact", not "how does any one112piece work". Prefer citing a specific region of a long file over the whole file.113114# Tracing the data flow115116The reading order lists what to read. The data flow lists what moves: where data117enters, what shape it takes at each hop, what transforms it, and where it comes118to rest or leaves.119120- Start at the boundaries: request bodies, CLI arguments, file reads, message121 queues, environment. Name the input as the codebase does122- Follow each input to its resting place: a database write, a response, a file,123 a rendered view. Stop there124- Name the shape at each hop, using the codebase's own type or table names, and125 the file that does the transforming126- Mark where the shape changes and where the same data is merely passed through.127 A hop that changes nothing can be collapsed128- Distinguish data flow from control flow. Which function calls which is control129 flow, and the breadth pass already covers it. Trace what the calls pass130- Cover the flows that explain the system, not every flow. One primary path,131 plus the ones that differ in kind: a background job, a write path against a132 read path, a stream against a request133- Name what crosses a trust or process boundary: unvalidated input, data that134 leaves for another service, secrets. Where validation or encoding happens is135 part of the shape136137# Verifying the order138139- Follow the order as a reader would: at each file, is every concept it uses140 either defined earlier or deferrable to a later dive? Move what fails141- Cut every dive and check the breadth pass still stands on its own142- Check every hop in the data flow names a real file and a real shape, both143 read, not inferred from a name. A hop you couldn't follow is a gap to report,144 not one to guess at145- Check the leftovers: each is either a file the reader can skip (say why) or a146 boundary you missed147- Circular dependencies between modules mean no order can be foundations-first.148 Pick the direction that reads better, and say which edge the reader must149 accept before its definition150151# Reporting152153A nested list, groups labelled so any dive can be skipped:154155- Open with the shape: how many files in scope, how many modules, and what you156 excluded157- **Data flow**: each traced flow as a chain of hops, `shape` → `shape`, with158 the file that performs each transform. One line of prose per flow saying what159 it accomplishes. Use a fenced `mermaid` `flowchart` instead when a flow160 branches, merges, or fans out enough that a chain misrepresents it. Put this161 before Pass 1: it is the map the file list is read against162- **Pass 1, structure and interfaces**: the breadth-first files in order, each163 with a one-line reason. Line or symbol ranges where a whole file is too much164- **Dive: `<module>`** for each module, in dependency order. Head each with one165 sentence on what the module does and what the reader gets from it, so they can166 skip it on that sentence alone. Then its files in order, each with a one-line167 reason. Nest a sub-pass and sub-dives inside a module that needed recursion168- Close with the leftovers, the circular dependencies you had to break, and169 anything you could not place