Strips executable lines from your code to prepare tasty context for your agent.
Walks a directory (respecting .gitignore), parses each supported source file
with ast-grep, and elides function bodies,
constant values, and long string literals — keeping declarations, signatures,
and types intact. The default output is a line-numbered per-file dump built
for agents, ready to paste into an LLM context window.
// before
pub fn checkout(cart: &Cart, user: &User) -> Result<Receipt> {
let total = cart.total();
charge(user, total)?;
Ok(Receipt::new(total))
}// after — comments/imports gone, body elided, signature kept verbatim
checkout.rs
1: pub fn checkout(cart: &Cart, user: &User) -> Result<Receipt> {
Each language is driven by a YAML rule file matched against the AST, not by
hardcoded per-language logic. Built-in support: Rust, PHP, TypeScript, TSX,
JavaScript, Python, Go, Java, C#, Ruby, C++, C, Kotlin, Swift, Scala, Bash, Lua,
Dart, Elixir, Haskell, Nix, Solidity, JSON, YAML, HTML, CSS, and HCL — every
grammar ast-grep bundles except Markdown (prose, nothing to strip). You can
add a language with a dynamic tree-sitter grammar, and extend or override any
language's rules from .contasty/config.toml — both without rebuilding
contasty.
cargo install contastyDownload a pre-built binary from the latest release.
contasty src/ > context.md # strip a directory
contasty src/lib.rs # strip a single file
contasty src/ tests/ > context.md # multiple paths (deduped union)
contasty src/lib.rs src/main.rs # several files at once
contasty 'src/**/*.rs' # glob (quote it; expanded internally)
contasty 'crates/*/src' # glob to dirs; each subtree is walked
contasty # default path is "."
contasty @api # saved query: .contasty/queries/api.cty.yaml
contasty --strip=comments,imports src/ # strip comments and imports
contasty --strip=tests src/ # also strip test functions
contasty --strip=all src/ # strip everything (alias: everything)
contasty --strip=all,!body src/ # strip all except bodies
contasty --strip=none src/ # strip nothing (keep all categories)
contasty --strip=none tests/ # per-path: keep everything in tests/
contasty src/ # default: line-numbered lines format
contasty --format=markdown src/ # reparseable Markdown document
contasty --format=json src/ # emit a JSON bundle instead
contasty --stats src/ # print compactization statistics
contasty --config path.toml src/ # use a specific config file (project layer)
contasty --ignore=disable src/ # include .gitignored files too
contasty --ignore=reverse src/ # only .gitignored files
contasty A --ignore=disable B --ignore=enable C # per-path mode switchingMultiple arguments resolve to a deduped, sorted union of source files. A folder is
walked .gitignore-aware; a glob is expanded internally (quote it) and a glob over
directories walks each subtree. A glob matching nothing warns; a missing path errors.
Output defaults to lines: a bare relative-path header per file, then each
surviving source line as N: <line> with its original line number, so an
elided body shows up as a gap you can read straight back from the file with
Read path offset=N. Pass --format=markdown for a reparseable Markdown
document with fenced code blocks, or --format=json for a pretty-printed JSON
bundle shaped as { "base": <dir>, "files": [{ "path", "lang", "content" }] }.
Four categories control what is stripped:
| Category | Default | Example |
|---|---|---|
comments |
stripped | --strip=comments |
imports |
stripped | --strip=imports |
tests |
kept | --strip=tests |
body |
stripped | --strip=body |
--strip is repeatable, interleaved with paths (find-style): each occurrence sets
the strip set for the paths that follow. Comma-separated; prefix a category with !
to remove it. all (alias everything) strips all four; none strips nothing.
--ignore=<mode> controls .gitignore filtering and is repeatable, interleaved
with paths (find-style). Each occurrence sets the mode for the paths that follow:
| Mode | Effect |
|---|---|
enable |
Respect .gitignore — only non-ignored (default) |
disable |
Include ignored files too (everything) |
reverse |
Only .gitignored files |
The default (before any --ignore) is enable. Query files can set their own mode
with the ignore: field (see docs/queries.md).
Category gating applies to every supported language — test and import rules in each built-in (or custom) rule file declare which category gates them, so the same flags work uniformly.
--stats prints original-vs-compacted line counts (code / comments / blanks) and
an approximate token figure (~tokens). That figure is a dependency-free estimate
(~bytes / 4), not a model tokenizer count — use it for relative comparison only.
Two architectures. contasty is a one-shot stripper: walk the tree, elide bodies
in place, print one document. The same-shape peer is repomix --compress. Each
is stronger at different things.
| contasty | repomix --compress | |
|---|---|---|
| Languages with body elision | 18 of 27 built-ins | 16, incl. Vue |
| Add a language without a rebuild | yes — dynamic grammar + rules | no |
| Extend / override strip rules | yes — .contasty/config.toml | no (fixed queries) |
| Gate comments (keep / drop) | yes — per-language toggle | yes — removeComments |
| Gate imports (keep / drop) | yes — --strip=imports | no (imports kept) |
| Gate tests (keep / drop) | yes — --strip=tests | no |
| Stripped-region output | line-anchored by default; reparseable {} skeleton via --format=markdown |
⋮---- placeholder markers |
| Runtime | single static binary | Node.js |
| Output formats | Lines (default), Markdown, JSON | XML, Markdown, JSON, plain |
| Token counting | no (by design) | yes, multi-tokenizer |
| Secret scanning | no | yes |
| Git integration (diffs, history) | no | yes |
| Remote repos (clone by URL) | no (local only) | yes |
| MCP server | no (CLI; agents shell out) | yes |
ctx also extracts signatures, but only for PHP. For interactive, query-on-demand context, see aider's repo map or jCodeMunch-MCP — a different approach: an index the agent queries live, not a static document.
Drop a .contasty/config.toml in your project root to tune compaction
thresholds, set default category inclusion, register dynamic grammars, and
extend or override per-language rules. All fields are optional. Layered under
a matching XDG global config ($XDG_CONFIG_HOME/contasty/config.toml, or
$HOME/.config/contasty/config.toml), project winning on a shared key — so a
grammar or rule file registered once in the global config works in every
project. See docs/languages.md and
docs/custom-rules.md.
Category stripping can be set cross-language under [strip] and refined
per language under [languages.<lang>]:
strip = ["comments", "imports", "body"] # cross-language defaults
[languages.rust]
strip = ["comments", "body"] # keep imports for Rust onlyCLI --strip overrides config for all languages. Config loads first; CLI wins.
Reusable selections can be saved as query files under
.contasty/queries/<name>.cty.yaml (or the XDG global queries dir) and
invoked by name: contasty @name. See docs/queries.md.
contasty ships a ready-to-install skill for coding agents (Claude Code and
compatible tools) at skills/contasty/. It teaches the agent
to reach for contasty when it needs a codebase's shape — an overview, a module's
public API, where something is declared — instead of reading every file in full,
opening real files only when it actually needs a body. Copy the
skills/contasty/ directory into your agent's skills folder; for Claude Code
that is ~/.claude/skills/.
contasty matches AST nodes with ast-grep rules, so a language is data, not code.
- Built-in: drop a rule file at
src/lang/rules/<lang>.yml(embedded at build time) and register the language inRegistry::new. No per-language matching logic in Rust. - Dynamic grammar: for a language ast-grep does not bundle, supply a compiled
native tree-sitter grammar (
.so) plus a rule file and register it under[languages.<lang>]with alibraryPathin.contasty/config.toml— no rebuild. - Extend / override: point an existing language at a user rule file with the
extend/overridekey of its[languages.<lang>]entry to append to (extend) or replace (override) its embedded rules.
The rule file format, dynamic .so grammars, JSON Schema, and editor
integration are documented in docs/languages.md; rule
extend/override in docs/custom-rules.md.
Prerequisites: Nix with flakes enabled.
direnv allow # or: nix develop
just outdatty-update # one-time: create outdatty.lock, then commit it
just check # fmt + clippy + tests + file-size + drift check
just build
just test
just cover # code coverage (70% minimum)
just fmt # format codeoutdatty gates files that must stay in
sync (see outdatty.yaml): just check fails when a source changes
but its dependents were not re-confirmed. Update them, then just outdatty-update.
See CONTRIBUTING.md for coding conventions.
MIT