diffctx 1.10.2__tar.gz → 1.12.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. {diffctx-1.10.2 → diffctx-1.12.0}/CHANGELOG.md +92 -0
  2. diffctx-1.12.0/PKG-INFO +273 -0
  3. diffctx-1.12.0/README.md +197 -0
  4. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/Cargo.lock +18 -18
  5. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/Cargo.toml +4 -3
  6. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/analytics.rs +6 -8
  7. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/limits.rs +8 -4
  8. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/needs.rs +4 -2
  9. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/scoring.rs +2 -2
  10. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/selection.rs +6 -6
  11. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/discovery.rs +25 -52
  12. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/history/cochange.rs +1 -4
  13. diffctx-1.12.0/diffctx/src/edges/mod.rs +282 -0
  14. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/filtering.rs +8 -8
  15. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/fragmentation.rs +12 -1
  16. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/git.rs +81 -15
  17. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/graph.rs +550 -118
  18. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/graph_export.rs +9 -12
  19. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/lib.rs +2 -0
  20. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/memory_pipeline.rs +4 -0
  21. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/parsers/tree_sitter_strategy.rs +6 -1
  22. diffctx-1.12.0/diffctx/src/peak_rss.rs +46 -0
  23. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/pipeline.rs +129 -12
  24. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/postpass.rs +23 -12
  25. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/project_graph.rs +18 -33
  26. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/pybridge.rs +53 -13
  27. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/render.rs +73 -12
  28. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/scoring.rs +17 -6
  29. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/select.rs +15 -0
  30. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/signatures.rs +22 -1
  31. diffctx-1.12.0/diffctx/src/token_corpus.rs +228 -0
  32. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/types.rs +14 -1
  33. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/utility/boltzmann.rs +2 -0
  34. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/tests/yaml_cases.rs +2 -5
  35. {diffctx-1.10.2 → diffctx-1.12.0}/pyproject.toml +3 -19
  36. {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/cli.py +214 -55
  37. diffctx-1.12.0/src/diffctx/diffctx/graph_analytics.py +293 -0
  38. {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/diffctx/graph_export.py +16 -8
  39. {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/diffctx/pipeline.py +22 -16
  40. diffctx-1.12.0/src/diffctx/diffctx/project_graph.py +36 -0
  41. {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/ignore.py +11 -3
  42. {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/logger.py +3 -2
  43. {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/main.py +116 -33
  44. {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/mcp/README.md +6 -0
  45. {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/mcp/server.py +35 -0
  46. {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/tokens.py +1 -4
  47. {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/tree.py +11 -0
  48. diffctx-1.12.0/src/diffctx/version.py +1 -0
  49. {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/writer.py +82 -23
  50. diffctx-1.10.2/PKG-INFO +0 -376
  51. diffctx-1.10.2/README.md +0 -298
  52. diffctx-1.10.2/diffctx/src/edges/mod.rs +0 -137
  53. diffctx-1.10.2/src/diffctx/diffctx/graph_analytics.py +0 -59
  54. diffctx-1.10.2/src/diffctx/diffctx/project_graph.py +0 -27
  55. diffctx-1.10.2/src/diffctx/version.py +0 -1
  56. {diffctx-1.10.2 → diffctx-1.12.0}/LICENSE +0 -0
  57. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/candidate_files.rs +0 -0
  58. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/analytics.rs +0 -0
  59. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/bm25.rs +0 -0
  60. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/budget.rs +0 -0
  61. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/category_weights.rs +0 -0
  62. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/edge_weights.rs +0 -0
  63. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/env_overrides.rs +0 -0
  64. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/extensions.rs +0 -0
  65. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/filtering.rs +0 -0
  66. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/fragmentation.rs +0 -0
  67. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/git.rs +0 -0
  68. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/graph_filtering.rs +0 -0
  69. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/importance.rs +0 -0
  70. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/mod.rs +0 -0
  71. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/mode.rs +0 -0
  72. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/parsers.rs +0 -0
  73. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/render.rs +0 -0
  74. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/tokenization.rs +0 -0
  75. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/weights.rs +0 -0
  76. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/core.rs +0 -0
  77. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/base.rs +0 -0
  78. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/config_edges/build_system.rs +0 -0
  79. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/config_edges/cicd.rs +0 -0
  80. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/config_edges/docker.rs +0 -0
  81. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/config_edges/generic.rs +0 -0
  82. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/config_edges/helm.rs +0 -0
  83. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/config_edges/kubernetes.rs +0 -0
  84. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/config_edges/mod.rs +0 -0
  85. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/document/mod.rs +0 -0
  86. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/history/mod.rs +0 -0
  87. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/ansible.rs +0 -0
  88. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/bazel.rs +0 -0
  89. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/c_family.rs +0 -0
  90. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/cargo_edges.rs +0 -0
  91. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/clojure.rs +0 -0
  92. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/css.rs +0 -0
  93. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/dart.rs +0 -0
  94. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/dbt.rs +0 -0
  95. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/dotnet.rs +0 -0
  96. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/elixir.rs +0 -0
  97. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/erlang.rs +0 -0
  98. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/go.rs +0 -0
  99. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/graphql.rs +0 -0
  100. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/haskell.rs +0 -0
  101. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/javascript.rs +0 -0
  102. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/julia.rs +0 -0
  103. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/jvm.rs +0 -0
  104. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/latex.rs +0 -0
  105. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/lua.rs +0 -0
  106. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/mod.rs +0 -0
  107. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/nim.rs +0 -0
  108. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/nix.rs +0 -0
  109. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/ocaml.rs +0 -0
  110. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/openapi.rs +0 -0
  111. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/perl.rs +0 -0
  112. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/php.rs +0 -0
  113. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/prisma.rs +0 -0
  114. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/protobuf.rs +0 -0
  115. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/python.rs +0 -0
  116. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/r_lang.rs +0 -0
  117. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/ruby.rs +0 -0
  118. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/rust_lang.rs +0 -0
  119. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/shell.rs +0 -0
  120. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/sql.rs +0 -0
  121. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/swift.rs +0 -0
  122. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/tags.rs +0 -0
  123. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/terraform.rs +0 -0
  124. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/zig.rs +0 -0
  125. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/similarity/lexical.rs +0 -0
  126. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/similarity/mod.rs +0 -0
  127. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/structural/containment.rs +0 -0
  128. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/structural/mod.rs +0 -0
  129. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/structural/sibling.rs +0 -0
  130. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/structural/testing.rs +0 -0
  131. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/interval.rs +0 -0
  132. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/languages.rs +0 -0
  133. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/main.rs +0 -0
  134. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/mode.rs +0 -0
  135. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/parsers/config_parser.rs +0 -0
  136. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/parsers/generic.rs +0 -0
  137. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/parsers/markdown.rs +0 -0
  138. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/parsers/mod.rs +0 -0
  139. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/ppr.rs +0 -0
  140. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/stopwords.rs +0 -0
  141. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/test_harness.rs +0 -0
  142. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/tokenizer.rs +0 -0
  143. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/utility/importance.rs +0 -0
  144. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/utility/mod.rs +0 -0
  145. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/utility/needs.rs +0 -0
  146. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/utility/scoring.rs +0 -0
  147. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/tests/common/mod.rs +0 -0
  148. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/tests/fixtures/garbage/garbage_api.py +0 -0
  149. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/tests/fixtures/garbage/garbage_constants.py +0 -0
  150. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/tests/fixtures/garbage/garbage_handlers.py +0 -0
  151. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/tests/fixtures/garbage/garbage_models.py +0 -0
  152. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/tests/fixtures/garbage/garbage_module.js +0 -0
  153. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/tests/fixtures/garbage/garbage_services.py +0 -0
  154. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/tests/fixtures/garbage/garbage_types.py +0 -0
  155. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/tests/fixtures/garbage/garbage_unrelated.yaml +0 -0
  156. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/tests/fixtures/garbage/garbage_utils.py +0 -0
  157. {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/tests/fixtures/garbage/garbage_validators.py +0 -0
  158. {diffctx-1.10.2 → diffctx-1.12.0}/rust-toolchain.toml +0 -0
  159. {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/__init__.py +0 -0
  160. {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/__main__.py +0 -0
  161. {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/clipboard.py +0 -0
  162. {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/diffctx/__init__.py +0 -0
  163. {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/mcp/__init__.py +0 -0
  164. {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/mcp/__main__.py +0 -0
  165. {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/mcp/formatting.py +0 -0
  166. {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/mcp/security.py +0 -0
  167. {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/py.typed +0 -0
@@ -7,6 +7,98 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.12.0] - 2026-07-23
11
+
12
+ ### Added
13
+
14
+ - `--timeout SECONDS` — wall-clock deadline for `--diff` analysis (default
15
+ 300); exceeding it exits `124` instead of hanging indefinitely (#70).
16
+ - `--no-ignores` — turns off every ignore rule (built-in patterns, project
17
+ `.gitignore`, `.diffctx/ignore`). `--no-default-ignores` only disables the
18
+ built-in list; its help now says so. Not supported with `--diff`.
19
+ - Output format is inferred from the `-o` extension when `-f` is omitted, so
20
+ `-o out.json` no longer writes Markdown into a `.json` file; a mismatch
21
+ between `-f` and the extension warns.
22
+
23
+ ### Fixed
24
+
25
+ - **All error logging was dead.** An import-time `NullHandler` made
26
+ `setup_logging` skip attaching a real handler, so `--log-level` was a no-op
27
+ and all 19 `logger.error/warning/exception` sites were silent — `diffctx . -o
28
+ /bad/path.md` exited 1 with no message at all.
29
+ - **diffctx invoked from inside a git hook silently analyzed the wrong
30
+ repository.** Git exports repo-locating env vars (`GIT_DIR`,
31
+ `GIT_INDEX_FILE`, `GIT_WORK_TREE`, ...) to hook subprocesses; inherited,
32
+ they overrode `-C` on every internal git call. All git spawns now scrub
33
+ these variables (`git_command()` in `git.rs`).
34
+ - YAML output preserved file content byte-exactly except for trailing
35
+ newlines; the block chomping indicator is now chosen per content.
36
+ - Arrow-function fragments bound to variables were never stub-eligible (#106).
37
+ - Decorated definitions rendered as a bare `@decorator` line without the
38
+ `class X:` / `def x():` header.
39
+ - `--max-depth`-pruned directories were labelled `_(empty directory)_` — a
40
+ factual lie to the reader; they now read
41
+ `_(children omitted: --max-depth reached)_` (`truncated: true` in
42
+ YAML/JSON).
43
+ - Mixed directory + glob arguments dropped the glob files' parent path from
44
+ node names.
45
+ - Double Ctrl-C printed a ~60-line traceback.
46
+ - Lock files `uv.lock`, `pdm.lock`, `bun.lock`, `bun.lockb`, `deno.lock` and
47
+ `flake.lock` leaked into output; they now join the other lock files in the
48
+ default ignore patterns.
49
+ - Large-repo hangs/OOM on trivial diffs (#70, #95): discovery no longer
50
+ re-reads and re-tokenizes the whole candidate universe per ensemble
51
+ strategy (one shared pass + a persistent per-blob token cache keyed by
52
+ `(blob OID, tokenizer epoch)`), and edge construction is two-pass with a
53
+ bounded per-source top-K instead of materializing up to tens of millions
54
+ of raw edges before the cap; pass 2 replays a compact 16-byte-per-emission
55
+ log instead of re-running the builders, so generation cost stays 1x.
56
+ Verified: gitpod 8000s-hang -> 35.7s,
57
+ pytorch 1848s-SIGKILL -> 7.4s, mui/material-ui OOM class recovered.
58
+ Outputs are bit-identical (gated by `benchmarks/equivalence_gate.py`).
59
+
60
+ ### Known limitations
61
+
62
+ - Near-dense edge emission on huge same-directory trees (observed: 199M
63
+ raw edges, 37GB peak on one mui/material-ui instance) remains expensive
64
+ even with bounded construction; tracked in #116.
65
+
66
+ ### Changed
67
+
68
+ - **The token budget is now a hard cap.** The changed-files post-pass no
69
+ longer exceeds the budget to guarantee representation: a changed file
70
+ whose cheapest representative does not fit stays unrepresented (visible
71
+ as changed-file retention < 1). `--budget 0` therefore yields an empty
72
+ selection (use `--full` for changed files only); CLI help updated.
73
+ - Latency telemetry: new `graph_build_ms` phase (graph construction was
74
+ previously misattributed to `scoring_ms`, which now measures pure
75
+ ranking) and `peak_rss_bytes` (in-process peak memory). Release builds
76
+ carry line tables (`debug = "line-tables-only"`) for profiling at no
77
+ runtime cost.
78
+ - CLI diagnostics are honest end to end: an exit-code table in `--help`
79
+ (2 usage, 3 environment, 4 empty diff, 124 timeout), flag-value validation
80
+ exits 2 instead of 1, git failures report a single line plus a
81
+ `git log --oneline` hint on unknown revisions, conflicting flags warn
82
+ (`-q`+`--log-level`, `--full`+selection flags, ...), a failed clipboard
83
+ copy warns before falling back to stdout, and `--tau` / `--scoring` /
84
+ `--alpha` / `--budget 0` help text describes what actually happens.
85
+ - `graph --summary` reports category shares as percentages, suppresses
86
+ degenerate top-referenced lists, detects cycles over dominant-direction
87
+ edges only, derives churn from `git log --since`, and disambiguates
88
+ duplicate mermaid labels to relative paths.
89
+
90
+ ## [1.11.0] - 2026-07-07
91
+
92
+ ### Changed
93
+
94
+ - **The default `--format` is now `md` (Markdown), previously `yaml`.** Markdown
95
+ is ~7% more token-efficient than YAML on real diffs and is preferred by
96
+ reviewers for code-fragment-heavy output (evidence:
97
+ `benchmarks/real_world_diff_bench/`). YAML remains available via `-f yaml`.
98
+ This also changes the default stdout format of `treemapper` (a passthrough
99
+ wrapper over this engine); downstream scripts that parse the default output
100
+ must now pass `-f yaml` explicitly. (#104)
101
+
10
102
  ## [1.10.0] - 2026-06-20
11
103
 
12
104
  ### Changed
@@ -0,0 +1,273 @@
1
+ Metadata-Version: 2.4
2
+ Name: diffctx
3
+ Version: 1.12.0
4
+ Classifier: Development Status :: 5 - Production/Stable
5
+ Classifier: Environment :: Console
6
+ Classifier: Intended Audience :: Developers
7
+ Classifier: Operating System :: OS Independent
8
+ Classifier: Programming Language :: Python :: 3 :: Only
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Programming Language :: Python :: 3.14
14
+ Classifier: Topic :: Software Development
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Classifier: Topic :: Software Development :: Version Control :: Git
17
+ Classifier: Topic :: Utilities
18
+ Classifier: Typing :: Typed
19
+ Requires-Dist: pathspec>=0.11,<2.0
20
+ Requires-Dist: black>=23.0.0,<27.0 ; extra == 'dev'
21
+ Requires-Dist: charset-normalizer>=3.0,<4.0 ; extra == 'dev'
22
+ Requires-Dist: coverage>=7.0,<8.0 ; extra == 'dev'
23
+ Requires-Dist: diffctx[tree-sitter] ; extra == 'dev'
24
+ Requires-Dist: hypothesis>=6.0,<7.0 ; extra == 'dev'
25
+ Requires-Dist: import-linter>=2.0,<3.0 ; extra == 'dev'
26
+ Requires-Dist: mypy>=1.0,<3.0 ; extra == 'dev'
27
+ Requires-Dist: pre-commit>=3.0,<5.0 ; extra == 'dev'
28
+ Requires-Dist: pygit2>=1.12,<2.0 ; extra == 'dev'
29
+ Requires-Dist: pytest>=7.0,<10.0 ; extra == 'dev'
30
+ Requires-Dist: pytest-asyncio>=0.23,<2.0 ; extra == 'dev'
31
+ Requires-Dist: pytest-cov>=3.0,<8.0 ; extra == 'dev'
32
+ Requires-Dist: pytest-timeout>=2.1,<3.0 ; extra == 'dev'
33
+ Requires-Dist: pytest-xdist>=3.0,<4.0 ; extra == 'dev'
34
+ Requires-Dist: pyyaml>=6.0.2,<8.0 ; extra == 'dev'
35
+ Requires-Dist: radon>=6.0,<7.0 ; extra == 'dev'
36
+ Requires-Dist: ruff>=0.4,<1.0 ; extra == 'dev'
37
+ Requires-Dist: tiktoken>=0.9,<1.0 ; extra == 'dev'
38
+ Requires-Dist: types-pyyaml>=6.0,<7.0 ; extra == 'dev'
39
+ Requires-Dist: charset-normalizer>=3.0,<4.0 ; extra == 'full'
40
+ Requires-Dist: diffctx[tree-sitter] ; extra == 'full'
41
+ Requires-Dist: anyio>=4.5,<5.0 ; extra == 'mcp'
42
+ Requires-Dist: mcp>=1.27,<2.0 ; extra == 'mcp'
43
+ Requires-Dist: tree-sitter>=0.21,<1.0 ; extra == 'tree-sitter'
44
+ Requires-Dist: tree-sitter-c>=0.21,<1.0 ; extra == 'tree-sitter'
45
+ Requires-Dist: tree-sitter-c-sharp>=0.21,<1.0 ; extra == 'tree-sitter'
46
+ Requires-Dist: tree-sitter-cpp>=0.22,<1.0 ; extra == 'tree-sitter'
47
+ Requires-Dist: tree-sitter-elixir>=0.3,<1.0 ; extra == 'tree-sitter'
48
+ Requires-Dist: tree-sitter-go>=0.21,<1.0 ; extra == 'tree-sitter'
49
+ Requires-Dist: tree-sitter-html>=0.23,<1.0 ; extra == 'tree-sitter'
50
+ Requires-Dist: tree-sitter-java>=0.21,<1.0 ; extra == 'tree-sitter'
51
+ Requires-Dist: tree-sitter-javascript>=0.21,<1.0 ; extra == 'tree-sitter'
52
+ Requires-Dist: tree-sitter-lua>=0.5,<1.0 ; extra == 'tree-sitter'
53
+ Requires-Dist: tree-sitter-php>=0.24,<1.0 ; extra == 'tree-sitter'
54
+ Requires-Dist: tree-sitter-python>=0.21,<1.0 ; extra == 'tree-sitter'
55
+ Requires-Dist: tree-sitter-ruby>=0.21,<1.0 ; extra == 'tree-sitter'
56
+ Requires-Dist: tree-sitter-rust>=0.21,<1.0 ; extra == 'tree-sitter'
57
+ Requires-Dist: tree-sitter-scala>=0.24,<1.0 ; extra == 'tree-sitter'
58
+ Requires-Dist: tree-sitter-swift>=0.0.1,<1.0 ; extra == 'tree-sitter'
59
+ Requires-Dist: tree-sitter-typescript>=0.21,<1.0 ; extra == 'tree-sitter'
60
+ Provides-Extra: dev
61
+ Provides-Extra: full
62
+ Provides-Extra: mcp
63
+ Provides-Extra: tree-sitter
64
+ License-File: LICENSE
65
+ Summary: Export codebase structure and contents for AI/LLM context
66
+ Keywords: ai,chatgpt,claude,code-analysis,code-context,code-review,code-to-prompt,codebase,context,context-selection,diff-context,directory-tree,export,git-diff,json,llm,llm-context,mcp,model-context-protocol,tree,yaml
67
+ Author-email: Nikolay Eremeev <nikolay.eremeev@outlook.com>
68
+ License-Expression: Apache-2.0
69
+ Requires-Python: >=3.10
70
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
71
+ Project-URL: Changelog, https://github.com/nikolay-e/diffctx/releases
72
+ Project-URL: Homepage, https://github.com/nikolay-e/diffctx
73
+ Project-URL: Issues, https://github.com/nikolay-e/diffctx/issues
74
+ Project-URL: Repository, https://github.com/nikolay-e/diffctx
75
+
76
+ # diffctx — smart diff context for LLM code review
77
+
78
+ [![CI](https://github.com/nikolay-e/diffctx/actions/workflows/ci.yml/badge.svg)](https://github.com/nikolay-e/diffctx/actions/workflows/ci.yml)
79
+ [![PyPI](https://img.shields.io/pypi/v/diffctx)](https://pypi.org/project/diffctx/)
80
+ [![License](https://img.shields.io/pypi/l/diffctx)](https://pypi.org/project/diffctx/)
81
+
82
+ **diffctx selects the minimum code an LLM needs to review a git diff.**
83
+ Instead of pasting whole files, it walks the dependency graph from the changed
84
+ lines outward and stops as soon as additional context stops paying for itself.
85
+
86
+ > Coming from [`treemapper`](https://pypi.org/project/treemapper/)? That name is
87
+ > deprecated — it was a thin wrapper around diffctx. Every command, flag, and
88
+ > API call works unchanged: `treemapper` → `diffctx`, `treemapper-mcp` → `diffctx-mcp`.
89
+
90
+ ## Why not just use `tree` or repomix?
91
+
92
+ | | `tree` | repomix | Claude Code Review | **diffctx** |
93
+ |---|:---:|:---:|:---:|:---:|
94
+ | **Primary use case** | directory listing | full repo export | automated PR review | **diff context for code review** |
95
+ | Smart diff context | ✗ | ✗ | ✓ | ✓ |
96
+ | Works with any LLM | ✓ | ✓ | Claude only | ✓ |
97
+ | Free / local / offline | ✓ | ✓ | $15–25/review | ✓ |
98
+ | GitHub required | ✗ | ✗ | ✓ | ✗ |
99
+ | Multiple output formats | ✗ | limited | — | YAML/JSON/MD/txt |
100
+ | Python API | ✗ | ✗ | ✗ | ✓ |
101
+ | MCP server | ✗ | ✗ | ✗ | ✓ |
102
+
103
+ ## Install
104
+
105
+ ```bash
106
+ uvx diffctx . --diff HEAD~1 # zero-install, run once via uv
107
+ pipx install diffctx # recommended: isolated CLI, no venv needed
108
+ pip install diffctx # or: into an active environment
109
+ pipx install 'diffctx[mcp]' # + MCP server for AI assistants
110
+ ```
111
+
112
+ A standalone binary (no Python required) is on the
113
+ [releases page](https://github.com/nikolay-e/diffctx/releases/latest).
114
+ The `[tree-sitter]` extra adds AST-level parsing for more accurate context
115
+ selection across 30+ languages.
116
+
117
+ ## Quick start
118
+
119
+ ```bash
120
+ diffctx . --diff HEAD~1 # smart context for last commit → paste into Claude/ChatGPT
121
+ diffctx . -f md -c # full codebase export → clipboard in Markdown
122
+ ```
123
+
124
+ ![diffctx demo](https://raw.githubusercontent.com/nikolay-e/diffctx/main/docs/demo.gif)
125
+
126
+ *`diffctx . --diff HEAD~1` selects only the fragments — functions, imports,
127
+ type definitions — that an LLM actually needs to review the last commit,
128
+ instead of dumping every changed file in full.*
129
+
130
+ ## Diff context mode
131
+
132
+ Finds the minimal set of code fragments needed to understand a change —
133
+ imports, callers, type definitions, config dependencies — across 50+ file
134
+ types. It builds a code graph (imports, co-changes, type refs), propagates
135
+ relevance from the changed lines outward, and stops when relevance drops below
136
+ `--tau` or the `--budget` token cap is reached.
137
+
138
+ | Flag | Default | Description |
139
+ |-------------|---------|--------------------------------------------------------------------------|
140
+ | `--scoring` | `ego` | `ego` = bounded expansion around changed nodes (fast, predictable radius); `ppr` = Personalized PageRank (global, smoother decay, slower); `bm25` = lexical retrieval against the diff hunks (baseline for sparse graphs) |
141
+ | `--budget` | auto | Hard token cap: `N` enforces a fixed cap, `-1` disables it, `0` is a strict-zero floor (empty selection; use `--full` for changed files only) |
142
+ | `--alpha` | 0.60 | PPR damping; higher = context clusters tighter around changes (`--scoring ppr` only) |
143
+ | `--tau` | 0.12 | Relevance threshold for full fragment content; lower-scoring fragments are stubbed or dropped (lower = more context) |
144
+ | `--full` | false | Only the changed files, every fragment, no related-code context |
145
+ | `--timeout` | 300 | Wall-clock deadline in seconds; on expiry diffctx exits 124 instead of hanging |
146
+
147
+ Calibration of `--alpha`, `--tau`, and the edge-weight priors:
148
+ [`docs/parameter-strategy.md`](docs/parameter-strategy.md). Theory:
149
+ [Context-Selection for Git Diff (Zenodo, 2026)](https://doi.org/10.5281/zenodo.18824580).
150
+
151
+ ### `graph` subcommand
152
+
153
+ Explore the underlying dependency graph directly, without a diff:
154
+
155
+ ```bash
156
+ diffctx graph . # Mermaid graph of directory deps (default)
157
+ diffctx graph . --summary # cycles, hotspots, coupling metrics
158
+ diffctx graph . --level fragment -f json # fragment-level graph as JSON
159
+ diffctx graph . --level file -f graphml -o g.xml # file-level graph as GraphML
160
+ ```
161
+
162
+ ## Usage
163
+
164
+ <!-- BEGIN USAGE -->
165
+ ```bash
166
+ # full codebase export:
167
+ diffctx . # Markdown to stdout + token count
168
+ diffctx . -f md -c # Markdown → clipboard
169
+ diffctx . -f json -o tree.json # JSON → file
170
+ diffctx . --no-content # structure only, no file contents
171
+ diffctx . --max-depth 3 # limit depth
172
+ diffctx . -i custom.ignore # custom ignore patterns
173
+
174
+ # diff context mode (requires git repo):
175
+ diffctx . --diff # uncommitted changes (working tree vs HEAD)
176
+ diffctx . --diff HEAD~1 # context for last commit
177
+ diffctx . --diff main..feature # context for feature branch
178
+ diffctx . --diff HEAD~1 --budget 30000 # limit to ~30k tokens
179
+ diffctx . --diff HEAD~1 -c # diff context to clipboard
180
+ ```
181
+ <!-- END USAGE -->
182
+
183
+ Every run reports token count and size on stderr — `12,847 tokens
184
+ (o200k_base), 52.3 KB` (tiktoken, the GPT-4o tokenizer; `~`-prefixed
185
+ approximation above 1 MB). `-c/--copy` sends output to the clipboard via
186
+ `pbcopy` (macOS), `clip` (Windows), or `wl-copy`/`xclip`/`xsel` (Linux).
187
+ Unreadable files are replaced by placeholders such as `<binary file: N bytes>`,
188
+ `<file too large: N bytes>`, or `<unreadable content: not utf-8>`.
189
+
190
+ ## Python API
191
+
192
+ ```python
193
+ from pathlib import Path
194
+ from diffctx import build_diff_context, map_directory, to_json, to_markdown, to_text, to_yaml
195
+
196
+ ctx = build_diff_context(
197
+ Path("."),
198
+ "HEAD~1..HEAD",
199
+ budget_tokens=None, # None = auto; 0 = strict-zero floor (empty); -1 = uncapped; N = hard cap
200
+ alpha=0.6,
201
+ tau=0.12,
202
+ full=False,
203
+ scoring_mode="ego",
204
+ timeout=300,
205
+ )
206
+ print(to_markdown(ctx))
207
+
208
+ tree = map_directory(
209
+ ".",
210
+ max_depth=None,
211
+ no_content=False,
212
+ max_file_bytes=None,
213
+ ignore_file=None,
214
+ no_default_ignores=False,
215
+ whitelist_file=None,
216
+ )
217
+ print(to_yaml(tree))
218
+ ```
219
+
220
+ ## MCP server
221
+
222
+ diffctx includes an [MCP](https://modelcontextprotocol.io) server that lets AI
223
+ assistants (Claude Code, Cursor, Windsurf, etc.) call diff context analysis
224
+ automatically during code review. Install with `pip install 'diffctx[mcp]'`
225
+ and add to your MCP client config (e.g. `~/.claude/mcp.json` for Claude Code):
226
+
227
+ ```json
228
+ {
229
+ "mcpServers": {
230
+ "diffctx": {
231
+ "command": "diffctx-mcp"
232
+ }
233
+ }
234
+ }
235
+ ```
236
+
237
+ The server exposes a `get_diff_context` tool that assistants call when
238
+ reviewing PRs, explaining changes, or investigating broken tests. Configs for
239
+ Cursor, Continue, Windsurf, and Zed:
240
+ [`src/diffctx/mcp/README.md`](src/diffctx/mcp/README.md).
241
+
242
+ ## Ignore patterns
243
+
244
+ Respects `.gitignore` and `.diffctx/ignore` automatically — hierarchically at
245
+ every directory level, with gitignore semantics (negation `!important.log`,
246
+ anchored `/root_only.txt`). `.diffctx/whitelist` acts as an include-only
247
+ filter, and the output file is always auto-ignored. `--no-default-ignores`
248
+ disables the built-in patterns; `--no-ignores` disables all ignore rules
249
+ (tree mode only).
250
+
251
+ ## Exit codes
252
+
253
+ | Code | Meaning |
254
+ |------|---------|
255
+ | `0` | Success — output contains content |
256
+ | `1` | Runtime error (bad path, permission denied, etc.) |
257
+ | `2` | Usage error (invalid flags/arguments) |
258
+ | `3` | Environment error (`--diff` outside a git repo, git not installed, no commits yet) |
259
+ | `4` | `--diff` produced no semantic context (clean tree, binary-only, everything filtered); output is still emitted. Deletion/rename-only diffs list `deleted_files`/`renamed_files` and exit `0` |
260
+ | `130`| Interrupted (Ctrl-C) |
261
+ | `141`| Broken pipe (e.g. piping into `head`) |
262
+
263
+ ## License
264
+
265
+ Apache 2.0
266
+
267
+ ---
268
+
269
+ - [Changelog](CHANGELOG.md)
270
+ - [Security policy](SECURITY.md) — threat model and vulnerability reporting
271
+ - [Parameter strategy](docs/parameter-strategy.md) — how `--alpha`,
272
+ `--tau`, and edge weights are calibrated
273
+
@@ -0,0 +1,197 @@
1
+ # diffctx — smart diff context for LLM code review
2
+
3
+ [![CI](https://github.com/nikolay-e/diffctx/actions/workflows/ci.yml/badge.svg)](https://github.com/nikolay-e/diffctx/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/diffctx)](https://pypi.org/project/diffctx/)
5
+ [![License](https://img.shields.io/pypi/l/diffctx)](https://pypi.org/project/diffctx/)
6
+
7
+ **diffctx selects the minimum code an LLM needs to review a git diff.**
8
+ Instead of pasting whole files, it walks the dependency graph from the changed
9
+ lines outward and stops as soon as additional context stops paying for itself.
10
+
11
+ > Coming from [`treemapper`](https://pypi.org/project/treemapper/)? That name is
12
+ > deprecated — it was a thin wrapper around diffctx. Every command, flag, and
13
+ > API call works unchanged: `treemapper` → `diffctx`, `treemapper-mcp` → `diffctx-mcp`.
14
+
15
+ ## Why not just use `tree` or repomix?
16
+
17
+ | | `tree` | repomix | Claude Code Review | **diffctx** |
18
+ |---|:---:|:---:|:---:|:---:|
19
+ | **Primary use case** | directory listing | full repo export | automated PR review | **diff context for code review** |
20
+ | Smart diff context | ✗ | ✗ | ✓ | ✓ |
21
+ | Works with any LLM | ✓ | ✓ | Claude only | ✓ |
22
+ | Free / local / offline | ✓ | ✓ | $15–25/review | ✓ |
23
+ | GitHub required | ✗ | ✗ | ✓ | ✗ |
24
+ | Multiple output formats | ✗ | limited | — | YAML/JSON/MD/txt |
25
+ | Python API | ✗ | ✗ | ✗ | ✓ |
26
+ | MCP server | ✗ | ✗ | ✗ | ✓ |
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ uvx diffctx . --diff HEAD~1 # zero-install, run once via uv
32
+ pipx install diffctx # recommended: isolated CLI, no venv needed
33
+ pip install diffctx # or: into an active environment
34
+ pipx install 'diffctx[mcp]' # + MCP server for AI assistants
35
+ ```
36
+
37
+ A standalone binary (no Python required) is on the
38
+ [releases page](https://github.com/nikolay-e/diffctx/releases/latest).
39
+ The `[tree-sitter]` extra adds AST-level parsing for more accurate context
40
+ selection across 30+ languages.
41
+
42
+ ## Quick start
43
+
44
+ ```bash
45
+ diffctx . --diff HEAD~1 # smart context for last commit → paste into Claude/ChatGPT
46
+ diffctx . -f md -c # full codebase export → clipboard in Markdown
47
+ ```
48
+
49
+ ![diffctx demo](https://raw.githubusercontent.com/nikolay-e/diffctx/main/docs/demo.gif)
50
+
51
+ *`diffctx . --diff HEAD~1` selects only the fragments — functions, imports,
52
+ type definitions — that an LLM actually needs to review the last commit,
53
+ instead of dumping every changed file in full.*
54
+
55
+ ## Diff context mode
56
+
57
+ Finds the minimal set of code fragments needed to understand a change —
58
+ imports, callers, type definitions, config dependencies — across 50+ file
59
+ types. It builds a code graph (imports, co-changes, type refs), propagates
60
+ relevance from the changed lines outward, and stops when relevance drops below
61
+ `--tau` or the `--budget` token cap is reached.
62
+
63
+ | Flag | Default | Description |
64
+ |-------------|---------|--------------------------------------------------------------------------|
65
+ | `--scoring` | `ego` | `ego` = bounded expansion around changed nodes (fast, predictable radius); `ppr` = Personalized PageRank (global, smoother decay, slower); `bm25` = lexical retrieval against the diff hunks (baseline for sparse graphs) |
66
+ | `--budget` | auto | Hard token cap: `N` enforces a fixed cap, `-1` disables it, `0` is a strict-zero floor (empty selection; use `--full` for changed files only) |
67
+ | `--alpha` | 0.60 | PPR damping; higher = context clusters tighter around changes (`--scoring ppr` only) |
68
+ | `--tau` | 0.12 | Relevance threshold for full fragment content; lower-scoring fragments are stubbed or dropped (lower = more context) |
69
+ | `--full` | false | Only the changed files, every fragment, no related-code context |
70
+ | `--timeout` | 300 | Wall-clock deadline in seconds; on expiry diffctx exits 124 instead of hanging |
71
+
72
+ Calibration of `--alpha`, `--tau`, and the edge-weight priors:
73
+ [`docs/parameter-strategy.md`](docs/parameter-strategy.md). Theory:
74
+ [Context-Selection for Git Diff (Zenodo, 2026)](https://doi.org/10.5281/zenodo.18824580).
75
+
76
+ ### `graph` subcommand
77
+
78
+ Explore the underlying dependency graph directly, without a diff:
79
+
80
+ ```bash
81
+ diffctx graph . # Mermaid graph of directory deps (default)
82
+ diffctx graph . --summary # cycles, hotspots, coupling metrics
83
+ diffctx graph . --level fragment -f json # fragment-level graph as JSON
84
+ diffctx graph . --level file -f graphml -o g.xml # file-level graph as GraphML
85
+ ```
86
+
87
+ ## Usage
88
+
89
+ <!-- BEGIN USAGE -->
90
+ ```bash
91
+ # full codebase export:
92
+ diffctx . # Markdown to stdout + token count
93
+ diffctx . -f md -c # Markdown → clipboard
94
+ diffctx . -f json -o tree.json # JSON → file
95
+ diffctx . --no-content # structure only, no file contents
96
+ diffctx . --max-depth 3 # limit depth
97
+ diffctx . -i custom.ignore # custom ignore patterns
98
+
99
+ # diff context mode (requires git repo):
100
+ diffctx . --diff # uncommitted changes (working tree vs HEAD)
101
+ diffctx . --diff HEAD~1 # context for last commit
102
+ diffctx . --diff main..feature # context for feature branch
103
+ diffctx . --diff HEAD~1 --budget 30000 # limit to ~30k tokens
104
+ diffctx . --diff HEAD~1 -c # diff context to clipboard
105
+ ```
106
+ <!-- END USAGE -->
107
+
108
+ Every run reports token count and size on stderr — `12,847 tokens
109
+ (o200k_base), 52.3 KB` (tiktoken, the GPT-4o tokenizer; `~`-prefixed
110
+ approximation above 1 MB). `-c/--copy` sends output to the clipboard via
111
+ `pbcopy` (macOS), `clip` (Windows), or `wl-copy`/`xclip`/`xsel` (Linux).
112
+ Unreadable files are replaced by placeholders such as `<binary file: N bytes>`,
113
+ `<file too large: N bytes>`, or `<unreadable content: not utf-8>`.
114
+
115
+ ## Python API
116
+
117
+ ```python
118
+ from pathlib import Path
119
+ from diffctx import build_diff_context, map_directory, to_json, to_markdown, to_text, to_yaml
120
+
121
+ ctx = build_diff_context(
122
+ Path("."),
123
+ "HEAD~1..HEAD",
124
+ budget_tokens=None, # None = auto; 0 = strict-zero floor (empty); -1 = uncapped; N = hard cap
125
+ alpha=0.6,
126
+ tau=0.12,
127
+ full=False,
128
+ scoring_mode="ego",
129
+ timeout=300,
130
+ )
131
+ print(to_markdown(ctx))
132
+
133
+ tree = map_directory(
134
+ ".",
135
+ max_depth=None,
136
+ no_content=False,
137
+ max_file_bytes=None,
138
+ ignore_file=None,
139
+ no_default_ignores=False,
140
+ whitelist_file=None,
141
+ )
142
+ print(to_yaml(tree))
143
+ ```
144
+
145
+ ## MCP server
146
+
147
+ diffctx includes an [MCP](https://modelcontextprotocol.io) server that lets AI
148
+ assistants (Claude Code, Cursor, Windsurf, etc.) call diff context analysis
149
+ automatically during code review. Install with `pip install 'diffctx[mcp]'`
150
+ and add to your MCP client config (e.g. `~/.claude/mcp.json` for Claude Code):
151
+
152
+ ```json
153
+ {
154
+ "mcpServers": {
155
+ "diffctx": {
156
+ "command": "diffctx-mcp"
157
+ }
158
+ }
159
+ }
160
+ ```
161
+
162
+ The server exposes a `get_diff_context` tool that assistants call when
163
+ reviewing PRs, explaining changes, or investigating broken tests. Configs for
164
+ Cursor, Continue, Windsurf, and Zed:
165
+ [`src/diffctx/mcp/README.md`](src/diffctx/mcp/README.md).
166
+
167
+ ## Ignore patterns
168
+
169
+ Respects `.gitignore` and `.diffctx/ignore` automatically — hierarchically at
170
+ every directory level, with gitignore semantics (negation `!important.log`,
171
+ anchored `/root_only.txt`). `.diffctx/whitelist` acts as an include-only
172
+ filter, and the output file is always auto-ignored. `--no-default-ignores`
173
+ disables the built-in patterns; `--no-ignores` disables all ignore rules
174
+ (tree mode only).
175
+
176
+ ## Exit codes
177
+
178
+ | Code | Meaning |
179
+ |------|---------|
180
+ | `0` | Success — output contains content |
181
+ | `1` | Runtime error (bad path, permission denied, etc.) |
182
+ | `2` | Usage error (invalid flags/arguments) |
183
+ | `3` | Environment error (`--diff` outside a git repo, git not installed, no commits yet) |
184
+ | `4` | `--diff` produced no semantic context (clean tree, binary-only, everything filtered); output is still emitted. Deletion/rename-only diffs list `deleted_files`/`renamed_files` and exit `0` |
185
+ | `130`| Interrupted (Ctrl-C) |
186
+ | `141`| Broken pipe (e.g. piping into `head`) |
187
+
188
+ ## License
189
+
190
+ Apache 2.0
191
+
192
+ ---
193
+
194
+ - [Changelog](CHANGELOG.md)
195
+ - [Security policy](SECURITY.md) — threat model and vulnerability reporting
196
+ - [Parameter strategy](docs/parameter-strategy.md) — how `--alpha`,
197
+ `--tau`, and edge weights are calibrated
@@ -63,9 +63,9 @@ dependencies = [
63
63
 
64
64
  [[package]]
65
65
  name = "anyhow"
66
- version = "1.0.102"
66
+ version = "1.0.103"
67
67
  source = "registry+https://github.com/rust-lang/crates.io-index"
68
- checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
68
+ checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
69
69
 
70
70
  [[package]]
71
71
  name = "base64"
@@ -123,9 +123,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
123
123
 
124
124
  [[package]]
125
125
  name = "clap"
126
- version = "4.6.1"
126
+ version = "4.6.2"
127
127
  source = "registry+https://github.com/rust-lang/crates.io-index"
128
- checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
128
+ checksum = "dd059f9da4f5c36b3787f65d38ccaab1cc315f07b01f89abc8359ee6a8205011"
129
129
  dependencies = [
130
130
  "clap_builder",
131
131
  "clap_derive",
@@ -133,9 +133,9 @@ dependencies = [
133
133
 
134
134
  [[package]]
135
135
  name = "clap_builder"
136
- version = "4.6.0"
136
+ version = "4.6.2"
137
137
  source = "registry+https://github.com/rust-lang/crates.io-index"
138
- checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
138
+ checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b"
139
139
  dependencies = [
140
140
  "anstream",
141
141
  "anstyle",
@@ -194,11 +194,12 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
194
194
 
195
195
  [[package]]
196
196
  name = "diffctx"
197
- version = "1.10.0"
197
+ version = "1.12.0"
198
198
  dependencies = [
199
199
  "anyhow",
200
200
  "clap",
201
201
  "glob",
202
+ "libc",
202
203
  "libtest-mimic",
203
204
  "mimalloc",
204
205
  "once_cell",
@@ -210,7 +211,6 @@ dependencies = [
210
211
  "serde_json",
211
212
  "serde_yaml",
212
213
  "similar",
213
- "smallvec",
214
214
  "tempfile",
215
215
  "thiserror",
216
216
  "tiktoken-rs",
@@ -399,9 +399,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
399
399
 
400
400
  [[package]]
401
401
  name = "libc"
402
- version = "0.2.185"
402
+ version = "0.2.186"
403
403
  source = "registry+https://github.com/rust-lang/crates.io-index"
404
- checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f"
404
+ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
405
405
 
406
406
  [[package]]
407
407
  name = "libmimalloc-sys"
@@ -606,9 +606,9 @@ dependencies = [
606
606
 
607
607
  [[package]]
608
608
  name = "regex"
609
- version = "1.12.4"
609
+ version = "1.13.1"
610
610
  source = "registry+https://github.com/rust-lang/crates.io-index"
611
- checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
611
+ checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
612
612
  dependencies = [
613
613
  "aho-corasick",
614
614
  "memchr",
@@ -618,9 +618,9 @@ dependencies = [
618
618
 
619
619
  [[package]]
620
620
  name = "regex-automata"
621
- version = "0.4.14"
621
+ version = "0.4.16"
622
622
  source = "registry+https://github.com/rust-lang/crates.io-index"
623
- checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
623
+ checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad"
624
624
  dependencies = [
625
625
  "aho-corasick",
626
626
  "memchr",
@@ -635,9 +635,9 @@ checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
635
635
 
636
636
  [[package]]
637
637
  name = "rustc-hash"
638
- version = "2.1.2"
638
+ version = "2.1.3"
639
639
  source = "registry+https://github.com/rust-lang/crates.io-index"
640
- checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
640
+ checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
641
641
 
642
642
  [[package]]
643
643
  name = "rustix"
@@ -964,9 +964,9 @@ dependencies = [
964
964
 
965
965
  [[package]]
966
966
  name = "tree-sitter-cmake"
967
- version = "0.7.1"
967
+ version = "0.7.4"
968
968
  source = "registry+https://github.com/rust-lang/crates.io-index"
969
- checksum = "7c1b35d1dd7396d24b3e826bb0f975b915ec7e9125b989d5e9d24ebb6a08509a"
969
+ checksum = "164e0c4f4236ec5ceff14824a5528615cf462e100467e49826442ff57d327061"
970
970
  dependencies = [
971
971
  "cc",
972
972
  "tree-sitter-language",