diffctx 1.18.0__tar.gz → 1.18.1__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 (169) hide show
  1. {diffctx-1.18.0 → diffctx-1.18.1}/CHANGELOG.md +46 -1
  2. {diffctx-1.18.0 → diffctx-1.18.1}/Cargo.lock +1 -1
  3. {diffctx-1.18.0 → diffctx-1.18.1}/PKG-INFO +2 -2
  4. {diffctx-1.18.0 → diffctx-1.18.1}/README.md +1 -1
  5. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/Cargo.toml +1 -1
  6. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/README.md +1 -1
  7. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/rust_lang.rs +13 -4
  8. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/hook.rs +185 -26
  9. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/impact.rs +296 -66
  10. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/locate.rs +26 -28
  11. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/pybridge.rs +10 -3
  12. {diffctx-1.18.0 → diffctx-1.18.1}/pyproject.toml +1 -1
  13. {diffctx-1.18.0 → diffctx-1.18.1}/src/diffctx/cli.py +4 -3
  14. {diffctx-1.18.0 → diffctx-1.18.1}/src/diffctx/mcp/server.py +28 -13
  15. diffctx-1.18.1/src/diffctx/version.py +1 -0
  16. diffctx-1.18.0/src/diffctx/version.py +0 -1
  17. {diffctx-1.18.0 → diffctx-1.18.1}/Cargo.toml +0 -0
  18. {diffctx-1.18.0 → diffctx-1.18.1}/LICENSE +0 -0
  19. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/analytics.rs +0 -0
  20. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/candidate_files.rs +0 -0
  21. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/change_class.rs +0 -0
  22. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/config/analytics.rs +0 -0
  23. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/config/bm25.rs +0 -0
  24. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/config/budget.rs +0 -0
  25. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/config/category_weights.rs +0 -0
  26. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/config/edge_weights.rs +0 -0
  27. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/config/env_overrides.rs +0 -0
  28. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/config/extensions.rs +0 -0
  29. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/config/filtering.rs +0 -0
  30. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/config/fragmentation.rs +0 -0
  31. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/config/git.rs +0 -0
  32. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/config/graph_filtering.rs +0 -0
  33. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/config/importance.rs +0 -0
  34. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/config/limits.rs +0 -0
  35. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/config/mod.rs +0 -0
  36. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/config/mode.rs +0 -0
  37. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/config/needs.rs +0 -0
  38. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/config/parsers.rs +0 -0
  39. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/config/render.rs +0 -0
  40. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/config/scoring.rs +0 -0
  41. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/config/selection.rs +0 -0
  42. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/config/tokenization.rs +0 -0
  43. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/config/weights.rs +0 -0
  44. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/core.rs +0 -0
  45. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/discovery.rs +0 -0
  46. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/base.rs +0 -0
  47. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/config_edges/build_system.rs +0 -0
  48. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/config_edges/cicd.rs +0 -0
  49. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/config_edges/docker.rs +0 -0
  50. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/config_edges/generic.rs +0 -0
  51. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/config_edges/helm.rs +0 -0
  52. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/config_edges/kubernetes.rs +0 -0
  53. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/config_edges/mod.rs +0 -0
  54. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/document/mod.rs +0 -0
  55. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/history/cochange.rs +0 -0
  56. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/history/mod.rs +0 -0
  57. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/mod.rs +0 -0
  58. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/ansible.rs +0 -0
  59. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/bazel.rs +0 -0
  60. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/c_family.rs +0 -0
  61. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/cargo_edges.rs +0 -0
  62. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/clojure.rs +0 -0
  63. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/css.rs +0 -0
  64. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/dart.rs +0 -0
  65. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/dbt.rs +0 -0
  66. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/dotnet.rs +0 -0
  67. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/elixir.rs +0 -0
  68. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/erlang.rs +0 -0
  69. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/go.rs +0 -0
  70. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/graphql.rs +0 -0
  71. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/haskell.rs +0 -0
  72. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/javascript.rs +0 -0
  73. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/julia.rs +0 -0
  74. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/jvm.rs +0 -0
  75. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/latex.rs +0 -0
  76. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/lua.rs +0 -0
  77. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/mod.rs +0 -0
  78. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/nim.rs +0 -0
  79. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/nix.rs +0 -0
  80. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/ocaml.rs +0 -0
  81. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/openapi.rs +0 -0
  82. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/perl.rs +0 -0
  83. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/php.rs +0 -0
  84. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/prisma.rs +0 -0
  85. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/protobuf.rs +0 -0
  86. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/python.rs +0 -0
  87. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/r_lang.rs +0 -0
  88. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/ruby.rs +0 -0
  89. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/shell.rs +0 -0
  90. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/sql.rs +0 -0
  91. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/swift.rs +0 -0
  92. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/tags.rs +0 -0
  93. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/terraform.rs +0 -0
  94. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/semantic/zig.rs +0 -0
  95. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/similarity/lexical.rs +0 -0
  96. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/similarity/mod.rs +0 -0
  97. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/structural/containment.rs +0 -0
  98. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/structural/mod.rs +0 -0
  99. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/structural/sibling.rs +0 -0
  100. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/edges/structural/testing.rs +0 -0
  101. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/effective_config.rs +0 -0
  102. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/excerpt.rs +0 -0
  103. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/facts/mod.rs +0 -0
  104. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/facts/scala.rs +0 -0
  105. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/filtering.rs +0 -0
  106. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/fragmentation.rs +0 -0
  107. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/git.rs +0 -0
  108. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/graph.rs +0 -0
  109. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/graph_export.rs +0 -0
  110. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/in_memory_harness.rs +0 -0
  111. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/interval.rs +0 -0
  112. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/languages.rs +0 -0
  113. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/lib.rs +0 -0
  114. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/main.rs +0 -0
  115. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/mode.rs +0 -0
  116. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/parsers/config_parser.rs +0 -0
  117. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/parsers/generic.rs +0 -0
  118. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/parsers/markdown.rs +0 -0
  119. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/parsers/mod.rs +0 -0
  120. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/parsers/tree_sitter_strategy.rs +0 -0
  121. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/paths.rs +0 -0
  122. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/peak_rss.rs +0 -0
  123. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/pipeline.rs +0 -0
  124. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/postpass.rs +0 -0
  125. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/ppr.rs +0 -0
  126. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/project_graph.rs +0 -0
  127. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/provenance.rs +0 -0
  128. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/render.rs +0 -0
  129. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/resource.rs +0 -0
  130. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/run_provenance.rs +0 -0
  131. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/sanitize.rs +0 -0
  132. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/scoring.rs +0 -0
  133. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/select.rs +0 -0
  134. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/signatures.rs +0 -0
  135. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/stopwords/code.txt +0 -0
  136. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/stopwords/docs.txt +0 -0
  137. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/stopwords.rs +0 -0
  138. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/test_rng.rs +0 -0
  139. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/testfiles.rs +0 -0
  140. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/token_corpus.rs +0 -0
  141. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/tokenizer.rs +0 -0
  142. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/types.rs +0 -0
  143. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/utility/boltzmann.rs +0 -0
  144. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/utility/importance.rs +0 -0
  145. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/utility/mod.rs +0 -0
  146. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/utility/needs.rs +0 -0
  147. {diffctx-1.18.0 → diffctx-1.18.1}/crates/diffctx-native/src/utility/objective.rs +0 -0
  148. {diffctx-1.18.0 → diffctx-1.18.1}/rust-toolchain.toml +0 -0
  149. {diffctx-1.18.0 → diffctx-1.18.1}/src/diffctx/__init__.py +0 -0
  150. {diffctx-1.18.0 → diffctx-1.18.1}/src/diffctx/__main__.py +0 -0
  151. {diffctx-1.18.0 → diffctx-1.18.1}/src/diffctx/_app.py +0 -0
  152. {diffctx-1.18.0 → diffctx-1.18.1}/src/diffctx/_diffctx.pyi +0 -0
  153. {diffctx-1.18.0 → diffctx-1.18.1}/src/diffctx/_native/__init__.py +0 -0
  154. {diffctx-1.18.0 → diffctx-1.18.1}/src/diffctx/_native/graph_analytics.py +0 -0
  155. {diffctx-1.18.0 → diffctx-1.18.1}/src/diffctx/_native/graph_export.py +0 -0
  156. {diffctx-1.18.0 → diffctx-1.18.1}/src/diffctx/_native/pipeline.py +0 -0
  157. {diffctx-1.18.0 → diffctx-1.18.1}/src/diffctx/_native/project_graph.py +0 -0
  158. {diffctx-1.18.0 → diffctx-1.18.1}/src/diffctx/clipboard.py +0 -0
  159. {diffctx-1.18.0 → diffctx-1.18.1}/src/diffctx/ignore.py +0 -0
  160. {diffctx-1.18.0 → diffctx-1.18.1}/src/diffctx/logger.py +0 -0
  161. {diffctx-1.18.0 → diffctx-1.18.1}/src/diffctx/mcp/__init__.py +0 -0
  162. {diffctx-1.18.0 → diffctx-1.18.1}/src/diffctx/mcp/__main__.py +0 -0
  163. {diffctx-1.18.0 → diffctx-1.18.1}/src/diffctx/mcp/fetch.py +0 -0
  164. {diffctx-1.18.0 → diffctx-1.18.1}/src/diffctx/mcp/legacy.py +0 -0
  165. {diffctx-1.18.0 → diffctx-1.18.1}/src/diffctx/mcp/security.py +0 -0
  166. {diffctx-1.18.0 → diffctx-1.18.1}/src/diffctx/py.typed +0 -0
  167. {diffctx-1.18.0 → diffctx-1.18.1}/src/diffctx/tokens.py +0 -0
  168. {diffctx-1.18.0 → diffctx-1.18.1}/src/diffctx/tree.py +0 -0
  169. {diffctx-1.18.0 → diffctx-1.18.1}/src/diffctx/writer.py +0 -0
@@ -7,6 +7,50 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.18.1] - 2026-09-30
11
+
12
+ ### Fixed
13
+
14
+ - **The plugin's impact hook never ran under macOS `/bin/bash` 3.2**: an empty
15
+ `gate=()` expanded under `set -u` is an unbound variable there, the pipeline
16
+ died right of the pipe and `|| true` hid it. One never-empty argument array.
17
+ - Hook command parsing: `git add … && git commit` reviews the working tree
18
+ (the add has not run when the hook sees the line — it reviewed the stale
19
+ index and was silent on the commonest agent pattern); `-m a -m b`, `-F file`
20
+ and other value flags no longer count as pathspecs; `git diff A B` and
21
+ `git diff main` are history, not the pending change; `cd dir && git …` and
22
+ a relative `-C` resolve against the agent's cwd.
23
+ - Impact: a test guards a caller only when it names the symbol *and* reaches
24
+ the caller's module (a Rust test mentioning `render` was the guard of a
25
+ Python `render()`, #312); a chunk caller named `main[88]` is looked up as
26
+ `main`; two-letter modules (`db.py`) keep their callers; `*out = f()` and
27
+ `#[derive]` lines are code, not comments; a wide changed-file list folds
28
+ into a count before a single caller is dropped, and a symbol keeps its
29
+ strongest caller; a run that hit a limit says `(partial: …)` and is never
30
+ `empty`; schema contracts are listed before public symbols with a `+N more`
31
+ count; SHA-256 repositories count commits.
32
+ - `diffctx.impact.v1` and `diffctx.locate.v1` required every field the
33
+ serializer may omit (`truncated`, `commit_count`, `deleted_files`, …), so no
34
+ real document validated; the schemas now require what is always present,
35
+ and a test validates real impact and locate output against them.
36
+ - MCP: the default `diff_ref` on a clean single-commit repository is the
37
+ working tree, not an error naming a range the caller never passed; the
38
+ `git status` guess runs off the event loop with a 10 s cap and never leaks
39
+ the command line into the response.
40
+ - CLI: `--mode impact -o out.yaml` is refused like `-f yaml` instead of
41
+ writing JSON silently; `--mode` help names the text default.
42
+ - Plugin installer: the download is unpacked beside its target so the final
43
+ `mv` is a rename, never a copy another session could execute half-written.
44
+ - MCP `mode=impact` with `paths` no longer marks the whole change reviewed.
45
+ - Landing page: the four-part iOS viewport contract (`viewport-fit=cover`,
46
+ status-bar style, safe-area insets, taller `html`).
47
+ - Privacy page names the release-binary download the plugin makes.
48
+ - `bitcheck.sh` ranges re-pinned on the rewritten history; the old anchors
49
+ kept the pre-rewrite tree, with third-party tokens in retired benchmark
50
+ diffs, reachable on both forges.
51
+ - Runtime image: one package layer (Sonar `docker:S7031`/`S6595`); PyJWT
52
+ 2.15.1 in `uv.lock` (GHSA-w6j9-cwv2-h6wq, transitive).
53
+
10
54
  ## [1.18.0] - 2026-09-29
11
55
 
12
56
  ### Added
@@ -1672,7 +1716,8 @@ Earlier releases shipped as `treemapper`; see
1672
1716
  <https://github.com/nikolay-e/diffctx/releases> for the corresponding GitHub
1673
1717
  release notes (`1.0.0` through `1.6.1`).
1674
1718
 
1675
- [Unreleased]: https://github.com/nikolay-e/diffctx/compare/v1.18.0...HEAD
1719
+ [Unreleased]: https://github.com/nikolay-e/diffctx/compare/v1.18.1...HEAD
1720
+ [1.18.1]: https://github.com/nikolay-e/diffctx/compare/v1.18.0...v1.18.1
1676
1721
  [1.18.0]: https://github.com/nikolay-e/diffctx/compare/v1.17.0...v1.18.0
1677
1722
  [1.17.0]: https://github.com/nikolay-e/diffctx/compare/v1.16.0...v1.17.0
1678
1723
  [1.16.0]: https://github.com/nikolay-e/diffctx/compare/v1.15.0...v1.16.0
@@ -194,7 +194,7 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
194
194
 
195
195
  [[package]]
196
196
  name = "diffctx"
197
- version = "1.18.0"
197
+ version = "1.18.1"
198
198
  dependencies = [
199
199
  "aho-corasick",
200
200
  "anyhow",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: diffctx
3
- Version: 1.18.0
3
+ Version: 1.18.1
4
4
  Classifier: Development Status :: 5 - Production/Stable
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -154,7 +154,7 @@ keeps its git meaning.
154
154
  | `--full` | false | Only the changed files, every fragment, no related-code context |
155
155
  | `--timeout` | 300 | Wall-clock deadline in seconds; on expiry the run stops cooperatively and emits a partial artifact whose `coverage` block names the limit (exit 0). 124 is the watchdog behind it, 30 s later, for a phase that could not stop |
156
156
  | `--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 |
157
- | `--mode` | `pack` | `locate` emits the same ranked selection as compact `diffctx.locate.v1` JSON — path, lines, score, provenance reasons, a blast-radius `summary` and per-item impact `group` (`test`/`type`/`config`), NO source bodies. Adds a `coverage` block naming what the run could not see (`unparsed_files`, `zero_edge_files`, `ppr_truncated`, `next_up`, a heuristic `confidence`) and an `overflow` ranking of what the budget left behind — omitted entirely when there is nothing to disclose. `diffctx . --diff --mode locate` = impact of your uncommitted change. The MCP tool takes it as `mode="locate"` |
157
+ | `--mode` | `pack` | `locate` emits the same ranked selection as compact `diffctx.locate.v1` JSON — path, lines, score, provenance reasons, a blast-radius `summary` and per-item impact `group` (`test`/`type`/`config`), NO source bodies. Adds a `coverage` block naming what the run could not see (`unparsed_files`, `zero_edge_files`, `ppr_truncated`, `next_up`, a heuristic `confidence`) and an `overflow` ranking of what the budget left behind — omitted entirely when there is nothing to disclose. `impact` answers, under 2k tokens, what the change reaches outside its diff: callers in files the diff does not touch, whether a test guards each, cross-commit overlap, and the public API and schema changes (text by default, `diffctx.impact.v1` JSON with `-f json`). The MCP tool takes both as `mode` |
158
158
 
159
159
  Every JSON/YAML artifact opens with `schema: diffctx.context.v1` and validates
160
160
  against [`schemas/diffctx.context.v1.json`](schemas/diffctx.context.v1.json),
@@ -95,7 +95,7 @@ keeps its git meaning.
95
95
  | `--full` | false | Only the changed files, every fragment, no related-code context |
96
96
  | `--timeout` | 300 | Wall-clock deadline in seconds; on expiry the run stops cooperatively and emits a partial artifact whose `coverage` block names the limit (exit 0). 124 is the watchdog behind it, 30 s later, for a phase that could not stop |
97
97
  | `--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 |
98
- | `--mode` | `pack` | `locate` emits the same ranked selection as compact `diffctx.locate.v1` JSON — path, lines, score, provenance reasons, a blast-radius `summary` and per-item impact `group` (`test`/`type`/`config`), NO source bodies. Adds a `coverage` block naming what the run could not see (`unparsed_files`, `zero_edge_files`, `ppr_truncated`, `next_up`, a heuristic `confidence`) and an `overflow` ranking of what the budget left behind — omitted entirely when there is nothing to disclose. `diffctx . --diff --mode locate` = impact of your uncommitted change. The MCP tool takes it as `mode="locate"` |
98
+ | `--mode` | `pack` | `locate` emits the same ranked selection as compact `diffctx.locate.v1` JSON — path, lines, score, provenance reasons, a blast-radius `summary` and per-item impact `group` (`test`/`type`/`config`), NO source bodies. Adds a `coverage` block naming what the run could not see (`unparsed_files`, `zero_edge_files`, `ppr_truncated`, `next_up`, a heuristic `confidence`) and an `overflow` ranking of what the budget left behind — omitted entirely when there is nothing to disclose. `impact` answers, under 2k tokens, what the change reaches outside its diff: callers in files the diff does not touch, whether a test guards each, cross-commit overlap, and the public API and schema changes (text by default, `diffctx.impact.v1` JSON with `-f json`). The MCP tool takes both as `mode` |
99
99
 
100
100
  Every JSON/YAML artifact opens with `schema: diffctx.context.v1` and validates
101
101
  against [`schemas/diffctx.context.v1.json`](schemas/diffctx.context.v1.json),
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "diffctx"
3
- version = "1.18.0"
3
+ version = "1.18.1"
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"
@@ -95,7 +95,7 @@ keeps its git meaning.
95
95
  | `--full` | false | Only the changed files, every fragment, no related-code context |
96
96
  | `--timeout` | 300 | Wall-clock deadline in seconds; on expiry the run stops cooperatively and emits a partial artifact whose `coverage` block names the limit (exit 0). 124 is the watchdog behind it, 30 s later, for a phase that could not stop |
97
97
  | `--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 |
98
- | `--mode` | `pack` | `locate` emits the same ranked selection as compact `diffctx.locate.v1` JSON — path, lines, score, provenance reasons, a blast-radius `summary` and per-item impact `group` (`test`/`type`/`config`), NO source bodies. Adds a `coverage` block naming what the run could not see (`unparsed_files`, `zero_edge_files`, `ppr_truncated`, `next_up`, a heuristic `confidence`) and an `overflow` ranking of what the budget left behind — omitted entirely when there is nothing to disclose. `diffctx . --diff --mode locate` = impact of your uncommitted change. The MCP tool takes it as `mode="locate"` |
98
+ | `--mode` | `pack` | `locate` emits the same ranked selection as compact `diffctx.locate.v1` JSON — path, lines, score, provenance reasons, a blast-radius `summary` and per-item impact `group` (`test`/`type`/`config`), NO source bodies. Adds a `coverage` block naming what the run could not see (`unparsed_files`, `zero_edge_files`, `ppr_truncated`, `next_up`, a heuristic `confidence`) and an `overflow` ranking of what the budget left behind — omitted entirely when there is nothing to disclose. `impact` answers, under 2k tokens, what the change reaches outside its diff: callers in files the diff does not touch, whether a test guards each, cross-commit overlap, and the public API and schema changes (text by default, `diffctx.impact.v1` JSON with `-f json`). The MCP tool takes both as `mode` |
99
99
 
100
100
  Every JSON/YAML artifact opens with `schema: diffctx.context.v1` and validates
101
101
  against [`schemas/diffctx.context.v1.json`](schemas/diffctx.context.v1.json),
@@ -32,7 +32,10 @@ static FN_DEF_RE: Lazy<Regex> = Lazy::new(|| {
32
32
  });
33
33
  static TYPE_REF_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"\b([A-Z]\w*)\b").unwrap());
34
34
  static FN_CALL_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"\b([a-z_]\w+)\s*[(<]").unwrap());
35
- static PATH_CALL_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"\b(\w+)::(\w+)").unwrap());
35
+ /// Whole `a::b::c` paths; every adjacent pair is a (module, symbol) call so
36
+ /// `crate::pricing::total(..)` names `pricing::total` and not only
37
+ /// `crate::pricing`, which a pairwise regex stopped at.
38
+ static PATH_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"\b\w+(?:::\w+)+").unwrap());
36
39
  static IMPL_RE: Lazy<Regex> =
37
40
  Lazy::new(|| Regex::new(r"(?m)^\s*impl(?:<[^>]*>)?\s+(\w+)\s+for\s+(\w+)").unwrap());
38
41
  static PUB_USE_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^\s*pub\s+use\s+([\w:]+)").unwrap());
@@ -107,9 +110,15 @@ fn extract_references(content: &str) -> References {
107
110
  fn_calls.insert(name.to_string());
108
111
  }
109
112
  }
110
- let path_calls: FxHashSet<(String, String)> = PATH_CALL_RE
111
- .captures_iter(content)
112
- .map(|c| (c[1].to_string(), c[2].to_string()))
113
+ let path_calls: FxHashSet<(String, String)> = PATH_RE
114
+ .find_iter(content)
115
+ .flat_map(|m| {
116
+ let segments: Vec<&str> = m.as_str().split("::").collect();
117
+ segments
118
+ .windows(2)
119
+ .map(|w| (w[0].to_string(), w[1].to_string()))
120
+ .collect::<Vec<_>>()
121
+ })
113
122
  .collect();
114
123
  References {
115
124
  type_refs,
@@ -22,7 +22,7 @@ use std::time::{Duration, Instant, SystemTime};
22
22
  use crate::config::limits::{DEFAULT_PPR_ALPHA, DEFAULT_SCORING};
23
23
  use crate::mode::ScoringMode;
24
24
 
25
- /// Bytes of context handed to the model; the impact renderer already caps
25
+ /// Characters of context handed to the model; the impact renderer already caps
26
26
  /// itself in tokens, this is the belt for the belt.
27
27
  const MAX_CONTEXT_CHARS: usize = 9_000;
28
28
 
@@ -110,7 +110,7 @@ fn words(command: &str) -> Vec<String> {
110
110
  out.push(std::mem::take(&mut cur));
111
111
  }
112
112
  }
113
- ';' | '|' | '&' => {
113
+ c if OPERATORS.contains(&c) => {
114
114
  if !cur.is_empty() {
115
115
  out.push(std::mem::take(&mut cur));
116
116
  }
@@ -126,30 +126,91 @@ fn words(command: &str) -> Vec<String> {
126
126
  out
127
127
  }
128
128
 
129
+ const OPERATORS: &[char] = &[';', '|', '&'];
130
+
129
131
  fn is_operator(w: &str) -> bool {
130
- matches!(w, ";" | "|" | "&")
132
+ w.chars().count() == 1 && w.chars().all(|c| OPERATORS.contains(&c))
133
+ }
134
+
135
+ /// Flags whose next word is a value, not a pathspec or a revision.
136
+ const VALUE_FLAGS: &[&str] = &[
137
+ "-m",
138
+ "-F",
139
+ "-C",
140
+ "-c",
141
+ "-t",
142
+ "-S",
143
+ "-G",
144
+ "-O",
145
+ "--message",
146
+ "--file",
147
+ "--author",
148
+ "--date",
149
+ "--fixup",
150
+ "--squash",
151
+ "--template",
152
+ "--reuse-message",
153
+ "--reedit-message",
154
+ "--trailer",
155
+ "--cleanup",
156
+ "--pathspec-from-file",
157
+ ];
158
+
159
+ /// The positional arguments before `--`, with the values of value-taking
160
+ /// flags skipped: `git commit -m a -m b` has none, `git commit -m x a.py`
161
+ /// has one.
162
+ fn positionals(args: &[String]) -> Vec<&str> {
163
+ let mut out = Vec::new();
164
+ let mut skip = false;
165
+ for a in args {
166
+ if skip {
167
+ skip = false;
168
+ continue;
169
+ }
170
+ if a == "--" {
171
+ break;
172
+ }
173
+ if VALUE_FLAGS.contains(&a.as_str()) {
174
+ skip = true;
175
+ } else if !a.starts_with('-') {
176
+ out.push(a.as_str());
177
+ }
178
+ }
179
+ out
131
180
  }
132
181
 
133
- /// Every git or gh statement of the command line: verb, `-C` directory and
134
- /// the arguments up to the next shell operator. `git add -A && git commit`
135
- /// is two statements, and the second is the one that matters.
182
+ /// Every git or gh statement of the command line: verb, directory (`-C`, or
183
+ /// a `cd` earlier on the line) and the arguments up to the next shell
184
+ /// operator. `git add -A && git commit` is two statements, and the second is
185
+ /// the one that matters.
136
186
  fn statements(command: &str) -> Vec<(String, Option<String>, Vec<String>)> {
137
187
  let ws = words(command);
138
188
  let mut found = Vec::new();
189
+ let mut cwd: Option<String> = None;
139
190
  let mut i = 0;
140
191
  while i < ws.len() {
141
192
  let prog = Path::new(&ws[i])
142
193
  .file_name()
143
194
  .map(|f| f.to_string_lossy().to_string())
144
195
  .unwrap_or_default();
196
+ if ws[i] == "cd" {
197
+ if let Some(target) = ws
198
+ .get(i + 1)
199
+ .filter(|w| !is_operator(w) && !w.starts_with('-'))
200
+ {
201
+ cwd = Some(join_dir(cwd.as_deref(), target));
202
+ }
203
+ i += 1;
204
+ continue;
205
+ }
145
206
  if prog == "git" || prog == "gh" {
146
207
  let mut j = i + 1;
147
- let mut dir = None;
208
+ let mut dir = cwd.clone();
148
209
  let mut verb = None;
149
210
  while j < ws.len() && !is_operator(&ws[j]) {
150
211
  let w = &ws[j];
151
212
  if prog == "git" && w == "-C" {
152
- dir = ws.get(j + 1).cloned();
213
+ dir = ws.get(j + 1).map(|d| join_dir(cwd.as_deref(), d));
153
214
  j += 2;
154
215
  continue;
155
216
  }
@@ -189,10 +250,38 @@ fn statements(command: &str) -> Vec<(String, Option<String>, Vec<String>)> {
189
250
  found
190
251
  }
191
252
 
253
+ /// A directory as the shell line spelled it: `/r` is absolute on every
254
+ /// platform here, and the join keeps the line's own separator.
255
+ fn is_absolute_dir(dir: &str) -> bool {
256
+ dir.starts_with('/') || Path::new(dir).is_absolute()
257
+ }
258
+
259
+ fn join_dir(base: Option<&str>, dir: &str) -> String {
260
+ match base {
261
+ Some(b) if !is_absolute_dir(dir) => format!("{}/{dir}", b.trim_end_matches('/')),
262
+ _ => dir.to_string(),
263
+ }
264
+ }
265
+
192
266
  pub fn detect(event: Event, command: &str) -> Option<Detected> {
193
- statements(command)
194
- .into_iter()
195
- .find_map(|(verb, dir, args)| detect_statement(event, &verb, dir, args))
267
+ // The hook runs before the line does: a `git add` earlier on it has not
268
+ // staged anything yet, so the commit that follows records the working
269
+ // tree, not the index this process can see.
270
+ let mut staged_ahead = false;
271
+ for (verb, dir, args) in statements(command) {
272
+ if matches!(verb.as_str(), "add" | "rm" | "mv") {
273
+ staged_ahead = true;
274
+ }
275
+ if let Some(mut detected) = detect_statement(event, &verb, dir, args) {
276
+ if staged_ahead {
277
+ if let Trigger::Commit { all } = &mut detected.trigger {
278
+ *all = true;
279
+ }
280
+ }
281
+ return Some(detected);
282
+ }
283
+ }
284
+ None
196
285
  }
197
286
 
198
287
  fn detect_statement(
@@ -211,10 +300,7 @@ fn detect_statement(
211
300
  let short_a = args
212
301
  .iter()
213
302
  .any(|a| a.starts_with('-') && !a.starts_with("--") && a.contains('a'));
214
- let pathspec = args.iter().any(|a| !a.starts_with('-') && a != "-m")
215
- && !has("-m")
216
- && !has("--message")
217
- || args.iter().filter(|a| !a.starts_with('-')).count() > 1;
303
+ let pathspec = !positionals(&args).is_empty() || has("--");
218
304
  Trigger::Commit {
219
305
  all: has("--all") || short_a || pathspec,
220
306
  }
@@ -249,8 +335,14 @@ fn detect_statement(
249
335
  Trigger::PullRequest
250
336
  }
251
337
  (Event::PostToolUse, "diff") => {
252
- // A diff between two revisions is history, not the pending change.
253
- if args.iter().any(|a| !a.starts_with('-') && a.contains("..")) {
338
+ // A diff between revisions, or against one other than HEAD, is
339
+ // history, not the pending change; a path before `--` is.
340
+ let revs = positionals(&args);
341
+ let looks_like_path = |r: &str| r.contains('/') || r.starts_with('.');
342
+ if revs.len() > 1
343
+ || revs.iter().any(|r| r.contains(".."))
344
+ || revs.iter().any(|r| *r != "HEAD" && !looks_like_path(r))
345
+ {
254
346
  return None;
255
347
  }
256
348
  Trigger::Inspect {
@@ -336,6 +428,22 @@ pub fn content_key(root: &Path, range: &str) -> Option<String> {
336
428
  (key.len() == 40).then_some(key)
337
429
  }
338
430
 
431
+ /// A stable key for an answer: FNV-1a over the repository root and the
432
+ /// rendered text — no dependency, no collisions that matter at this scale.
433
+ fn text_key(root: &Path, text: &str) -> String {
434
+ let mut h: u64 = 0xcbf2_9ce4_8422_2325;
435
+ for b in root
436
+ .to_string_lossy()
437
+ .bytes()
438
+ .chain([0u8])
439
+ .chain(text.bytes())
440
+ {
441
+ h ^= u64::from(b);
442
+ h = h.wrapping_mul(0x0000_0100_0000_01b3);
443
+ }
444
+ format!("answer-{h:016x}")
445
+ }
446
+
339
447
  fn cache_dir() -> Option<PathBuf> {
340
448
  let base = std::env::var_os("DIFFCTX_CACHE_DIR")
341
449
  .map(PathBuf::from)
@@ -407,16 +515,16 @@ fn prepare(event: Event, stdin_json: &str) -> Option<Prepared> {
407
515
  let payload: serde_json::Value = serde_json::from_str(stdin_json).ok()?;
408
516
  let command = payload["tool_input"]["command"].as_str()?;
409
517
  let detected = detect(event, command)?;
410
- let cwd = detected
411
- .dir
412
- .clone()
518
+ // A relative directory on the command line is relative to where the
519
+ // agent ran it, which the payload names; this process's cwd is a guess.
520
+ let base = payload["cwd"]
521
+ .as_str()
413
522
  .map(PathBuf::from)
414
- .or_else(|| payload["cwd"].as_str().map(PathBuf::from))
415
523
  .or_else(|| std::env::current_dir().ok())?;
416
- let root = if cwd.is_absolute() {
417
- cwd
418
- } else {
419
- std::env::current_dir().ok()?.join(cwd)
524
+ let root = match detected.dir.as_deref() {
525
+ Some(dir) if is_absolute_dir(dir) => PathBuf::from(dir),
526
+ Some(dir) => base.join(dir),
527
+ None => base,
420
528
  };
421
529
  let root = crate::git::find_toplevel(&root)?;
422
530
  let range = range_for(&root, &detected.trigger)?;
@@ -442,6 +550,16 @@ fn impact_context(event: Event, root: &Path, range: &str) -> Option<String> {
442
550
  return None;
443
551
  }
444
552
  let text = truncate_chars(&crate::impact::render_markdown(&output), MAX_CONTEXT_CHARS);
553
+ // The worktree, the index after `git add`, and the commit itself name
554
+ // different diffs of one change, and the header says so; the substance
555
+ // is the symbols and their callers, and a substance already shown this
556
+ // day is not shown again (#314).
557
+ let substance = serde_json::to_string(&output.changed).unwrap_or_default();
558
+ let answer_key = text_key(root, &substance);
559
+ if is_seen(&answer_key) {
560
+ return None;
561
+ }
562
+ mark_seen(&answer_key);
445
563
  let lead = match event {
446
564
  Event::PreToolUse => "diffctx reviewed the change this command is about to record.",
447
565
  Event::PostToolUse => "diffctx reviewed the pending change you just inspected.",
@@ -554,10 +672,37 @@ mod tests {
554
672
  assert_eq!(pre("gh pr create --fill"), Some(Trigger::PullRequest));
555
673
  assert!(pre("gh pr view 12").is_none());
556
674
  assert!(pre("git status && git log").is_none());
675
+ // The add has not run when the hook sees the line: the commit
676
+ // records the working tree, not the index of this moment.
557
677
  assert_eq!(
558
678
  pre("git add -A && git commit -m x && git push"),
679
+ Some(Trigger::Commit { all: true })
680
+ );
681
+ assert_eq!(
682
+ pre("git commit -m a -m b"),
683
+ Some(Trigger::Commit { all: false })
684
+ );
685
+ assert_eq!(
686
+ pre("git commit -F msg.txt"),
559
687
  Some(Trigger::Commit { all: false })
560
688
  );
689
+ assert_eq!(
690
+ pre("git commit -m x src/a.py"),
691
+ Some(Trigger::Commit { all: true })
692
+ );
693
+ assert_eq!(
694
+ detect(Event::PreToolUse, "cd sub/app && git commit -m x").unwrap(),
695
+ Detected {
696
+ trigger: Trigger::Commit { all: false },
697
+ dir: Some("sub/app".to_string())
698
+ }
699
+ );
700
+ assert_eq!(
701
+ detect(Event::PreToolUse, "cd /r && git -C sub commit -m x")
702
+ .unwrap()
703
+ .dir,
704
+ Some("/r/sub".to_string())
705
+ );
561
706
  }
562
707
 
563
708
  #[test]
@@ -572,6 +717,20 @@ mod tests {
572
717
  Some(Trigger::Inspect { staged: false })
573
718
  );
574
719
  assert!(post("git diff HEAD~3..HEAD").is_none());
720
+ assert!(post("git diff HEAD~1 HEAD").is_none());
721
+ assert!(post("git diff main").is_none());
722
+ assert_eq!(
723
+ post("git diff HEAD"),
724
+ Some(Trigger::Inspect { staged: false })
725
+ );
726
+ assert_eq!(
727
+ post("git diff -- src/"),
728
+ Some(Trigger::Inspect { staged: false })
729
+ );
730
+ assert_eq!(
731
+ post("git diff src/a.py"),
732
+ Some(Trigger::Inspect { staged: false })
733
+ );
575
734
  assert!(post("git commit -m x").is_none());
576
735
  assert!(pre("git diff").is_none());
577
736
  }
@@ -580,7 +739,7 @@ mod tests {
580
739
  fn the_first_statement_wins_and_operators_end_it() {
581
740
  assert_eq!(
582
741
  pre("git add -A; git commit -m 'a; b' | cat"),
583
- Some(Trigger::Commit { all: false })
742
+ Some(Trigger::Commit { all: true })
584
743
  );
585
744
  assert!(pre("git log | grep commit").is_none());
586
745
  }