diffctx 1.18.3__tar.gz → 1.19.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 (181) hide show
  1. {diffctx-1.18.3 → diffctx-1.19.0}/CHANGELOG.md +399 -1
  2. {diffctx-1.18.3 → diffctx-1.19.0}/Cargo.lock +14 -3
  3. diffctx-1.19.0/PKG-INFO +135 -0
  4. diffctx-1.19.0/README.md +75 -0
  5. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/Cargo.toml +10 -1
  6. diffctx-1.19.0/crates/diffctx-native/README.md +75 -0
  7. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/bindings.rs +277 -89
  8. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/candidate_files.rs +15 -7
  9. diffctx-1.19.0/crates/diffctx-native/src/change_class.rs +637 -0
  10. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/core.rs +32 -3
  11. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/discovery.rs +7 -0
  12. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/base.rs +172 -1
  13. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/config_edges/generic.rs +40 -7
  14. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/config_edges/helm.rs +7 -1
  15. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/history/cochange.rs +25 -25
  16. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/mod.rs +194 -23
  17. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/jvm.rs +123 -2
  18. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/nim.rs +51 -12
  19. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/nix.rs +14 -2
  20. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/ocaml.rs +14 -1
  21. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/ruby.rs +31 -3
  22. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/structural/testing.rs +27 -0
  23. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/effective_config.rs +1 -0
  24. diffctx-1.19.0/crates/diffctx-native/src/facts/kotlin.rs +1285 -0
  25. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/facts/mod.rs +1 -0
  26. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/fragmentation.rs +2 -1
  27. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/git.rs +295 -65
  28. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/graph.rs +6 -0
  29. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/graph_export.rs +113 -9
  30. diffctx-1.19.0/crates/diffctx-native/src/hook.rs +2751 -0
  31. diffctx-1.19.0/crates/diffctx-native/src/impact.rs +3860 -0
  32. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/in_memory_harness.rs +3 -1
  33. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/interval.rs +12 -0
  34. diffctx-1.19.0/crates/diffctx-native/src/kotlin_bindings.rs +1054 -0
  35. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/lib.rs +1 -0
  36. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/locate.rs +10 -16
  37. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/main.rs +74 -19
  38. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/parsers/mod.rs +41 -0
  39. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/parsers/tree_sitter_strategy.rs +524 -19
  40. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/pipeline.rs +479 -136
  41. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/postpass.rs +33 -24
  42. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/pybridge.rs +60 -15
  43. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/render.rs +26 -21
  44. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/resource.rs +85 -5
  45. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/select.rs +226 -31
  46. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/source.rs +79 -28
  47. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/token_corpus.rs +3 -1
  48. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/utility/objective.rs +2 -0
  49. {diffctx-1.18.3 → diffctx-1.19.0}/pyproject.toml +7 -8
  50. {diffctx-1.18.3 → diffctx-1.19.0}/src/diffctx/_app.py +81 -28
  51. {diffctx-1.18.3 → diffctx-1.19.0}/src/diffctx/_diffctx.pyi +4 -3
  52. {diffctx-1.18.3 → diffctx-1.19.0}/src/diffctx/_native/graph_export.py +6 -6
  53. {diffctx-1.18.3 → diffctx-1.19.0}/src/diffctx/_native/pipeline.py +2 -0
  54. {diffctx-1.18.3 → diffctx-1.19.0}/src/diffctx/cli.py +21 -10
  55. {diffctx-1.18.3 → diffctx-1.19.0}/src/diffctx/mcp/legacy.py +7 -4
  56. {diffctx-1.18.3 → diffctx-1.19.0}/src/diffctx/mcp/security.py +11 -0
  57. {diffctx-1.18.3 → diffctx-1.19.0}/src/diffctx/mcp/server.py +58 -24
  58. diffctx-1.19.0/src/diffctx/version.py +1 -0
  59. {diffctx-1.18.3 → diffctx-1.19.0}/src/diffctx/writer.py +2 -1
  60. diffctx-1.18.3/PKG-INFO +0 -372
  61. diffctx-1.18.3/README.md +0 -312
  62. diffctx-1.18.3/crates/diffctx-native/README.md +0 -312
  63. diffctx-1.18.3/crates/diffctx-native/src/change_class.rs +0 -169
  64. diffctx-1.18.3/crates/diffctx-native/src/hook.rs +0 -1768
  65. diffctx-1.18.3/crates/diffctx-native/src/impact.rs +0 -1848
  66. diffctx-1.18.3/src/diffctx/version.py +0 -1
  67. {diffctx-1.18.3 → diffctx-1.19.0}/Cargo.toml +0 -0
  68. {diffctx-1.18.3 → diffctx-1.19.0}/LICENSE +0 -0
  69. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/analytics.rs +0 -0
  70. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/config/analytics.rs +0 -0
  71. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/config/bm25.rs +0 -0
  72. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/config/budget.rs +0 -0
  73. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/config/category_weights.rs +0 -0
  74. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/config/edge_weights.rs +0 -0
  75. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/config/env_overrides.rs +0 -0
  76. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/config/extensions.rs +0 -0
  77. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/config/filtering.rs +0 -0
  78. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/config/fragmentation.rs +0 -0
  79. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/config/git.rs +0 -0
  80. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/config/graph_filtering.rs +0 -0
  81. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/config/importance.rs +0 -0
  82. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/config/limits.rs +0 -0
  83. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/config/mod.rs +0 -0
  84. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/config/mode.rs +0 -0
  85. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/config/needs.rs +0 -0
  86. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/config/parsers.rs +0 -0
  87. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/config/render.rs +0 -0
  88. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/config/scoring.rs +0 -0
  89. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/config/selection.rs +0 -0
  90. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/config/tokenization.rs +0 -0
  91. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/config/weights.rs +0 -0
  92. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/container.rs +0 -0
  93. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/config_edges/build_system.rs +0 -0
  94. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/config_edges/cicd.rs +0 -0
  95. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/config_edges/docker.rs +0 -0
  96. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/config_edges/kubernetes.rs +0 -0
  97. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/config_edges/mod.rs +0 -0
  98. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/document/mod.rs +0 -0
  99. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/history/mod.rs +0 -0
  100. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/ansible.rs +0 -0
  101. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/bazel.rs +0 -0
  102. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/c_family.rs +0 -0
  103. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/cargo_edges.rs +0 -0
  104. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/clojure.rs +0 -0
  105. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/css.rs +0 -0
  106. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/dart.rs +0 -0
  107. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/dbt.rs +0 -0
  108. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/dotnet.rs +0 -0
  109. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/elixir.rs +0 -0
  110. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/erlang.rs +0 -0
  111. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/go.rs +0 -0
  112. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/graphql.rs +0 -0
  113. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/haskell.rs +0 -0
  114. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/javascript.rs +0 -0
  115. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/julia.rs +0 -0
  116. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/latex.rs +0 -0
  117. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/lua.rs +0 -0
  118. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/mod.rs +0 -0
  119. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/openapi.rs +0 -0
  120. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/perl.rs +0 -0
  121. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/php.rs +0 -0
  122. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/prisma.rs +0 -0
  123. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/protobuf.rs +0 -0
  124. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/python.rs +0 -0
  125. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/r_lang.rs +0 -0
  126. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/rust_lang.rs +0 -0
  127. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/shell.rs +0 -0
  128. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/sql.rs +0 -0
  129. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/swift.rs +0 -0
  130. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/tags.rs +0 -0
  131. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/terraform.rs +0 -0
  132. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/zig.rs +0 -0
  133. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/similarity/lexical.rs +0 -0
  134. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/similarity/mod.rs +0 -0
  135. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/structural/containment.rs +0 -0
  136. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/structural/mod.rs +0 -0
  137. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/edges/structural/sibling.rs +0 -0
  138. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/excerpt.rs +0 -0
  139. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/facts/scala.rs +0 -0
  140. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/filtering.rs +0 -0
  141. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/languages.rs +0 -0
  142. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/mode.rs +0 -0
  143. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/parsers/config_parser.rs +0 -0
  144. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/parsers/generic.rs +0 -0
  145. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/parsers/markdown.rs +0 -0
  146. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/paths.rs +0 -0
  147. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/peak_rss.rs +0 -0
  148. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/ppr.rs +0 -0
  149. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/project_graph.rs +0 -0
  150. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/provenance.rs +0 -0
  151. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/run_provenance.rs +0 -0
  152. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/sanitize.rs +0 -0
  153. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/scoring.rs +0 -0
  154. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/signatures.rs +0 -0
  155. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/stopwords/code.txt +0 -0
  156. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/stopwords/docs.txt +0 -0
  157. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/stopwords.rs +0 -0
  158. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/test_rng.rs +0 -0
  159. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/testfiles.rs +0 -0
  160. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/tokenizer.rs +0 -0
  161. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/types.rs +0 -0
  162. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/utility/boltzmann.rs +0 -0
  163. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/utility/importance.rs +0 -0
  164. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/utility/mod.rs +0 -0
  165. {diffctx-1.18.3 → diffctx-1.19.0}/crates/diffctx-native/src/utility/needs.rs +0 -0
  166. {diffctx-1.18.3 → diffctx-1.19.0}/rust-toolchain.toml +0 -0
  167. {diffctx-1.18.3 → diffctx-1.19.0}/src/diffctx/__init__.py +0 -0
  168. {diffctx-1.18.3 → diffctx-1.19.0}/src/diffctx/__main__.py +0 -0
  169. {diffctx-1.18.3 → diffctx-1.19.0}/src/diffctx/_native/__init__.py +0 -0
  170. {diffctx-1.18.3 → diffctx-1.19.0}/src/diffctx/_native/graph_analytics.py +0 -0
  171. {diffctx-1.18.3 → diffctx-1.19.0}/src/diffctx/_native/project_graph.py +0 -0
  172. {diffctx-1.18.3 → diffctx-1.19.0}/src/diffctx/clipboard.py +0 -0
  173. {diffctx-1.18.3 → diffctx-1.19.0}/src/diffctx/ignore.py +0 -0
  174. {diffctx-1.18.3 → diffctx-1.19.0}/src/diffctx/logger.py +0 -0
  175. {diffctx-1.18.3 → diffctx-1.19.0}/src/diffctx/mcp/__init__.py +0 -0
  176. {diffctx-1.18.3 → diffctx-1.19.0}/src/diffctx/mcp/__main__.py +0 -0
  177. {diffctx-1.18.3 → diffctx-1.19.0}/src/diffctx/mcp/fetch.py +0 -0
  178. {diffctx-1.18.3 → diffctx-1.19.0}/src/diffctx/mcp/launch.py +0 -0
  179. {diffctx-1.18.3 → diffctx-1.19.0}/src/diffctx/py.typed +0 -0
  180. {diffctx-1.18.3 → diffctx-1.19.0}/src/diffctx/tokens.py +0 -0
  181. {diffctx-1.18.3 → diffctx-1.19.0}/src/diffctx/tree.py +0 -0
@@ -7,6 +7,402 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.19.0] - 2026-10-14
11
+
12
+ ### Added
13
+
14
+ - Kotlin (`.kt`, `.kts`) parses with tree-sitter, through
15
+ `brokk-tree-sitter-kotlin` 0.4.6 — the fwcd grammar as its maintained fork
16
+ publishes it, since fwcd's own crate still pins tree-sitter < 0.23. On
17
+ 1,036 real files it lost no declaration and broke on none;
18
+ `tree-sitter-kotlin-sg` 0.4.1 silently dropped seven classes (a bodyless
19
+ annotated class swallowed the next one) and `tree-sitter-kotlin-ng` broke
20
+ whole classes on a `get(...)` call. Classes, interfaces, objects,
21
+ companions (`Companion` when unnamed), functions, extension functions,
22
+ secondary constructors (named after their class), properties (a property
23
+ spans its getter and setter; a local `val` is not a member) and type
24
+ aliases become fragments (#303). This reverses the 1.7.0 removal, which
25
+ dropped `.kt` read through the Java grammar, not a Kotlin one.
26
+ - Kotlin has a binding model, read off shared syntax facts (one parse per
27
+ blob, shared by the fragmenter, `--symbol` and impact): a use binds through
28
+ its import or alias, the declared package (not the directory), the
29
+ receiver's declared type — a local, a parameter, a property of the class
30
+ or of a source-visible base class, a constructor or a function with a
31
+ written return type, called plainly or on a typed name
32
+ (`val x = repo.find(id)`) — an object or companion qualifier, or the
33
+ implicit `this`, which in an `inner class` reaches the outer instance too.
34
+ What the syntax cannot settle stays a candidate with its reason
35
+ (`receiver_unresolved`, `overloaded`, `wildcard_import`, `dispatch`,
36
+ `project_dependency_unknown`); `internal` stops at the project, which its
37
+ test source set shares. Callable references (`Obj::f`) are references,
38
+ implementations of a changed abstract member are their own relation, a
39
+ removed declaration is found by comparing the file's declarations at the
40
+ base and the head, and a public contract changes when the header does —
41
+ parameter defaults and all. In the context graph a Kotlin member use links
42
+ only to the definition it resolves to: a candidate suggests, it does not
43
+ link.
44
+
45
+ ### Security
46
+
47
+ - Impact and `--symbol` no longer name a withheld file that refers to the
48
+ change. 1.18.4's fix covered the changed side only: a file under
49
+ `.diffctx/ignore` or with a secret name that called a removed definition or
50
+ named a deleted path still appeared as a caller or a stale reference. The
51
+ withhold policy now applies inside the `git grep` readers, so every surface
52
+ gets only admissible files, and the files a read withheld count toward
53
+ `withheld_files`. The header says "N file(s) withheld by ignore rules"
54
+ rather than "N more", since they need not be changed files.
55
+
56
+ ### Fixed
57
+
58
+ - **`A..B` is what `B` changed since it forked from `A`**, in every mode and
59
+ at every entry point (CLI, MCP, the hook, the Action): the range resolves
60
+ once to the merge base and `B`'s commit. Read as two trees, a base that
61
+ moved on after the fork put its own later commits into the change as
62
+ reverts — a PR's target branch in the Action (#485), a remote-tracking
63
+ ref a `pull` on the same line had just moved in the hook (#462, which now
64
+ also measures a push from where the push itself found the ref). The
65
+ Action deepens a shallow checkout until the fork point is known, and says
66
+ so when it is not. Histories with no common commit keep git's two-tree
67
+ reading.
68
+ - A range is answered from its snapshot alone. The Helm chart root was
69
+ probed on the disk, so a working tree without the range's `Chart.yaml`
70
+ dropped the chart's templates from pack and locate (#408). A snapshot
71
+ read that failed — out of time, cancelled, a broken stream — read as an
72
+ absent file and now names its limit; a snapshot listing that failed no
73
+ longer hands discovery or the token cache the working tree's files in its
74
+ place (#443).
75
+ - The changed-file count of uncommitted work is what git would commit.
76
+ Untracked files are analysed as before but named apart (`untracked_files`,
77
+ "2 changed file(s), 1 untracked"), and an untracked binary — a local
78
+ database, a build artefact — is no source change at all; two `.db` files
79
+ and a notes file read as "5 changed file(s)" for a 2-file edit (#418,
80
+ #423). A search for a name defined only in an untracked file is answered
81
+ with its callers, as the inspection that follows already was (#448).
82
+ - `--full A..B` listed a file the range deleted, or renamed away, as
83
+ changed and showed its old body read from `A`. One rule now names what a
84
+ range's head no longer holds, for every mode; staged pack, `--full` and
85
+ locate are pinned to read the index alone, renames and mode changes
86
+ included (#409).
87
+ - A one-second impact run ends near its second: the loops over input-sized
88
+ lists (stale-reference lines, unanalysed and namer files, test edges)
89
+ iterate through the deadline instead of polling it by hand, and the
90
+ stale-reference loop, which had no poll, ran such a run to 4.5 s (now
91
+ 1.04 s, #510). A deadline that loop hit is named in `limits`; it was read
92
+ before the loop ran, so a run that had dropped its stale references
93
+ looked complete. `DIFFCTX_TEST_DEADLINE_AFTER_POLLS=n` stops a run at its
94
+ `n`th poll, so tests stop it at every point without racing the clock,
95
+ and a corpus case its deadline stopped says so instead of failing on a
96
+ score (#390).
97
+ - A call inside an f-string replacement field (`f"{fence(text)}"`) or a
98
+ template-literal substitution (`` `${formatBytes(n)}` ``) is a caller. The
99
+ hand lexer that impact and the binding resolver read code through
100
+ blanked every quoted byte, interpolations included, so both read
101
+ "0 callers" (#460, #475). Comments and string literals now come off the
102
+ file's grammar, interpolations kept, in every language with one; the
103
+ lexer and its special cases (`//` as floor division, a Rust lifetime,
104
+ `*` block-comment lines) are gone (#506).
105
+ - A removed definition is one the file declared at the base and does not
106
+ declare at the head, both read off the grammar. Read off removed lines by
107
+ regex, a Rust tail expression `Err("zero")` became a removed member named
108
+ `Err` with every `Err(` in the crate as its callers, a bare `int(text)` in
109
+ a Python method a removed `int`, and a private function next to an
110
+ `impl` a removed API (#515, #458). A member is as public as the class or
111
+ interface it sits in, by nesting, not by the nearest class above it.
112
+ - A caller of a removed definition is code that can still reach it:
113
+ through an import the binding model resolves to the file it left (a
114
+ removed member through its imported class), else from the same runtime.
115
+ By name alone, `subprocess.run`, another file's own `run`, a shell `die`
116
+ and a TypeScript `test.describe` were callers of a deleted Python script's
117
+ functions, and the import line was a second caller (#470, #471, #458,
118
+ #450). Files the range deleted resolve as files an import still names.
119
+ - A dependency bump is a change: impact names the lock files a range
120
+ changes (`lockfile_changes`) and never answers "No changes to analyse" for
121
+ them (#495).
122
+ - The hook's search pre-filter in the plugin script read the session's
123
+ cwd, not the Grep's own `path`, and ignored untracked files, so a search
124
+ over a pending change elsewhere never reached the binary. `bash -lc
125
+ "git commit …"` and `$(git -C r commit)` are named as unread rather than
126
+ logged as no trigger; reminders and gate denials are logged as such. A
127
+ cache directory the hook cannot write falls back to the temporary
128
+ directory instead of silently rerunning every preview (#407).
129
+ - Before a commit or push the hook says what it did when it has no impact
130
+ to show: one line when the change reaches nothing outside the diff, and
131
+ one when it could not read the command (a git inside a subshell, a `cd`
132
+ it cannot resolve), with the command to run instead. Silence then means
133
+ the hook did not run, not a clean review; the hook log keeps tens of
134
+ thousands of runs so an audit reads a whole session (#464).
135
+ - The MCP tool timed out at 300 s on a monorepo range the CLI answered in
136
+ four seconds (#457), for two reasons. It widened a subdirectory
137
+ `repo_path` to the whole repository; it now analyses the directory asked
138
+ about, as the CLI does with the same path. And impact grepped every
139
+ removed name as a fixed string in every file, so a deleted `call` matched
140
+ each `callback` in the repository's data and the run never stopped
141
+ (#413): the grep now reads whole words in code files only, at most 20
142
+ lines a file, and a name on more than 500 lines is answered "too common to
143
+ trace by name" instead of listed.
144
+ - Impact is fitted to its cap on the text the reader receives: markdown was
145
+ shed against the size of its JSON, so a 148-token answer cited a
146
+ 2000-token cap and lost its contract block; an omitted count printed
147
+ under the previous list's heading (#451). `--budget` (CLI) and
148
+ `budget_tokens` (MCP) set the cap, which stayed 2000 whatever was asked,
149
+ and a shed answer says which flag lists the rest (#466).
150
+ - The MCP tool's refusal of `symbol` together with `diff_ref` or
151
+ `fragment_ids` names the call that answers: drop them and ask in
152
+ `mode="impact"`, which looks the name up in the working tree (#499).
153
+ - Impact no longer reads "not examined" as "nothing there". A failed `git
154
+ grep` or file listing marks the run partial (`read_failed`) instead of
155
+ dropping the removed definition's callers and the stale references with
156
+ exit 0; a failed snapshot listing no longer falls back to the working
157
+ tree. A changed code file over the size cap or unreadable is named in
158
+ `unanalysed_files` (it answered `"empty": true` while pack said
159
+ `file_too_large`). Limits follow pack's rule, so the contribution and
160
+ total-byte caps, which drop callers, are named; the per-node edge cap
161
+ every repository trips still is not. A caller whose edge that cap cut
162
+ faces the same tests an edge would have and stays a caller (`--symbol`
163
+ answered "0 callers" for a function its route handler calls). A
164
+ definition changed only in comments, formatting or position is listed
165
+ in `layout_only` and the answer says its callers were not traced, where
166
+ it said "No resolved static callers"; the hook's search answer says the
167
+ same instead of claiming none. Under a limit, "nothing calls them" reads
168
+ "no caller found in this partial run". Unparsed files that name a
169
+ camelCase definition are disclosed: identifiers are stored lowercased,
170
+ and the lookup compared them as written.
171
+ - Impact no longer calls a definition whose only callers are inside the diff
172
+ one that "nothing calls": a helper extracted from a function the same
173
+ commit changes read as dead code, on 62–75% of that list across three
174
+ ranges of this repository. Such definitions are listed under "Changed,
175
+ called only from inside the diff, no static test link" and in the new
176
+ `untested_called_in_diff` field; an in-diff source counts when it passes
177
+ the tests any caller passes. 1.18.3 never printed the clause, so 1.18.4
178
+ added the false statement (#441).
179
+ - A control statement removed from a method body is no removed member:
180
+ `match (a, b) {` in a Rust `impl` read as a removed API `match`, and
181
+ every file spelling the keyword became its caller. `when`, `try`,
182
+ `synchronized`, `using`, `lock`, `fixed`, `foreach` and `elseif` are
183
+ filtered with it (#492).
184
+ - Impact looks a fragment id up as the fragment, not as the signature
185
+ variant that shares its id when the signature spans the whole fragment: a
186
+ Kotlin class whose header ends in `) {` answered `--symbol` with "0
187
+ definition(s)".
188
+ - The JVM builder links an owner-qualified short member (`UI.DP`) to its
189
+ declaration. Names under three characters never linked by name, so a
190
+ constant read through its object was missed.
191
+ - A JVM framework annotation registers what it annotates and nothing else:
192
+ a class-level `@Component` or `@Service` no longer marks every method a
193
+ registered entry point (which hid its missing test link); method-level
194
+ entry points (`@GetMapping`, `@Scheduled`, `@Bean`, …) count only when the
195
+ file imports them from a framework package.
196
+ - Impact names a caller by its whole definition, not by the signature
197
+ variant the graph linked: a use in the body — a constructor call after a
198
+ long parameter list — was dropped from the caller's sites.
199
+ - A name shared across runtimes no runtime crosses is neither a caller nor
200
+ a test link: a Kotlin test no longer reads as guarding a Swift function of
201
+ the same name, and a Kotlin or Swift spelling of a Python or TypeScript
202
+ name is not its caller. JVM languages still call each other, JavaScript
203
+ its HTML and components, Swift and Objective-C their C (#487, #458).
204
+ - A changed symbol whose name is spelled in a file in view that did not
205
+ parse cleanly lists that file (`missed_in`, "callers may be missing")
206
+ instead of reading as one nothing calls (#454).
207
+ - JVM tests pair with their subject the way JVM projects name them:
208
+ `FooTest`, `FooTests`, `FooSpec` and `FooIT` beside `Foo` are discovered
209
+ and linked in both directions, and `src/<set>/<java|kotlin|scala|groovy>/`
210
+ is a source root, so a test importing `com.acme.Foo` reaches
211
+ `src/main/kotlin/com/acme/Foo.kt`. A related test file is not a guard: a
212
+ method its tests never call keeps "no static test link".
213
+ - `--symbol Owner.name` reads Kotlin's owners and receivers from the syntax:
214
+ `String.pad` names the extension, `Worker.Companion.create` the companion
215
+ member; and the declaration search that keeps a capped query's definition
216
+ file knows `fun` and `typealias`.
217
+ - A Kotlin type the text declares at the start of a line but the parse did
218
+ not read — a grammar's silent misreading, with no error node — marks the
219
+ file analysed only in part (0 of yay-tsa's 1,036 files with the shipped
220
+ grammar; local classes count as read).
221
+ - The parse outcome is recorded when the fragmenter parses a file, not
222
+ inferred from the fragments that came out: a file that parsed with errors
223
+ is reported as analysed only in part even when a definition survived, and
224
+ a timed-out parse is named as such.
225
+
226
+ - Impact names the changed code files it could not read for definitions —
227
+ no grammar for the language in this build, or a parse that broke — in its
228
+ header and in `unanalysed_files`, and never calls such a change empty: a
229
+ Kotlin-only commit read "0 callers outside the diff" and the commit hook
230
+ stayed silent (#454). A script that parses cleanly and defines nothing is
231
+ not listed.
232
+ - `--symbol` no longer answers only "no definition found" when the files
233
+ naming the symbol were read as text: it names them and the reason, so an
234
+ unparsed definition is not mistaken for a missing one (#454).
235
+ - Locate's `unparsed_files` uses the same test: code with no grammar in this
236
+ build (Kotlin, F#, Fortran) or a broken parse is listed and lowers
237
+ confidence; a script that parses cleanly and defines nothing (a
238
+ `build.gradle.kts`, a Python script of top-level statements) no longer is.
239
+ Data files (`.sql`, `.toml`, `.txt`) stay unlisted (#330).
240
+
241
+ ### Changed
242
+
243
+ - **Plugin hook: a commit, merge, cherry-pick, revert or push is reviewed
244
+ from what git recorded, once the line ran** — read off the reflog (`HEAD`'s
245
+ last entry, a remote-tracking ref's "update by push"), never predicted
246
+ from the command text. The prediction took fifteen parser commits in
247
+ nine days and still reviewed another session's staged file for
248
+ `git commit --pathspec-from-file` instead of the paths the commit took
249
+ (#508). A loop, `xargs`, `git apply --cached`, `git --git-dir … commit` or
250
+ an escaped quote now stage what they stage, and the review says what the
251
+ commit took (#405, #436). A commit of a change already shown gets a
252
+ one-line pointer instead of silence (#501); a push of several refs
253
+ reviews the first and counts the rest; a `--squash` merge is reviewed by
254
+ the commit that records it. `L=/abs; cd $L && git commit` follows the
255
+ variable the line sets (#422). The opt-in strict gate still reviews
256
+ before git runs, and only what needs no prediction: the index a plain
257
+ `git commit` records; a commit line that stages for itself is denied with
258
+ the instruction to stage as its own command. Without the gate the impact
259
+ now arrives with the commit's result, which is when Claude Code delivered
260
+ the old preview too (#429).
261
+ - Claude plugin: the MCP server and the hooks launch `uvx diffctx[mcp]==X`
262
+ with no other option, and `plugin/uv.lock` pins the release's whole
263
+ dependency tree with PyPI hashes, the shape the plugin directory reads as a
264
+ locked launch ("Launcher lock invalid" before). `constraints.txt` is gone:
265
+ uv resolves diffctx's dependencies within their declared ranges, as every
266
+ other uvx install already did.
267
+ - CI and the pre-commit hook run the `impact`, `hook`, `bindings`, `staged`,
268
+ `git_faults`, `in_memory` and `context_schema` test targets, which no gate
269
+ ran before.
270
+
271
+ ## [1.18.4] - 2026-10-07
272
+
273
+ ### Security
274
+
275
+ - Impact no longer names a file the run withholds (`.diffctx/ignore`, a
276
+ secret name): a definition removed from it, its deletion or its rename
277
+ reached the answer while pack reported only a withheld count.
278
+
279
+ ### Changed
280
+
281
+ - The MCP server runs on the MCP Python SDK 2.3 (`mcp>=2.3,<3`): it answers
282
+ the `server/discover` probe Claude Code opens with, instead of rejecting it
283
+ with 31 validation errors on every connect, and still accepts every
284
+ `initialize` revision back to 2024-11-05 (#366).
285
+ - `ChangeClass` gains `layout`: a file whose diff changes only whitespace,
286
+ comments, quote style, trailing commas, semicolons or import order. Pack
287
+ ranks it with `mechanical` (#370).
288
+ - `diffctx graph --level file|directory` now applies to `-f json`, `-f
289
+ graphml` and the `--summary` counts; without `--level` they stay per
290
+ fragment (#352).
291
+
292
+ ### Fixed
293
+
294
+ - Plugin hook: the commit is previewed as the index it will start from — the
295
+ line's own `git add`/`git rm` replayed on a copy of the index, `-a`, a
296
+ pathspec with `--only` semantics, `-i`, `--amend`, a first commit, a linked
297
+ worktree — instead of the whole working tree; a here-document message is no
298
+ pathspec; a multi-commit cherry-pick is reviewed whole; `cd ~/repo` is
299
+ followed; a diff written to a file is no inspection; an empty answer stays
300
+ empty after the commit lands (#384, #373, #380, #377). The session hint
301
+ names `--symbol` again (#379).
302
+ - Impact: the header counts every caller its lines call untested, says
303
+ "callers unresolved" instead of "0 caller(s)" when no binding model could
304
+ resolve them, says when the run stopped early, and counts changed
305
+ definitions no test reaches (#386, #387, #335). A caller registered by a
306
+ decorator (`@register_node`, `@app.route`) is no longer called untested
307
+ (#388). Adding fields or parameters with defaults collapses the call sites
308
+ into one signature-compatible line (#388); formatting- or comment-only edits
309
+ are no contract and list no callers (#370). An unchanged caller in a changed
310
+ file is listed (#362). `pub` inside a `pub(crate)` module is no public API
311
+ (#359); a script named for a schema is no schema contract (#374). A deleted
312
+ path is matched by its path, a relative path, its module name or a
313
+ basename no other file has, never by a bare word (#369, #383). A removed
314
+ member and the remaining `?.()` calls to it are reported (#367).
315
+ - Impact on a large changed module took 74 s on one core; per-file indexes and
316
+ memoized test links bring it to 4 s with byte-identical output (#385). The
317
+ Python CLI's watchdog waits the binary's 30 s grace past `--timeout`, so a
318
+ run that stops at its deadline answers partially instead of exiting 124
319
+ with nothing (#382).
320
+ - MCP: `diff_ref="HEAD"` is documented as the uncommitted work, and a clean
321
+ tree answers with the hint that `HEAD~1..HEAD` is the last commit (#368).
322
+ - Selection: changed code is placed by evidence tier (production, tests,
323
+ config and docs, mechanical or layout edits, generated files) and by how
324
+ densely each piece carries the change, not cheapest-first; a core the
325
+ first pass could only place as its signature stub gets its body when the
326
+ difference fits (#296, #327). A Django migration ("Generated by Django")
327
+ is no longer a generated file ranked last (#311).
328
+ - Graph: a range's own commits no longer vouch for its files in the
329
+ co-change channel, which now links two files once, representative to
330
+ representative (#340). The tags fallback no longer links files of a
331
+ language its own builder parsed (a stdlib module name, a keyword); a key
332
+ most config files carry (`workflow_dispatch` in every CI workflow) links
333
+ no source file to all of them (#286, #297); a setting spelled whole
334
+ (`database_url` in two of four environment files) still links its reader.
335
+ A bare `--diff X` or a time window reads history only up to its base.
336
+ - Imports that spell paths resolve through one rule — next to the importing
337
+ file, then from the root, `..` folded: Nim `import vendor/palette`, Nix
338
+ `import ../vendor/palette.nix` (and a directory's `default.nix`), Ruby
339
+ `require_relative`; OCaml `open Palette` links `palette.ml` (#285). A
340
+ TypeScript import resolves with the nearest `tsconfig*.json`, its relative
341
+ `extends` folded in, not only the root one (#372).
342
+ - Plugin hook: `merge --squash`, `merge -S` and `cherry-pick -S` keep their
343
+ target; a pathspec commit previews the paths the index knows, never an
344
+ untracked file under them; `git apply`, `update-index`, `read-tree` and
345
+ the like before a commit preview the working tree; a step in another
346
+ repository stages nothing; the preview reads a copy of the index, so a
347
+ held index lock neither blocks it nor is taken; a first commit is checked
348
+ once it lands; a manual run that found nothing leaves no reminder;
349
+ `git status && git diff --cached` reviews the index; the `impact_gate`
350
+ option no longer silences PostToolUse answers; merging the branch's own
351
+ upstream (`git merge --ff-only origin/main`) is silence.
352
+ - Impact: a definition moved to another module names the importers of the
353
+ old one; removed methods are found in classes split into fragments, and
354
+ `def self.x`, `func (r T) x`, `const x = () =>` name `x`; a deletion below
355
+ a definition does not change it; a lexical test or caller link stops at
356
+ a sibling project's manifest; a function nested in another has callers
357
+ only inside it, and an untyped `x.name` attribute read is no possible
358
+ caller. Formatting-only detection keeps a docstring
359
+ that is not one, and no longer calls a change layout when it moves a line
360
+ out of its Python or YAML block, drops a one-element tuple's comma, edits
361
+ a mid-word `#` (`${#a}`), or removes a `;` before a line starting with `(`
362
+ or `[` in JS/TS. Compatible extension requires the new field to be
363
+ appended, and a relative path to a moved file is a stale reference.
364
+ - `--symbol` accepts `Owner.name`, `Owner::name` and dotted names; a
365
+ malformed one exits 2 in both CLIs, and over MCP a malformed or unknown
366
+ symbol is a refusal the agent can read, not "Error executing tool"; a
367
+ name in more than 256 files keeps its definition past the cap.
368
+ - The Python budget fitter drops a `layout` witness with `mechanical` ones
369
+ and ranks `unknown` with content, as the native CLI does.
370
+ - Release: post-publish smokes wait until both pip and uv resolve the version
371
+ and fail with a named error after 10 minutes; dispatched runs are found by
372
+ dispatch time (#364).
373
+ - Plugin hook: a path git cannot hash (an untracked nested repository, a
374
+ moved submodule) no longer turns the answer into silence logged as
375
+ "clean"; a multi-commit cherry-pick touching a non-UTF-8 file is reviewed;
376
+ a `cd` that a search printed is not taken for where it ran. `--diff staged`
377
+ writes a copy of the index, so a held `index.lock` is no "unmerged entries?".
378
+ - Impact: a Rust type and its `impl` blocks are one definition with its
379
+ callers, a trait method is reached through its trait (`Display`, not
380
+ `std`), and an unresolved name counts definitions, not "files" (#403).
381
+ Python `//` hides no call; a root `"baseUrl": "."` resolves bare
382
+ specifiers and overlapping `paths` take the longest prefix (#414). A
383
+ non-ASCII path, a `diff.context` setting, a deleted call statement, a
384
+ deletion right under a caller, a column-0 line removed from a body and a
385
+ call still naming a moved Rust function's old module no longer hide or
386
+ invent callers; a body call gaining an argument and an added `__slots__`
387
+ are not signature-compatible; `pub mod r#type;` and an attributed
388
+ `pub mod` are public; a same-language sibling module importing the
389
+ qualified name keeps its test link. `--symbol Owner.name` answers only for
390
+ that owner, and `PATH:Owner::name` parses. A call spread into an object
391
+ (`...workboxPolicy({..})`) is a call, not a member access: four Vite
392
+ configs read as zero callers.
393
+ - Impact counts the files the run withholds instead of reading them as
394
+ nothing: the header says `N more withheld by ignore rules` for a range and
395
+ `N file(s) naming it withheld` for `--symbol`, and that their calls and
396
+ tests were not read (`withheld_files` in the JSON) (#392).
397
+ - Impact: a definition moved within its file with its text unchanged lists
398
+ no callers, as a formatting-only edit does; moved and edited, it still
399
+ does. `@value.setter`, `@attr.s`, `@define`, `@enum.unique` and Lombok
400
+ annotations shape a definition and register it nowhere, so an untested
401
+ setter reads untested instead of "registered by" (#414).
402
+ - The CLI and the MCP server report only the engine's own "nothing defines
403
+ it" as a refusal; a `KeyError` or `IndexError` is an internal error with its
404
+ traceback. Git output is decoded as UTF-8 on every platform.
405
+
10
406
  ## [1.18.3] - 2026-10-04
11
407
 
12
408
  ### Added
@@ -1903,7 +2299,9 @@ Earlier releases shipped as `treemapper`; see
1903
2299
  <https://github.com/nikolay-e/diffctx/releases> for the corresponding GitHub
1904
2300
  release notes (`1.0.0` through `1.6.1`).
1905
2301
 
1906
- [Unreleased]: https://github.com/nikolay-e/diffctx/compare/v1.18.3...HEAD
2302
+ [Unreleased]: https://github.com/nikolay-e/diffctx/compare/v1.19.0...HEAD
2303
+ [1.19.0]: https://github.com/nikolay-e/diffctx/compare/v1.18.4...v1.19.0
2304
+ [1.18.4]: https://github.com/nikolay-e/diffctx/compare/v1.18.3...v1.18.4
1907
2305
  [1.18.3]: https://github.com/nikolay-e/diffctx/compare/v1.18.2...v1.18.3
1908
2306
  [1.18.2]: https://github.com/nikolay-e/diffctx/compare/v1.18.1...v1.18.2
1909
2307
  [1.18.1]: https://github.com/nikolay-e/diffctx/compare/v1.18.0...v1.18.1
@@ -94,6 +94,16 @@ version = "2.11.1"
94
94
  source = "registry+https://github.com/rust-lang/crates.io-index"
95
95
  checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
96
96
 
97
+ [[package]]
98
+ name = "brokk-tree-sitter-kotlin"
99
+ version = "0.4.6"
100
+ source = "registry+https://github.com/rust-lang/crates.io-index"
101
+ checksum = "11536d4605247354ad8992b7ae605e85508c07d64ed0710ff8494558660e40d0"
102
+ dependencies = [
103
+ "cc",
104
+ "tree-sitter-language",
105
+ ]
106
+
97
107
  [[package]]
98
108
  name = "bstr"
99
109
  version = "1.13.1"
@@ -107,9 +117,9 @@ dependencies = [
107
117
 
108
118
  [[package]]
109
119
  name = "cc"
110
- version = "1.2.60"
120
+ version = "1.2.56"
111
121
  source = "registry+https://github.com/rust-lang/crates.io-index"
112
- checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20"
122
+ checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
113
123
  dependencies = [
114
124
  "find-msvc-tools",
115
125
  "shlex",
@@ -194,10 +204,11 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
194
204
 
195
205
  [[package]]
196
206
  name = "diffctx"
197
- version = "1.18.3"
207
+ version = "1.19.0"
198
208
  dependencies = [
199
209
  "aho-corasick",
200
210
  "anyhow",
211
+ "brokk-tree-sitter-kotlin",
201
212
  "clap",
202
213
  "dunce",
203
214
  "glob",
@@ -0,0 +1,135 @@
1
+ Metadata-Version: 2.4
2
+ Name: diffctx
3
+ Version: 1.19.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: hypothesis>=6.0,<7.0 ; extra == 'dev'
24
+ Requires-Dist: import-linter>=2.0,<3.0 ; extra == 'dev'
25
+ Requires-Dist: mypy>=1.0,<3.0 ; extra == 'dev'
26
+ Requires-Dist: pre-commit>=3.0,<5.0 ; extra == 'dev'
27
+ Requires-Dist: pygit2>=1.12,<2.0 ; extra == 'dev'
28
+ Requires-Dist: pytest>=7.0,<10.0 ; extra == 'dev'
29
+ Requires-Dist: pytest-asyncio>=0.23,<2.0 ; extra == 'dev'
30
+ Requires-Dist: pytest-cov>=3.0,<8.0 ; extra == 'dev'
31
+ Requires-Dist: pytest-timeout>=2.1,<3.0 ; extra == 'dev'
32
+ Requires-Dist: pytest-xdist>=3.0,<4.0 ; extra == 'dev'
33
+ Requires-Dist: pyyaml>=6.0.2,<8.0 ; extra == 'dev'
34
+ Requires-Dist: radon>=6.0,<7.0 ; extra == 'dev'
35
+ Requires-Dist: ruff>=0.4,<1.0 ; extra == 'dev'
36
+ Requires-Dist: tiktoken>=0.9,<1.0 ; extra == 'dev'
37
+ Requires-Dist: types-pyyaml>=6.0,<7.0 ; extra == 'dev'
38
+ Requires-Dist: charset-normalizer>=3.0,<4.0 ; extra == 'full'
39
+ Requires-Dist: anyio>=4.5,<5.0 ; extra == 'mcp'
40
+ Requires-Dist: mcp>=2.3,<3 ; extra == 'mcp'
41
+ Provides-Extra: dev
42
+ Provides-Extra: full
43
+ Provides-Extra: mcp
44
+ License-File: LICENSE
45
+ Summary: Selects the minimum code an LLM needs to review a git diff
46
+ 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
47
+ Home-Page: https://diffctx.com/
48
+ Author-email: Nikolay Eremeev <nikolay.eremeev@outlook.com>
49
+ License-Expression: Apache-2.0
50
+ Requires-Python: >=3.10
51
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
52
+ Project-URL: Author, https://nikolay-eremeev.com
53
+ Project-URL: Changelog, https://github.com/nikolay-e/diffctx/blob/main/CHANGELOG.md
54
+ Project-URL: Documentation, https://diffctx.com/
55
+ Project-URL: Homepage, https://diffctx.com/
56
+ Project-URL: Issues, https://github.com/nikolay-e/diffctx/issues
57
+ Project-URL: Paper, https://doi.org/10.5281/zenodo.18824579
58
+ Project-URL: Repository, https://github.com/nikolay-e/diffctx
59
+
60
+ # diffctx — what a git change reaches, and the code to review it
61
+
62
+ [![CI](https://github.com/nikolay-e/diffctx/actions/workflows/ci.yml/badge.svg)](https://github.com/nikolay-e/diffctx/actions/workflows/ci.yml)
63
+ [![PyPI](https://img.shields.io/pypi/v/diffctx)](https://pypi.org/project/diffctx/)
64
+ [![crates.io](https://img.shields.io/crates/v/diffctx)](https://crates.io/crates/diffctx)
65
+ [![npm](https://img.shields.io/npm/v/diffctx)](https://www.npmjs.com/package/diffctx)
66
+ [![MCP Registry](https://img.shields.io/badge/MCP_Registry-io.github.nikolay--e%2Fdiffctx-blue)](https://registry.modelcontextprotocol.io/v0/servers?search=io.github.nikolay-e/diffctx)
67
+
68
+ **diffctx tells an agent or a reviewer what a change reaches outside its
69
+ diff** — the callers, the tests that reach them, the contracts it crosses —
70
+ **and selects the code needed to understand it** under a token budget. Local,
71
+ deterministic, no index, no model calls. Caller resolution is static and per
72
+ language; where it cannot resolve, the answer says so instead of printing a
73
+ zero.
74
+
75
+ > Formerly published as `treemapper` — every command, flag, and API call works unchanged.
76
+
77
+ ## Install
78
+
79
+ ```bash
80
+ # Claude Code: the MCP server, /diffctx:impact, and hooks that run it on each commit and push
81
+ claude plugin marketplace add nikolay-e/diffctx
82
+ claude plugin install diffctx@diffctx
83
+
84
+ # any MCP client
85
+ claude mcp add diffctx -- uvx --from 'diffctx[mcp]' diffctx-mcp
86
+
87
+ # CLI, zero-install
88
+ uvx diffctx . --diff HEAD~1
89
+ ```
90
+
91
+ pipx, pip, cargo, npm, Docker, Scoop, other MCP clients and the Python API:
92
+ [integrations](docs/product/integrations.md).
93
+
94
+ ## Usage
95
+
96
+ ```bash
97
+ diffctx . --diff --mode impact # uncommitted work: callers outside the diff, their tests, contracts
98
+ diffctx . --symbol parse_config # the same for a name, no change needed
99
+ diffctx . --diff main...feature # the code to review a branch, packed under the auto budget
100
+ diffctx . --diff HEAD~1 --budget 12000 # the last commit, capped at 12k o200k tokens
101
+ diffctx . --diff 24h --mode locate # today's work as ranked JSON, no source bodies
102
+ diffctx . # whole-tree export, Markdown
103
+ ```
104
+
105
+ `--diff` takes a git range, `staged`, or a duration window ending now (`24h`,
106
+ `90min`, `2w`). Every flag, default and exit code:
107
+ [command-line reference](docs/product/cli.md).
108
+
109
+ ![diffctx demo](https://raw.githubusercontent.com/nikolay-e/diffctx/main/docs/demo/demo.gif)
110
+
111
+ ## How it compares
112
+
113
+ Whole-repo packers export everything; code-graph servers answer queries
114
+ against a maintained index. diffctx is **diff-seeded**: the input is a change,
115
+ the output is what it touches and what explains it. Measured results, and when
116
+ the other two fit better: [COMPARISON.md](COMPARISON.md).
117
+
118
+ ## More
119
+
120
+ - [Documentation site](https://diffctx.com/) — the pipeline end to end
121
+ - [Command-line reference](docs/product/cli.md) — rendered from `diffctx --help`
122
+ - [Integrations](docs/product/integrations.md) — install, MCP clients, Python API
123
+ - [FAQ](docs/product/faq.md) — heuristic or oracle, ignore rules, monorepos, secrets
124
+ - [Token counting](docs/product/token-budget.md) — `--budget` for non-GPT models
125
+ - [GitHub Action](docs/product/github-action.md) — diff context as a CI step
126
+ - [Benchmarks](BENCHMARKS.md) and the [paper](https://doi.org/10.5281/zenodo.18824579)
127
+ - [Changelog](CHANGELOG.md) · [Security](SECURITY.md)
128
+ - [Parameter strategy](docs/engineering/parameter-strategy.md)
129
+
130
+ Apache 2.0
131
+
132
+ <!-- mcp-name: io.github.nikolay-e/diffctx -->
133
+ <!-- Ownership marker read from the PyPI description by the MCP registry. -->
134
+ <!-- Must survive edits verbatim: one space after the colon, case-sensitive. -->
135
+