summarize walks your source tree into one AI-ready Markdown file
A Go CLI that renders a project's filtered source code into a single summary document, with optional gzip, JSON and an LLM chat TUI.
What it is
summarize (github.com/andreimerlescu/summarize) is a single-purpose Go binary: turn the source code of a directory into one Markdown document that can be pasted to an AI. The author's stated workflow is make summary, then cat the generated file and ask an LLM questions about it.
The rendered output has this shape (receive() in internal.go writes it):
# Project Summary - <filename>
Generated by github.com/andreimerlescu/summarize <version>
AI Instructions are the user requests that you analyze their project workspace ...
### Workspace
`<absolute path of source directory>`
## filename.go
The `os.Stat` for the ... is:
{ "name": ..., "size": ..., "mode": ... }
Source Code:
<full source code>
Each included file gets a ## heading (the README's example shows ###, but the writer uses two levels), its os.Stat metadata as indented JSON (name, size, mode) inside a json block, and the complete contents in a fenced block tagged with the extension.
How it decides what to include
The walk is driven by three lists that map to flags and environment variables:
- Include (
-i,SUMMARIZE_INCLUDE_EXT) — extensions worth summarizing. The default include list coversgo, ts, tf, sh, py, js, Makefile, mod, Dockerfile, dockerignore, gitignore, esconfigs, md. - Exclude (
-x,SUMMARIZE_EXCLUDE_EXT) — extensions to drop. - Skip (
-s,SUMMARIZE_IGNORE_CONTAINS) — path substrings to avoid entirely.
The exclude list (-x) is long and practical — archives, certificates and keys, executables, images, audio, video, fonts, documents, IDE artifacts, package-manager locks, temp files, source maps. The skip list (-s), by contrast, holds path substrings such as .git/, node_modules/, logs/. The intent is that summarize does its best to ignore files it can't render to a text file, including things commonly linked to secrets.
A special useExpanded list value can request the expanded lists. When you pass exactly one item and it equals useExpanded, preprocess() swaps in the full expanded lists via extendedDefaultInclude, extendedDefaultExclude and extendedDefaultAvoid. The environment variables append to, rather than replace, those defaults.
The walker itself (summarize in internal.go) is a callback passed to filepath.Walk. For each non-directory it checks the skip substrings against both filename and full path (prefix, suffix and contains), then derives the extension with filepath.Ext, lowercases it, trims the dot, and compares against the exclude list before calling populate, which adds the path to each extension bucket currently present in the sync.Map; later, seen keeps any one file from being analyzed more than once.
Concurrency, throttled
Processing is deliberately concurrent but bounded. Two semaphores from the author's own sema package keep things sane:
throttlerlimits how many extensions are investigated at once.maxFileSemaphore, default 369 (-mf), caps files open simultaneously.
The flow is: data.Range(iterate) launches one goroutine per extension; each investigate simplifies its path list, then spawns an analyze goroutine per file. Each analyze reads the file and pushes a Result{Path, Contents, Size} onto resultsChan. A receiver (receive) drains that channel into a growing buffer.
The output is capped: -max (default 1_776_369 bytes) stops adding files once the running total reaches or exceeds it. The default filename is timestamped with the pattern 2006.01.02.15.04.05.UTC, written under an auto-created summaries/ directory in the current working tree — which is why the README recommends gitignoring and dockerignoring that folder, and why it ignores its own output so it doesn't recursively summarize itself.
Configuration: flags plus environment
Configuration runs through the author's figtree/v2 library. configure() builds a figtree plant with defaults for every option, then applies validators (for example kMaxFiles must be 1–369, kSourceDir non-empty). A YAML/JSON config file can seed values; its path comes from SUMMARIZE_CONFIG_FILE, defaulting to ./config.yaml.
The key flags:
| Flag | Meaning | |------|---------| | -d | Source directory (default .) | | -o | Output directory (default summaries/) | | -f | Summary filename (timestamped by default) | | -i, -x, -s | Include / exclude extensions, skip substrings | | -mf | Max files processed concurrently (369) | | -max | Output size cap in bytes | | -print | Write summary to STDOUT instead of a file | | -write | Force writing the file | | -json | Render output as JSON | | -gz | gzip-compress the contents, append .gz | | -ndf | Skip dotfiles | | -v, -debug, -expand | Version / verbose logging / show expanded lists |
The SUMMARIZE_ALWAYS_PRINT, SUMMARIZE_ALWAYS_WRITE, SUMMARIZE_ALWAYS_JSON and SUMMARIZE_ALWAYS_COMPRESS booleans let you bake those behaviors in without typing the flags each time.
The AI layer
Two files add LLM features. ai.go implements the AI options defined in configure(): provider (default ollama, model default qwen3:8b), max tokens, seed, memory, caching and timeout. The API key is supplied via SUMMARIZE_AI_API_KEY, left empty for ollama.
chat.go implements the -chat terminal UI that opens when you pass -chat. It starts with the freshly built summary as context and lets you converse in a terminal UI; receive reads the resulting chat log. In receive, if kChat is set, that chat log is prepended to the final document under a "## Chat Log" heading.
configure.go defines a kAiEnabled option backed by the SUMMARIZE_DISABLE_AI environment variable, though nothing in the returned code reads that flag to gate the chat or AI calls.
Output paths
render() decides where the bytes go. If gzip was requested it compresses and appends .gz. Then:
- if neither
-printnor-write, it writes the file (0644) — this is the default; - if
-write, it also writes; - if
-print, it prints to STDOUT, or as indented JSON when combined with-json, then exits.
The README's example config shows a YAML/JSON file carrying include lists and paths:
{"i": "go,sh,ts", "d": "/home/user/work/project", "o": "/home/user/summaries/project"}
Building
Built for Go 1.24 (the module declares go 1.24.5). Install with:
go install github.com/andreimerlescu/summarize@latest
The Makefile cross-compiles darwin, linux and windows binaries into bin/, strips symbols (-ldflags "-s -w"), and provides a summary target that installs the tool and runs it against its own source with go,Makefile,mod. The Makefile defines a test target that runs ./test.sh.
Bottom line
This is a focused developer utility rather than a library: you run it in a project directory, get one Markdown file of everything an AI would need to answer questions about that codebase, and optionally keep chatting with your configured LLM provider (ollama by default) inside a terminal UI. The concurrency controls, output size cap, default exclusions and environment overrides are the practical details that make it usable on real repositories without flooding your context window.
andreimerlescu/summarize: 1 MB in 26 files, indexed Nov 9, 2026 after 0.00 hours of indexing. License: MIT. Source: https://github.com/andreimerlescu/summarize.
Ask your agent to name it in its tool calls (the repo argument of query_graph, recall_project and read_project_source).
Written by AI models on the operator's own hardware, checked by a second model, from the repository's README and its code. Back to the shelf