diffctx 1.12.2__tar.gz → 1.12.3__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 (158) hide show
  1. {diffctx-1.12.2 → diffctx-1.12.3}/CHANGELOG.md +80 -6
  2. {diffctx-1.12.2 → diffctx-1.12.3}/Cargo.lock +11 -11
  3. diffctx-1.12.3/Cargo.toml +20 -0
  4. {diffctx-1.12.2 → diffctx-1.12.3}/PKG-INFO +43 -13
  5. {diffctx-1.12.2/crates/diffctx-native → diffctx-1.12.3}/README.md +42 -12
  6. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/Cargo.toml +4 -4
  7. {diffctx-1.12.2 → diffctx-1.12.3/crates/diffctx-native}/README.md +42 -12
  8. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/analytics.rs +53 -14
  9. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/candidate_files.rs +26 -2
  10. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/analytics.rs +0 -2
  11. diffctx-1.12.3/crates/diffctx-native/src/config/env_overrides.rs +273 -0
  12. diffctx-1.12.3/crates/diffctx-native/src/discovery.rs +579 -0
  13. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/mod.rs +5 -2
  14. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/filtering.rs +93 -0
  15. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/git.rs +567 -9
  16. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/graph.rs +340 -30
  17. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/graph_export.rs +57 -0
  18. diffctx-1.12.3/crates/diffctx-native/src/interval.rs +210 -0
  19. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/languages.rs +69 -0
  20. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/parsers/generic.rs +55 -0
  21. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/parsers/tree_sitter_strategy.rs +517 -1
  22. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/pipeline.rs +138 -19
  23. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/postpass.rs +98 -0
  24. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/ppr.rs +231 -0
  25. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/pybridge.rs +26 -190
  26. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/render.rs +115 -7
  27. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/select.rs +256 -3
  28. diffctx-1.12.3/crates/diffctx-native/src/signatures.rs +312 -0
  29. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/token_corpus.rs +111 -0
  30. {diffctx-1.12.2 → diffctx-1.12.3}/pyproject.toml +4 -2
  31. {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/_app.py +45 -11
  32. {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/_native/pipeline.py +47 -4
  33. {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/cli.py +65 -2
  34. {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/mcp/README.md +11 -2
  35. {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/mcp/security.py +23 -5
  36. {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/mcp/server.py +137 -30
  37. diffctx-1.12.3/src/diffctx/version.py +1 -0
  38. {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/writer.py +29 -6
  39. diffctx-1.12.2/Cargo.toml +0 -12
  40. diffctx-1.12.2/crates/diffctx-native/src/config/env_overrides.rs +0 -125
  41. diffctx-1.12.2/crates/diffctx-native/src/discovery.rs +0 -293
  42. diffctx-1.12.2/crates/diffctx-native/src/interval.rs +0 -78
  43. diffctx-1.12.2/crates/diffctx-native/src/signatures.rs +0 -144
  44. diffctx-1.12.2/src/diffctx/version.py +0 -1
  45. {diffctx-1.12.2 → diffctx-1.12.3}/LICENSE +0 -0
  46. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/bm25.rs +0 -0
  47. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/budget.rs +0 -0
  48. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/category_weights.rs +0 -0
  49. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/edge_weights.rs +0 -0
  50. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/extensions.rs +0 -0
  51. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/filtering.rs +0 -0
  52. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/fragmentation.rs +0 -0
  53. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/git.rs +0 -0
  54. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/graph_filtering.rs +0 -0
  55. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/importance.rs +0 -0
  56. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/limits.rs +0 -0
  57. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/mod.rs +0 -0
  58. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/mode.rs +0 -0
  59. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/needs.rs +0 -0
  60. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/parsers.rs +0 -0
  61. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/render.rs +0 -0
  62. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/scoring.rs +0 -0
  63. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/selection.rs +0 -0
  64. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/tokenization.rs +0 -0
  65. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/weights.rs +0 -0
  66. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/core.rs +0 -0
  67. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/base.rs +0 -0
  68. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/config_edges/build_system.rs +0 -0
  69. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/config_edges/cicd.rs +0 -0
  70. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/config_edges/docker.rs +0 -0
  71. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/config_edges/generic.rs +0 -0
  72. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/config_edges/helm.rs +0 -0
  73. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/config_edges/kubernetes.rs +0 -0
  74. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/config_edges/mod.rs +0 -0
  75. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/document/mod.rs +0 -0
  76. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/history/cochange.rs +0 -0
  77. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/history/mod.rs +0 -0
  78. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/ansible.rs +0 -0
  79. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/bazel.rs +0 -0
  80. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/c_family.rs +0 -0
  81. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/cargo_edges.rs +0 -0
  82. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/clojure.rs +0 -0
  83. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/css.rs +0 -0
  84. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/dart.rs +0 -0
  85. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/dbt.rs +0 -0
  86. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/dotnet.rs +0 -0
  87. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/elixir.rs +0 -0
  88. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/erlang.rs +0 -0
  89. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/go.rs +0 -0
  90. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/graphql.rs +0 -0
  91. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/haskell.rs +0 -0
  92. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/javascript.rs +0 -0
  93. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/julia.rs +0 -0
  94. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/jvm.rs +0 -0
  95. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/latex.rs +0 -0
  96. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/lua.rs +0 -0
  97. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/mod.rs +0 -0
  98. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/nim.rs +0 -0
  99. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/nix.rs +0 -0
  100. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/ocaml.rs +0 -0
  101. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/openapi.rs +0 -0
  102. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/perl.rs +0 -0
  103. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/php.rs +0 -0
  104. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/prisma.rs +0 -0
  105. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/protobuf.rs +0 -0
  106. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/python.rs +0 -0
  107. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/r_lang.rs +0 -0
  108. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/ruby.rs +0 -0
  109. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/rust_lang.rs +0 -0
  110. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/shell.rs +0 -0
  111. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/sql.rs +0 -0
  112. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/swift.rs +0 -0
  113. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/tags.rs +0 -0
  114. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/terraform.rs +0 -0
  115. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/zig.rs +0 -0
  116. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/similarity/lexical.rs +0 -0
  117. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/similarity/mod.rs +0 -0
  118. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/structural/containment.rs +0 -0
  119. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/structural/mod.rs +0 -0
  120. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/structural/sibling.rs +0 -0
  121. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/structural/testing.rs +0 -0
  122. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/excerpt.rs +0 -0
  123. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/fragmentation.rs +0 -0
  124. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/lib.rs +0 -0
  125. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/main.rs +0 -0
  126. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/memory_pipeline.rs +0 -0
  127. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/mode.rs +0 -0
  128. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/parsers/config_parser.rs +0 -0
  129. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/parsers/markdown.rs +0 -0
  130. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/parsers/mod.rs +0 -0
  131. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/peak_rss.rs +0 -0
  132. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/project_graph.rs +0 -0
  133. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/scoring.rs +0 -0
  134. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/stopwords.rs +0 -0
  135. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/test_harness.rs +0 -0
  136. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/tokenizer.rs +0 -0
  137. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/types.rs +0 -0
  138. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/utility/boltzmann.rs +0 -0
  139. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/utility/importance.rs +0 -0
  140. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/utility/mod.rs +0 -0
  141. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/utility/needs.rs +0 -0
  142. {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/utility/scoring.rs +0 -0
  143. {diffctx-1.12.2 → diffctx-1.12.3}/rust-toolchain.toml +0 -0
  144. {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/__init__.py +0 -0
  145. {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/__main__.py +0 -0
  146. {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/_native/__init__.py +0 -0
  147. {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/_native/graph_analytics.py +0 -0
  148. {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/_native/graph_export.py +0 -0
  149. {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/_native/project_graph.py +0 -0
  150. {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/clipboard.py +0 -0
  151. {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/ignore.py +0 -0
  152. {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/logger.py +0 -0
  153. {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/mcp/__init__.py +0 -0
  154. {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/mcp/__main__.py +0 -0
  155. {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/mcp/formatting.py +0 -0
  156. {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/py.typed +0 -0
  157. {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/tokens.py +0 -0
  158. {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/tree.py +0 -0
@@ -7,8 +7,74 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Added
11
+
12
+ - **Scoop install on Windows.** This repository is now itself the bucket:
13
+ `scoop bucket add diffctx https://github.com/nikolay-e/diffctx` then
14
+ `scoop install diffctx/diffctx`. The manifest moved from `packaging/scoop/`
15
+ to `bucket/diffctx.json`, where `scoop bucket add` reads it, so CD pushing
16
+ the regenerated manifest to `main` is the publication step. Previously the
17
+ manifest was correct but unreachable — no bucket existed, so
18
+ `scoop search diffctx` found nothing.
19
+
20
+ - `--with-raw-diff` bundles git's raw unified diff ahead of the selected
21
+ context in every Python-CLI format (md/yaml/json/txt) and in the Python API
22
+ (`build_diff_context(..., with_raw_diff=True)`). Additive only — selection is
23
+ byte-identical with and without it — and not charged to `--budget`; the
24
+ stderr token summary reports the real output size and breaks out the patch's
25
+ share. Lock-file (#112), ignored and secret-like sections stay omitted. Not
26
+ in the native binary or the MCP server yet (#150).
27
+ - Token counting is documented explicitly: every count and `--budget` use
28
+ tiktoken `o200k_base` (GPT-4o family) and are approximate for other model
29
+ families — `docs/product/token-budget.md`, plus a `Token counting` section in
30
+ `--help` (#150).
31
+ - A GitHub Action (`action.yml`) runs diffctx as a CI step and exposes the
32
+ context file, its exact token count and an `empty` flag as step outputs, so a
33
+ downstream job can feed an LLM without re-tokenizing
34
+ (`docs/product/github-action.md`, #145).
35
+ - The Claude Code plugin declares its MCP server self-bootstrapping:
36
+ `uvx --from 'diffctx[mcp]' diffctx-mcp`. Installing the plugin no longer
37
+ assumes `pip install 'diffctx[mcp]'` has already happened — `uv` fetches it
38
+ on first use. (The entry point rather than the `diffctx mcp` subcommand: the
39
+ subcommand only exists from this release on, so on any older version the
40
+ bare command would try to map a directory named `mcp`.)
41
+ - `diffctx mcp` starts the MCP server, alongside the existing `diffctx-mcp`
42
+ console script. MCP registries publish a *package* name, and clients that
43
+ derive the executable from it run `diffctx` — which started a tree-mapping
44
+ run and wrote 31 MB of the working directory into the protocol transport
45
+ instead of speaking MCP. Both spellings now reach the same server.
46
+
47
+ ### Security
48
+
49
+ - **A diff range could re-enable repository-configured diff commands.** The
50
+ range validator allowed a leading `-`, so `--ext-diff` or `--textconv` passed
51
+ as a "range" landed in argv *after* the `--no-ext-diff --no-textconv` that
52
+ every diff invocation sets — undoing them and letting `.gitattributes` run
53
+ external commands. Neither side of a range may now begin with a dash, and
54
+ single revisions are additionally rejected for whitespace or control
55
+ characters (a newline split one `cat-file --batch` request into two). Refs
56
+ starting with a dash are unaddressable on a git command line, so nothing
57
+ legitimate is lost.
58
+ - MCP tool calls had no wall-clock deadline — the CLI's watchdog covered only
59
+ the CLI, leaving parse, fragmentation and scoring unbounded on the MCP path.
60
+ All three tools now share the CLI's 300 s default.
61
+ - `SECURITY.md` documents prompt injection via repository content honestly: it
62
+ is inherent to moving repository text into a model's context, diffctx does
63
+ not detect or neutralize it, and output must be treated as untrusted.
64
+
10
65
  ### Fixed
11
66
 
67
+ - **A changed file could vanish from the diff entirely when `.gitignore`
68
+ excluded its parent directory.** `check-ignore --no-index` — required for
69
+ `.diffctx/ignore` to apply to tracked files — also revives git's rule that a
70
+ file cannot be re-included once a parent directory is excluded. pandoc
71
+ excludes every dotted root entry with `/*.*` and re-includes `!.github/**`;
72
+ git keeps the tracked workflow file, diffctx dropped it, and a one-file
73
+ commit rendered as an empty selection with exit 4. An exclusion inherited
74
+ from an excluded ancestor no longer counts; a pattern matching the path
75
+ itself still excludes it, so `.diffctx/ignore` and per-directory
76
+ `.gitignore` rules are unaffected (#153).
77
+
12
78
  - **The container images ran as root.** The published 1.12.1 image had no
13
79
  `USER` directive, so every `docker run` executed as uid 0 while the README
14
80
  promised an unprivileged user. The image now runs as uid 10001.
@@ -20,8 +86,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
20
86
  - **The native binary hard-capped every run at 4096 tokens.** `--budget`
21
87
  carried a fixed clap default instead of leaving the budget unset, so the
22
88
  auto-sizing the Python CLI has always used never ran on the binary shipped
23
- via crates.io, npm, the container images, Scoop, AUR and the release
24
- archives — the same command returned a truncated selection there (14-file
89
+ via crates.io, npm, the container images and the release archives — the
90
+ same command returned a truncated selection there (14-file
25
91
  self-eat range: 15 fragments across 10 files, against 172 across 44). The
26
92
  binary now defaults to auto sizing, and `--budget -1` means unlimited as it
27
93
  does in Python.
@@ -58,6 +124,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
58
124
  actionable facts about such a run — were dropped. They are now always
59
125
  written.
60
126
 
127
+ ### Removed
128
+
129
+ - **AUR support.** `diffctx-bin` had never been submitted (the AUR RPC reported
130
+ `resultcount: 0`), the publishing job gated on a credential this repository
131
+ does not hold, and every release regenerated a PKGBUILD nobody could install.
132
+ Arch users are served by `pipx install diffctx` and `cargo install diffctx`.
133
+
61
134
  ### Changed
62
135
 
63
136
  - **Lock files no longer render their hunks in diff mode.** A dependency bump
@@ -98,10 +171,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
98
171
  the release tag and smoke-tested against a real repository before the tag
99
172
  moves: `docker run --rm -v "$PWD:/repo" ghcr.io/nikolay-e/diffctx . --diff HEAD~1`.
100
173
  Mirrored to Docker Hub as `nikolajer/diffctx`.
101
- - Packaging manifests generated from the release checksums: Scoop
102
- (`packaging/scoop/diffctx.json`), AUR (`packaging/aur/`) and an npm wrapper
103
- (`packaging/npm/`) that downloads the platform binary and verifies its
104
- SHA-256 against the published checksum.
174
+ - npm wrapper (`packaging/npm/`) that downloads the platform binary and
175
+ verifies its SHA-256 against the published checksum. Scoop
176
+ (`packaging/scoop/diffctx.json`) and AUR (`packaging/aur/`) manifests are
177
+ generated from the same checksums but are not published to any bucket or
178
+ to the AUR.
105
179
 
106
180
  ### Fixed
107
181
 
@@ -179,9 +179,9 @@ dependencies = [
179
179
 
180
180
  [[package]]
181
181
  name = "crossbeam-epoch"
182
- version = "0.9.18"
182
+ version = "0.9.20"
183
183
  source = "registry+https://github.com/rust-lang/crates.io-index"
184
- checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
184
+ checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
185
185
  dependencies = [
186
186
  "crossbeam-utils",
187
187
  ]
@@ -194,7 +194,7 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
194
194
 
195
195
  [[package]]
196
196
  name = "diffctx"
197
- version = "1.12.2"
197
+ version = "1.12.3"
198
198
  dependencies = [
199
199
  "anyhow",
200
200
  "clap",
@@ -399,9 +399,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
399
399
 
400
400
  [[package]]
401
401
  name = "libc"
402
- version = "0.2.186"
402
+ version = "0.2.189"
403
403
  source = "registry+https://github.com/rust-lang/crates.io-index"
404
- checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
404
+ checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
405
405
 
406
406
  [[package]]
407
407
  name = "libmimalloc-sys"
@@ -1105,9 +1105,9 @@ dependencies = [
1105
1105
 
1106
1106
  [[package]]
1107
1107
  name = "tree-sitter-javascript"
1108
- version = "0.23.1"
1108
+ version = "0.25.0"
1109
1109
  source = "registry+https://github.com/rust-lang/crates.io-index"
1110
- checksum = "bf40bf599e0416c16c125c3cec10ee5ddc7d1bb8b0c60fa5c4de249ad34dc1b1"
1110
+ checksum = "68204f2abc0627a90bdf06e605f5c470aa26fdcb2081ea553a04bdad756693f5"
1111
1111
  dependencies = [
1112
1112
  "cc",
1113
1113
  "tree-sitter-language",
@@ -1211,9 +1211,9 @@ dependencies = [
1211
1211
 
1212
1212
  [[package]]
1213
1213
  name = "tree-sitter-python"
1214
- version = "0.23.6"
1214
+ version = "0.25.0"
1215
1215
  source = "registry+https://github.com/rust-lang/crates.io-index"
1216
- checksum = "3d065aaa27f3aaceaf60c1f0e0ac09e1cb9eb8ed28e7bcdaa52129cffc7f4b04"
1216
+ checksum = "6bf85fd39652e740bf60f46f4cda9492c3a9ad75880575bf14960f775cb74a1c"
1217
1217
  dependencies = [
1218
1218
  "cc",
1219
1219
  "tree-sitter-language",
@@ -1271,9 +1271,9 @@ dependencies = [
1271
1271
 
1272
1272
  [[package]]
1273
1273
  name = "tree-sitter-swift"
1274
- version = "0.6.0"
1274
+ version = "0.7.3"
1275
1275
  source = "registry+https://github.com/rust-lang/crates.io-index"
1276
- checksum = "d65aeb41726119416567d0333ec17580ac4abfb96db1f67c4bd638c65f9992fe"
1276
+ checksum = "fe36052155b9dd69ca82b3b8f1b4ccfb2d867125ac1a4db1dd7331829242668c"
1277
1277
  dependencies = [
1278
1278
  "cc",
1279
1279
  "tree-sitter-language",
@@ -0,0 +1,20 @@
1
+ [workspace]
2
+ members = ["crates/diffctx-native"]
3
+ resolver = "2"
4
+
5
+ [profile.release]
6
+ opt-level = 3
7
+ lto = "fat"
8
+ codegen-units = 1
9
+ strip = true
10
+ panic = "abort"
11
+ debug = "line-tables-only"
12
+ split-debuginfo = "packed"
13
+
14
+ # `cargo test --release` with panic = "abort" compiles the whole dependency
15
+ # graph twice (abort for the cdylib lib artifact, unwind for the test
16
+ # universe); the hashless cdylib output names then collide (cargo#6313).
17
+ # Tests run through this profile instead: same optimizations, one universe.
18
+ [profile.release-unwind]
19
+ inherits = "release"
20
+ panic = "unwind"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: diffctx
3
- Version: 1.12.2
3
+ Version: 1.12.3
4
4
  Classifier: Development Status :: 5 - Production/Stable
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -105,6 +105,9 @@ changed lines and stops once more context stops paying for itself.
105
105
  | Python API | ✗ | ✗ | ✗ | ✓ |
106
106
  | MCP server | ✗ | ✗ | ✗ | ✓ |
107
107
 
108
+ Fuller positioning — measured results, and when a whole-repo packer or a
109
+ persistent code-graph server fits better: [COMPARISON.md](COMPARISON.md).
110
+
108
111
  ## Install
109
112
 
110
113
  ```bash
@@ -122,6 +125,13 @@ npx diffctx . --diff HEAD~1 # npm wrapper over the native binary
122
125
  docker run --rm -v "$PWD:/repo" ghcr.io/nikolay-e/diffctx . --diff HEAD~1
123
126
  ```
124
127
 
128
+ On Windows, via Scoop (this repository is the bucket):
129
+
130
+ ```powershell
131
+ scoop bucket add diffctx https://github.com/nikolay-e/diffctx
132
+ scoop install diffctx/diffctx
133
+ ```
134
+
125
135
  The image runs as a non-root user (uid 10001) and writes to stdout — the
126
136
  native binary has no `-o` flag, so redirect to capture: `... --diff HEAD~1 >
127
137
  context.yaml`.
@@ -158,11 +168,12 @@ the `--budget` token cap is hit.
158
168
  | Flag | Default | Description |
159
169
  |-------------|---------|--------------------------------------------------------------------------|
160
170
  | `--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) |
161
- | `--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) |
171
+ | `--budget` | auto | Hard cap in o200k_base tokens (see [Token counting](docs/product/token-budget.md)): `N` enforces a fixed cap, `-1` disables it, `0` is a strict-zero floor (empty selection; use `--full` for changed files only) |
162
172
  | `--alpha` | 0.60 | PPR damping; higher = context clusters tighter around changes (`--scoring ppr` only) |
163
173
  | `--tau` | 0.12 | Relevance threshold for full fragment content; lower-scoring fragments are stubbed or dropped (lower = more context) |
164
174
  | `--full` | false | Only the changed files, every fragment, no related-code context |
165
175
  | `--timeout` | 300 | Wall-clock deadline in seconds; on expiry diffctx exits 124 instead of hanging |
176
+ | `--with-raw-diff` | false | Also embed git's raw unified diff ahead of the selected fragments — additive (selection unchanged), not charged to `--budget`, lock/ignored/secret-like sections omitted. Python CLI only |
166
177
 
167
178
  Calibration of `--alpha`, `--tau`, and the edge-weight priors:
168
179
  [`docs/engineering/parameter-strategy.md`](docs/engineering/parameter-strategy.md).
@@ -186,19 +197,20 @@ diffctx graph . --level file -f graphml -o g.xml # file-level graph as GraphML
186
197
  <!-- BEGIN USAGE -->
187
198
  ```bash
188
199
  # full codebase export:
189
- diffctx . # Markdown to stdout + token count
190
- diffctx . -f md -c # Markdown → clipboard
191
- diffctx . -f json -o tree.json # JSON → file
192
- diffctx . --no-content # structure only, no file contents
193
- diffctx . --max-depth 3 # limit depth
194
- diffctx . -i custom.ignore # custom ignore patterns
200
+ diffctx . # Markdown to stdout + token count
201
+ diffctx . -f md -c # Markdown → clipboard
202
+ diffctx . -f json -o tree.json # JSON → file
203
+ diffctx . --no-content # structure only, no file contents
204
+ diffctx . --max-depth 3 # limit depth
205
+ diffctx . -i custom.ignore # custom ignore patterns
195
206
 
196
207
  # diff context mode (requires git repo):
197
- diffctx . --diff # uncommitted changes (working tree vs HEAD)
198
- diffctx . --diff HEAD~1 # context for last commit
199
- diffctx . --diff main..feature # context for feature branch
200
- diffctx . --diff HEAD~1 --budget 30000 # limit to ~30k tokens
201
- diffctx . --diff HEAD~1 -c # diff context to clipboard
208
+ diffctx . --diff # uncommitted changes (working tree vs HEAD)
209
+ diffctx . --diff HEAD~1 # context for last commit
210
+ diffctx . --diff main..feature # context for feature branch
211
+ diffctx . --diff HEAD~1 --budget 30000 # limit to ~30k tokens
212
+ diffctx . --diff HEAD~1 -c # diff context to clipboard
213
+ diffctx . --diff HEAD~1 --with-raw-diff # raw patch + selected context
202
214
  ```
203
215
  <!-- END USAGE -->
204
216
 
@@ -209,6 +221,11 @@ Every run reports token count and size on stderr — `12,847 tokens
209
221
  `<binary file: N bytes>`, `<file too large: N bytes>`, or
210
222
  `<unreadable content: not utf-8>`.
211
223
 
224
+ Counts come from tiktoken's `o200k_base` encoder and are exact only for the
225
+ GPT-4o family; Claude, Gemini and others tokenize differently, so treat
226
+ `--budget` as an upper bound in o200k tokens and leave headroom. Details:
227
+ [`docs/product/token-budget.md`](docs/product/token-budget.md).
228
+
212
229
  ## Python API
213
230
 
214
231
  ```python
@@ -224,6 +241,7 @@ ctx = build_diff_context(
224
241
  full=False,
225
242
  scoring_mode="ego",
226
243
  timeout=300,
244
+ with_raw_diff=False, # True also embeds the raw unified diff (not charged to budget)
227
245
  )
228
246
  print(to_markdown(ctx))
229
247
 
@@ -264,6 +282,12 @@ anchored `/root_only.txt`). `.diffctx/whitelist` acts as an include-only filter,
264
282
  and the output file is always auto-ignored. `--no-default-ignores` disables the
265
283
  built-in patterns; `--no-ignores` disables all ignore rules (tree mode only).
266
284
 
285
+ An excluded path never appears in the output in any role: in diff mode it is
286
+ dropped both from `changed_files` and from the candidate universe, so it cannot
287
+ come back as a related-context fragment either (including under `--full`). The
288
+ same guarantee covers secret-like paths (`id_rsa`, `*.pem`, `*.key`, ...),
289
+ which are filtered even without an ignore entry.
290
+
267
291
  ## Token cache
268
292
 
269
293
  Diff mode caches per-blob tokenization under
@@ -313,6 +337,12 @@ Apache 2.0
313
337
 
314
338
  - [Documentation site](https://nikolay-e.github.io/diffctx/) — the pipeline
315
339
  end to end: diff → fragments → graph → relevance → selection
340
+ - [GitHub Action](docs/product/github-action.md) — diff context as a CI step
341
+ for LLM review
342
+ - [Token counting](docs/product/token-budget.md) — which encoder, and what
343
+ `--budget` means for non-GPT models
344
+ - [Comparison](COMPARISON.md) — measured results, and when a whole-repo packer
345
+ or a persistent code-graph server fits better
316
346
  - [Changelog](CHANGELOG.md)
317
347
  - [Security policy](SECURITY.md) — threat model and vulnerability reporting
318
348
  - [Parameter strategy](docs/engineering/parameter-strategy.md) — how `--alpha`,
@@ -27,6 +27,9 @@ changed lines and stops once more context stops paying for itself.
27
27
  | Python API | ✗ | ✗ | ✗ | ✓ |
28
28
  | MCP server | ✗ | ✗ | ✗ | ✓ |
29
29
 
30
+ Fuller positioning — measured results, and when a whole-repo packer or a
31
+ persistent code-graph server fits better: [COMPARISON.md](COMPARISON.md).
32
+
30
33
  ## Install
31
34
 
32
35
  ```bash
@@ -44,6 +47,13 @@ npx diffctx . --diff HEAD~1 # npm wrapper over the native binary
44
47
  docker run --rm -v "$PWD:/repo" ghcr.io/nikolay-e/diffctx . --diff HEAD~1
45
48
  ```
46
49
 
50
+ On Windows, via Scoop (this repository is the bucket):
51
+
52
+ ```powershell
53
+ scoop bucket add diffctx https://github.com/nikolay-e/diffctx
54
+ scoop install diffctx/diffctx
55
+ ```
56
+
47
57
  The image runs as a non-root user (uid 10001) and writes to stdout — the
48
58
  native binary has no `-o` flag, so redirect to capture: `... --diff HEAD~1 >
49
59
  context.yaml`.
@@ -80,11 +90,12 @@ the `--budget` token cap is hit.
80
90
  | Flag | Default | Description |
81
91
  |-------------|---------|--------------------------------------------------------------------------|
82
92
  | `--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) |
83
- | `--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) |
93
+ | `--budget` | auto | Hard cap in o200k_base tokens (see [Token counting](docs/product/token-budget.md)): `N` enforces a fixed cap, `-1` disables it, `0` is a strict-zero floor (empty selection; use `--full` for changed files only) |
84
94
  | `--alpha` | 0.60 | PPR damping; higher = context clusters tighter around changes (`--scoring ppr` only) |
85
95
  | `--tau` | 0.12 | Relevance threshold for full fragment content; lower-scoring fragments are stubbed or dropped (lower = more context) |
86
96
  | `--full` | false | Only the changed files, every fragment, no related-code context |
87
97
  | `--timeout` | 300 | Wall-clock deadline in seconds; on expiry diffctx exits 124 instead of hanging |
98
+ | `--with-raw-diff` | false | Also embed git's raw unified diff ahead of the selected fragments — additive (selection unchanged), not charged to `--budget`, lock/ignored/secret-like sections omitted. Python CLI only |
88
99
 
89
100
  Calibration of `--alpha`, `--tau`, and the edge-weight priors:
90
101
  [`docs/engineering/parameter-strategy.md`](docs/engineering/parameter-strategy.md).
@@ -108,19 +119,20 @@ diffctx graph . --level file -f graphml -o g.xml # file-level graph as GraphML
108
119
  <!-- BEGIN USAGE -->
109
120
  ```bash
110
121
  # full codebase export:
111
- diffctx . # Markdown to stdout + token count
112
- diffctx . -f md -c # Markdown → clipboard
113
- diffctx . -f json -o tree.json # JSON → file
114
- diffctx . --no-content # structure only, no file contents
115
- diffctx . --max-depth 3 # limit depth
116
- diffctx . -i custom.ignore # custom ignore patterns
122
+ diffctx . # Markdown to stdout + token count
123
+ diffctx . -f md -c # Markdown → clipboard
124
+ diffctx . -f json -o tree.json # JSON → file
125
+ diffctx . --no-content # structure only, no file contents
126
+ diffctx . --max-depth 3 # limit depth
127
+ diffctx . -i custom.ignore # custom ignore patterns
117
128
 
118
129
  # diff context mode (requires git repo):
119
- diffctx . --diff # uncommitted changes (working tree vs HEAD)
120
- diffctx . --diff HEAD~1 # context for last commit
121
- diffctx . --diff main..feature # context for feature branch
122
- diffctx . --diff HEAD~1 --budget 30000 # limit to ~30k tokens
123
- diffctx . --diff HEAD~1 -c # diff context to clipboard
130
+ diffctx . --diff # uncommitted changes (working tree vs HEAD)
131
+ diffctx . --diff HEAD~1 # context for last commit
132
+ diffctx . --diff main..feature # context for feature branch
133
+ diffctx . --diff HEAD~1 --budget 30000 # limit to ~30k tokens
134
+ diffctx . --diff HEAD~1 -c # diff context to clipboard
135
+ diffctx . --diff HEAD~1 --with-raw-diff # raw patch + selected context
124
136
  ```
125
137
  <!-- END USAGE -->
126
138
 
@@ -131,6 +143,11 @@ Every run reports token count and size on stderr — `12,847 tokens
131
143
  `<binary file: N bytes>`, `<file too large: N bytes>`, or
132
144
  `<unreadable content: not utf-8>`.
133
145
 
146
+ Counts come from tiktoken's `o200k_base` encoder and are exact only for the
147
+ GPT-4o family; Claude, Gemini and others tokenize differently, so treat
148
+ `--budget` as an upper bound in o200k tokens and leave headroom. Details:
149
+ [`docs/product/token-budget.md`](docs/product/token-budget.md).
150
+
134
151
  ## Python API
135
152
 
136
153
  ```python
@@ -146,6 +163,7 @@ ctx = build_diff_context(
146
163
  full=False,
147
164
  scoring_mode="ego",
148
165
  timeout=300,
166
+ with_raw_diff=False, # True also embeds the raw unified diff (not charged to budget)
149
167
  )
150
168
  print(to_markdown(ctx))
151
169
 
@@ -186,6 +204,12 @@ anchored `/root_only.txt`). `.diffctx/whitelist` acts as an include-only filter,
186
204
  and the output file is always auto-ignored. `--no-default-ignores` disables the
187
205
  built-in patterns; `--no-ignores` disables all ignore rules (tree mode only).
188
206
 
207
+ An excluded path never appears in the output in any role: in diff mode it is
208
+ dropped both from `changed_files` and from the candidate universe, so it cannot
209
+ come back as a related-context fragment either (including under `--full`). The
210
+ same guarantee covers secret-like paths (`id_rsa`, `*.pem`, `*.key`, ...),
211
+ which are filtered even without an ignore entry.
212
+
189
213
  ## Token cache
190
214
 
191
215
  Diff mode caches per-blob tokenization under
@@ -235,6 +259,12 @@ Apache 2.0
235
259
 
236
260
  - [Documentation site](https://nikolay-e.github.io/diffctx/) — the pipeline
237
261
  end to end: diff → fragments → graph → relevance → selection
262
+ - [GitHub Action](docs/product/github-action.md) — diff context as a CI step
263
+ for LLM review
264
+ - [Token counting](docs/product/token-budget.md) — which encoder, and what
265
+ `--budget` means for non-GPT models
266
+ - [Comparison](COMPARISON.md) — measured results, and when a whole-repo packer
267
+ or a persistent code-graph server fits better
238
268
  - [Changelog](CHANGELOG.md)
239
269
  - [Security policy](SECURITY.md) — threat model and vulnerability reporting
240
270
  - [Parameter strategy](docs/engineering/parameter-strategy.md) — how `--alpha`,
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "diffctx"
3
- version = "1.12.2"
3
+ version = "1.12.3"
4
4
  edition = "2024"
5
5
  rust-version = "1.85"
6
6
  description = "Selects the minimum code an LLM needs to review a git diff: walks the dependency graph outward from changed lines and stops when extra context stops paying for itself"
@@ -72,8 +72,8 @@ lang-extra = [
72
72
  mimalloc = { version = "0.1", default-features = false }
73
73
  clap = { version = "4", features = ["derive"] }
74
74
  tree-sitter = "0.25"
75
- tree-sitter-python = { version = "0.23", optional = true }
76
- tree-sitter-javascript = { version = "0.23", optional = true }
75
+ tree-sitter-python = { version = "0.25", optional = true }
76
+ tree-sitter-javascript = { version = "0.25", optional = true }
77
77
  tree-sitter-typescript = { version = "0.23", optional = true }
78
78
  tree-sitter-go = { version = "0.25", optional = true }
79
79
  tree-sitter-rust = { version = "0.24", optional = true }
@@ -84,7 +84,7 @@ tree-sitter-c-sharp = { version = "0.23", optional = true }
84
84
  tree-sitter-ruby = { version = "0.23", optional = true }
85
85
  tree-sitter-php = { version = "0.24", optional = true }
86
86
  tree-sitter-scala = { version = "0.26", optional = true }
87
- tree-sitter-swift = { version = "0.6", optional = true }
87
+ tree-sitter-swift = { version = "0.7", optional = true }
88
88
  tree-sitter-html = { version = "0.23", optional = true }
89
89
  tree-sitter-bash = { version = "0.25", optional = true }
90
90
  tree-sitter-css = { version = "0.25", optional = true }
@@ -27,6 +27,9 @@ changed lines and stops once more context stops paying for itself.
27
27
  | Python API | ✗ | ✗ | ✗ | ✓ |
28
28
  | MCP server | ✗ | ✗ | ✗ | ✓ |
29
29
 
30
+ Fuller positioning — measured results, and when a whole-repo packer or a
31
+ persistent code-graph server fits better: [COMPARISON.md](COMPARISON.md).
32
+
30
33
  ## Install
31
34
 
32
35
  ```bash
@@ -44,6 +47,13 @@ npx diffctx . --diff HEAD~1 # npm wrapper over the native binary
44
47
  docker run --rm -v "$PWD:/repo" ghcr.io/nikolay-e/diffctx . --diff HEAD~1
45
48
  ```
46
49
 
50
+ On Windows, via Scoop (this repository is the bucket):
51
+
52
+ ```powershell
53
+ scoop bucket add diffctx https://github.com/nikolay-e/diffctx
54
+ scoop install diffctx/diffctx
55
+ ```
56
+
47
57
  The image runs as a non-root user (uid 10001) and writes to stdout — the
48
58
  native binary has no `-o` flag, so redirect to capture: `... --diff HEAD~1 >
49
59
  context.yaml`.
@@ -80,11 +90,12 @@ the `--budget` token cap is hit.
80
90
  | Flag | Default | Description |
81
91
  |-------------|---------|--------------------------------------------------------------------------|
82
92
  | `--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) |
83
- | `--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) |
93
+ | `--budget` | auto | Hard cap in o200k_base tokens (see [Token counting](docs/product/token-budget.md)): `N` enforces a fixed cap, `-1` disables it, `0` is a strict-zero floor (empty selection; use `--full` for changed files only) |
84
94
  | `--alpha` | 0.60 | PPR damping; higher = context clusters tighter around changes (`--scoring ppr` only) |
85
95
  | `--tau` | 0.12 | Relevance threshold for full fragment content; lower-scoring fragments are stubbed or dropped (lower = more context) |
86
96
  | `--full` | false | Only the changed files, every fragment, no related-code context |
87
97
  | `--timeout` | 300 | Wall-clock deadline in seconds; on expiry diffctx exits 124 instead of hanging |
98
+ | `--with-raw-diff` | false | Also embed git's raw unified diff ahead of the selected fragments — additive (selection unchanged), not charged to `--budget`, lock/ignored/secret-like sections omitted. Python CLI only |
88
99
 
89
100
  Calibration of `--alpha`, `--tau`, and the edge-weight priors:
90
101
  [`docs/engineering/parameter-strategy.md`](docs/engineering/parameter-strategy.md).
@@ -108,19 +119,20 @@ diffctx graph . --level file -f graphml -o g.xml # file-level graph as GraphML
108
119
  <!-- BEGIN USAGE -->
109
120
  ```bash
110
121
  # full codebase export:
111
- diffctx . # Markdown to stdout + token count
112
- diffctx . -f md -c # Markdown → clipboard
113
- diffctx . -f json -o tree.json # JSON → file
114
- diffctx . --no-content # structure only, no file contents
115
- diffctx . --max-depth 3 # limit depth
116
- diffctx . -i custom.ignore # custom ignore patterns
122
+ diffctx . # Markdown to stdout + token count
123
+ diffctx . -f md -c # Markdown → clipboard
124
+ diffctx . -f json -o tree.json # JSON → file
125
+ diffctx . --no-content # structure only, no file contents
126
+ diffctx . --max-depth 3 # limit depth
127
+ diffctx . -i custom.ignore # custom ignore patterns
117
128
 
118
129
  # diff context mode (requires git repo):
119
- diffctx . --diff # uncommitted changes (working tree vs HEAD)
120
- diffctx . --diff HEAD~1 # context for last commit
121
- diffctx . --diff main..feature # context for feature branch
122
- diffctx . --diff HEAD~1 --budget 30000 # limit to ~30k tokens
123
- diffctx . --diff HEAD~1 -c # diff context to clipboard
130
+ diffctx . --diff # uncommitted changes (working tree vs HEAD)
131
+ diffctx . --diff HEAD~1 # context for last commit
132
+ diffctx . --diff main..feature # context for feature branch
133
+ diffctx . --diff HEAD~1 --budget 30000 # limit to ~30k tokens
134
+ diffctx . --diff HEAD~1 -c # diff context to clipboard
135
+ diffctx . --diff HEAD~1 --with-raw-diff # raw patch + selected context
124
136
  ```
125
137
  <!-- END USAGE -->
126
138
 
@@ -131,6 +143,11 @@ Every run reports token count and size on stderr — `12,847 tokens
131
143
  `<binary file: N bytes>`, `<file too large: N bytes>`, or
132
144
  `<unreadable content: not utf-8>`.
133
145
 
146
+ Counts come from tiktoken's `o200k_base` encoder and are exact only for the
147
+ GPT-4o family; Claude, Gemini and others tokenize differently, so treat
148
+ `--budget` as an upper bound in o200k tokens and leave headroom. Details:
149
+ [`docs/product/token-budget.md`](docs/product/token-budget.md).
150
+
134
151
  ## Python API
135
152
 
136
153
  ```python
@@ -146,6 +163,7 @@ ctx = build_diff_context(
146
163
  full=False,
147
164
  scoring_mode="ego",
148
165
  timeout=300,
166
+ with_raw_diff=False, # True also embeds the raw unified diff (not charged to budget)
149
167
  )
150
168
  print(to_markdown(ctx))
151
169
 
@@ -186,6 +204,12 @@ anchored `/root_only.txt`). `.diffctx/whitelist` acts as an include-only filter,
186
204
  and the output file is always auto-ignored. `--no-default-ignores` disables the
187
205
  built-in patterns; `--no-ignores` disables all ignore rules (tree mode only).
188
206
 
207
+ An excluded path never appears in the output in any role: in diff mode it is
208
+ dropped both from `changed_files` and from the candidate universe, so it cannot
209
+ come back as a related-context fragment either (including under `--full`). The
210
+ same guarantee covers secret-like paths (`id_rsa`, `*.pem`, `*.key`, ...),
211
+ which are filtered even without an ignore entry.
212
+
189
213
  ## Token cache
190
214
 
191
215
  Diff mode caches per-blob tokenization under
@@ -235,6 +259,12 @@ Apache 2.0
235
259
 
236
260
  - [Documentation site](https://nikolay-e.github.io/diffctx/) — the pipeline
237
261
  end to end: diff → fragments → graph → relevance → selection
262
+ - [GitHub Action](docs/product/github-action.md) — diff context as a CI step
263
+ for LLM review
264
+ - [Token counting](docs/product/token-budget.md) — which encoder, and what
265
+ `--budget` means for non-GPT models
266
+ - [Comparison](COMPARISON.md) — measured results, and when a whole-repo packer
267
+ or a persistent code-graph server fits better
238
268
  - [Changelog](CHANGELOG.md)
239
269
  - [Security policy](SECURITY.md) — threat model and vulnerability reporting
240
270
  - [Parameter strategy](docs/engineering/parameter-strategy.md) — how `--alpha`,