Skip to content

Explore unfamiliar code

Reading unfamiliar code through an agent burns tokens fast: whole-file dumps to “get oriented”, re-reads to find where a type lives, and log-sized context for what a 40-line function does. CtxCtl inverts the flow — map first, then read only the territory you need. All commands below are read-only and stateless; nothing is indexed or cached.

outline prints one line per symbol — kind, name, line range, signature — plus token-savings stats. It is the cheapest orientation pass over any supported file:

Terminal window
ctxctl outline src/server.rs
# src/server.rs [12 symbols, ~2.1 KB -> ~410 tokens, saved ~80%]
fn handle_request L:42-58 pub async fn handle_request(&self, id: u64)
struct RequestHandler L:12-40 pub struct RequestHandler {
const MAX_RETRIES L:60 const MAX_RETRIES: u32 = 3;

From this alone the agent already knows the file’s shape without reading it. Very large files fold their symbol list past a threshold (default 50) into a ... [N symbols omitted] marker; the header always keeps the total count. --no-doc drops doc comments if you want the bare minimum.

Once the outline names the interesting symbol, you rarely need its whole body immediately:

Terminal window
# Declaration line only
ctxctl symbol src/server.rs --name handle_request --signature
# Signature + fold marker for the body
ctxctl symbol src/server.rs --name handle_request --compact

--compact returns an AST-pruned view: the signature (plus decorators on python), a // ... [N lines omitted] fold marker (# … for python), and a bare closing line when present. It is byte-stable and re-parses cleanly, so downstream tooling can still consume it.

Both flags answer the common agent question — “what does this take and return?” — for a fraction of a full read.

For the real implementation, symbol without extra flags returns the verbatim slice of that symbol from the original source:

Terminal window
ctxctl symbol src/server.rs --name handle_request
# handle_request src/server.rs:42-58 (58 tokens, saved ~85%)
pub async fn handle_request(&self, id: u64) -> Result<String, Error> {
let row = self.db.get(id).await?;
Ok(row.to_string())
}

The slice preserves comments, formatting, and indentation exactly — it is source, not a summary. Useful refinements:

  • --kind <k> narrows same-named matches (a method versus a local variable). With duplicates, the first in source order wins unless narrowed.
  • --lines 3-10 sub-ranges within the symbol once you have located the exact lines.
  • Exit code 4 means the symbol was not found — usually a typo or the wrong file, not a missing feature.

For ranges outside any single symbol — a block of module docs, a config table — use read, which needs no AST at all:

Terminal window
ctxctl read src/server.rs --lines 100-150
ctxctl read src/server.rs --lines 100-150,200-210 # multiple ranges, comma-separated

Open-ended ranges clamp to the end of the file (--lines 10-). Exit code 2 means the range was invalid or out of bounds.

Before jumping across files, check what the file depends on. deps lists imports with kind and line number:

Terminal window
ctxctl deps src/main.rs
# src/main.rs [3 imports, ~512 B -> ~64 tokens, saved ~88%]
external serde L:1
local crate::lib L:2

The local / external split tells the agent which jumps stay inside the repository (worth an outline) and which cross into dependencies (usually not worth reading at all).

The same workflow covers more than source code — the symbol engine has backends for markup and styles:

  • Markdown: every ATX/setext heading is a symbol, and a heading’s slice spans its whole section — so symbol README.md --name Install returns exactly that section.
  • CSS/SCSS: rulesets are symbols (the selector list is the name), nested @media blocks included.
  • HTML: elements carrying an id attribute are addressable by that id.

Agents exploring docs-heavy repositories get the same outline-then-slice discipline without any special casing.

A complete cheap-exploration pass looks like this:

Terminal window
ctxctl outline src/server.rs # map
ctxctl deps src/server.rs # what it hangs off
ctxctl symbol src/server.rs --name handle_request --compact # shape of the target
ctxctl symbol src/server.rs --name handle_request # body, verbatim
ctxctl read src/server.rs --lines 100-110 # anything outside symbols

Each step’s output ends with the next step’s decision point, and every step is far smaller than the file. For making command output just as small, see Compress command output; the reasoning behind slices-not-summaries is in Principles.