contasty

Make your contexttasty — reduce a code base to a syrup of signatures.

Leave out: bodies, comments, imports, tests.

checkout.rscontasty
1pub fn checkout(cart: &Cart, user: &User) -> Result<Receipt> {}
2 let total = cart.total();
3 charge(user, total)?;
4 Ok(Receipt::new(total))
5}

what it does

Bodies out. Signatures in.

contasty walks a directory (respecting .gitignore), parses each supported source file with ast-grep, and elides function bodies, constant values, and long strings — keeping declarations, signatures, and types intact. The result is one Markdown (or JSON) bundle, ready to paste into an LLM context window.

your source

use crate::cart::Cart;
/// Charge the user, emit a receipt.
pub fn checkout(c: &Cart) -> Receipt {
    let t = c.total();
    charge(t)?;
    Receipt::new(t)
}

contasty output

pub fn checkout(c: &Cart) -> Receipt {}



↓ comments & imports gone, body reduced, signature kept ↓

reduce to taste

Four ingredients you can leave out

--strip picks what to remove, per path, find-style. Comments, imports, and bodies go by default; test signatures stay. Try to toggle them off/on.

service.rs
use crate::db::Pool;
// open a pooled connection
pub fn connect(p: &Pool) -> Conn {
    let c = p.acquire();
    c.ping()?;
    c
}

#[cfg(test)]
mod tests {
    #[test]
    fn pings() {}
}

comments stripped by default

Doc and line comments dropped. Re-add with --strip=!comments.

imports stripped by default

Use / import / require lines removed — the indentation goes too, no blank stub.

body stripped by default

Function, method, and closure bodies elided to an empty {} — still reparseable.

tests kept by default

Test signatures stay so the agent sees intent. Drop them with --strip=tests.

27 flavors

Every grammar ast-grep bundles, except Markdown

Each language is data, not code: a YAML ast-grep rule file matched against the tree. 18 do full body elision; the rest strip comments, imports, long strings, and values.

Rust
PHP
TypeScript
TSX
JavaScript
Python
Go
Java
C#
Ruby
C++
C
Kotlin
Swift
Scala
Dart
Solidity
HTML
Bash
Lua
Elixir
Haskell
Nix
JSON
YAML
CSS
HCL
body elision (18) comments / imports / strings (9)

Need another? Add a language with a compiled .so tree-sitter grammar plus a rule file — no rebuild.

context facts

See the reduction

contasty --stats reports original-vs-compacted line counts and a rough token figure instead of the bundle. Fewer tokens, same shape — the agent still sees every signature and type.

contasty --stats src/

get a taste

Install

# from crates.io
cargo install contasty

# with nix — run without installing, or add it to your profile
nix run github:mlavrinenko/contasty -- src/
nix profile install github:mlavrinenko/contasty

# strip a tree, write Markdown context
contasty src/ > context.md

# default-strip src/, keep tests/ whole, JSON output
contasty src/ --strip=none tests/ --format=json

Or grab a pre-built binary from the latest release. Single static binary — no Node, no runtime.

taste test

contasty vs repomix --compress

Same idea — walk, elide, print one document. Different strengths.

contastyrepomix --compress
Languages with body elision18 of 27 built-ins16
Add a language, no rebuildyesno
Extend / override strip rulesyesno
Gate comments / imports / tests / bodyyespartial
Optional reformat of resultyesno
Runtimesingle static binaryNode.js
Token countingno (by design)yes, multi-tokenizer
Remote repos / MCP servernoyes

Full table, plus ctx / aider repo-map comparisons, in the README.

the pantry

More to taste

Query files

Saved, reusable *.cty.yaml selectors in gitignore syntax — import & union scopes.

docs/queries.md →

Custom rules

Extend or override any language's strip rules from contasty.toml.

docs/custom-rules.md →

Dynamic grammars

Bring a compiled .so tree-sitter grammar for a language ast-grep doesn't bundle.

docs/languages.md →

Reformatting

Optional cosmetic tidy of the stripped output via embedded Topiary or a shell-out.

docs/reformatting.md →

Markdown or JSON

--format=json emits { base, files: [{ path, lang, content }] }.

usage →

.gitignore modes

--ignore=enable|disable|reverse, per path — include ignored files or only them.

usage →