Skip to main content

AST-based structural outline for source files (C#, C++, Python, TypeScript/JavaScript, Java, Kotlin, Scala, Go, Rust, PHP, Ruby, CSS, SCSS, SQL, Markdown, YAML) — tree-sitter-powered, LLM-agent-first. Complement to ast-grep: ast-grep searches, ast-outline overviews.

Project description

ast-outline

English · Русский · 简体中文

Fast, AST-based structural outline for source files — classes, methods, signatures with line numbers, but no method bodies. Built for LLM coding agents that should read the shape of a file before reading the whole thing.

Sibling to ast-grep in the ast-* family: ast-grep searches code structurally, ast-outline overviews it.

Code: Apache 2.0 Docs: CC BY 4.0 PyPI Python: 3.10+ Status: beta

📖 Documentation: https://ast-outline.github.io/ · Site source: ast-outline/ast-outline.github.io

ast-outline™ by Dmitrii Zaitsev (dim-s) — original project at https://github.com/ast-outline/ast-outline (created 2026-04-22). Code under Apache 2.0 (v0.6.0+; v0.5.x and earlier remain available under MIT), documentation under CC BY 4.0 — reuse of this README's prose requires visible attribution. See Licensing & attribution below.


Purpose

ast-outline exists to make LLM coding agents faster, cheaper, and smarter when navigating unfamiliar code.

Modern agentic coding tools (Claude Code, Cursor's agent mode, Aider, Copilot Chat, custom CLI agents) explore codebases by reading files directly — not via embeddings or vector search. That approach is reliable but has a cost: on a 1000-line file, the agent pays for 1000 lines of tokens just to answer "what methods exist here?".

ast-outline closes that gap. It's a pre-reading layer for agents:

  1. Token savings — typically 2–10×. An outline replaces a full file read when the agent only needs structural understanding.
  2. Faster exploration. A whole module's public API fits on one screen.
  3. Precise navigation. Every declaration has a line range (L42-58). The agent goes straight to the method body it needs.
  4. AST accuracy, not fuzzy match. show and inheritance rendering understand real syntax — no false positives from comments or strings.
  5. Zero infrastructure. No index, no cache, no embeddings, no network. Live, always fresh, invisible to your repo.

The typical agent workflow

Before ast-outline:

Agent: Read Player.cs            # 1200 lines of tokens
Agent: Read Enemy.cs             # 800 lines of tokens
Agent: Read DamageSystem.cs      # 400 lines of tokens
...

With ast-outline:

Agent: ast-outline digest src/Combat         # ~100 lines, whole module
Agent: ast-outline Player.cs                 # signatures only, 2–10× smaller
Agent: ast-outline show Player.cs TakeDamage # just the method body

Result: same understanding, a fraction of the tokens, a fraction of the round-trips.


Design philosophy

Stateless. No index, no cache, no embeddings, no network. Parse on demand, print, exit.

Opposite of RAG-style codebase indexers (Cursor, Bloop, Continue, the embedding-MCP crowd). Modern LLM agents are sharp enough to chain ast-outline with grep, find, ast-grep and other unix tools and navigate real code fast — without reading whole files, and without a local index earning its complexity.

And no MCP server for ast-outline itself — for a stateless CLI, agents get more leverage piping and parallelising it in bash than through an MCP shim wrapping the same calls.


Supported languages

Language Extensions
C# .cs
C++ .cpp, .cc, .cxx, .c++, .h, .hpp, .hh, .hxx, .h++, .ipp, .tpp, .inl, .cppm, .ixx
Python .py, .pyi
TypeScript .ts, .tsx
JavaScript .js, .jsx, .mjs, .cjs (parsed by the TypeScript grammar)
Java .java
Kotlin .kt, .kts
Scala .scala, .sc
Go .go
Rust .rs
PHP .php, .phtml, .phps, .php8
Ruby .rb, .rake, .gemspec, .ru, Rakefile, Gemfile (incl. Rails)
CSS .css
SCSS .scss (mixins, functions, variables, placeholders; & resolves against parent)
SQL .sql (tables w/ columns, views, types, enums, functions, procedures, triggers, indexes, sequences, schemas, domains; PostgreSQL primary, MySQL/SQLite usable)
Markdown .md, .markdown, .mdx, .mdown
YAML .yaml, .yml
What each adapter recognises
  • Java — classes, interfaces, @interface, enums, records, sealed hierarchies, generics, throws, Javadoc.
  • Kotlin — classes, interfaces, fun interface, object / companion object, data / sealed / enum / annotation classes, extension functions, suspend / inline / const / lateinit, generics with where constraints, typealias, KDoc.
  • Scala — Scala 2 + Scala 3: classes, traits, object / case object, case class, sealed hierarchies, Scala 3 enum / given / using / extension, indentation-based bodies, higher-kinded types, context bounds, opaque type, type aliases, Scaladoc.
  • Go — packages, structs (with method-grouping under receiver), interfaces, struct/interface embedding as inheritance, generics (Go 1.18+), type aliases + defined types, iota enum-blocks, doc-comment chains.
  • C++ — namespaces (single-chain old-style folds into a::b::c, anonymous → <anonymous>, inline preserved), classes / structs / unions / enums (classic + enum class), templates (header preserved as signature prefix on class / function / member templates), virtual / pure-virtual / const / noexcept qualifiers, ctors / dtors / operators (incl. conversion operators like operator bool()), = default / = delete, public: / protected: / private: access blocks with C++-correct defaults, base-class clauses with access + virtual markers, out-of-class definitions (Widget::draw), #include directives as imports. Unreal Engine UCLASS() / UFUNCTION() / GENERATED_BODY() macros parse without interference.
  • Rust — modules (recursive), structs (regular / tuple / unit), unions, enums with all variant shapes, traits with supertraits as bases, impl block regrouping under the target type (inherent + impl Trait for Foo adds Trait to bases), extern "C" blocks, macro_rules!, type aliases, generics + lifetimes + where clauses, pub / pub(crate) visibility, outer doc comments (///, /** */) and #[...] attributes.
  • PHP — modern PHP 8.x and the still-deployed 7.4 LTS line: namespaces (file-scoped + bracketed), classes (abstract / final / readonly and combinations), interfaces, traits, PHP 8.1 enums (pure + backed), methods, magic ctor / dtor (__construct → ctor, __destruct → dtor), PHP 8.0 constructor property promotion (promoted parameters surface as fields), single + multi-variable properties, PHP 8.3 typed class constants, PHP 8.0 #[Attr] attributes, top-level use / use function / use const / grouped use Foo\{A, B}, plus top-level include / include_once / require / require_once for pre-Composer / WordPress / Drupal-7 codebases. Tested on real WordPress core (no parse errors on files up to 291 KB).
  • Ruby — modules (with module Foo::Bar qualified form + old-style nested-module collapse to A::B::C), classes with < Super superclass + include / extend / prepend mixins surfaced on the type header, methods, def self.foo singleton methods (marked [static]), class << self block (unwraps flat with [static] markers), operators (+, <=>, [], []=, -@, +@, ==, !, …), attr_accessor / attr_reader / attr_writer (one field per symbol with marker), alias / alias_method. Visibility tracked as a state machine — bare private / public / protected flips subsequent decls; private :foo, :bar / private_class_method :baz retroactively mark named methods. Rails associations recognised by default (has_many / has_one / belongs_to / has_and_belongs_to_many surface as fields with marker). Convention-named Rakefile / Gemfile resolve via basename match. require / require_relative / load / autoload collected as imports; lazy loads inside method bodies counted into [+ N conditional includes].
  • CSS — rules (.foo, .bar { ... }), at-rules (@media, @supports, @layer, @keyframes, @container, @font-face), CSS native nesting with &. Each rule carries the bare simple-selector tokens it styles, so find_symbols(".btn-primary") returns every cascade-relevant definition (top-level, inside @media, themed, descendant in .modal) with the wrapping at-rule visible in the breadcrumb. Pseudo-classes and attribute filters stripped for matching — .btn-primary:hover and .btn-primary[disabled] both match .btn-primary. :is(.a, .b) / :where(.a, .b) recurse (additive); :not(...) / :has(...) don't. @import collected as imports.
  • SCSS — full CSS coverage plus @mixin name($args) (callable, gets () in digest), @function name($args), top-level $variable: value (with !default), %placeholder extend-only selectors. Sass privacy convention applied — names with leading _ / - marked private and hidden under --include-private=False, mirroring what Sass itself doesn't export via @use. Nested rules with & resolve against each parent simple selector — .card { &__header { } } is findable as .card__header; multi-selector parents propagate (a, .link { &:hover { } } is findable as both a and .link). @use, @forward, and legacy @import collected as imports.
  • SQL — DDL-focused: CREATE TABLE (with each column emitted as a KIND_FIELD child carrying the source-true column line — id INTEGER PRIMARY KEY, email TEXT NOT NULL UNIQUE); CREATE VIEW and CREATE MATERIALIZED VIEW distinguished via native_kind; CREATE TYPE foo AS (...) (composite → KIND_RECORD with field children); CREATE TYPE foo AS ENUM (...) (enum with member children); CREATE FUNCTION with full parameter list + return type; CREATE TRIGGER (native_kind="trigger"); CREATE INDEX and CREATE SEQUENCE (KIND_FIELD + native_kind); CREATE SCHEMA (KIND_NAMESPACE). -- line and /* … */ block comments preceding a statement attach as docs. CREATE EXTENSION collected as imports. PL/pgSQL function bodies parse as opaque $$ … $$ strings — the header extracts cleanly, and parse errors inside bodies are excluded from error_count. Regex fallback recovers four constructs the upstream grammar can't parse: CREATE [OR REPLACE] PROCEDURE (KIND_FUNCTION + native_kind="procedure"), CREATE DOMAIN (KIND_FIELD + native_kind="domain"), LOAD 'lib' and IMPORT FOREIGN SCHEMA … (both into imports). The fallback is line-anchored and gated by AST-derived skip ranges (comment / marginalia / literal / block subtrees) — red herrings like CREATE PROCEDURE text inside a comment, string literal, or an outer function body don't produce spurious declarations. Dialect coverage: PostgreSQL is the primary target (every modern construct works); MySQL and SQLite schemas extract tables / columns / indexes / views cleanly with some error_count > 0 noise on dialect-specific syntax (ENGINE=InnoDB, AUTOINCREMENT, inline KEY constraints); MSSQL / T-SQL, Oracle PL/SQL, and BigQuery have partial coverage — bracketed identifiers, GO separators, VARCHAR2, and STRUCT<> / backtick names degrade. Powered by tree-sitter-sql (DerekStride).
  • Markdown — heading TOC + fenced code blocks.
  • YAML — key hierarchy with line ranges, [i] sequence paths, multi-document separators, format-detect for Kubernetes / OpenAPI / GitHub Actions in the header.

Adding another language is a single new adapter file. See src/ast_outline/adapters/.

YAML caveats

Real-world YAML files routinely surface a # WARNING: N parse errors header — tree-sitter-yaml's strict parser flags fairly innocuous inconsistencies (like a sequence item nested inside an unexpected mapping context) and the error region can spread well beyond the actual broken line. The adapter's recovery walk salvages most useful structure around such regions; treat the outline as best-effort and fall back to Read for the affected region when the answer is load-bearing.

show for YAML matches keys, not value text. show file.yaml "some phrase" will not find a phrase that lives inside a string value — for free-text searches inside values, use grep/rg. ast-outline is structural; it complements text search rather than replacing it.


Install

uv tool install ast-outline

Installs the ast-outline CLI globally into ~/.local/bin (macOS / Linux) or %USERPROFILE%\.local\bin (Windows). Don't have uv?

curl -LsSf https://astral.sh/uv/install.sh | sh                                          # macOS / Linux
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"       # Windows

Update / uninstall: uv tool upgrade ast-outline / uv tool uninstall ast-outline.

Other install options (pipx, pip, from source, bundled script)
pipx install ast-outline
pip  install ast-outline                                          # into an active venv

# Latest main instead of the PyPI release:
uv tool install git+https://github.com/ast-outline/ast-outline.git

# Bundled one-shot installer (also installs uv if missing):
curl -LsSf https://raw.githubusercontent.com/ast-outline/ast-outline/main/scripts/install.sh | bash    # macOS / Linux
iwr -useb https://raw.githubusercontent.com/ast-outline/ast-outline/main/scripts/install.ps1 | iex     # Windows

Quick start

# Structural outline of one file
ast-outline path/to/Player.cs
ast-outline path/to/user_service.py

# Outline a whole directory (recurses supported extensions)
ast-outline src/

# Print the source of one specific method
ast-outline show Player.cs TakeDamage

# Several methods at once
ast-outline show Player.cs TakeDamage Heal Die

# Compact public-API map of a whole module
ast-outline digest src/Services

# Built-in guide
ast-outline help
ast-outline help show

Using with LLM coding agents

This is the main use case. The agent learns about ast-outline from a short snippet in your repo's AGENTS.md / CLAUDE.md / GEMINI.md (or whatever persistent-context file your tool reads). Two paths to get the snippet there:

Automatic — ast-outline setup-prompt (recommended)

Inside Claude Code / Codex CLI / Gemini CLI / Cursor, ask the agent:

Run ast-outline setup-prompt and follow its instructions.

The agent reads a checklist from stdout and walks you through:

  1. Verify ast-outline is installed (offers to install via uv tool install / pipx / pip if missing, with your consent).
  2. Check PyPI for a newer release; if available, mention the upgrade command — never auto-upgrades.
  3. Pick the right target file for your tooling — ./AGENTS.md (cross-tool default, covers Codex CLI / Claude Code via @AGENTS.md / Gemini CLI with settings.json / Cursor), or the native single-vendor file (./CLAUDE.md, ./GEMINI.md) if you only use one CLI.
  4. Append the canonical snippet wrapped in <!-- ast-outline:start --> ... <!-- ast-outline:end --> markers so re-runs upgrade the block without duplicating. Diff-aware on re-run — never overwrites your manual edits silently.
  5. Optionally patch existing exploration-oriented subagents in .claude/agents/ / .codex/agents/ / .gemini/agents/, with per-agent permission.

Safe to re-run after every pip install -U ast-outline to refresh the bundled snippet.

Manual — ast-outline prompt

The same snippet, no agent involvement — pipe it where you want:

ast-outline prompt >> AGENTS.md
ast-outline prompt >> .claude/CLAUDE.md
ast-outline prompt | pbcopy   # macOS clipboard

Use this when you don't have a coding-agent CLI handy, or when you want full control over file edits.

Prompt snippet (copy-paste)

## Code exploration — prefer `ast-outline` over full reads

For `.cs`, `.cpp`, `.cc`, `.cxx`, `.h`, `.hpp`, `.hh`, `.py`, `.pyi`,
`.ts`, `.tsx`, `.js`, `.jsx`, `.java`, `.kt`, `.kts`, `.scala`, `.sc`,
`.go`, `.rs`, `.php`, `.phtml`, `.rb`, `.rake`, `.gemspec`, `.css`,
`.scss`, `.sql`, `.md`, and `.yaml`/`.yml` files, read structure with
`ast-outline` before opening full contents.

Pick the smallest of these that answers your question — they're a
broad-to-narrow menu, not a sequence; skip straight to `show` when
you already know the symbol:

1. **Unfamiliar directory**`ast-outline digest <paths…>`: one-page map
   of every file's types and public methods. Each file is tagged with a
   size label — `[tiny]` / `[medium]` / `[large]` / `[huge]` — plus
   `[broken]` when parse errors may have left the outline partial.
   `[huge]` files (≥100k tokens) collapse to header-only in the digest;
   call `ast-outline outline <path>` on them when you need full structure.

2. **File-level shape**`ast-outline <paths…>`: signatures with line
   ranges, no bodies (2–10× smaller than a full read on non-trivial
   files). A `# WARNING: N parse errors` line in the header means the
   outline is partial — read the source for the affected region.

3. **One method, type, markdown heading, or yaml key**`ast-outline show <file> <Symbol>`. Suffix matching: `TakeDamage`
   for one method; `User` for an entire type — class, struct, interface,
   trait, enum (whole body, useful when a file holds several types);
   `Player.TakeDamage` when ambiguous. Multiple at once:
   `ast-outline show Player.cs TakeDamage Heal Die`.
   For markdown, the symbol is heading text and matching is
   case-insensitive substring — `"installation"` finds
   `"2.1 Installation (macOS / Linux)"`. For yaml, the symbol is a
   dotted key path (`spec.containers[0].image`) — `show` matches keys,
   not values, so for free-text search inside values use `grep`.
   For css/scss, the symbol is a selector token (`.btn-primary`,
   `$var`) — pseudos and attribute filters are stripped, so
   `.btn-primary` finds the rule even when it carries `:hover` or
   nests in `.modal`.
   For sql, the symbol is a table or column name (`users`,
   `users.email`) — `show users` returns the table definition,
   `show users.email` returns one column line.
   Add `--signature` to any of the above to return header only
   (docs + attrs + signature, no body) — useful after `digest`, when
   you have the name and want the contract, not the implementation.

`outline` and `digest` accept multiple paths in one call (files and
directories, mixed languages OK) — batch instead of looping. Type
headers in both renderers carry inheritance as `: Base, Trait`, so the
shape of class hierarchies is visible without a separate query.

When you need to know **what a file pulls in** or **where a referenced
type / function comes from**, add `--imports` to `outline` or `digest`.
The file header gets an `imports:` line listing every
`import` / `use` / `using` statement verbatim in the language's native
syntax — `from .core import X`, `use foo::Bar`,
`import { X } from './foo'`, `use App\Foo`, `require_once 'config.php'`,
`require "json"`.
Read the imports, then call `outline` / `show` on the source file
instead of grepping for the definition. Skip the flag for routine
structure reads — it adds one line per file.

A trailing `[+ N conditional includes]` on the imports line means
N more dependencies live inside `if` / `try` / loop / function bodies
— read the file directly when you need the full dependency picture.

Fall back to a full read only when you need context beyond the body
`show` returned. `ast-outline help` for flags.

Heads up: subagents

CLAUDE.md / AGENTS.md reach only the main agent. Claude Code's isolated subagents (built-in Explore, anything in .claude/agents/*.md) see only their own system prompt. To make Explore use ast-outline, shadow it with .claude/agents/Explore.md (or ~/.claude/agents/Explore.md) and put the ast-outline prompt output in the body.

Cursor, Aider, and direct API clients have no isolated subagents — CLAUDE.md / system prompt is enough there.

Why this helps

  • Fresh subagents with shallow context (like Claude Code's Explore agent) can scan a whole module in one call instead of 10–20 Read/grep rounds.
  • "Where is X defined?" becomes one show call once the agent has spotted the symbol in digest or outline.
  • Line ranges (L42-58) turn the outline into a precise navigator — the agent reads only the lines it needs.
  • AST-based type headers carry real : Base, Trait inheritance with no false positives from string literals, comments, or unrelated name mentions — unlike grep.

Works with

  • Claude Code (+ custom subagents like Explore, codebase-scout)
  • Cursor agent mode
  • Aider
  • Copilot Chat / Workspace
  • Any custom agent on the Claude / OpenAI / Gemini APIs
  • Humans (the format is readable; show is a nice alternative to grep -A 20)

Commands

outline — default

Print the file's classes, methods, properties, fields with line ranges.

ast-outline path/to/File.cs
ast-outline path/to/module.py --no-private --no-fields

Flags:

  • --no-private — hide private members (Python: names starting with _)
  • --no-fields — hide field declarations
  • --no-docs — hide /// XML-doc / docstrings
  • --no-attrs — hide [Attributes] / @decorators
  • --no-lines — hide line-number suffixes
  • --imports — show file's imports (see below)
  • --glob PATTERN — restrict directory mode to a pattern

--imports — see what each file depends on

outline and digest both accept --imports. When set, each file's header is followed by an imports: line listing its import / use / using statements verbatim, in the language's own syntax — no synthetic format for the agent to learn:

$ ast-outline service.py --imports
# src/services/user_service.py [medium] (140 lines, ~1,200 tokens, 1 types, 5 methods)
# imports: from .core import UserBase; from .utils import parse_id; from typing import Optional
class UserService(UserBase):  L8-138
    ...

Multi-line and grouped forms are flattened: Go's import (...) block becomes individual import "fmt" lines; multi-line TypeScript import { X, Y } from './long' collapses to one line. Imports inside function or class bodies are omitted — only file-level dependencies are shown.

Useful when the agent needs to know where a referenced type lives, or what a file pulls in, before deciding which file to read next.

show — extract source of a symbol

ast-outline show File.cs TakeDamage
ast-outline show File.cs PlayerController.TakeDamage   # disambiguate overloads
ast-outline show service.py UserService.get
ast-outline show File.cs TakeDamage Heal Die           # several at once

For code, matching is suffix-based: Foo.Bar matches any *.Foo.Bar. If multiple declarations match, all are printed with a summary.

For markdown, matching is case-insensitive substring per dotted part. LLM agents rarely remember the exact decoration of a heading (number prefixes like 1., trailing (Feb 2026), (Confidence: 70%)), so a fuzzy core works:

ast-outline show forecast.md "current analysis"
# → matches `## 1. CURRENT ANALYSIS (Feb 2026)`

ast-outline show forecast.md "scenario.transit"
# → matches `### SCENARIO A: "MANAGED TRANSIT"` under any parent
#   heading containing "scenario"

If the substring matches several headings, all are printed and the disambiguation summary lands on stderr — tighten the query to narrow.

digest — one-page module map

ast-outline digest src/

Sample output:

# legend: name()=callable, name [kind]=non-callable, marker name()=method modifier (async/static/override/…), [N overloads]=N callables share name, [deprecated]=obsolete, L<a>-<b>=line range, : Base, …=inheritance
src/services/
  __init__.py [tiny] (8 lines, ~74 tokens, 1 fields)
  user_service.py [medium] (140 lines, ~1,200 tokens, 1 types, 5 methods)
    @Service abstract class UserService [deprecated] : IUserService  L8-138
      async get(), async search(), abstract create(), delete(), update_v1() [deprecated]

  auth_service.py [medium] (95 lines, ~840 tokens, 1 types, 4 methods)
    [ApiController] sealed class AuthService  L10-95
      async login(), logout(), refresh(), override verify_token()

  legacy_repo.py [large] [broken] (5234 lines, ~52,000 tokens, ...)

The first line is a self-describing legend so an LLM can read the output cold without ast-outline prompt loaded. The legend is dynamic — only entries whose token shape actually appears in the body are listed, so a YAML- or markdown-only batch (no callables, no kinds, no inheritance) emits no legend at all, and code batches keep a legend pruned to the subset of tokens they use. Tokens follow the universal programming-doc convention — name() for a callable, name [kind] for a property/field/event/etc., method markers (async, static, abstract, override, virtual, plus language-native forms: Kotlin open / suspend, Python @staticmethod / @classmethod / @abstractmethod, Java @Override) prefix the name source-true so each language reads in its own idiom. [N overloads] flags when several callables share a name; [deprecated] whenever a type or member carries @Deprecated / [Obsolete] / #[deprecated]. Type headers also carry inline decorators / attributes (@dataclass, [ApiController], #[derive(Debug)]) and semantic modifiers (abstract, sealed, static, final, open, partial) so runtime contracts and instantiation rules read off at a glance. Members are joined with , ; types that have a body get a trailing blank line as a paragraph break, empty types stack tightly so digest stays compact. Source-language keywords (Rust trait, Scala object, Kotlin data class) are preserved in the type header instead of the canonical kind.

Each filename gets a descriptive size label — [tiny] (under ~500 tokens), [medium] (500–5000), [large] (5000–100k), [huge] (100k+). A [broken] marker appears next to the size label when the parse hit syntax errors and the outline may be partial. [tiny] / [medium] / [large] describe the file without changing what gets rendered; [huge] is also a behavioral marker — in digest only, the file collapses to its header line so a directory full of generated SDKs / vendored mega-files doesn't bloat the output. outline and show ignore the [huge] collapse — when an agent explicitly opens one file, it gets the full structure regardless of size. The labels describe the file; the agent picks Read / outline / show based on its task — the tool informs, the agent decides.

The label conventions live in the canonical agent prompt (ast-outline prompt) so they're paid for once per session, not on every digest call. Size class is calibrated against an approximate token count (len(chars)/4, ±15-20% vs real BPE tokenizers — fine for the heuristic). The same ~N tokens count appears in every outline header too.

setup-prompt — let an agent wire ast-outline in (automatic, recommended)

ast-outline setup-prompt

Prints an install-time checklist for one-shot consumption by a coding agent. Tell your agent:

Run ast-outline setup-prompt and follow its instructions.

The agent walks you through: verify the CLI (offer to install via uv tool install / pipx / pip if missing, with consent), check PyPI for a newer release (offer to upgrade, never auto-upgrades), pick the right target file for your tooling (./AGENTS.md cross-tool default; ./CLAUDE.md / ./GEMINI.md for single-vendor users; or the matching ~/.<tool>/... global file), append the snippet inside <!-- ast-outline:start --> ... <!-- ast-outline:end --> markers (diff-aware on re-run — never overwrites your manual edits silently), and optionally patch existing exploration subagents in .claude/agents/ / .codex/agents/ / .gemini/agents/ with per-agent permission.

Cross-vendor universal — same checklist works in Claude Code, Codex CLI, Gemini CLI, and Cursor; the agent adapts to your shell (which / where.exe / Get-Command) and to the conversation's human language. In headless mode (codex exec, claude -p, Gemini non-interactive, CI) it restricts itself to read-only checks plus the AGENTS.md write at project-local scope, skipping anything that would need consent and listing the skipped items in the final report.

The CLI itself does no file I/O — it just emits the checklist text. The active coding agent performs every edit using its native tools, so each change is reviewable before it lands.

prompt — print the agent snippet (manual install path)

ast-outline prompt
ast-outline prompt >> AGENTS.md

Prints the canonical copy-paste snippet that steers LLM coding agents to prefer ast-outline over full file reads. English, universal across Claude Opus 4.7 / Sonnet 4.6 / Haiku 4.5, OpenAI GPT-5.x, and Gemini 3.x. Running it always emits the current recommended version.

This is the manual path — pipe it where you want, no agent involvement. For the automated equivalent (recommended), see setup-prompt above.

Distinct from setup-prompt:

  • prompt is the use-time snippet — lives in AGENTS.md, steers every code-reading turn. Manual, one-shot.
  • setup-prompt is the install-time checklist — gets prompt's output into the right file, with version checks, diff-aware re-run, and optional subagent patches. Automatic, one-shot.

Output format

The format is designed to be LLM-friendly: Python-style indentation, line-number suffixes in L<start>-<end> form, doc-comments preserved. The header summarises scale and flags partial parses.

C#

# Player.cs (142 lines, 3 types, 12 methods, 5 fields)
namespace Game.Player
    [RequireComponent(typeof(Rigidbody2D))] public class PlayerController : MonoBehaviour, IDamageable  L10-120
        [SerializeField] private float speed = 5f  L12
        public int CurrentHealth { get; private set; }  L15
        /// <summary>Apply damage.</summary>
        public void TakeDamage(int amount)  L30-48
        private void Die()  L50-55

Python

# user_service.py (70 lines, 2 types, 5 methods, 3 fields)
@dataclass class User  L16-29
    def display_name(self) -> str  L26-29
        """Human-friendly label."""

class UserService  L31-58
    def __init__(self, storage: Storage) -> None  L34-35
    def get(self, user_id: int) -> User | None  L37-42
        """Look up a user by id."""
    def save(self, user: User) -> None  L44-46

show with ancestor context

ast-outline show <file> <Symbol> prints a # in: ... breadcrumb between the header and the body so you know what the extracted code is nested inside, without a second outline call:

# Player.cs:30-48  Game.Player.PlayerController.TakeDamage  (method)
# in: namespace Game.Player → public class PlayerController : MonoBehaviour, IDamageable
/// <summary>Apply damage.</summary>
public void TakeDamage(int amount) { ... }

Top-level symbols (no enclosing namespace/type) have no breadcrumb.

Partial parses

When tree-sitter recovers from syntax errors, the outline is kept but a second header line flags the gap:

# broken.java (16 lines, 1 types, 3 methods)
# WARNING: 3 parse errors — output may be incomplete

Agents should treat these files as partial and read the source directly for the affected region.

Differences are language-idiomatic:

  • C# /// XML-doc appears above the signature.
  • Python """docstrings""" appear below the signature with one extra indent (matching Python semantics).
  • C# attributes ([Attr]) and Python decorators (@foo) are inlined with the declaration.
  • C# property accessors { get; private set; } are preserved.

How it works (briefly)

  • Parses source with tree-sitter — real AST, not regex.
  • Language-specific adapters convert the AST to a uniform Declaration intermediate representation.
  • Language-agnostic renderers produce outline / digest / search output.
  • Purely local, no network, no indexing, no cache — just reads and parses the files you ask about.

No vector database, no embedding, no RAG. This is deliberate — the philosophy matches how agentic coding tools like Claude Code actually work.


Development

git clone https://github.com/ast-outline/ast-outline.git
cd ast-outline

# Create a venv and install in editable mode
uv venv
uv pip install -e .

# Run against the included samples
.venv/bin/ast-outline tests/sample.cs
.venv/bin/ast-outline tests/sample.py
.venv/bin/ast-outline digest tests/

Running the tests

Tests are an optional dev dependency — end users don't pull them in. Install them once and run via pytest:

# Install pytest into the same venv as the editable install
uv pip install -e ".[dev]"

# Run the full suite (takes ~0.1s)
.venv/bin/pytest

# Just one file, verbose
.venv/bin/pytest tests/unit/test_csharp_adapter.py -v

# Match by test name
.venv/bin/pytest -k file_scoped_namespace -v

The suite covers every adapter (C#, C++, Python, TypeScript/JS, Java, Kotlin, Scala, Go, Rust, PHP, Ruby, CSS, SCSS, SQL, Markdown, YAML), the language-agnostic renderers, symbol search, and the CLI end-to-end. Fixtures live under tests/fixtures/; tests never reach outside that directory. New behaviour should come with a test; new languages should ship with a dedicated fixture directory and a tests/unit/test_<lang>_adapter.py file.

Adding a new language

Create src/ast_outline/adapters/<lang>.py implementing the LanguageAdapter protocol (see adapters/base.py). Then register it in adapters/__init__.py. The core renderers and CLI pick it up automatically — no further wiring needed.


Roadmap

  • TypeScript / JavaScript adapter (.ts, .tsx, .js, .jsx, .mjs, .cjs)
  • Java adapter (.java) — classes, interfaces, @interface, enums, records, sealed hierarchies, generics, throws, Javadoc
  • Kotlin adapter (.kt, .kts) — classes, interfaces, fun interface, object / companion object, data / sealed / enum / annotation classes, extension functions, suspend / inline / const / lateinit, generics with where constraints, typealias, KDoc
  • Scala adapter (.scala, .sc) — Scala 2 + Scala 3: classes, traits, object / case object, case class, sealed hierarchies, Scala 3 enum / given / using / extension, indentation-based bodies, higher-kinded types, context bounds, opaque type, type aliases, Scaladoc
  • Go adapter (.go) — packages, structs (with method-grouping under receiver), interfaces, struct/interface embedding as inheritance, generics (Go 1.18+), type aliases + defined types, iota enum-blocks, doc-comment chains
  • Rust adapter (.rs) — modules (recursive), structs (regular / tuple / unit), unions, enums with all variant shapes, traits + supertraits as bases, impl block regrouping under the target type (inherent + impl Trait for Foo adds Trait to bases), extern "C" blocks, macro_rules!, type aliases, generics + lifetimes + where clauses, full visibility classifier (pub / pub(crate) / pub(super) / pub(in path)), outer doc comments + #[...] attributes
  • PHP adapter (.php, .phtml, .phps, .php8) — modern PHP 8.x + 7.4 LTS: namespaces (file-scoped + bracketed), classes (abstract / final / readonly and combinations), interfaces, traits, PHP 8.1 enums (pure + backed), methods, magic ctor / dtor, PHP 8.0 ctor property promotion, multi-variable properties, PHP 8.3 typed class constants, PHP 8.0 #[Attr] attributes, top-level use (incl. grouped) + include / require, robust on real WordPress core
  • Ruby adapter (.rb, .rake, .gemspec, .ru, plus Rakefile / Gemfile by basename) — modules with qualified-form (module Foo::Bar) + nested-module collapse, classes with superclass + include / extend / prepend mixins, methods, def self.foo singleton methods + class << self blocks (both render [static]), full operator coverage (+ / <=> / [] / []= / -@ / +@ / == / ! / …), attr_accessor / attr_reader / attr_writer (one field per symbol with marker), alias / alias_method, visibility state machine (private / protected / public flips + targeted private :foo), Rails associations recognised by default (has_many / has_one / belongs_to / has_and_belongs_to_many), require / require_relative / load / autoload as imports
  • Markdown adapter (.md, .markdown, .mdx, .mdown) — heading TOC + code blocks
  • YAML adapter (.yaml, .yml) — key hierarchy, [i] sequence paths, multi-document support, format-detect for Kubernetes / OpenAPI / GitHub Actions
  • --format json output mode for programmatic consumers
  • Optional multiprocessing for very large codebases (>500 files)

Contributions welcome.


Project history

  • 2026-04-22 — Repository created on GitHub as dim-s/code-outline. First public commit, v0.2.0b0.
  • 2026-04-22 — Russian and Chinese READMEs added; TypeScript / JavaScript adapter shipped same day.
  • 2026-04-23 — Kotlin adapter; prompt subcommand.
  • 2026-04-24 — Scala adapter. Renamed code-outlineast-outline (v0.3.0). GitHub repo renamed to dim-s/ast-outline.
  • 2026-04-25 — Go adapter.
  • 2026-04-28# note: … LLM-friendly error contract on stdout with rc=0; substring matching for markdown headings.
  • 2026-04-30 — YAML adapter; per-file size labels + token estimate in digest headers; Rust adapter.
  • 2026-05-01 — v0.4.0: digest method markers ([async] / [unsafe] / [const] / [suspend] / [static] / [abstract] / [override] / [classmethod] / [property]); type modifiers, attrs, and [deprecated] tag. v0.4.1.
  • 2026-05-02 — Published to PyPI as ast-outline. v0.4.2 / v0.4.3 / v0.5.0 (code-outline CLI alias dropped) / v0.5.1 (implements command dropped — outline/digest already render : Base) / v0.5.2 (--imports flag) / v0.5.3 (--version flag).
  • 2026-05-03v0.6.0: relicense from MIT to Apache License 2.0, with documentation separately licensed under CC BY 4.0. The previous MIT text is retained in LICENSE-MIT for compatibility with downstream forks of the 0.5.x tree.
  • 2026-05-03 — Repository transferred from dim-s/ast-outline to the ast-outline GitHub Organization. Old dim-s/ast-outline URLs continue to redirect. Copyright remains with Dmitrii Zaitsev (dim-s); the GitHub org is hosting infrastructure, not a new copyright holder.
  • 2026-05-03 — v0.6.2: PHP adapter (.php, .phtml, .phps, .php8) targeting modern PHP 8.x and the still-deployed 7.4 LTS line. Verified on real WordPress core (no parse errors on files up to 291 KB). Introduces ParseResult.conditional_imports_count — a common-IR counter for imports skipped because they live outside the file's static top level (e.g. WordPress wp-load.php whose every require lives in an if/else chain); renderers append [+ N conditional includes] to the imports line so agents see the file has dynamic dependencies. v0.6.3: counter extended to Python (lazy import inside fn / class), Rust (use inside fn / closures), and Scala (import inside method bodies).

For the full record, see git log and the GitHub release page.


Licensing & attribution

Copyright © 2026 Dmitrii Zaitsev (GitHub: dim-s) and ast-outline contributors.

This project uses two separate licenses for two different kinds of work:

What License File
Source code (src/, tests, build config) — v0.6.0 and later Apache 2.0 LICENSE
Source code — v0.5.3 and earlier MIT LICENSE-MIT
Documentation & prose (this README, translated READMEs, CLI help text, prompt files, digest legend, design docs) CC BY 4.0 LICENSE-DOCS

All three are permissive — you can fork, use commercially, port to other languages, ship in a product. The split exists so that attribution requirements are explicit for each kind of content. Forks of the 0.5.x tree may continue under MIT; new development happens under Apache 2.0.

If you reuse the code (v0.6.0+)

Keep the LICENSE (Apache 2.0) and NOTICE files in your distribution. Apache 2.0 §4 requires you to:

  • include the LICENSE file
  • include the NOTICE file in any "NOTICE" text file distributed with your work
  • carry forward attribution notices (do not strip the copyright header)
  • in modified files, add a notice stating that you changed the files

If you reuse the prose

If your project copies non-trivial portions of this documentation — paragraphs, the workflow snippets, the digest legend, the marker vocabulary, the # note: CLI convention's wording — CC BY 4.0 requires visible attribution. Use this format (verbatim or equivalent):

Based on ast-outline by Dmitrii Zaitsev (dim-s), licensed under CC BY 4.0.

Place it where users will see it (typically the README of your derivative work).

Trademark

ast-outline™ is an unregistered trademark of Dmitrii Zaitsev (dim-s), used to identify the original project at https://github.com/ast-outline/ast-outline. Apache License 2.0 §6 explicitly excludes any grant of trademark rights. Forks, language ports, and rebranded distributions must use a different name to avoid user confusion. "Inspired by ast-outline" or "based on ast-outline" wording in your README is fine and encouraged; using ast-outline itself as your project / package / binary name is not.

If you maintain a published package called ast-outline on any registry (crates.io, npm, PyPI, Homebrew, etc.) that is not the project at the URL above, please rename it.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ast_outline-0.7.7.tar.gz (386.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ast_outline-0.7.7-py3-none-any.whl (205.4 kB view details)

Uploaded Python 3

File details

Details for the file ast_outline-0.7.7.tar.gz.

File metadata

  • Download URL: ast_outline-0.7.7.tar.gz
  • Upload date:
  • Size: 386.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.11

File hashes

Hashes for ast_outline-0.7.7.tar.gz
Algorithm Hash digest
SHA256 c5ca93a4e7c1b255aabe2c7b244b6d6219523e10a794e63fdf682f03b52ee476
MD5 c1d43e6f11a67098e612adafaaf9a58c
BLAKE2b-256 8d5ca5ca2a7281c02d30bf3d00f71b1714eb755ee6258de00ef9a578d8130e71

See more details on using hashes here.

File details

Details for the file ast_outline-0.7.7-py3-none-any.whl.

File metadata

  • Download URL: ast_outline-0.7.7-py3-none-any.whl
  • Upload date:
  • Size: 205.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.11

File hashes

Hashes for ast_outline-0.7.7-py3-none-any.whl
Algorithm Hash digest
SHA256 7466541bee946310f8e31bd641f151b958e050a15c15d3b6e86f8b61572dcdf7
MD5 33e66a4136dab69381e070005c65959a
BLAKE2b-256 5e7ac244fa6dd7b4bcea6745b3369414ed2bf53c63cf3fe4f6f37d86b7992ac0

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page