Generate focused, token-efficient LLM context files from your project.
Project description
cxtree
Generate focused, token-efficient LLM context files from your project.
cxtree walks a project directory and produces a structured Markdown file
containing the directory tree and relevant source files as code blocks — ready
to paste into any LLM chat as context.
The key idea: instead of dumping everything into the context window, you control exactly what the LLM sees — per directory, per file, per class, per function — using a YAML configuration file.
Installation
pip install cxtree
Or with uv:
uv add cxtree
Quick start
# 1. Generate abstract-tree.yaml in your project root
cxtree init
# 2. Edit abstract-tree.yaml to configure what the LLM sees (optional)
# 3. Generate context.md
cxtree create
Then paste context.md into any LLM chat.
Commands
All commands accept -r / --root to point at a project directory other than ..
init
Traverses the project, discovers files, and writes abstract-tree.yaml to the
project root. If a file already exists it is preserved — only new entries are
added.
cxtree init
cxtree init -r path/to/project
--folder flag — stores both abstract-tree.yaml and context.md inside a
.abstract-tree/ subdirectory (which gets a .gitignore that excludes its
contents from git). Useful for keeping the project root clean.
cxtree init --folder
Switching between folder and normal mode re-runs automatically and cleans up the previous location.
--default docs|code|include — sets the x_root tag written into the
cxtree: header of abstract-tree.yaml. This tag is the default inherited by
every entry in the tree. Defaults to docs.
cxtree init --default include # x_root: include — show full source by default
cxtree init --default code # x_root: code — code bodies, no docstrings
--docs / --code / --include — write the chosen tag explicitly on
every entry in the tree instead of embedding extracted docstring text. The
structure (file → class → method) is still built; only the values change.
| Flag | Effect on each entry |
|---|---|
--docs |
x_abstract: docs — docstring pulled from source at render time |
--code |
x_abstract: code — code body rendered, docstring stripped |
--include |
x_abstract: include — full source rendered |
Without any of these flags (the default) init extracts docstrings from the
source and embeds them as text in the yaml, so they can be read and edited
independently of the code.
cxtree init --docs # fast shorthand: reference docstrings, don't embed them
cxtree init --include # full-source mode for everything
cxtree init --default include --docs # x_root=include but docs tags everywhere
create
Reads abstract-tree.yaml and generates context.md.
cxtree create
cxtree create -o docs/llm-context.md # custom output path
--docs / --code / --include — override the effective tag on every
file at render time without modifying any configuration files. Useful for a
quick one-off render in a different mode.
| Flag | Effect |
|---|---|
--docs |
Render only docstrings for every symbol |
--code |
Render code bodies, strip docstrings |
--include |
Render full source (code + docstrings) |
cxtree create --code # one-shot code render, no yaml changes
cxtree create --docs # one-shot docs render
--max-lines N (default: 3 000) — maximum number of lines allowed in a
single context.md. When the generated content exceeds this limit the file is
not written at the current level; instead one context.md is created per
immediate subfolder and the process recurses until each file fits within the
limit or a leaf directory is reached.
cxtree create --max-lines 2000 # split if content > 2 000 lines
cxtree create -n 500 # short form
Split-mode artefacts are spread across the project tree:
project/
├── domain/
│ ├── context.md # only domain/base.py (direct file)
│ └── users/
│ └── context.md # auth.py + models.py
└── settings/
└── context.md # config.py
Every create run first removes all existing context.md files under the
project root (excluding .abstract-tree/) so stale split files from a
previous -n <small> run never accumulate alongside the new output.
cxtree rm removes all context.md files in subdirectories as well as the
root one. When --output is given explicitly, --max-lines is ignored and a
single file is always written.
leafs
Splits the flat root abstract-tree.yaml into per-directory abstract.yaml
child files. Useful for large projects where many directories need fine-grained
control.
cxtree leafs
Each directory with file-level entries gets its own abstract.yaml. The root
file is reduced to directory stubs. Re-running leafs on an already-split
project merges changes back first, then re-splits.
flatten
The reverse of leafs. Reads all child abstract.yaml files and merges their
entries back into the root file, then deletes them.
cxtree flatten # flatten everything
cxtree flatten domain # flatten only the domain/ subtree
cxtree flatten domain.users
tree
Prints a coloured directory tree of the project respecting the same exclusion
rules as init.
cxtree tree
Folders are shown in orange, .py files in blue, everything else in white.
-n / --max-lines N (default: 3 000) — overlay line-budget percentages on
the tree. A percentage is shown at directories where create --max-lines N
would actually write a context.md:
- The directory's content fits within the budget (≤ 100 % → not split further), and
- Its parent overflows (> 100 % → was split), forcing this directory to get its own file. The project root is shown when it fits and no splitting would occur at all.
Leaf directories (no subdirectories with content) always show their percentage even when they exceed 100 %, because there is nothing left to split into. A magenta label signals that the module is too large and should be broken up into smaller sub-packages.
| Colour | Range |
|---|---|
| green | ≤ 80 % |
| yellow | 80 – 90 % |
| red | 90 – 100 % |
| magenta | > 100 % (leaf, cannot split further) |
cxtree tree # uses default budget of 3 000 lines
cxtree tree -n 500 # tighter budget — more split points visible
Example with -n 190 on a medium-sized project:
APP_2
├── api
│ ├── v1 49%
│ ├── middleware.py
│ └── routes.py
├── core
│ ├── cache 61%
│ ├── config.py
│ └── events.py
├── domain 67%
└── workers 76%
api (111 %) and core (105 %) overflow so they are unlabelled. Their children
that fit the budget (v1, cache) are labelled instead. domain and workers
fit directly under the overflowing root, so they are labelled too.
rm
Removes all cxtree-generated artefacts under the given directory in a single pass, regardless of whether the project is in normal or folder mode:
.abstract-tree/folderabstract-tree.yamlat project rootcontext.mdat project root- All
abstract.yamlchild files in subdirectories - All
context.mdfiles in subdirectories (split-mode artefacts fromcreate --max-lines)
cxtree rm
abstract-tree.yaml
abstract-tree.yaml is the root configuration file. It has two sections:
- A
cxtree:header block with project-wide settings. - Directory and file entries that control what the LLM sees.
Header block
cxtree:
x_root: docs # default tag for the whole project
is_flat: true # true = only root file used; false = leaf mode
ext_found: [py, toml] # written by init — informational only
config:
x_rm_empty_lines: false # strip all blank lines from output
x_rm_empty_lines_docs: true # strip blank lines from doc-only sections
include:
x_extensions: [py] # file extensions included in context
exclude:
x_startswith: [".", "__"] # skip files/dirs starting with these prefixes
x_folders: [".venv", "node_modules", "__pycache__"]
Directory entries
Directories use dot-notation keys with is_dir: true. init generates these
automatically.
domain:
is_dir: true
domain.users:
is_dir: true
models.py: docs
auth.py: include
File entries
Files are nested under their directory key (or at root level for files in the project root). The value controls what the LLM sees.
domain.users:
is_dir: true
models.py: docs # show docstrings only
auth.py: include # show full source
legacy.py: exclude # hide completely
base.py: "Shared base classes — no details needed." # replace with text
Tags
Tags control how a file (or symbol) is rendered.
| Tag | What the LLM sees |
|---|---|
docs |
Docstrings only — code bodies are replaced with # ... |
code |
Code bodies only — docstrings are stripped |
include |
Full source: code + docstrings |
exclude |
Hidden — not included in context at all |
Tags are inherited top-down. The x_root value in the header is the starting
point; any entry without an explicit tag inherits from its parent.
Symbol-level configuration
For Python files you can configure individual classes and functions.
domain.users:
is_dir: true
auth.py:
x_abstract:
- "Authentication service — login, logout, token validation."
class:
AuthService:
x_abstract:
- "Handles login, logout and token validation."
def:
login: docs # show docstring only
logout: docs
validate_token: include # show full source
_sign: exclude # hide private helper
def:
create_token: docs
__init__, __post_init__, __new__ and other lifecycle dunders are always
skipped — they are never emitted even with include.
x_abstract on a file or class sets a description shown above its content.
Use a list for multi-line descriptions:
auth.py:
x_abstract:
- "Authentication service."
- "Tokens are HMAC-signed. No external JWT library required."
Text replacements
Assign any string to a directory or file entry to replace it entirely with that text. No further content is shown.
domain.legacy:
is_dir: true
old_service.py: "Deprecated. Superseded by domain.users.services."
Multiline replacement using a YAML list:
domain.users:
is_dir: true
models.py:
- "User entity with id, username, email, is_active, roles."
- "Session entity binding a user_id to a token and expiry."
x_hard_abstract (directory-level)
Setting x_hard_abstract on a directory entry replaces the entire directory
with a single summary line — no files inside are walked.
workers:
is_dir: true
x_hard_abstract: "Background workers for cleanup and reporting."
Set it to "off" to disable the override without removing the key:
workers:
is_dir: true
x_hard_abstract: "off"
abstract-leaf.yaml
Place an abstract-leaf.yaml file inside any directory to provide the
highest-priority flat overrides for that directory. Entries here override
everything else — tags, symbol config, even the include_extensions filter.
# domain/abstract-leaf.yaml
notifications: "Email and SMS dispatchers — not relevant for this task."
models.py: "User entity and Notification entity."
Keys = filenames or subdirectory names within that directory only. Values must be plain strings.
To deactivate an entry without deleting it, prefix the key with . or __
(the default exclude_startswith prefixes):
# disabled — notifications/ is walked normally
.notifications: "Email and SMS dispatchers — not relevant for this task."
abstract-leaf.yaml is never included in the context output itself.
Inline source tags
Fine-tune what gets shown inside a function or method body using inline comments.
# ++ — show N lines from this point
The number of + characters determines how many lines are shown starting from
and including the tagged line. # ++ = 2 lines, # +++ = 3 lines, etc.
def build_app(config: AppConfig) -> dict:
user_svc = UserService(config.db_url) # ++
notif_svc = NotificationService(...)
# ← both lines above are shown; rest of body is compressed to # ...
# --- — hide N lines
The number of - characters determines how many lines are hidden. The tagged
line and the N−1 lines that follow are replaced by a single # --- placeholder.
# --- = 3 lines hidden, # ---- = 4 lines, etc.
def process(self, request: dict) -> dict:
token = header[len(prefix):] # ---
request["_token"] = token # ← this line is hidden (part of the 3)
return request
Both tags preserve the indentation of the tagged line in the placeholder.
Leaf mode (per-directory child files)
Run leafs to split the flat root file into one abstract.yaml per directory.
This is useful when many directories need independent, detailed configuration.
project/
├── abstract-tree.yaml # root — directory stubs only
├── domain/
│ └── abstract.yaml # file entries for domain/
└── api/
└── abstract.yaml # file entries for api/
Child abstract.yaml files use the same tag and symbol syntax. They are
identified by abstract-depth: (set automatically by leafs) which must match
the directory's actual depth from the project root.
x_is_flat and x_hard_abstract in child abstracts
A subdirectory entry inside a child abstract can carry two control keys:
# domain/abstract.yaml
abstract-depth: 1
parent-dirs: [domain]
users:
is_dir: true
x_is_flat: false # false = keep its own child abstract
x_hard_abstract: "off" # placeholder — replace "off" with text to activate
x_is_flat: true— merge this subdirectory back into the parent on the nextleafsrun instead of keeping its own child abstract.x_hard_abstract: "<text>"— replace the entire subdirectory with a summary in the context output."off"= feature inactive.
Folder mode
Use --folder to keep generated files out of the project root:
cxtree init --folder
cxtree create # reads and writes inside .abstract-tree/
The .abstract-tree/ directory contains:
.abstract-tree/
├── .gitignore # excludes everything inside from git
├── abstract-tree.yaml
└── context.md
Switch back to normal mode by running init without --folder:
cxtree init # deletes .abstract-tree/, writes to project root
Example workflow
# Initial setup
cxtree init
# Review abstract-tree.yaml, tune tags and descriptions, then generate
cxtree create
# For large projects: split into per-directory files
cxtree leafs
# Edit individual abstract.yaml files in each directory, then regenerate
cxtree create
# Merge a subtree back (e.g. after simplifying domain/)
cxtree flatten domain
# Clean up everything
cxtree rm
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cxtree-0.1.2.tar.gz.
File metadata
- Download URL: cxtree-0.1.2.tar.gz
- Upload date:
- Size: 54.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7778cc9e8aa29621d2fa31721d322647c1e42a6cec2e7c713eaab88e2b0b0821
|
|
| MD5 |
091c600bc2011261c0ae3403b6d43c99
|
|
| BLAKE2b-256 |
555f35c246f3d063807ec24afb03c9bd0cb931a9ef622a67f8b3bd6b721c8dac
|
File details
Details for the file cxtree-0.1.2-py3-none-any.whl.
File metadata
- Download URL: cxtree-0.1.2-py3-none-any.whl
- Upload date:
- Size: 41.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ca81db3597a05c768fed8dba921b3f878cfc08f4147933b9ea7e51fb4619950c
|
|
| MD5 |
49e53dc69b5640e7284368991e7cadc7
|
|
| BLAKE2b-256 |
259b716cb5d4316adc499674dcaa0c015ff138bab6996ceda415a5e3eb358857
|