diffctx 1.10.0__tar.gz → 1.10.2__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 (160) hide show
  1. {diffctx-1.10.0 → diffctx-1.10.2}/PKG-INFO +1 -1
  2. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/Cargo.lock +22 -56
  3. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/Cargo.toml +3 -3
  4. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/git.rs +129 -0
  5. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/main.rs +47 -11
  6. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/memory_pipeline.rs +1 -0
  7. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/parsers/markdown.rs +54 -13
  8. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/pipeline.rs +43 -2
  9. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/postpass.rs +105 -2
  10. {diffctx-1.10.0 → diffctx-1.10.2}/pyproject.toml +1 -1
  11. {diffctx-1.10.0 → diffctx-1.10.2}/src/diffctx/cli.py +26 -5
  12. {diffctx-1.10.0 → diffctx-1.10.2}/src/diffctx/ignore.py +16 -3
  13. {diffctx-1.10.0 → diffctx-1.10.2}/src/diffctx/main.py +32 -11
  14. {diffctx-1.10.0 → diffctx-1.10.2}/src/diffctx/mcp/__main__.py +8 -1
  15. diffctx-1.10.2/src/diffctx/version.py +1 -0
  16. {diffctx-1.10.0 → diffctx-1.10.2}/src/diffctx/writer.py +39 -25
  17. diffctx-1.10.0/src/diffctx/version.py +0 -1
  18. {diffctx-1.10.0 → diffctx-1.10.2}/CHANGELOG.md +0 -0
  19. {diffctx-1.10.0 → diffctx-1.10.2}/LICENSE +0 -0
  20. {diffctx-1.10.0 → diffctx-1.10.2}/README.md +0 -0
  21. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/analytics.rs +0 -0
  22. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/candidate_files.rs +0 -0
  23. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/config/analytics.rs +0 -0
  24. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/config/bm25.rs +0 -0
  25. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/config/budget.rs +0 -0
  26. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/config/category_weights.rs +0 -0
  27. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/config/edge_weights.rs +0 -0
  28. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/config/env_overrides.rs +0 -0
  29. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/config/extensions.rs +0 -0
  30. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/config/filtering.rs +0 -0
  31. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/config/fragmentation.rs +0 -0
  32. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/config/git.rs +0 -0
  33. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/config/graph_filtering.rs +0 -0
  34. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/config/importance.rs +0 -0
  35. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/config/limits.rs +0 -0
  36. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/config/mod.rs +0 -0
  37. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/config/mode.rs +0 -0
  38. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/config/needs.rs +0 -0
  39. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/config/parsers.rs +0 -0
  40. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/config/render.rs +0 -0
  41. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/config/scoring.rs +0 -0
  42. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/config/selection.rs +0 -0
  43. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/config/tokenization.rs +0 -0
  44. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/config/weights.rs +0 -0
  45. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/core.rs +0 -0
  46. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/discovery.rs +0 -0
  47. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/base.rs +0 -0
  48. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/config_edges/build_system.rs +0 -0
  49. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/config_edges/cicd.rs +0 -0
  50. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/config_edges/docker.rs +0 -0
  51. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/config_edges/generic.rs +0 -0
  52. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/config_edges/helm.rs +0 -0
  53. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/config_edges/kubernetes.rs +0 -0
  54. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/config_edges/mod.rs +0 -0
  55. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/document/mod.rs +0 -0
  56. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/history/cochange.rs +0 -0
  57. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/history/mod.rs +0 -0
  58. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/mod.rs +0 -0
  59. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/ansible.rs +0 -0
  60. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/bazel.rs +0 -0
  61. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/c_family.rs +0 -0
  62. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/cargo_edges.rs +0 -0
  63. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/clojure.rs +0 -0
  64. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/css.rs +0 -0
  65. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/dart.rs +0 -0
  66. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/dbt.rs +0 -0
  67. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/dotnet.rs +0 -0
  68. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/elixir.rs +0 -0
  69. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/erlang.rs +0 -0
  70. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/go.rs +0 -0
  71. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/graphql.rs +0 -0
  72. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/haskell.rs +0 -0
  73. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/javascript.rs +0 -0
  74. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/julia.rs +0 -0
  75. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/jvm.rs +0 -0
  76. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/latex.rs +0 -0
  77. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/lua.rs +0 -0
  78. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/mod.rs +0 -0
  79. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/nim.rs +0 -0
  80. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/nix.rs +0 -0
  81. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/ocaml.rs +0 -0
  82. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/openapi.rs +0 -0
  83. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/perl.rs +0 -0
  84. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/php.rs +0 -0
  85. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/prisma.rs +0 -0
  86. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/protobuf.rs +0 -0
  87. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/python.rs +0 -0
  88. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/r_lang.rs +0 -0
  89. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/ruby.rs +0 -0
  90. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/rust_lang.rs +0 -0
  91. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/shell.rs +0 -0
  92. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/sql.rs +0 -0
  93. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/swift.rs +0 -0
  94. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/tags.rs +0 -0
  95. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/terraform.rs +0 -0
  96. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/semantic/zig.rs +0 -0
  97. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/similarity/lexical.rs +0 -0
  98. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/similarity/mod.rs +0 -0
  99. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/structural/containment.rs +0 -0
  100. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/structural/mod.rs +0 -0
  101. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/structural/sibling.rs +0 -0
  102. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/edges/structural/testing.rs +0 -0
  103. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/filtering.rs +0 -0
  104. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/fragmentation.rs +0 -0
  105. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/graph.rs +0 -0
  106. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/graph_export.rs +0 -0
  107. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/interval.rs +0 -0
  108. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/languages.rs +0 -0
  109. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/lib.rs +0 -0
  110. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/mode.rs +0 -0
  111. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/parsers/config_parser.rs +0 -0
  112. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/parsers/generic.rs +0 -0
  113. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/parsers/mod.rs +0 -0
  114. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/parsers/tree_sitter_strategy.rs +0 -0
  115. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/ppr.rs +0 -0
  116. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/project_graph.rs +0 -0
  117. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/pybridge.rs +0 -0
  118. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/render.rs +0 -0
  119. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/scoring.rs +0 -0
  120. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/select.rs +0 -0
  121. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/signatures.rs +0 -0
  122. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/stopwords.rs +0 -0
  123. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/test_harness.rs +0 -0
  124. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/tokenizer.rs +0 -0
  125. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/types.rs +0 -0
  126. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/utility/boltzmann.rs +0 -0
  127. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/utility/importance.rs +0 -0
  128. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/utility/mod.rs +0 -0
  129. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/utility/needs.rs +0 -0
  130. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/src/utility/scoring.rs +0 -0
  131. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/tests/common/mod.rs +0 -0
  132. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/tests/fixtures/garbage/garbage_api.py +0 -0
  133. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/tests/fixtures/garbage/garbage_constants.py +0 -0
  134. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/tests/fixtures/garbage/garbage_handlers.py +0 -0
  135. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/tests/fixtures/garbage/garbage_models.py +0 -0
  136. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/tests/fixtures/garbage/garbage_module.js +0 -0
  137. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/tests/fixtures/garbage/garbage_services.py +0 -0
  138. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/tests/fixtures/garbage/garbage_types.py +0 -0
  139. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/tests/fixtures/garbage/garbage_unrelated.yaml +0 -0
  140. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/tests/fixtures/garbage/garbage_utils.py +0 -0
  141. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/tests/fixtures/garbage/garbage_validators.py +0 -0
  142. {diffctx-1.10.0 → diffctx-1.10.2}/diffctx/tests/yaml_cases.rs +0 -0
  143. {diffctx-1.10.0 → diffctx-1.10.2}/rust-toolchain.toml +0 -0
  144. {diffctx-1.10.0 → diffctx-1.10.2}/src/diffctx/__init__.py +0 -0
  145. {diffctx-1.10.0 → diffctx-1.10.2}/src/diffctx/__main__.py +0 -0
  146. {diffctx-1.10.0 → diffctx-1.10.2}/src/diffctx/clipboard.py +0 -0
  147. {diffctx-1.10.0 → diffctx-1.10.2}/src/diffctx/diffctx/__init__.py +0 -0
  148. {diffctx-1.10.0 → diffctx-1.10.2}/src/diffctx/diffctx/graph_analytics.py +0 -0
  149. {diffctx-1.10.0 → diffctx-1.10.2}/src/diffctx/diffctx/graph_export.py +0 -0
  150. {diffctx-1.10.0 → diffctx-1.10.2}/src/diffctx/diffctx/pipeline.py +0 -0
  151. {diffctx-1.10.0 → diffctx-1.10.2}/src/diffctx/diffctx/project_graph.py +0 -0
  152. {diffctx-1.10.0 → diffctx-1.10.2}/src/diffctx/logger.py +0 -0
  153. {diffctx-1.10.0 → diffctx-1.10.2}/src/diffctx/mcp/README.md +0 -0
  154. {diffctx-1.10.0 → diffctx-1.10.2}/src/diffctx/mcp/__init__.py +0 -0
  155. {diffctx-1.10.0 → diffctx-1.10.2}/src/diffctx/mcp/formatting.py +0 -0
  156. {diffctx-1.10.0 → diffctx-1.10.2}/src/diffctx/mcp/security.py +0 -0
  157. {diffctx-1.10.0 → diffctx-1.10.2}/src/diffctx/mcp/server.py +0 -0
  158. {diffctx-1.10.0 → diffctx-1.10.2}/src/diffctx/py.typed +0 -0
  159. {diffctx-1.10.0 → diffctx-1.10.2}/src/diffctx/tokens.py +0 -0
  160. {diffctx-1.10.0 → diffctx-1.10.2}/src/diffctx/tree.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: diffctx
3
- Version: 1.10.0
3
+ Version: 1.10.2
4
4
  Classifier: Development Status :: 5 - Production/Stable
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -355,12 +355,6 @@ version = "0.5.0"
355
355
  source = "registry+https://github.com/rust-lang/crates.io-index"
356
356
  checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
357
357
 
358
- [[package]]
359
- name = "hermit-abi"
360
- version = "0.5.2"
361
- source = "registry+https://github.com/rust-lang/crates.io-index"
362
- checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
363
-
364
358
  [[package]]
365
359
  name = "id-arena"
366
360
  version = "2.3.0"
@@ -411,23 +405,23 @@ checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f"
411
405
 
412
406
  [[package]]
413
407
  name = "libmimalloc-sys"
414
- version = "0.1.47"
408
+ version = "0.1.49"
415
409
  source = "registry+https://github.com/rust-lang/crates.io-index"
416
- checksum = "2d1eacfa31c33ec25e873c136ba5669f00f9866d0688bea7be4d3f7e43067df6"
410
+ checksum = "6a45a52f43e1c16f667ccfe4dd8c85b7f7c204fd5e3bf46c5b0db9a5c3c0b8e9"
417
411
  dependencies = [
418
412
  "cc",
419
413
  ]
420
414
 
421
415
  [[package]]
422
416
  name = "libtest-mimic"
423
- version = "0.7.3"
417
+ version = "0.8.2"
424
418
  source = "registry+https://github.com/rust-lang/crates.io-index"
425
- checksum = "cc0bda45ed5b3a2904262c1bb91e526127aa70e7ef3758aba2ef93cf896b9b58"
419
+ checksum = "14e6ba06f0ade6e504aff834d7c34298e5155c6baca353cc6a4aaff2f9fd7f33"
426
420
  dependencies = [
421
+ "anstream",
422
+ "anstyle",
427
423
  "clap",
428
424
  "escape8259",
429
- "termcolor",
430
- "threadpool",
431
425
  ]
432
426
 
433
427
  [[package]]
@@ -459,9 +453,9 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
459
453
 
460
454
  [[package]]
461
455
  name = "mimalloc"
462
- version = "0.1.50"
456
+ version = "0.1.52"
463
457
  source = "registry+https://github.com/rust-lang/crates.io-index"
464
- checksum = "b3627c4272df786b9260cabaa46aec1d59c93ede723d4c3ef646c503816b0640"
458
+ checksum = "2d4139bb28d14ad1facf21d5eb8825051b326e172d216b39f6d31df53cc97862"
465
459
  dependencies = [
466
460
  "libmimalloc-sys",
467
461
  ]
@@ -475,16 +469,6 @@ dependencies = [
475
469
  "windows-sys",
476
470
  ]
477
471
 
478
- [[package]]
479
- name = "num_cpus"
480
- version = "1.17.0"
481
- source = "registry+https://github.com/rust-lang/crates.io-index"
482
- checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
483
- dependencies = [
484
- "hermit-abi",
485
- "libc",
486
- ]
487
-
488
472
  [[package]]
489
473
  name = "once_cell"
490
474
  version = "1.21.4"
@@ -622,9 +606,9 @@ dependencies = [
622
606
 
623
607
  [[package]]
624
608
  name = "regex"
625
- version = "1.12.3"
609
+ version = "1.12.4"
626
610
  source = "registry+https://github.com/rust-lang/crates.io-index"
627
- checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
611
+ checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
628
612
  dependencies = [
629
613
  "aho-corasick",
630
614
  "memchr",
@@ -645,9 +629,9 @@ dependencies = [
645
629
 
646
630
  [[package]]
647
631
  name = "regex-syntax"
648
- version = "0.8.10"
632
+ version = "0.8.11"
649
633
  source = "registry+https://github.com/rust-lang/crates.io-index"
650
- checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
634
+ checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
651
635
 
652
636
  [[package]]
653
637
  name = "rustc-hash"
@@ -721,9 +705,9 @@ dependencies = [
721
705
 
722
706
  [[package]]
723
707
  name = "serde_json"
724
- version = "1.0.149"
708
+ version = "1.0.150"
725
709
  source = "registry+https://github.com/rust-lang/crates.io-index"
726
- checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
710
+ checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
727
711
  dependencies = [
728
712
  "indexmap",
729
713
  "itoa",
@@ -772,9 +756,9 @@ dependencies = [
772
756
 
773
757
  [[package]]
774
758
  name = "smallvec"
775
- version = "1.15.1"
759
+ version = "1.15.2"
776
760
  source = "registry+https://github.com/rust-lang/crates.io-index"
777
- checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
761
+ checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
778
762
 
779
763
  [[package]]
780
764
  name = "streaming-iterator"
@@ -818,15 +802,6 @@ dependencies = [
818
802
  "windows-sys",
819
803
  ]
820
804
 
821
- [[package]]
822
- name = "termcolor"
823
- version = "1.4.1"
824
- source = "registry+https://github.com/rust-lang/crates.io-index"
825
- checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
826
- dependencies = [
827
- "winapi-util",
828
- ]
829
-
830
805
  [[package]]
831
806
  name = "thiserror"
832
807
  version = "2.0.18"
@@ -856,15 +831,6 @@ dependencies = [
856
831
  "cfg-if",
857
832
  ]
858
833
 
859
- [[package]]
860
- name = "threadpool"
861
- version = "1.8.1"
862
- source = "registry+https://github.com/rust-lang/crates.io-index"
863
- checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
864
- dependencies = [
865
- "num_cpus",
866
- ]
867
-
868
834
  [[package]]
869
835
  name = "tiktoken-rs"
870
836
  version = "0.12.0"
@@ -1058,9 +1024,9 @@ dependencies = [
1058
1024
 
1059
1025
  [[package]]
1060
1026
  name = "tree-sitter-go"
1061
- version = "0.23.4"
1027
+ version = "0.25.0"
1062
1028
  source = "registry+https://github.com/rust-lang/crates.io-index"
1063
- checksum = "b13d476345220dbe600147dd444165c5791bf85ef53e28acbedd46112ee18431"
1029
+ checksum = "c8560a4d2f835cc0d4d2c2e03cbd0dde2f6114b43bc491164238d333e28b16ea"
1064
1030
  dependencies = [
1065
1031
  "cc",
1066
1032
  "tree-sitter-language",
@@ -1244,9 +1210,9 @@ dependencies = [
1244
1210
 
1245
1211
  [[package]]
1246
1212
  name = "tree-sitter-r"
1247
- version = "1.2.0"
1213
+ version = "1.3.0"
1248
1214
  source = "registry+https://github.com/rust-lang/crates.io-index"
1249
- checksum = "429133cbda9f8a46e03ef3aae6abb6c3d22875f8585cad472138101bfd517255"
1215
+ checksum = "ffc9954ec870dcad6cffdd302b405306c68cf031ed79a78cd9746f6740d9fe20"
1250
1216
  dependencies = [
1251
1217
  "cc",
1252
1218
  "tree-sitter-language",
@@ -1264,9 +1230,9 @@ dependencies = [
1264
1230
 
1265
1231
  [[package]]
1266
1232
  name = "tree-sitter-rust"
1267
- version = "0.23.3"
1233
+ version = "0.24.2"
1268
1234
  source = "registry+https://github.com/rust-lang/crates.io-index"
1269
- checksum = "ca8ccb3e3a3495c8a943f6c3fd24c3804c471fd7f4f16087623c7fa4c0068e8a"
1235
+ checksum = "439e577dbe07423ec2582ac62c7531120dbfccfa6e5f92406f93dd271a120e45"
1270
1236
  dependencies = [
1271
1237
  "cc",
1272
1238
  "tree-sitter-language",
@@ -70,8 +70,8 @@ tree-sitter = "0.25"
70
70
  tree-sitter-python = { version = "0.23", optional = true }
71
71
  tree-sitter-javascript = { version = "0.23", optional = true }
72
72
  tree-sitter-typescript = { version = "0.23", optional = true }
73
- tree-sitter-go = { version = "0.23", optional = true }
74
- tree-sitter-rust = { version = "0.23", optional = true }
73
+ tree-sitter-go = { version = "0.25", optional = true }
74
+ tree-sitter-rust = { version = "0.24", optional = true }
75
75
  tree-sitter-java = { version = "0.23", optional = true }
76
76
  tree-sitter-c = { version = "0.24", optional = true }
77
77
  tree-sitter-cpp = { version = "0.23", optional = true }
@@ -126,7 +126,7 @@ pyo3 = { version = "0.29", features = ["extension-module", "abi3-py310"], option
126
126
  [dev-dependencies]
127
127
  tempfile = "3"
128
128
  serde_yaml = "0.9"
129
- libtest-mimic = "0.7"
129
+ libtest-mimic = "0.8"
130
130
 
131
131
  [[test]]
132
132
  name = "yaml_cases"
@@ -500,6 +500,135 @@ pub fn get_untracked_files(repo_root: &Path) -> Result<Vec<PathBuf>> {
500
500
  .collect())
501
501
  }
502
502
 
503
+ /// Rewrites one `.diffctx/ignore` pattern line to be anchored to the
504
+ /// directory that contains the `.diffctx/` folder (`rel`, repo-root-relative,
505
+ /// "" for the repo root itself). Mirrors `_process_ignore_line` in the
506
+ /// Python tree-mode ignore resolver (`src/diffctx/ignore.py`) so a pattern
507
+ /// declared in `sub/.diffctx/ignore` only ever matches within `sub/`.
508
+ fn anchor_diffctx_ignore_line(line: &str, rel: &str) -> String {
509
+ let (neg, pat) = match line.strip_prefix('!') {
510
+ Some(rest) => (true, rest),
511
+ None => (false, line),
512
+ };
513
+ let pat_no_trailing_slash = pat.trim_end_matches('/');
514
+ let full = if pat_no_trailing_slash.starts_with('/') || pat_no_trailing_slash.contains('/') {
515
+ let anchored = pat.trim_start_matches('/');
516
+ if rel.is_empty() {
517
+ format!("/{anchored}")
518
+ } else {
519
+ format!("/{rel}/{anchored}")
520
+ }
521
+ } else if rel.is_empty() {
522
+ pat.to_string()
523
+ } else {
524
+ format!("{rel}/**/{pat}")
525
+ };
526
+ if neg { format!("!{full}") } else { full }
527
+ }
528
+
529
+ /// Finds every `.diffctx/ignore` file tracked or present in `repo_root`
530
+ /// (any depth) and returns its patterns rewritten to be repo-root-relative,
531
+ /// ready to feed into a combined gitignore-syntax exclude file.
532
+ fn collect_diffctx_ignore_patterns(repo_root: &Path) -> Vec<String> {
533
+ let Ok(files) = run_git_z(
534
+ repo_root,
535
+ &[
536
+ "ls-files",
537
+ "-z",
538
+ "--cached",
539
+ "--others",
540
+ "--exclude-standard",
541
+ "--",
542
+ ":(glob)**/.diffctx/ignore",
543
+ ],
544
+ ) else {
545
+ return Vec::new();
546
+ };
547
+
548
+ let mut patterns = Vec::new();
549
+ for raw in &files {
550
+ let rel_path = unquote_c_style(raw);
551
+ if !rel_path.ends_with(".diffctx/ignore") {
552
+ continue;
553
+ }
554
+ let rel_dir = rel_path
555
+ .strip_suffix(".diffctx/ignore")
556
+ .unwrap_or("")
557
+ .trim_end_matches('/');
558
+ let Ok(content) = std::fs::read_to_string(repo_root.join(&rel_path)) else {
559
+ continue;
560
+ };
561
+ for line in content.lines() {
562
+ let line = line.trim_end();
563
+ if line.is_empty() || line.starts_with('#') {
564
+ continue;
565
+ }
566
+ patterns.push(anchor_diffctx_ignore_line(line, rel_dir));
567
+ }
568
+ }
569
+ patterns
570
+ }
571
+
572
+ /// Returns the subset of `rel_paths` (repo-root-relative) excluded by either
573
+ /// `.gitignore` (via git's own engine, so nesting/negation/`**` are handled
574
+ /// correctly) or `.diffctx/ignore` (patterns anchored per-directory and fed
575
+ /// to git as a temporary `core.excludesFile`, so the same engine evaluates
576
+ /// both mechanisms uniformly). Best-effort: any failure returns an empty set
577
+ /// rather than blocking the diff pipeline on an ignore-resolution problem.
578
+ pub fn find_ignored_paths(repo_root: &Path, rel_paths: &[String]) -> FxHashSet<String> {
579
+ if rel_paths.is_empty() {
580
+ return FxHashSet::default();
581
+ }
582
+
583
+ let diffctx_patterns = collect_diffctx_ignore_patterns(repo_root);
584
+ let temp_excludes = if diffctx_patterns.is_empty() {
585
+ None
586
+ } else {
587
+ let path = std::env::temp_dir().join(format!("diffctx-ignore-{}.tmp", std::process::id()));
588
+ match std::fs::write(&path, diffctx_patterns.join("\n")) {
589
+ Ok(()) => Some(path),
590
+ Err(_) => None,
591
+ }
592
+ };
593
+
594
+ let mut args: Vec<String> = vec!["check-ignore".into(), "--no-index".into()];
595
+ if let Some(ref path) = temp_excludes {
596
+ args.insert(0, format!("core.excludesFile={}", path.display()));
597
+ args.insert(0, "-c".into());
598
+ }
599
+ args.push("--".into());
600
+ args.extend(rel_paths.iter().cloned());
601
+ let arg_refs: Vec<&str> = args.iter().map(String::as_str).collect();
602
+
603
+ let result = (|| -> Result<FxHashSet<String>> {
604
+ let mut cmd = Command::new("git");
605
+ cmd.arg("-C")
606
+ .arg(repo_root)
607
+ .args(&arg_refs)
608
+ .stdout(Stdio::piped())
609
+ .stderr(Stdio::piped());
610
+ let child = cmd.spawn()?;
611
+ let output = wait_with_timeout(child, Duration::from_secs(git_timeout()), &arg_refs)?;
612
+ // Exit code 1 from `check-ignore` means "none of the given paths are
613
+ // ignored" — not a failure. Any other non-zero code is a real error.
614
+ if !output.status.success() && output.status.code() != Some(1) {
615
+ let stderr = String::from_utf8_lossy(&output.stderr);
616
+ return Err(GitError::CommandFailed(format!(
617
+ "git check-ignore failed: {}",
618
+ stderr.trim()
619
+ )));
620
+ }
621
+ let stdout = String::from_utf8_lossy(&output.stdout);
622
+ Ok(stdout.lines().map(unquote_c_style).collect())
623
+ })();
624
+
625
+ if let Some(path) = temp_excludes {
626
+ let _ = std::fs::remove_file(path);
627
+ }
628
+
629
+ result.unwrap_or_default()
630
+ }
631
+
503
632
  pub struct CatFileBatch {
504
633
  repo_root: PathBuf,
505
634
  child: Option<Child>,
@@ -3,6 +3,9 @@ use mimalloc::MiMalloc;
3
3
  static GLOBAL: MiMalloc = MiMalloc;
4
4
 
5
5
  use std::path::PathBuf;
6
+ use std::sync::mpsc;
7
+ use std::thread;
8
+ use std::time::Duration;
6
9
 
7
10
  use anyhow::Result;
8
11
  use clap::Parser;
@@ -60,17 +63,50 @@ fn main() -> Result<()> {
60
63
  eprintln!("error: {e}");
61
64
  std::process::exit(2);
62
65
  });
63
- let output = build_diff_context(
64
- &cli.path,
65
- cli.diff_ref.as_deref(),
66
- Some(cli.budget),
67
- cli.alpha,
68
- cli.tau,
69
- cli.no_content,
70
- cli.full,
71
- scoring_mode,
72
- cli.timeout,
73
- )?;
66
+
67
+ // The per-phase git timeout does not bound in-process phases (parse, graph
68
+ // build, scoring), so a pathological repo can hang far past `--timeout`
69
+ // until OOM/SIGKILL (#70). Run the pipeline on a worker thread and enforce
70
+ // `--timeout` as a true total wall-clock ceiling: on expiry, fail fast with
71
+ // the actionable-error contract instead of hanging unbounded.
72
+ let timeout = cli.timeout;
73
+ let path = cli.path.clone();
74
+ let diff_ref = cli.diff_ref.clone();
75
+ let budget = cli.budget;
76
+ let alpha = cli.alpha;
77
+ let tau = cli.tau;
78
+ let no_content = cli.no_content;
79
+ let full = cli.full;
80
+ let (tx, rx) = mpsc::channel();
81
+ thread::spawn(move || {
82
+ let result = build_diff_context(
83
+ &path,
84
+ diff_ref.as_deref(),
85
+ Some(budget),
86
+ alpha,
87
+ tau,
88
+ no_content,
89
+ full,
90
+ scoring_mode,
91
+ timeout,
92
+ );
93
+ let _ = tx.send(result);
94
+ });
95
+
96
+ let output = match rx.recv_timeout(Duration::from_secs(timeout)) {
97
+ Ok(result) => result?,
98
+ Err(mpsc::RecvTimeoutError::Timeout) => {
99
+ eprintln!(
100
+ "diffctx: pipeline exceeded {timeout}s wall-clock deadline; aborting before \
101
+ OOM/SIGKILL. Narrow the review with an explicit '--diff <from>..<to>' range or \
102
+ run on a smaller subtree, or raise '--timeout'."
103
+ );
104
+ std::process::exit(124);
105
+ }
106
+ Err(mpsc::RecvTimeoutError::Disconnected) => {
107
+ anyhow::bail!("diffctx: pipeline worker terminated unexpectedly");
108
+ }
109
+ };
74
110
 
75
111
  match cli.format.as_str() {
76
112
  "json" => {
@@ -162,6 +162,7 @@ pub fn build_diff_context_in_memory(
162
162
  Path::new("."),
163
163
  &[],
164
164
  None,
165
+ &core_ids,
165
166
  );
166
167
 
167
168
  let dummy_root = Path::new(".");
@@ -54,8 +54,8 @@ impl FragmentationStrategy for MarkdownStrategy {
54
54
  let total_lines = lines.len() as u32;
55
55
  let mut fragments: Vec<Fragment> = Vec::new();
56
56
 
57
- for (idx, &(start_line, level, _)) in headings.iter().enumerate() {
58
- let end_line = find_section_end(&headings, idx, level, total_lines);
57
+ for (idx, &(start_line, _level, _)) in headings.iter().enumerate() {
58
+ let end_line = find_section_end(&headings, idx, total_lines);
59
59
  if end_line < start_line {
60
60
  continue;
61
61
  }
@@ -90,16 +90,57 @@ impl FragmentationStrategy for MarkdownStrategy {
90
90
  }
91
91
  }
92
92
 
93
- fn find_section_end(
94
- headings: &[(u32, u32, String)],
95
- idx: usize,
96
- level: u32,
97
- total_lines: u32,
98
- ) -> u32 {
99
- for &(next_line, next_level, _) in &headings[idx + 1..] {
100
- if next_level <= level {
101
- return next_line - 1;
102
- }
93
+ /// A heading's own fragment ends at the very next heading, regardless of
94
+ /// level not at the next heading of the *same or higher* level. The old
95
+ /// same-or-higher rule made a heading's span include every nested
96
+ /// subsection beneath it, so a lone top-level `#` heading (the common case:
97
+ /// one H1 title, many `##`/`###` children) spanned the entire rest of the
98
+ /// document. A one-line change in the H1's own preamble then flagged that
99
+ /// all-encompassing fragment as `role: "changed"`, dumping the whole file
100
+ /// instead of just the touched paragraph (#91).
101
+ fn find_section_end(headings: &[(u32, u32, String)], idx: usize, total_lines: u32) -> u32 {
102
+ match headings.get(idx + 1) {
103
+ Some(&(next_line, _, _)) => next_line - 1,
104
+ None => total_lines,
105
+ }
106
+ }
107
+
108
+ #[cfg(test)]
109
+ mod tests {
110
+ use super::*;
111
+
112
+ /// Regression for #91: a lone H1 title followed only by `##` children
113
+ /// must not produce a single fragment spanning the whole document — the
114
+ /// H1's own fragment is just its direct preamble, up to the first child.
115
+ #[test]
116
+ fn h1_fragment_stops_at_first_child_heading_not_eof() {
117
+ let content =
118
+ "# Title\n\nPreamble line.\n\n## Section One\nBody one.\n\n## Section Two\nBody two.\n";
119
+ let fragments = MarkdownStrategy.fragment(Arc::from("doc.md"), content);
120
+
121
+ let title = fragments
122
+ .iter()
123
+ .find(|f| f.content.starts_with("# Title"))
124
+ .expect("Title fragment present");
125
+ assert_eq!(title.id.start_line, 1);
126
+ assert_eq!(
127
+ title.id.end_line, 4,
128
+ "H1 fragment should stop before '## Section One', not run to EOF"
129
+ );
130
+ }
131
+
132
+ #[test]
133
+ fn sibling_headings_still_each_get_their_own_fragment() {
134
+ let content =
135
+ "## Section One\nBody one.\n\n## Section Two\nBody two.\n### Nested\nNested body.\n";
136
+ let fragments = MarkdownStrategy.fragment(Arc::from("doc.md"), content);
137
+ assert_eq!(fragments.len(), 3);
138
+
139
+ let nested = fragments
140
+ .iter()
141
+ .find(|f| f.content.starts_with("### Nested"))
142
+ .expect("Nested fragment present");
143
+ assert_eq!(nested.id.start_line, 6);
144
+ assert_eq!(nested.id.end_line, 8);
103
145
  }
104
- total_lines
105
146
  }
@@ -131,6 +131,13 @@ pub fn compute_scored_state(
131
131
  return Ok(empty_scored_state(root_dir));
132
132
  }
133
133
 
134
+ let ignored_rel_paths = resolve_ignored_paths(&root_dir, &hunks);
135
+ hunks.retain(|h| !is_ignored_path(&root_dir, Path::new(&*h.path), &ignored_rel_paths));
136
+
137
+ if hunks.is_empty() {
138
+ return Ok(empty_scored_state(root_dir));
139
+ }
140
+
134
141
  let diff_text = git::get_diff_text(&root_dir, diff_range)?;
135
142
 
136
143
  let mut changed_files = git::get_changed_files(&root_dir, diff_range)?;
@@ -152,7 +159,9 @@ pub fn compute_scored_state(
152
159
  .into_iter()
153
160
  .filter(|f| {
154
161
  let resolved = f.canonicalize().unwrap_or_else(|_| f.clone());
155
- !excluded.contains(&resolved) && !is_secret_path(f)
162
+ !excluded.contains(&resolved)
163
+ && !is_secret_path(f)
164
+ && !is_ignored_path(&root_dir, f, &ignored_rel_paths)
156
165
  })
157
166
  .collect();
158
167
 
@@ -412,6 +421,7 @@ pub fn select_with_params(
412
421
  &state.root_dir,
413
422
  &state.preferred_revs,
414
423
  batch_reader.as_mut(),
424
+ &state.core_ids,
415
425
  );
416
426
  if let Some(mut r) = batch_reader {
417
427
  r.close();
@@ -493,6 +503,31 @@ fn is_secret_path(path: &Path) -> bool {
493
503
  )
494
504
  }
495
505
 
506
+ fn rel_path_string(root_dir: &Path, path: &Path) -> Option<String> {
507
+ path.strip_prefix(root_dir)
508
+ .ok()
509
+ .map(|p| p.to_string_lossy().replace('\\', "/"))
510
+ }
511
+
512
+ /// Resolves `.gitignore` / `.diffctx/ignore` exclusions (#85) for every path
513
+ /// touched by `hunks`, in one batched git call. diff mode previously only
514
+ /// ever excluded a hardcoded set of secret-like filenames (`is_secret_path`)
515
+ /// — a file a user explicitly excluded via `.diffctx/ignore` still had its
516
+ /// changed content surfaced in full.
517
+ fn resolve_ignored_paths(root_dir: &Path, hunks: &[crate::types::DiffHunk]) -> FxHashSet<String> {
518
+ let rel_paths: Vec<String> = hunks
519
+ .iter()
520
+ .filter_map(|h| rel_path_string(root_dir, Path::new(&*h.path)))
521
+ .collect();
522
+ git::find_ignored_paths(root_dir, &rel_paths)
523
+ }
524
+
525
+ fn is_ignored_path(root_dir: &Path, path: &Path, ignored_rel_paths: &FxHashSet<String>) -> bool {
526
+ rel_path_string(root_dir, path)
527
+ .map(|rel| ignored_rel_paths.contains(&rel))
528
+ .unwrap_or(false)
529
+ }
530
+
496
531
  fn build_diff_context_full(
497
532
  root_dir: &Path,
498
533
  diff_range: Option<&str>,
@@ -512,8 +547,14 @@ fn build_diff_context_full(
512
547
  if hunks.is_empty() {
513
548
  return Ok(empty_output(&root_dir));
514
549
  }
550
+ let ignored_rel_paths = resolve_ignored_paths(&root_dir, &hunks);
551
+ hunks.retain(|h| !is_ignored_path(&root_dir, Path::new(&*h.path), &ignored_rel_paths));
552
+ if hunks.is_empty() {
553
+ return Ok(empty_output(&root_dir));
554
+ }
515
555
  let mut changed_files = git::get_changed_files(&root_dir, diff_range)?;
516
- changed_files.retain(|f| !is_secret_path(f));
556
+ changed_files
557
+ .retain(|f| !is_secret_path(f) && !is_ignored_path(&root_dir, f, &ignored_rel_paths));
517
558
  if changed_files.is_empty() {
518
559
  return Ok(empty_output(&root_dir));
519
560
  }