A pi extension that builds on the Sweep blog post "Read File" approach, using smart AST-based file outlining, and registers an Explorer subagent for @tintinweb/pi-subagents.
The extension takes pi's built-in read tool and makes it smarter about what a large file really holds before you spend tokens reading every line of it:
| Input | Returns |
|---|---|
| Small file (<200 lines) | Full file content |
| Large file, supported language | AST structural outline with code previews and line ranges |
| Large file, unsupported language | Line-count preview with first/last lines |
| Directory path | Directory listing with sizes and modified times |
Any file with offset/limit |
Raw section content (drill-down) |
Any file with ranges |
Multiple non-contiguous sections in one call (auto-merged) |
| Non-existent path | Fuzzy suggestions for similar paths (can be disabled in config) |
The outline surfaces every structural symbol (classes, functions, methods, interfaces, enums, structs, traits, impl blocks) with its line range, its nesting depth, and a short code preview so you can recognize a symbol without reading the whole body. Reading this way costs less because the format does three things at once:
- Fewer tool calls: a single
rangescall replaces several separate reads - Fewer drill-downs: a preview is often enough to know what a symbol means without opening it
- Less output overall: adjacent ranges merge automatically before anything is sent back
Without extension (1 call, ~5000 tokens):
read("large-file.ts") → Full 500-line file content
With extension (1-2 calls, ~800 tokens):
read("large-file.ts") → Outline with previews (~500 tokens)
read("large-file.ts", ranges: [{offset:100,limit:50}, {offset:300,limit:30}]) → 2 sections (~300 tokens)
| Language | Parser | Structural Symbols |
|---|---|---|
| JavaScript / JSX | tree-sitter-javascript |
classes, functions, methods, arrow functions |
| TypeScript / TSX | tree-sitter-typescript |
+ interfaces, enums, type aliases, decorators |
| Python | tree-sitter-python |
classes, functions, async defs, decorated definitions |
| Go | tree-sitter-go |
functions, methods (with receivers), structs, interfaces, const/var blocks |
| Rust | tree-sitter-rust |
functions, structs, enums, traits, impl blocks, macros |
| Solidity | tree-sitter-solidity |
contracts, interfaces, libraries, functions, modifiers, events, errors, structs, enums, constructors, fallback/receive |
| Smali | tree-sitter-smali |
classes, interfaces, enums, methods, fields, annotations |
| Java | tree-sitter-java |
classes, interfaces, enums, records, annotations, methods, constructors, fields, enum constants |
| SCSS | tree-sitter-scss |
rulesets, placeholders, variables, mixins, functions, @use/@forward/@import, extends |
| Sass (indented) | tree-sitter-sass |
rulesets, placeholders, variables, mixins, functions, @use/@forward/@import, extends |
The SCSS grammar covers the bracket syntax (.scss). The indented .sass dialect
is handled by a separate vendored grammar; both are compiled to WASM and shipped
under src/parsers/vendor/.
| Command | Description |
|---|---|
/codebase-reader [on|off] [local|global] |
Enable or disable smart file outlining (bare command shows current status). Default scope: global |
/codebase-reader-model [local|global] |
Open an interactive searchable model selector for the Explorer subagent. Default scope: global |
/codebase-reader-subagent [library|auto] [local|global] |
Show subagent library status or configure preference (@tintinweb/pi-subagents, pi-subagents, or auto). Default scope: global |
/codebase-reader-settings [global|local] |
Edit the TOML configuration file (default: global; use local for project-level .pi/codebase-reader.toml) |
/codebase-reader-explorer [reinstall|uninstall] |
Manage the Explorer subagent. reinstall forces a fresh write; uninstall removes the agent file. Without arguments, shows status. |
/build-context <mission_brief> |
Paraphrase a mission brief via the session model (tools restricted to subagent only) then delegate to the Explorer subagent for structured exploration |
pi install git:github.com/HanzCEO/pi-codebase-readerOr load directly for development:
pi -e ./src/index.tsThe Explorer subagent works with either subagent extension. Install one (or both):
# Option A: @tintinweb/pi-subagents
pi install npm:@tintinweb/pi-subagents
# Option B: nicobailon/pi-subagents
pi install npm:pi-subagentsBoth libraries can be installed at once. The agent definition file is written in a format both understand. Run /codebase-reader-subagent to see which one is detected.
Stored in .pi/codebase-reader.toml (project) or ~/.pi/agent/codebase-reader.toml (global):
[general]
enabled = true
threshold_tokens = 10000
suggest_similar = true
include_previews = true
preview_lines = 3
[explorer]
model = "anthropic/claude-sonnet-4-20250514"
thinking = "medium"
max_turns = 30
[parsing]
max_outline_depth = 10| Key | Default | Description |
|---|---|---|
general.enabled |
true |
Enable/disable smart file outlining |
general.threshold_tokens |
10000 |
Token budget for AST outlines; outlines exceeding this are progressively shallowed |
general.suggest_similar |
true |
When a file path is not found, suggests similar paths via recursive fuzzy matching |
general.include_previews |
true |
Include code previews (first few lines) in outline output |
general.preview_lines |
3 |
Number of lines to preview per symbol in outline |
explorer.model |
anthropic/claude-sonnet-4-20250514 |
Model used by the Explorer subagent |
explorer.thinking |
"medium" |
Thinking level for the Explorer subagent |
explorer.max_turns |
30 |
Maximum agentic turns for the Explorer subagent |
parsing.max_outline_depth |
10 |
Maximum nesting depth for AST outlines |
The extension registers an explorer agent type compatible with both subagent libraries.
Use the Agent tool:
Agent({
subagent_type: "explorer",
prompt: "Analyze the request handler in src/server.ts lines 120-350",
run_in_background: true
})
Use the subagent tool:
subagent({
agent: "explorer",
task: "Analyze the request handler in src/server.ts lines 120-350"
})
The explorer subagent has tools read, grep, find, bash, ls, repo_tree, and connected_tree and is specialized for deep-dive code exploration.
The explorer agent calls the dedicated grep tool directly rather than going through bash. That choice keeps the output structured: file paths and line numbers arrive already separated instead of buried in raw terminal text, and it removes the cost of spawning a shell for every search. When you change the model via /codebase-reader-model or settings via /codebase-reader-settings, the explorer agent definition file is rewritten automatically, so the subagent library picks the change up on next reload.
Run /codebase-reader-subagent (without arguments) to see which library is detected and active.
The explorer agent file is managed for you, and you stay in control of it:
| Event | Action |
|---|---|
| Extension load | Creates explorer.md in ~/.pi/agent/agents/ |
| Session start | Reinstalls explorer.md to ensure it's current |
| Session shutdown | Removes explorer.md to keep things clean |
| Config change | Run /codebase-reader-explorer reinstall to update |
Manual management:
# Reinstall the explorer agent (e.g., after config changes)
/codebase-reader-explorer reinstall
# Remove the explorer agent
/codebase-reader-explorer uninstall
# Show explorer agent status
/codebase-reader-explorerHere is the full cycle from call to sections, so you can see what a real exchange looks like:
- Agent calls
read("large-file.ts") - Extension parses the file with tree-sitter AST
- Returns an outline with code previews and line ranges:
server.ts (TypeScript) 2855 lines, ~22.8K tokens
├── class App (5 children) [1:850]
│ ├── constructor(config) (3 children) [15:250]
│ │ ```
│ │ constructor(private config: AppConfig) {
│ │ this.db = createDatabase(config.dbUrl);
│ │ this.cache = new LRUCache({ max: 1000 });
│ │ ```
│ ├── handleRequest(req) [252:550]
│ │ ```
│ │ async handleRequest(req: Request): Promise<Response> {
│ │ const url = new URL(req.url);
│ │ if (url.pathname === '/api/users') {
│ │ ```
│ └── ...
├── function main() [852:900]
│ ```
│ async function main() {
│ const config = loadConfig();
│ const app = new App(config);
│ ```
├── interface Config [902:920]
└── type Options [922:930]
Use read with offset/limit to view specific sections, or ranges for multiple sections.
-
Agent reads specific sections:
Single section (one call):
read("large-file.ts", { offset: 252, limit: 300 })Multiple sections (one call, auto-merged):
read("large-file.ts", ranges: [ { offset: 15, limit: 235 }, // constructor { offset: 252, limit: 300 }, // handleRequest { offset: 852, limit: 50 } // main ])
When a requested file is not found, the extension falls back on recursive fuzzy matching to suggest what you probably meant. read("src/comands.ts"), for instance, might come back with src/commands.ts and src/config.ts so a typo costs a glance rather than a hunt. Set general.suggest_similar = false in the configuration to turn this off.
The Explorer subagent is built to spend as little as possible per call:
- Calls the
greptool directly (not via bash) for pattern matching - Uses
repo_treefor a repository overview instead of severallscalls - Uses
connected_treefor import analysis instead of tracing imports by hand
Agents that skip this setup often reach for bash to run grep by hand:
# Bash overhead + unstructured output
grep -rn "func.*Create" core/vm/The optimized explorer agent, by contrast, hands the work to a dedicated tool:
# Dedicated tool + structured results
grep(pattern: "func.*Create", path: "core/vm")The result is a tool surface that does the same work with less friction:
- 30-40% fewer tool calls
- 20-30% token savings
- Structered output the model reasons over directly
- No shell process spawned for each search
The /codebase-reader-model command opens a fully interactive terminal UI that:
- Lists all available models from pi's model registry (deduplicated, sorted)
- Provides real-time keyboard filtering as you type
- Supports arrow key navigation, Enter to select, and Esc to cancel
- Persists the selection to configuration and updates the Explorer subagent automatically
The extension hooks into pi's session lifecycle:
session_start: Reloads configuration and re-registers the Explorer agent for each new sessionsubagents:ready: Listens for the@tintinweb/pi-subagentsreadiness signal to confirm the Explorer agent is available- Auto-detection: Both
@tintinweb/pi-subagents(via Symbol) andpi-subagents(via globalThis runtime key) are detected automatically on session start
This project uses GitHub Actions for CI (see .github/workflows/), running tests and type checks on every push.