Skip to content

Repository files navigation

treezip

compressed directory trees for llm prompts. zero dependencies, aggressively de-noised.

npx treezip ./my-project

the problem

you paste a tree dump into an llm so it understands your codebase. two things go wrong:

  1. it's verbose. every file gets its own line, its own indent guides, its own copy of the extension. a 400-file repo eats thousands of tokens on pure structure.
  2. it's full of junk. node_modules, DerivedData, __pycache__, .turbo, coverage reports, .DS_Store — none of it helps the model, all of it costs tokens. one xcode build folder can turn a 100-line tree into an 11,000-line one. (that's a real number — see below.)

treezip fixes both. it compresses the structure losslessly with brace notation, and it hardcodes a multi-ecosystem noise filter so build artifacts never reach the output in the first place.

real-world result on a swift app with a checked-in derived-data dir and agent worktrees lying around:

lines
plain tree 11,261
treezip 98

same source files visible. 99% less noise.

install

npm install -g treezip

or don't install at all:

npx treezip ./my-project

before / after

tree (43 lines):

my-project/
├── docs
│   ├── api-reference.md
│   ├── contributing.md
│   └── getting-started.md
├── package.json
├── public
│   └── images
│       ├── avatar.png
│       ├── background.png
│       ├── badge.png
│       ├── banner.png
│       ├── feature.png
│       ├── hero.png
│       ├── icon.png
│       ├── logo.png
│       ├── pattern.png
│       ├── placeholder.png
│       └── screenshot.png
├── README.md
├── src
│   ├── App.tsx
│   ├── components
│   │   ├── Button.tsx
│   │   ├── Card.tsx
│   │   ├── Header.tsx
│   │   ├── Modal.tsx
│   │   └── Sidebar.tsx
│   ├── hooks
│   │   ├── useApi.ts
│   │   ├── useAuth.ts
│   │   └── useTheme.ts
│   ├── index.ts
│   └── utils
│       ├── api.ts
│       ├── auth.ts
│       ├── format.ts
│       └── validate.ts
├── tests
│   ├── Button.test.tsx
│   ├── Card.test.tsx
│   ├── Header.test.tsx
│   ├── Modal.test.tsx
│   ├── setup.ts
│   └── Sidebar.test.tsx
└── tsconfig.json

treezip (12 lines):

my-project/
├── {.gitignore,README.md,package.json,tsconfig.json}
├── docs/{api-reference,contributing,getting-started}.md
├── public/images/{avatar,background,badge,banner,feature,...}.png
├── src/
│   ├── {App.tsx,index.ts}
│   ├── components/{Button,Card,Header,Modal,Sidebar}.tsx
│   ├── hooks/{useApi,useAuth,useTheme}.ts
│   └── utils/{api,auth,format,validate}.ts
└── tests/
    ├── setup.ts
    └── {Button,Card,Header,Modal,Sidebar}.test.tsx

every brace group decompresses to exactly one path per entry — the compression is lossless (except explicit ... truncation, which is marked so the model knows more files exist).

the format

pattern meaning example
{a,b,c}.ext siblings sharing an extension {Button,Card,Modal}.tsx
{a.x,b.y} siblings with mixed extensions {README.md,tsconfig.json}
dir/{stems}.ext whole dir shares one extension utils/{api,auth,format}.ts
dir/file single-child chain collapsed scripts/build.sh
{a,b,...}.ext truncated — more files exist images/{logo,hero,...}.png

compression rules

  1. mixed directories (files + subdirs): all files fold into one {...} set, subdirs listed after
  2. files-only directories: grouped by shared extension, compound-aware — .test.ts groups separately from .ts, .config.js from .js
  3. inline collapse: a directory that compresses to a single item renders on one line
  4. single-child chains: a/b/c.txt when each level has exactly one child
  5. truncation: groups over 8 items show the first 5 + ...

self-documenting header

every output starts with a 3-line legend:

# COMPRESSED FILE TREE — AI READ GUIDE
# {a,b,c}.ext  → siblings sharing extension   | scripts/f.js → collapsed single-child dir
# {...}.ext     → truncated list (more exist)  | decompress: expand braces × each entry = 1 path

paste the output anywhere — the model gets the decoding instructions for free, no system-prompt setup needed.

the noise filter

this is the part most tree tools don't do. ripgrep, fd, tokei, eza all delegate to .gitignore — which is great until the repo has no gitignore, or the junk is inside a worktree, or someone pointed -derivedDataPath at a folder inside the repo. treezip ships its own opinionated filter, built from the github/gitignore templates, vendor docs, and the default-ignore lists of repomix/gitingest. it works in three layers:

layer 1 — basename blocklist

distinctive names that are noise in any repo, skipped unconditionally:

  • vcs internals: .git (dir and the worktree/submodule pointer file), .hg, .svn, .bzr, .jj, .sapling
  • os junk: .DS_Store, .Spotlight-V100, .Trashes, .fseventsd, Thumbs.db, desktop.ini, $RECYCLE.BIN, __MACOSX, ._* appledouble files
  • js/ts: node_modules, .pnpm, .next, .nuxt, .output, .turbo, .parcel-cache, .vite, .svelte-kit, .astro, .angular, .docusaurus, .expo, .wrangler, .vercel, .netlify, .serverless, coverage, .nyc_output, storybook-static, *.tsbuildinfo, .eslintcache
  • python: __pycache__, .venv, venv, .tox, .nox, .pytest_cache, .mypy_cache, .ruff_cache, .hypothesis, .ipynb_checkpoints, *.egg-info, htmlcov, *.pyc, .coverage
  • swift/xcode: DerivedData, .build, .swiftpm, xcuserdata, Carthage, SourcePackages, Index.noindex, ModuleCache.noindex, Intermediates.noindex, CompilationCache.noindex, SDKStatCaches.noindex
  • everything else: .gradle (jvm), zig-cache/.zig-cache/zig-out, _build/.elixir_ls (elixir), .dart_tool (flutter), CMakeFiles/_deps/cmake-build-* (cmake), .vs/.idea (ide caches), .bundle/.yardoc (ruby), .terraform/.terragrunt-cache/.vagrant/.direnv (infra)

layer 2 — content markers

build roots can be named anything. xcode's -derivedDataPath lets you point builds at .test-app-build/ or whatever/; python venvs get called env, myenv, env310. name-matching can't catch these — so treezip peeks one level inside every directory:

  • contains pyvenv.cfg → it's a venv, skip it
  • contains Index.noindex / ModuleCache.noindex / SDKStatCaches.noindex / CompilationCache.noindex → it's a derived-data root, skip it

whatever it's named, however deep it hides.

layer 3 — context-aware rules

generic words like target and build are dangerous to blanket-ignore — a docs folder named build/ is real content. so these only get skipped when a sibling file proves the ecosystem:

dir skipped only next to
target/ Cargo.toml or pom.xml
build/ build.gradle, build.gradle.kts, settings.gradle(.kts)
deps/ mix.exs
bin/, obj/ a .csproj/.fsproj/.vbproj/.sln

plus parent-scoped rules where a dot-dir is half config, half cache:

  • .claude/worktrees hidden, .claude/skills kept
  • .yarn/cache, .yarn/unplugged hidden, .yarn/patches, .yarn/releases kept

what never gets hidden

signal stays, always:

  • lockfiles — package-lock.json, pnpm-lock.yaml, yarn.lock, bun.lock, Cargo.lock, Package.resolved, poetry.lock, uv.lock
  • .env, .envrc — you want to know those exist
  • .vscode, .github — committed config, often load-bearing
  • .xcodeproj bundles — project definition is source (only the xcuserdata inside is junk)

programmatic api

import { scan, compress, render, countNodes } from "treezip";

const tree = scan("./my-project");          // filtered TreeNode
const compressed = compress(tree);           // CompressedItem[]
const { dirs, files } = countNodes(tree);    // post-filter counts
const output = render("my-project/", compressed);
console.log(output);

the noise predicates are exported too, if you want the filter without the tree:

import { isNoiseDir, isNoiseFile, hasNoiseMarker, NOISE_DIRS, NOISE_FILES } from "treezip";

isNoiseDir("node_modules", "my-project", []);        // true
isNoiseDir("build", "repo", ["build.gradle"]);        // true (gradle proven)
isNoiseDir("build", "repo", ["README.md"]);           // false (could be real)
hasNoiseMarker(["pyvenv.cfg", "bin", "lib"]);          // true (it's a venv)

design notes

  • zero dependencies. stdlib fs + path, nothing else. install is instant, supply chain is empty.
  • symlinks are skipped, so cycles can't happen and the tree never leaves the root.
  • deterministic output: files first, then dirs, both alphabetical — same input, same bytes out.
  • the root is never filtered. if you explicitly run treezip ./node_modules, you get node_modules — you asked, treezip answers.
  • releases are automated: every push to main runs the test suite, bumps the patch version, tags, and publishes to npm.

license

mit

About

Compressed directory trees for AI prompts. Zero dependencies.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages