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: |4 Produce a reading order for a codebase: a map of how data flows through it, a5 breadth-first pass over structure and interfaces, then optional depth-first6 dives per module.7argument-hint: '[repo, subtree, or commit to produce a reading order for]'8allowed-tools: Bash(jj file list *)9---1011Produce a reading order for the target codebase: a map of how data flows through12it, a breadth-first pass that teaches its structure and interfaces, then13optional depth-first dives per module, each one skippable. Read the code. Don't14change anything.1516# Target1718```!19jj file list | head -n 50020```2122```!23jj show --stat24```2526Arguments: $ARGUMENTS2728Produce the reading order for the target named in the arguments if given.29Otherwise produce it for the whole repository listed above. If the arguments30name the current commit, scope the order to the files it changes plus whatever a31reader must read first to understand them.3233The listing above is truncated: a starting point, not the enumeration.3435# Principles3637- The order teaches, it doesn't inventory. Every file earns its place by what it38 lets the reader understand next. A file nothing depends on and nothing39 explains doesn't belong in the order40- Breadth before depth, at every level. A level's pass covers the shape of the41 whole thing: what the parts are, how they interact, where control enters. Only42 then does the reader descend into one part43- Each dive is skippable. After the breadth-first pass, a reader who skips every44 dive should still understand what the system does and how its pieces fit.45 Nothing in a later group may be a prerequisite for an earlier one46- Interfaces before implementations. Type definitions, schemas, public exports,47 route tables, and config define the vocabulary. The code that implements them48 assumes it49- Recurse only where complexity requires it. A module of three files gets a flat50 list. A module with its own submodules gets its own breadth-first pass, then51 its own dives52- Say why, briefly. One line per file or group: what the reader gets from it.53 Without that the order is unusable for deciding what to skip5455These aren't exhaustive. Reason from first principles when none fits cleanly.5657# Workflow58591. Enumerate the files in scope mechanically (`jj file list`, a glob, the60 commit's changed files), not from memory. Exclude generated output, vendored61 code, lockfiles, and snapshots, and note that you did622. Read the orientation files first: README, CLAUDE.md, package manifests,63 workspace config, build config, and the entry points they name. These64 determine the top-level decomposition. Don't guess it from directory names65 alone663. Partition the files into modules (see "Finding module boundaries"). Each67 in-scope file goes to one module, to the breadth-first pass, or to the68 leftovers694. Build the breadth-first pass: the smallest set of files that conveys the70 whole system's structure, flow, and interfaces. See "What goes in a breadth71 pass". Order it entry point first, then the interfaces control flows through725. Trace the data flow (see "Tracing the data flow") so the reader has a map of73 what moves through the system before reading the files that move it746. Order the modules by dependence, foundations first, so a dive never assumes a75 later one767. For each module, build its own reading order. If small, list its files in77 dependency order. If it has submodules or more than ten files, recurse: a78 breadth pass for that module, then its own skippable dives798. Verify the order (see "Verifying the order") and fix what fails809. Report as in "Reporting". Don't edit any files8182# Finding module boundaries8384A module is a set of files that is conceptually one unit. Evidence, strongest85first:8687- A directory with its own index, entry point, or public exports, and a name88 that describes it89- A cluster whose files import each other freely but reach the rest of the90 codebase through a few named symbols91- A package or workspace member with its own manifest92- Files sharing a naming prefix or suffix that marks a role (`*.route.ts`,93 `handlers/`)94- A test file's scope: what one test file covers is one unit95- A coherent part of the domain vocabulary, even when the files are scattered9697Directory layout is evidence, not proof. When imports contradict the tree, trust98the imports and say so in the report. Files that fit no module go to the99leftovers.100101# What goes in a breadth pass102103At the top level, and again inside any module big enough to recurse:104105- The entry point or points: `main`, the server bootstrap, the CLI root, the106 exported index107- The public interface: exported types, schemas, protocol or API definitions,108 the database schema109- The wiring that shows how parts connect: dependency injection setup, the110 router, the plugin registry, the module index111- One representative end-to-end path through the system, named as a path: the112 request that comes in, the handler it reaches, the store it writes113- Configuration and build files only where they change how the code is read114115Keep it small. Ten to twenty files at the top level of a large repo, enough to116answer "what are the pieces and how do they interact", not "how does any one117piece work". Prefer citing a specific region of a long file over the whole file.118119# Tracing the data flow120121The reading order lists what to read. The data flow lists what moves: where data122enters, what shape it takes at each hop, what transforms it, and where it comes123to rest or leaves.124125- Start at the boundaries: request bodies, CLI arguments, file reads, message126 queues, environment. Name the input as the codebase does127- Follow each input to its resting place: a database write, a response, a file,128 a rendered view. Stop there129- Name the shape at each hop, using the codebase's own type or table names, and130 the file that does the transforming131- Mark where the shape changes and where the same data is merely passed through.132 A hop that changes nothing can be collapsed133- Distinguish data flow from control flow. Which function calls which is control134 flow, and the breadth pass already covers it. Trace what the calls pass135- Cover the flows that explain the system, not every flow. One primary path,136 plus the ones that differ in kind: a background job, a write path against a137 read path, a stream against a request138- Name what crosses a trust or process boundary: unvalidated input, data that139 leaves for another service, secrets. Where validation or encoding happens is140 part of the shape141142# Verifying the order143144- Follow the order as a reader would: at each file, is every concept it uses145 either defined earlier or deferrable to a later dive? Move what fails146- Cut every dive and check the breadth pass still stands on its own147- Check every hop in the data flow names a real file and a real shape, both148 read, not inferred from a name. A hop you couldn't follow is a gap to report,149 not one to guess at150- Check the leftovers: each is either a file the reader can skip (say why) or a151 boundary you missed152- Circular dependencies between modules mean no order can be foundations-first.153 Pick the direction that reads better, and say which edge the reader must154 accept before its definition155156# Reporting157158A nested list, groups labelled so any dive can be skipped:159160- Open with the shape: how many files in scope, how many modules, and what you161 excluded162- **Data flow**: each traced flow as a chain of hops, `shape` → `shape`, with163 the file that performs each transform. One line of prose per flow saying what164 it accomplishes. Use a fenced `mermaid` `flowchart` instead when a flow165 branches, merges, or fans out enough that a chain misrepresents it. Put this166 before Pass 1: it is the map the file list is read against167- **Pass 1, structure and interfaces**: the breadth-first files in order, each168 with a one-line reason. Line or symbol ranges where a whole file is too much169- **Dive: `<module>`** for each module, in dependency order. Head each with one170 sentence on what the module does and what the reader gets from it, so they can171 skip it on that sentence alone. Then its files in order, each with a one-line172 reason. Nest a sub-pass and sub-dives inside a module that needed recursion173- Close with the leftovers, the circular dependencies you had to break, and174 anything you could not place