All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog 1.1.0, and this project adheres to Semantic Versioning.
- The default
--formatis nowmd(Markdown), previouslyyaml. Markdown is ~7% more token-efficient than YAML on real diffs and is preferred by reviewers for code-fragment-heavy output (evidence:benchmarks/real_world_diff_bench/). YAML remains available via-f yaml. This also changes the default stdout format oftreemapper(a passthrough wrapper over this engine); downstream scripts that parse the default output must now pass-f yamlexplicitly. (#104)
- The default
--tau(stopping threshold) is now 0.12 across the CLI, MCP, and Python API — the calibrated grid optimum — bringing the Python side in line with the standalone Rust binary (was 0.08). Diff-context selections are slightly tighter by default. - The MCP
get_tree_map/get_file_contextdefaultmax_file_bytesis now 256 KB, matching the documented CLI default (was 100 KB).
- Document/citation edges can no longer create self-edges (a fragment linking to
itself);
Graph::add_edgenow rejectssrc == dst. - Test→source import edges no longer match
importstatements that appear inside comments or string literals (the import regex is now line-anchored). - The MCP
get_file_contextclipboard confirmation now reports the number of files actually copied, excluding files skipped for exceeding the size cap.
- Corrected the AST-parsing language count (was "12 languages", now "30+"),
aligned the Rust crate version with the wheel, completed the MCP server README
(all three tools documented), and fixed the documented
EGO.per_hop_decaydefault (0.5). Documented thescoring_mode/timeoutPython API params, clarified--budget/--tauhelp text, and marked theDIFFCTX_OP_*/DIFFCTX_*tuning env vars as an experimental, non-public interface.
- Private-key and keystore files are now excluded from output in both tree
mapping and
--diffcontext, since such material is never legitimate LLM context:*.pem,*.key,*.pfx,*.p12,*.keystore,*.jks, and SSH private keysid_rsa/id_dsa/id_ecdsa/id_ed25519(public.pubkeys stay visible). The--diffpath previously applied no ignore filtering at all, so a changed key file would have leaked into context. Use--no-default-ignoresto opt out of tree-mode default ignores. (.envfiles are intentionally still included — a changed.envis legitimate change context; redacting secret values is a separate planned content-scan feature.)
- Diffs touching files larger than 100 KB no longer produce empty output. A
changed file (e.g. a 142 KB
Math.h) was subject to the same 100 KB cap as context-discovery candidates, so the whole diff yielded "no semantic context". Changed files — the subject of the diff — are now parsed up to 5 MB. - C/C++ symbol names for variable declarations now report the variable, not
the type: a changed
const unsigned blane = …is labeledblane, notunsigned. Coversinit/array/pointer/function declarators and C++reference/parenthesizeddeclarators (int &r,int (*f)()). - Changed-file fragments are no longer dropped by the generated-file reduction (cap of 5 + 30-line content truncation), which could discard the small fragment covering the edited hunk and mislocate the change.
- Binary detection no longer misclassifies changed text files that embed ANSI escape / control bytes (snapshot and terminal-recording fixtures); it now uses git's NUL-byte heuristic, so such files are no longer silently dropped.
- Diff-context selection is now deterministic across processes: ego-graph score accumulation, context-fragment capping, and the greedy selection heap use stable tie-breaks, so identical inputs always yield byte-identical output.
- Python module-import edges are bidirectional (matching every other language and Python's own symbol references), so a changed imported module now propagates relevance back to its importers.
git cat-fileblob reads are bounded (drain-and-reject above 16 MB) instead of allocating an arbitrarily large buffer up front.
Supersedes 1.9.0, which was tagged but never published to PyPI (release-process hiccup); 1.9.1 ships the same changes. There is no 1.9.0 on PyPI.
- Diff-context output now leads with an orientation header —
commit_messageandchanged_files— in every format (YAML/JSON/Markdown/text), so a reader sees what changed before reading any fragment. - Each fragment carries a
roleofchangedwhen it overlaps the diff hunks (omitted for supporting context). Changed code is emitted first; context follows, ordered by descending per-file relevance instead of alphabetically.
- Line-contiguous fragments of the same role within a file are merged into a single entry, cutting the per-fragment scaffolding that dominated output made up of one-line snippets (lossless on line coverage).
get_changed_filesandget_untracked_filescanonicalize paths consistently with deleted/discovered files, preventing duplicate fragments when a tracked path traverses a symlinked directory.- Signature extraction no longer terminates at braces inside parameter defaults
or annotations (e.g. Python
def f(x={}):), which previously truncated the signature mid-parameter-list. - The post-pass that rescues unrepresented changed files reuses the open
git cat-file --batchreader instead of spawning agit showper file. - Degraded token-count fallback returns 0 for the empty string (was 1).
- Public
diffctx.run(argv=None, *, prog=None, version=None)engine entry. It is the same execution path as thediffctxconsole script but accepts an injected program name and version string, so a downstream wrapper (e.g. thetreemapperdistribution) can present its own branding in--help,--version, and error prefixes without duplicating the CLI.main()now delegates torun()with the defaultdiffctxidentity — no behavior change for existing callers. diffctx.mcp.__main__.main(prog=..., extra=...)accepts an injected program name and install-extra string for the missing-dependency hint, so wrappers can re-expose the MCP entry point with their own name.
1.7.0 - 2026-05-22
- README documents the
graphsubcommand and the--scoring {ppr,ego,bm25}flag (default:ego); the absolutist "Uses Personalized PageRank" language has been replaced with a table covering all three scoring modes. SECURITY.mdnow ships a "Threat model" section that scopes diffctx as a local CLI (filesystem + git subprocess, no network) and documents that the optionaldiffctx-mcpserver confines its filesystem reach viaDIFFCTX_ALLOWED_PATHS.- Footer of
README.mdlinksCHANGELOG.md,SECURITY.md, anddocs/parameter-strategy.mdso they are no longer orphaned.
- Package renamed from
treemappertodiffctx. PyPI distribution, CLI binary, MCP server binary, and Python import path all usediffctx. ThetreemapperPyPI package remains at 1.6.1 (frozen); installdiffctxfor all new development. TheTREEMAPPER_ALLOWED_PATHSenvironment variable is nowDIFFCTX_ALLOWED_PATHS. - Single source of truth for the package version: the Python wheel reads its
version from
Cargo.toml(via maturin) instead of duplicating it inpyproject.toml, eliminating the Rust-crate-vs-Python-package version desync that previously shipped to PyPI. --diffnow defaults toHEADwhen no range is supplied, matching the most common invocation (diffctx . --diff→ diff of the working tree againstHEAD) and saving an argument in the 30-second demo path.numpymoved from a required runtime dependency into the[tree-sitter]extra, so default installs no longer pull a ~20 MB scientific stack the core tree-mapping mode does not use.- CLI error messages are now actionable: instead of a raw Python traceback,
invalid
--diffranges, missing git repositories, and unreadable paths print a one-lineError: <what> — try: <next step>and exit with code2for user-input errors (1is reserved for runtime failures). automerge.ymlGitHub Actions workflow hardened: explicit minimalpermissions:block, pinned action SHAs, and a guard that refuses to auto-merge anything touching.github/,pyproject.toml, orCargo.toml.
- Replaced every user-reachable
unwrap()/expect()in the Rust core (tokenizer.rs,git.rs,scoring.rs,pybridge.rs) with properPyRuntimeError/GitErrorpropagation. A malformed diff, a missing BPE table, or an oversized hunk-header integer no longer aborts the Python interpreter viapanic = "abort". diffctx-mcpentry point now guards against being launched without the[mcp]extra installed and prints an install hint instead of anImportErrortraceback.
- Dropped Kotlin and F# from the language matrix: tree-sitter grammars for both were silently misaligned with the project's import-resolution rules and produced misleading edge weights. They will return once the grammars are vetted.
- MCP server (
diffctx-mcp) now refuses to traverse outside the directories listed inDIFFCTX_ALLOWED_PATHS(OS-pathsep-separated) and refuses to start if the envvar is unset when run as a network-facing process. SeeSECURITY.mdfor the threat model.
Earlier releases shipped as treemapper; see
https://pypi.org/project/treemapper/#history for legacy versions and
https://github.com/nikolay-e/diffctx/releases for the corresponding GitHub
release notes (1.0.0 through 1.6.1).