Skip to main content

Understand an unfamiliar codebase before editing it

The first task in an unfamiliar repository is not to collect every file. It is to build a small, testable model of how the software starts, where responsibilities live, and which parts deserve closer inspection before a change.

This recipe uses ctx in layers. Each layer answers a different question, and source code remains the final check when static relationships are ambiguous or incomplete.

:::note Worked-example provenance The ctx-on-ctx measurements were captured from commit 0794eb1 with ctx 0.3.5 on 2026-07-14. They illustrate the workflow and will change as the repository and index evolve. :::

Quickest version

ctx index
ctx query stats
ctx map --budget 1200
ctx query find <entry-point-name>
ctx source <entry-point-name> --file <manifest-confirmed-file>

Confirm build metadata and trace one task-relevant path before editing. Rankings and graph edges are leads; source, manifests, tests, and compatibility documentation establish meaning.

The orientation brief

Before editing, aim to write down:

  • the repository's languages, build units, and executable or library entry points;
  • the major subsystems and the responsibility of each;
  • one verified execution path relevant to the likely task;
  • tests, configuration, persistence, and public-contract locations;
  • high-risk or highly connected areas worth treating carefully;
  • uncertainties that static analysis could not resolve.

This is an investigation brief, not a claim that you understand every file.

1. Refresh the evidence

Build or incrementally update the index before trusting any symbol or graph result:

ctx index

The summary establishes scale and confirms what ctx indexed. In ctx itself, the current index reported 110 files, 2,161 symbols, and 13,360 edges. Those totals describe the indexed model, not the entire Git repository: ignored, generated, unsupported, or non-code files may be absent.

If a result contradicts source you can see, reindex before developing a theory about the code.

2. Survey shape and structural pressure

Start with the compact human-readable statistics and a deliberately small map:

ctx query stats
ctx map --budget 1200

query stats shows scale, symbol-heavy files, and highly connected functions. map adds the repository tree and fits ranked symbols into the requested budget. Use both as a list of leads:

  • a symbol-heavy file may be a schema, registry, generated structure, or test suite;
  • high fan-in may identify a useful shared primitive rather than unhealthy coupling;
  • high fan-out may identify a composition root, parser, or dispatcher;
  • the map's first symbol is the highest-ranked indexed symbol, not necessarily the program's entry point.

In ctx itself, the 1,200-token map began with a test helper named find, followed by shared methods such as Metrics::get. The statistics also highlighted src/db/schema.rs and src/rules.rs. This was useful evidence about centrality and code concentration, but it was not an architecture description.

Prefer the default text output for a first read. ctx query stats --json includes the complete per-file table and is substantially larger; use it when a script needs the full dataset.

3. Establish the real entry points

Confirm build metadata and package manifests before relying on naming conventions. For this Rust repository:

ctx Cargo.toml src/lib.rs --format markdown --no-tree
ctx query find main
ctx source main --file src/main.rs

The manifest proves that src/main.rs is the ctx binary and src/lib.rs is the library root. query find main also finds the performance harness and several Python script entry points, which is exactly why the manifest and --file disambiguation matter.

Apply the same principle in other ecosystems: inspect package.json, pyproject.toml, go.mod, service manifests, deployment configuration, or framework registration before declaring a symbol to be an entry point.

Read the entry-point source even when explain succeeds. Current ctx indexes record the direct Rust function item in spawn(run_main) as a uses relationship rather than claiming it is a call. That evidence appears in ctx explain main --file src/main.rs and ctx query deps main --file src/main.rs, while callers, impact, and call-graph results remain limited to actual calls edges. Indexes created before this parser capability need one ctx index --force rebuild. ctx source still establishes the thread boundary and execution semantics that the relationship alone cannot.

4. Trace one distinctive execution path

Move one hop at a time and prefer distinctive symbol names:

ctx explain run_main --file src/main.rs
ctx query deps run_main --file src/main.rs --depth 2
ctx source run_main --file src/main.rs
ctx explain run --file src/main.rs

This verified the CLI path in ctx itself:

main -> run_main -> run(args) -> command-specific run_* function

The source adds meaning that the edge list cannot: main creates a larger-stack worker thread, run_main initializes the Rayon pool and parses arguments, and run dispatches subcommands while preserving exit-code behavior.

Graph results are hypotheses to check, especially for generic names. Resolved callers are tied to the selected symbol identity, while unresolved same-language call evidence is reported separately and should be checked against source. Static indexing may also miss or approximate macros, closure variables, function pointers, reflection, dynamic dispatch, generated code, and runtime registration. Direct Rust free-function callbacks are resolved only when exactly one Rust function item matches; ambiguous names remain unresolved uses evidence.

Use the call snippet and source location to accept or reject each important relationship. A result that merely shares a name is not evidence of a real call path.

5. Focus on a subsystem without mistaking focus for isolation

Once an entry point or subsystem path is known, bias the map toward it:

ctx map --focus src/main.rs --budget 2000

On this repository, focus promoted src/main.rs::run and the command handlers that it dispatches. It still retained globally important symbols from other areas. --focus is a relevance bias, not a closed dependency slice and not a path filter.

Follow the useful leads with narrower commands:

ctx source run --file src/main.rs
ctx query deps run --file src/main.rs --depth 1
ctx query find run_index
ctx explain run_index --file src/commands/index.rs

Choose the branch relevant to the task instead of recursively expanding every command. For an indexing change, continue into run_index, the indexer, parsers, database, and indexing tests. For a CLI-output change, follow argument parsing, dispatch, output formatting, JSON envelopes, exits, and command tests instead.

6. Read the boundaries the graph cannot infer

Complete the brief with repository evidence outside the call graph:

ctx Cargo.toml src/lib.rs src/cli.rs --count-only
ctx Cargo.toml src/lib.rs src/cli.rs --format markdown --no-tree
ctx query find config
ctx query find test

Use --count-only before packaging a larger known set. Token budgets omit whole files rather than truncating them, so an important entry point can disappear when it does not fit. The generated wrapper and headings also add output overhead beyond the selected files' content-token count.

Then inspect the repository's contributor instructions, compatibility policy, CI configuration, and user documentation. ctx can locate code relationships, but it cannot infer which JSON fields, CLI exits, persisted names, or release artifacts the project promises to keep compatible.

What worked, and what did not

The workflow was exercised against ctx's own indexed repository before this recipe was written.

TechniqueVerified useLimitation observed
ctx indexRefreshes the model and reports indexed scaleIndexed totals are not repository totals
ctx query statsFinds concentration and highly connected symbolsCentrality does not explain intent
ctx map --budgetGives a fast tree and ranked leadsTests and generic helpers may rank first
ctx query findFinds candidate entry points across languagesCommon names require disambiguation
ctx sourceConfirms the actual transition and local intentShows one symbol, not the whole runtime path
ctx explain and graph queriesAccelerate caller/dependency explorationSame-name false positives and unresolved dynamic edges remain
ctx map --focusPromotes a path or symbol and nearby handlersFocus biases ranking; it does not isolate a subsystem

The safe pattern is therefore survey, identify, trace, verify, and record uncertainty. No one command replaces that loop.

Write the brief before editing

Use a compact handoff format:

Runtime and build units:
Entry points:
Major subsystems:
Verified task-relevant path:
Tests and fixtures:
Configuration and persistence:
Compatibility surfaces:
Structural pressure points:
Unresolved or dynamic relationships:
Files to inspect next, with reasons:

For ctx itself, the resulting first-pass model is: a Rust CLI and library share indexing, database, parsing, context-selection, and governance modules; src/main.rs owns CLI dispatch; src/commands/ adapts subcommands to library behavior; src/lib.rs exposes the public module surface; SQLite stores the live code index; optional DuckDB-backed analytics and Parquet snapshots support analytical and longitudinal workflows; tests and governance scripts cover contracts that the call graph alone cannot describe.

That model is specific enough to choose the next investigation without pretending that every reported relationship is exact.

Give the workflow to an agent

Orient yourself in this repository before proposing or making edits. Refresh the ctx index, inspect
the compact statistics and a small map, then confirm build metadata and real entry points from
source. Trace one task-relevant execution path with disambiguated symbols, callers, dependencies,
and source. Treat centrality as a lead, map focus as a ranking bias, and graph edges as hypotheses
that require source verification. Report the architecture, vocabulary, tests, compatibility
surfaces, next files with reasons, and any unresolved dynamic relationships. Do not load or
summarize the whole repository.

Next in Cookbook v2

The next recipe will turn this orientation model into the smallest useful, token-budgeted context for a concrete task. Later recipes will use the same verified entry points for reuse discovery, blast-radius analysis, implementation, debugging, and review.