Why I wanted this
Context windows are still the biggest practical limit when you are moving fast. I do not want to dump whole files into prompts, but I also do not want to lose the details that let an LLM make safe edits. llm-tldr sits in the middle: it compresses code into a structured summary that is still precise enough to edit against.
The other reason is speed. llm-tldr runs a daemon per project, so once it is warmed it can answer queries in milliseconds instead of re-parsing the repo every time.
What llm-tldr gives you
llm-tldr organizes code into analysis layers so you only pay for the depth you need:
- Layer 1 (structure): what functions, classes, and files exist
- Layer 2 (call graph): who calls what
- Layer 3 (control flow): branching and complexity inside a function
- Layer 4 (data flow): how values move through a function
- Layer 5 (program slice): the minimal lines that affect a specific line
It also keeps a per-project cache in .tldr/ and runs a background daemon, so repeated queries are fast.
Quick start with uv (recommended)
This is the smallest setup I could make that still feels smooth:
# Install uv if you do not have it
curl -LsSf https://astral.sh/uv/install.sh | sh
# One-off run without installing a global tool
uvx --from llm-tldr tldr warm . --lang all
# Or install a tool for repeat usage
uv tool install --from llm-tldr tldr
Once the cache is warm, try a few commands:
# Structure overview (fast, shallow)
tldr structure . --lang typescript
# Per-file summaries
tldr extract scripts/generate-sitemap.mjs
# Call graph and impact
tldr context generateSitemap --project . --depth 2
tldr impact generateSitemap .
# Control/data flow for a specific function
tldr cfg scripts/generate-sitemap.mjs generateSitemap
tldr dfg scripts/generate-sitemap.mjs generateSitemap
Claude hooks refresher (the important bits)
From the Claude hooks docs, here is what matters for a practical setup:
- Hooks are configured in
~/.claude/settings.json,.claude/settings.json, or.claude/settings.local.json - Each hook is a command or prompt hook bound to events like
SessionStartandUserPromptSubmit - Hooks can match on tool names or patterns using
matcher - Exit code
0injects stdout,2blocks with stderr, other non-zero is logged but does not block SessionStarthooks can set env vars by writing toCLAUDE_ENV_FILE
I only need two events: warm the cache at session start and inject context on user prompts.
Hook setup (copy/paste)
Create two scripts in .claude/hooks and make them executable.
.claude/hooks/tldr-warm.sh
#!/usr/bin/env bash
set -euo pipefail
project_dir="${CLAUDE_PROJECT_DIR:-$PWD}"
tldr_lang="${TLDR_WARM_LANG:-all}"
if command -v uvx >/dev/null 2>&1; then
uvx --from llm-tldr tldr warm "$project_dir" --lang "$tldr_lang" --background >/dev/null 2>&1 || true
exit 0
fi
if command -v uv >/dev/null 2>&1; then
uv tool run --from llm-tldr tldr warm "$project_dir" --lang "$tldr_lang" --background >/dev/null 2>&1 || true
exit 0
fi
exit 0
.claude/hooks/tldr-on-prompt.py
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.10"
# dependencies = []
# ///
import json
import os
import re
import shlex
import shutil
import subprocess
import sys
def resolve_tldr_cmd():
if shutil.which("uvx"):
return ["uvx", "--from", "llm-tldr", "tldr"]
if shutil.which("uv"):
return ["uv", "tool", "run", "--from", "llm-tldr", "tldr"]
return None
def main() -> int:
payload = sys.stdin.read()
if not payload.strip():
return 0
try:
data = json.loads(payload)
except json.JSONDecodeError:
return 0
prompt = data.get("prompt") or ""
match = re.search(r"tldr:\\s*(.+)", prompt, re.IGNORECASE)
if not match:
return 0
command_line = match.group(1).strip()
if not command_line:
return 0
argv = shlex.split(command_line)
if not argv:
return 0
action = argv[0]
allowed = {
"context",
"extract",
"structure",
"semantic",
"search",
"impact",
"cfg",
"dfg",
"slice",
"imports",
"importers",
}
if action not in allowed:
return 0
tldr_cmd = resolve_tldr_cmd()
if not tldr_cmd:
return 0
project_dir = os.environ.get("CLAUDE_PROJECT_DIR") or os.getcwd()
args = list(argv)
if action == "context" and "--project" not in args:
args += ["--project", project_dir]
if action == "structure" and len(args) == 1:
args.append(project_dir)
try:
result = subprocess.run(
tldr_cmd + args,
check=True,
capture_output=True,
text=True,
)
except subprocess.CalledProcessError:
return 0
output = result.stdout.strip()
if not output:
return 0
max_lines = int(os.environ.get("TLDR_MAX_LINES", "200"))
lines = output.splitlines()
if len(lines) > max_lines:
lines = lines[:max_lines]
lines.append("... (truncated)")
sys.stdout.write("## TLDR\n")
sys.stdout.write("\n".join(lines))
sys.stdout.write("\n")
return 0
if __name__ == "__main__":
raise SystemExit(main())
Wire them up in .claude/settings.local.json (project-local):
{
"permissions": {
"allow": ["Bash(uv:*)", "Bash(uvx:*)"]
},
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "TLDR_WARM_LANG=all \"${CLAUDE_PROJECT_DIR}\"/.claude/hooks/tldr-warm.sh",
"timeout": 120
}
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "TLDR_MAX_LINES=200 \"${CLAUDE_PROJECT_DIR}\"/.claude/hooks/tldr-on-prompt.py",
"timeout": 20
}
]
}
]
}
}
Using it in prompts
The prompt hook looks for a tldr: directive anywhere in your prompt. A few examples:
Can you update the sitemap script?
tldr: extract scripts/generate-sitemap.mjs
We need to trace how the blog routes are generated.
tldr: context buildRouteTree --project src --depth 2
Where is this import used?
tldr: importers src/routeTree.gen.ts
The hook runs the command, injects the TLDR output, and Claude sees it as part of your prompt.
Gotchas and small tips
- Semantic search uses embedding models and may download large files the first time. Start with structure and context first.
- The cache lives in
.tldr/. Add it to.gitignoreif you do not want churn in your repo. - If you want a wider slice, increase
TLDR_MAX_LINESor add a secondtldr:line in the prompt.
Why this feels worth it
This workflow is not just about shrinking tokens. It is about steering the model toward the exact scaffolding it needs: structure, call graph, and the handful of lines that matter. Once the hooks are in place, it feels like a zero-cost habit. You ask a question, the right context appears, and you keep moving.