diffctx 1.10.0__tar.gz → 1.10.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 (160) hide show
  1. {diffctx-1.10.0 → diffctx-1.10.1}/PKG-INFO +1 -1
  2. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/Cargo.lock +22 -56
  3. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/Cargo.toml +3 -3
  4. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/main.rs +47 -11
  5. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/memory_pipeline.rs +1 -0
  6. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/pipeline.rs +1 -0
  7. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/postpass.rs +105 -2
  8. {diffctx-1.10.0 → diffctx-1.10.1}/pyproject.toml +1 -1
  9. {diffctx-1.10.0 → diffctx-1.10.1}/src/diffctx/main.py +2 -2
  10. {diffctx-1.10.0 → diffctx-1.10.1}/src/diffctx/mcp/__main__.py +1 -1
  11. diffctx-1.10.1/src/diffctx/version.py +1 -0
  12. {diffctx-1.10.0 → diffctx-1.10.1}/src/diffctx/writer.py +39 -25
  13. diffctx-1.10.0/src/diffctx/version.py +0 -1
  14. {diffctx-1.10.0 → diffctx-1.10.1}/CHANGELOG.md +0 -0
  15. {diffctx-1.10.0 → diffctx-1.10.1}/LICENSE +0 -0
  16. {diffctx-1.10.0 → diffctx-1.10.1}/README.md +0 -0
  17. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/analytics.rs +0 -0
  18. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/candidate_files.rs +0 -0
  19. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/config/analytics.rs +0 -0
  20. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/config/bm25.rs +0 -0
  21. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/config/budget.rs +0 -0
  22. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/config/category_weights.rs +0 -0
  23. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/config/edge_weights.rs +0 -0
  24. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/config/env_overrides.rs +0 -0
  25. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/config/extensions.rs +0 -0
  26. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/config/filtering.rs +0 -0
  27. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/config/fragmentation.rs +0 -0
  28. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/config/git.rs +0 -0
  29. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/config/graph_filtering.rs +0 -0
  30. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/config/importance.rs +0 -0
  31. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/config/limits.rs +0 -0
  32. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/config/mod.rs +0 -0
  33. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/config/mode.rs +0 -0
  34. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/config/needs.rs +0 -0
  35. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/config/parsers.rs +0 -0
  36. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/config/render.rs +0 -0
  37. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/config/scoring.rs +0 -0
  38. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/config/selection.rs +0 -0
  39. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/config/tokenization.rs +0 -0
  40. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/config/weights.rs +0 -0
  41. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/core.rs +0 -0
  42. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/discovery.rs +0 -0
  43. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/base.rs +0 -0
  44. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/config_edges/build_system.rs +0 -0
  45. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/config_edges/cicd.rs +0 -0
  46. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/config_edges/docker.rs +0 -0
  47. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/config_edges/generic.rs +0 -0
  48. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/config_edges/helm.rs +0 -0
  49. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/config_edges/kubernetes.rs +0 -0
  50. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/config_edges/mod.rs +0 -0
  51. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/document/mod.rs +0 -0
  52. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/history/cochange.rs +0 -0
  53. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/history/mod.rs +0 -0
  54. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/mod.rs +0 -0
  55. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/ansible.rs +0 -0
  56. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/bazel.rs +0 -0
  57. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/c_family.rs +0 -0
  58. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/cargo_edges.rs +0 -0
  59. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/clojure.rs +0 -0
  60. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/css.rs +0 -0
  61. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/dart.rs +0 -0
  62. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/dbt.rs +0 -0
  63. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/dotnet.rs +0 -0
  64. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/elixir.rs +0 -0
  65. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/erlang.rs +0 -0
  66. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/go.rs +0 -0
  67. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/graphql.rs +0 -0
  68. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/haskell.rs +0 -0
  69. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/javascript.rs +0 -0
  70. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/julia.rs +0 -0
  71. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/jvm.rs +0 -0
  72. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/latex.rs +0 -0
  73. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/lua.rs +0 -0
  74. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/mod.rs +0 -0
  75. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/nim.rs +0 -0
  76. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/nix.rs +0 -0
  77. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/ocaml.rs +0 -0
  78. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/openapi.rs +0 -0
  79. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/perl.rs +0 -0
  80. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/php.rs +0 -0
  81. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/prisma.rs +0 -0
  82. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/protobuf.rs +0 -0
  83. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/python.rs +0 -0
  84. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/r_lang.rs +0 -0
  85. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/ruby.rs +0 -0
  86. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/rust_lang.rs +0 -0
  87. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/shell.rs +0 -0
  88. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/sql.rs +0 -0
  89. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/swift.rs +0 -0
  90. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/tags.rs +0 -0
  91. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/terraform.rs +0 -0
  92. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/semantic/zig.rs +0 -0
  93. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/similarity/lexical.rs +0 -0
  94. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/similarity/mod.rs +0 -0
  95. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/structural/containment.rs +0 -0
  96. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/structural/mod.rs +0 -0
  97. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/structural/sibling.rs +0 -0
  98. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/edges/structural/testing.rs +0 -0
  99. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/filtering.rs +0 -0
  100. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/fragmentation.rs +0 -0
  101. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/git.rs +0 -0
  102. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/graph.rs +0 -0
  103. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/graph_export.rs +0 -0
  104. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/interval.rs +0 -0
  105. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/languages.rs +0 -0
  106. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/lib.rs +0 -0
  107. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/mode.rs +0 -0
  108. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/parsers/config_parser.rs +0 -0
  109. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/parsers/generic.rs +0 -0
  110. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/parsers/markdown.rs +0 -0
  111. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/parsers/mod.rs +0 -0
  112. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/parsers/tree_sitter_strategy.rs +0 -0
  113. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/ppr.rs +0 -0
  114. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/project_graph.rs +0 -0
  115. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/pybridge.rs +0 -0
  116. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/render.rs +0 -0
  117. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/scoring.rs +0 -0
  118. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/select.rs +0 -0
  119. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/signatures.rs +0 -0
  120. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/stopwords.rs +0 -0
  121. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/test_harness.rs +0 -0
  122. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/tokenizer.rs +0 -0
  123. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/types.rs +0 -0
  124. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/utility/boltzmann.rs +0 -0
  125. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/utility/importance.rs +0 -0
  126. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/utility/mod.rs +0 -0
  127. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/utility/needs.rs +0 -0
  128. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/src/utility/scoring.rs +0 -0
  129. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/tests/common/mod.rs +0 -0
  130. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/tests/fixtures/garbage/garbage_api.py +0 -0
  131. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/tests/fixtures/garbage/garbage_constants.py +0 -0
  132. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/tests/fixtures/garbage/garbage_handlers.py +0 -0
  133. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/tests/fixtures/garbage/garbage_models.py +0 -0
  134. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/tests/fixtures/garbage/garbage_module.js +0 -0
  135. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/tests/fixtures/garbage/garbage_services.py +0 -0
  136. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/tests/fixtures/garbage/garbage_types.py +0 -0
  137. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/tests/fixtures/garbage/garbage_unrelated.yaml +0 -0
  138. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/tests/fixtures/garbage/garbage_utils.py +0 -0
  139. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/tests/fixtures/garbage/garbage_validators.py +0 -0
  140. {diffctx-1.10.0 → diffctx-1.10.1}/diffctx/tests/yaml_cases.rs +0 -0
  141. {diffctx-1.10.0 → diffctx-1.10.1}/rust-toolchain.toml +0 -0
  142. {diffctx-1.10.0 → diffctx-1.10.1}/src/diffctx/__init__.py +0 -0
  143. {diffctx-1.10.0 → diffctx-1.10.1}/src/diffctx/__main__.py +0 -0
  144. {diffctx-1.10.0 → diffctx-1.10.1}/src/diffctx/cli.py +0 -0
  145. {diffctx-1.10.0 → diffctx-1.10.1}/src/diffctx/clipboard.py +0 -0
  146. {diffctx-1.10.0 → diffctx-1.10.1}/src/diffctx/diffctx/__init__.py +0 -0
  147. {diffctx-1.10.0 → diffctx-1.10.1}/src/diffctx/diffctx/graph_analytics.py +0 -0
  148. {diffctx-1.10.0 → diffctx-1.10.1}/src/diffctx/diffctx/graph_export.py +0 -0
  149. {diffctx-1.10.0 → diffctx-1.10.1}/src/diffctx/diffctx/pipeline.py +0 -0
  150. {diffctx-1.10.0 → diffctx-1.10.1}/src/diffctx/diffctx/project_graph.py +0 -0
  151. {diffctx-1.10.0 → diffctx-1.10.1}/src/diffctx/ignore.py +0 -0
  152. {diffctx-1.10.0 → diffctx-1.10.1}/src/diffctx/logger.py +0 -0
  153. {diffctx-1.10.0 → diffctx-1.10.1}/src/diffctx/mcp/README.md +0 -0
  154. {diffctx-1.10.0 → diffctx-1.10.1}/src/diffctx/mcp/__init__.py +0 -0
  155. {diffctx-1.10.0 → diffctx-1.10.1}/src/diffctx/mcp/formatting.py +0 -0
  156. {diffctx-1.10.0 → diffctx-1.10.1}/src/diffctx/mcp/security.py +0 -0
  157. {diffctx-1.10.0 → diffctx-1.10.1}/src/diffctx/mcp/server.py +0 -0
  158. {diffctx-1.10.0 → diffctx-1.10.1}/src/diffctx/py.typed +0 -0
  159. {diffctx-1.10.0 → diffctx-1.10.1}/src/diffctx/tokens.py +0 -0
  160. {diffctx-1.10.0 → diffctx-1.10.1}/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.1
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"
@@ -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(".");
@@ -412,6 +412,7 @@ pub fn select_with_params(
412
412
  &state.root_dir,
413
413
  &state.preferred_revs,
414
414
  batch_reader.as_mut(),
415
+ &state.core_ids,
415
416
  );
416
417
  if let Some(mut r) = batch_reader {
417
418
  r.close();
@@ -58,9 +58,16 @@ fn pick_smallest_fitting(
58
58
  candidates: &[Fragment],
59
59
  selected_ids: &FxHashSet<FragmentId>,
60
60
  budget_left: u32,
61
+ core_ids: &FxHashSet<FragmentId>,
61
62
  ) -> Option<Fragment> {
62
63
  let mut sorted: Vec<&Fragment> = candidates.iter().collect();
63
- sorted.sort_by_key(|f| f.token_count);
64
+ // Prefer a fragment that actually covers the diff hunk (core_ids, i.e.
65
+ // what render.rs marks `role: "changed"`) over an unrelated same-file
66
+ // fragment (e.g. a one-line function_signature stub). Sorting by
67
+ // token_count alone picks whichever candidate is smallest regardless of
68
+ // relevance, which can silently hide the real change behind a tiny
69
+ // signature-only stub for the same file (#83).
70
+ sorted.sort_by_key(|f| (!core_ids.contains(&f.id), f.token_count));
64
71
  for cand in sorted {
65
72
  if cand.token_count == 0 || selected_ids.contains(&cand.id) {
66
73
  continue;
@@ -208,6 +215,7 @@ pub fn ensure_changed_files_represented(
208
215
  root_dir: &Path,
209
216
  preferred_revs: &[String],
210
217
  mut batch_reader: Option<&mut CatFileBatch>,
218
+ core_ids: &FxHashSet<FragmentId>,
211
219
  ) {
212
220
  let selected_paths: FxHashSet<String> = selected
213
221
  .iter()
@@ -258,7 +266,9 @@ pub fn ensure_changed_files_represented(
258
266
  candidates
259
267
  };
260
268
 
261
- if let Some(picked) = pick_smallest_fitting(&candidates, &selected_ids, budget_left) {
269
+ if let Some(picked) =
270
+ pick_smallest_fitting(&candidates, &selected_ids, budget_left, core_ids)
271
+ {
262
272
  if !interval_idx.overlaps(&picked) {
263
273
  budget_left = budget_left.saturating_sub(picked.token_count);
264
274
  selected_ids.insert(picked.id.clone());
@@ -268,3 +278,96 @@ pub fn ensure_changed_files_represented(
268
278
  }
269
279
  }
270
280
  }
281
+
282
+ #[cfg(test)]
283
+ mod tests {
284
+ use super::*;
285
+
286
+ fn frag(
287
+ path: &str,
288
+ start: u32,
289
+ end: u32,
290
+ kind: crate::types::FragmentKind,
291
+ tokens: u32,
292
+ ) -> Fragment {
293
+ Fragment {
294
+ id: FragmentId::new(Arc::from(path), start, end),
295
+ kind,
296
+ content: Arc::from(format!("fragment {path}:{start}-{end}")),
297
+ identifiers: FxHashSet::default(),
298
+ token_count: tokens,
299
+ symbol_name: None,
300
+ }
301
+ }
302
+
303
+ /// Regression for #83: when a changed file has no selected fragment and
304
+ /// the postpass fallback must pick one, a same-file signature stub that
305
+ /// is merely *smaller* must not be preferred over a same-file fragment
306
+ /// that actually covers the diff hunk (core_ids), as long as the core
307
+ /// fragment also fits the remaining budget.
308
+ #[test]
309
+ fn ensure_changed_files_represented_prefers_core_fragment_when_it_fits() {
310
+ let core = frag("a.ts", 10, 20, crate::types::FragmentKind::Function, 60);
311
+ let stub = frag(
312
+ "a.ts",
313
+ 10,
314
+ 10,
315
+ crate::types::FragmentKind::FunctionSignature,
316
+ 10,
317
+ );
318
+ let all_fragments = vec![core.clone(), stub.clone()];
319
+ let core_ids: FxHashSet<FragmentId> = std::iter::once(core.id.clone()).collect();
320
+ let changed_files = vec![PathBuf::from("a.ts")];
321
+ let mut selected: Vec<Fragment> = Vec::new();
322
+
323
+ ensure_changed_files_represented(
324
+ &mut selected,
325
+ &all_fragments,
326
+ &changed_files,
327
+ 100,
328
+ Path::new("."),
329
+ &[],
330
+ None,
331
+ &core_ids,
332
+ );
333
+
334
+ assert_eq!(selected.len(), 1, "expected exactly one fallback fragment");
335
+ assert_eq!(
336
+ selected[0].id, core.id,
337
+ "fallback picked the signature stub instead of the fragment covering the actual diff hunk"
338
+ );
339
+ }
340
+
341
+ /// When the core fragment does NOT fit the remaining budget, falling
342
+ /// back to the smaller non-core stub is still the correct behavior
343
+ /// (some representation beats none).
344
+ #[test]
345
+ fn ensure_changed_files_represented_falls_back_to_stub_when_core_does_not_fit() {
346
+ let core = frag("a.ts", 10, 20, crate::types::FragmentKind::Function, 60);
347
+ let stub = frag(
348
+ "a.ts",
349
+ 10,
350
+ 10,
351
+ crate::types::FragmentKind::FunctionSignature,
352
+ 10,
353
+ );
354
+ let all_fragments = vec![core.clone(), stub.clone()];
355
+ let core_ids: FxHashSet<FragmentId> = std::iter::once(core.id.clone()).collect();
356
+ let changed_files = vec![PathBuf::from("a.ts")];
357
+ let mut selected: Vec<Fragment> = Vec::new();
358
+
359
+ ensure_changed_files_represented(
360
+ &mut selected,
361
+ &all_fragments,
362
+ &changed_files,
363
+ 15,
364
+ Path::new("."),
365
+ &[],
366
+ None,
367
+ &core_ids,
368
+ );
369
+
370
+ assert_eq!(selected.len(), 1);
371
+ assert_eq!(selected[0].id, stub.id);
372
+ }
373
+ }
@@ -5,7 +5,7 @@ requires = [ "maturin>=1.10,<1.15" ]
5
5
 
6
6
  [project]
7
7
  name = "diffctx"
8
- version = "1.10.0"
8
+ version = "1.10.1"
9
9
  description = "Export codebase structure and contents for AI/LLM context"
10
10
  readme = "README.md"
11
11
  keywords = [
@@ -40,9 +40,9 @@ def _ensure_git_repo(root_dir: Path) -> None:
40
40
  stderr=subprocess.DEVNULL,
41
41
  check=False,
42
42
  )
43
- except (OSError, FileNotFoundError) as exc:
43
+ except OSError as exc:
44
44
  print(
45
- f"diffctx: --diff requires git to be installed and on PATH ({exc}); " "install git or run without --diff.",
45
+ f"diffctx: --diff requires git to be installed and on PATH ({exc}); install git or run without --diff.",
46
46
  file=sys.stderr,
47
47
  )
48
48
  sys.exit(_EXIT_ENVIRONMENT)
@@ -8,7 +8,7 @@ def main(prog: str = "diffctx-mcp", extra: str = "diffctx[mcp]") -> None:
8
8
  from diffctx.mcp.server import run_server
9
9
  except ImportError:
10
10
  print(
11
- f"{prog}: missing optional dependencies for MCP server mode.\n" f"Install with: pip install '{extra}'",
11
+ f"{prog}: missing optional dependencies for MCP server mode.\nInstall with: pip install '{extra}'",
12
12
  file=sys.stderr,
13
13
  )
14
14
  sys.exit(2)
@@ -0,0 +1 @@
1
+ __version__ = "1.10.1"
@@ -190,6 +190,29 @@ def _write_text_fragment(file: TextIO, frag: dict[str, Any], indent: str = "") -
190
190
  file.write(f"{content_indent}{line}\n")
191
191
 
192
192
 
193
+ def _write_tree_text_diff_context(file: TextIO, tree: dict[str, Any]) -> None:
194
+ if tree.get("commit_message"):
195
+ file.write(f" change: {tree['commit_message']}\n")
196
+ if tree.get("changed_files"):
197
+ file.write(f" changed files: {', '.join(str(p) for p in tree['changed_files'])}\n")
198
+ for frag in tree["fragments"]:
199
+ _write_text_fragment(file, frag, " ")
200
+
201
+
202
+ def _write_tree_text_children(file: TextIO, children: list[dict[str, Any]]) -> None:
203
+ for i, child in enumerate(children):
204
+ connector = _TREE_LAST if i == len(children) - 1 else _TREE_BRANCH
205
+ _write_tree_text_node(file, child, "", connector)
206
+
207
+
208
+ def _write_tree_text_content(file: TextIO, content: str) -> None:
209
+ if not content:
210
+ file.write("(empty file)\n")
211
+ else:
212
+ for line in content.rstrip("\n").split("\n"):
213
+ file.write(f"{line}\n")
214
+
215
+
193
216
  def write_tree_text(file: TextIO, tree: dict[str, Any]) -> None:
194
217
  name = tree.get("name", "")
195
218
  tree_type = tree.get("type", "")
@@ -202,24 +225,11 @@ def write_tree_text(file: TextIO, tree: dict[str, Any]) -> None:
202
225
  file.write(f"{name}/\n")
203
226
 
204
227
  if tree_type == "diff_context" and tree.get("fragments"):
205
- if tree.get("commit_message"):
206
- file.write(f" change: {tree['commit_message']}\n")
207
- if tree.get("changed_files"):
208
- file.write(f" changed files: {', '.join(str(p) for p in tree['changed_files'])}\n")
209
- for frag in tree["fragments"]:
210
- _write_text_fragment(file, frag, " ")
228
+ _write_tree_text_diff_context(file, tree)
211
229
  elif tree.get("children"):
212
- children = tree["children"]
213
- for i, child in enumerate(children):
214
- connector = _TREE_LAST if i == len(children) - 1 else _TREE_BRANCH
215
- _write_tree_text_node(file, child, "", connector)
230
+ _write_tree_text_children(file, tree["children"])
216
231
  elif "content" in tree:
217
- content = tree["content"]
218
- if not content:
219
- file.write("(empty file)\n")
220
- else:
221
- for line in content.rstrip("\n").split("\n"):
222
- file.write(f"{line}\n")
232
+ _write_tree_text_content(file, tree["content"])
223
233
 
224
234
 
225
235
  def _is_placeholder(content: str) -> bool:
@@ -324,6 +334,18 @@ def _write_markdown_fragment(file: TextIO, frag: dict[str, Any]) -> None:
324
334
  _write_md_code_block(file, frag["content"], lang, "")
325
335
 
326
336
 
337
+ def _write_markdown_diff_context(file: TextIO, tree: dict[str, Any]) -> None:
338
+ if tree.get("commit_message"):
339
+ file.write(f"> {tree['commit_message']}\n\n")
340
+ if tree.get("changed_files"):
341
+ file.write("**Changed files:**\n\n")
342
+ for path in tree["changed_files"]:
343
+ file.write(f"- {_escape_md_inline_code(str(path))}\n")
344
+ file.write("\n")
345
+ for frag in tree["fragments"]:
346
+ _write_markdown_fragment(file, frag)
347
+
348
+
327
349
  def write_tree_markdown(file: TextIO, tree: dict[str, Any]) -> None:
328
350
  name = tree.get("name", "")
329
351
  tree_type = tree.get("type", "")
@@ -335,15 +357,7 @@ def write_tree_markdown(file: TextIO, tree: dict[str, Any]) -> None:
335
357
  file.write(f"# {name}/\n\n")
336
358
 
337
359
  if tree_type == "diff_context" and tree.get("fragments"):
338
- if tree.get("commit_message"):
339
- file.write(f"> {tree['commit_message']}\n\n")
340
- if tree.get("changed_files"):
341
- file.write("**Changed files:**\n\n")
342
- for path in tree["changed_files"]:
343
- file.write(f"- {_escape_md_inline_code(str(path))}\n")
344
- file.write("\n")
345
- for frag in tree["fragments"]:
346
- _write_markdown_fragment(file, frag)
360
+ _write_markdown_diff_context(file, tree)
347
361
  elif tree.get("children"):
348
362
  for child in tree["children"]:
349
363
  _write_markdown_node(file, child, 1)
@@ -1 +0,0 @@
1
- __version__ = "1.10.0"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes