diffctx 1.12.0__tar.gz → 1.12.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 (163) hide show
  1. {diffctx-1.12.0 → diffctx-1.12.1}/CHANGELOG.md +23 -0
  2. {diffctx-1.12.0 → diffctx-1.12.1}/PKG-INFO +17 -3
  3. {diffctx-1.12.0 → diffctx-1.12.1}/README.md +15 -2
  4. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/Cargo.lock +1 -1
  5. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/Cargo.toml +10 -9
  6. diffctx-1.12.1/diffctx/README.md +210 -0
  7. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/main.rs +5 -2
  8. {diffctx-1.12.0 → diffctx-1.12.1}/pyproject.toml +1 -1
  9. diffctx-1.12.1/src/diffctx/version.py +1 -0
  10. diffctx-1.12.0/diffctx/tests/common/mod.rs +0 -274
  11. diffctx-1.12.0/diffctx/tests/fixtures/garbage/garbage_api.py +0 -31
  12. diffctx-1.12.0/diffctx/tests/fixtures/garbage/garbage_constants.py +0 -11
  13. diffctx-1.12.0/diffctx/tests/fixtures/garbage/garbage_handlers.py +0 -27
  14. diffctx-1.12.0/diffctx/tests/fixtures/garbage/garbage_models.py +0 -25
  15. diffctx-1.12.0/diffctx/tests/fixtures/garbage/garbage_module.js +0 -23
  16. diffctx-1.12.0/diffctx/tests/fixtures/garbage/garbage_services.py +0 -37
  17. diffctx-1.12.0/diffctx/tests/fixtures/garbage/garbage_types.py +0 -31
  18. diffctx-1.12.0/diffctx/tests/fixtures/garbage/garbage_unrelated.yaml +0 -8
  19. diffctx-1.12.0/diffctx/tests/fixtures/garbage/garbage_utils.py +0 -31
  20. diffctx-1.12.0/diffctx/tests/fixtures/garbage/garbage_validators.py +0 -23
  21. diffctx-1.12.0/diffctx/tests/yaml_cases.rs +0 -276
  22. diffctx-1.12.0/src/diffctx/version.py +0 -1
  23. {diffctx-1.12.0 → diffctx-1.12.1}/LICENSE +0 -0
  24. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/analytics.rs +0 -0
  25. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/candidate_files.rs +0 -0
  26. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/config/analytics.rs +0 -0
  27. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/config/bm25.rs +0 -0
  28. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/config/budget.rs +0 -0
  29. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/config/category_weights.rs +0 -0
  30. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/config/edge_weights.rs +0 -0
  31. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/config/env_overrides.rs +0 -0
  32. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/config/extensions.rs +0 -0
  33. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/config/filtering.rs +0 -0
  34. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/config/fragmentation.rs +0 -0
  35. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/config/git.rs +0 -0
  36. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/config/graph_filtering.rs +0 -0
  37. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/config/importance.rs +0 -0
  38. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/config/limits.rs +0 -0
  39. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/config/mod.rs +0 -0
  40. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/config/mode.rs +0 -0
  41. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/config/needs.rs +0 -0
  42. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/config/parsers.rs +0 -0
  43. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/config/render.rs +0 -0
  44. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/config/scoring.rs +0 -0
  45. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/config/selection.rs +0 -0
  46. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/config/tokenization.rs +0 -0
  47. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/config/weights.rs +0 -0
  48. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/core.rs +0 -0
  49. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/discovery.rs +0 -0
  50. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/base.rs +0 -0
  51. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/config_edges/build_system.rs +0 -0
  52. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/config_edges/cicd.rs +0 -0
  53. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/config_edges/docker.rs +0 -0
  54. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/config_edges/generic.rs +0 -0
  55. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/config_edges/helm.rs +0 -0
  56. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/config_edges/kubernetes.rs +0 -0
  57. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/config_edges/mod.rs +0 -0
  58. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/document/mod.rs +0 -0
  59. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/history/cochange.rs +0 -0
  60. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/history/mod.rs +0 -0
  61. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/mod.rs +0 -0
  62. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/ansible.rs +0 -0
  63. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/bazel.rs +0 -0
  64. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/c_family.rs +0 -0
  65. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/cargo_edges.rs +0 -0
  66. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/clojure.rs +0 -0
  67. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/css.rs +0 -0
  68. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/dart.rs +0 -0
  69. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/dbt.rs +0 -0
  70. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/dotnet.rs +0 -0
  71. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/elixir.rs +0 -0
  72. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/erlang.rs +0 -0
  73. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/go.rs +0 -0
  74. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/graphql.rs +0 -0
  75. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/haskell.rs +0 -0
  76. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/javascript.rs +0 -0
  77. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/julia.rs +0 -0
  78. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/jvm.rs +0 -0
  79. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/latex.rs +0 -0
  80. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/lua.rs +0 -0
  81. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/mod.rs +0 -0
  82. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/nim.rs +0 -0
  83. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/nix.rs +0 -0
  84. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/ocaml.rs +0 -0
  85. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/openapi.rs +0 -0
  86. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/perl.rs +0 -0
  87. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/php.rs +0 -0
  88. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/prisma.rs +0 -0
  89. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/protobuf.rs +0 -0
  90. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/python.rs +0 -0
  91. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/r_lang.rs +0 -0
  92. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/ruby.rs +0 -0
  93. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/rust_lang.rs +0 -0
  94. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/shell.rs +0 -0
  95. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/sql.rs +0 -0
  96. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/swift.rs +0 -0
  97. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/tags.rs +0 -0
  98. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/terraform.rs +0 -0
  99. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/zig.rs +0 -0
  100. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/similarity/lexical.rs +0 -0
  101. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/similarity/mod.rs +0 -0
  102. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/structural/containment.rs +0 -0
  103. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/structural/mod.rs +0 -0
  104. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/structural/sibling.rs +0 -0
  105. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/edges/structural/testing.rs +0 -0
  106. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/filtering.rs +0 -0
  107. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/fragmentation.rs +0 -0
  108. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/git.rs +0 -0
  109. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/graph.rs +0 -0
  110. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/graph_export.rs +0 -0
  111. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/interval.rs +0 -0
  112. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/languages.rs +0 -0
  113. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/lib.rs +0 -0
  114. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/memory_pipeline.rs +0 -0
  115. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/mode.rs +0 -0
  116. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/parsers/config_parser.rs +0 -0
  117. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/parsers/generic.rs +0 -0
  118. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/parsers/markdown.rs +0 -0
  119. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/parsers/mod.rs +0 -0
  120. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/parsers/tree_sitter_strategy.rs +0 -0
  121. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/peak_rss.rs +0 -0
  122. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/pipeline.rs +0 -0
  123. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/postpass.rs +0 -0
  124. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/ppr.rs +0 -0
  125. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/project_graph.rs +0 -0
  126. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/pybridge.rs +0 -0
  127. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/render.rs +0 -0
  128. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/scoring.rs +0 -0
  129. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/select.rs +0 -0
  130. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/signatures.rs +0 -0
  131. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/stopwords.rs +0 -0
  132. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/test_harness.rs +0 -0
  133. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/token_corpus.rs +0 -0
  134. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/tokenizer.rs +0 -0
  135. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/types.rs +0 -0
  136. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/utility/boltzmann.rs +0 -0
  137. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/utility/importance.rs +0 -0
  138. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/utility/mod.rs +0 -0
  139. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/utility/needs.rs +0 -0
  140. {diffctx-1.12.0 → diffctx-1.12.1}/diffctx/src/utility/scoring.rs +0 -0
  141. {diffctx-1.12.0 → diffctx-1.12.1}/rust-toolchain.toml +0 -0
  142. {diffctx-1.12.0 → diffctx-1.12.1}/src/diffctx/__init__.py +0 -0
  143. {diffctx-1.12.0 → diffctx-1.12.1}/src/diffctx/__main__.py +0 -0
  144. {diffctx-1.12.0 → diffctx-1.12.1}/src/diffctx/cli.py +0 -0
  145. {diffctx-1.12.0 → diffctx-1.12.1}/src/diffctx/clipboard.py +0 -0
  146. {diffctx-1.12.0 → diffctx-1.12.1}/src/diffctx/diffctx/__init__.py +0 -0
  147. {diffctx-1.12.0 → diffctx-1.12.1}/src/diffctx/diffctx/graph_analytics.py +0 -0
  148. {diffctx-1.12.0 → diffctx-1.12.1}/src/diffctx/diffctx/graph_export.py +0 -0
  149. {diffctx-1.12.0 → diffctx-1.12.1}/src/diffctx/diffctx/pipeline.py +0 -0
  150. {diffctx-1.12.0 → diffctx-1.12.1}/src/diffctx/diffctx/project_graph.py +0 -0
  151. {diffctx-1.12.0 → diffctx-1.12.1}/src/diffctx/ignore.py +0 -0
  152. {diffctx-1.12.0 → diffctx-1.12.1}/src/diffctx/logger.py +0 -0
  153. {diffctx-1.12.0 → diffctx-1.12.1}/src/diffctx/main.py +0 -0
  154. {diffctx-1.12.0 → diffctx-1.12.1}/src/diffctx/mcp/README.md +0 -0
  155. {diffctx-1.12.0 → diffctx-1.12.1}/src/diffctx/mcp/__init__.py +0 -0
  156. {diffctx-1.12.0 → diffctx-1.12.1}/src/diffctx/mcp/__main__.py +0 -0
  157. {diffctx-1.12.0 → diffctx-1.12.1}/src/diffctx/mcp/formatting.py +0 -0
  158. {diffctx-1.12.0 → diffctx-1.12.1}/src/diffctx/mcp/security.py +0 -0
  159. {diffctx-1.12.0 → diffctx-1.12.1}/src/diffctx/mcp/server.py +0 -0
  160. {diffctx-1.12.0 → diffctx-1.12.1}/src/diffctx/py.typed +0 -0
  161. {diffctx-1.12.0 → diffctx-1.12.1}/src/diffctx/tokens.py +0 -0
  162. {diffctx-1.12.0 → diffctx-1.12.1}/src/diffctx/tree.py +0 -0
  163. {diffctx-1.12.0 → diffctx-1.12.1}/src/diffctx/writer.py +0 -0
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Added
11
+
12
+ - Standalone native binaries (linux x86_64/aarch64, macOS arm64, Windows
13
+ x64) are built and attached to every GitHub release. The README had
14
+ promised them since 1.9.x; releases only ever carried wheels and an sdist.
15
+ - The Rust engine is published to crates.io as the `diffctx` crate
16
+ (`cargo install diffctx` for the native CLI, `cargo add diffctx` to embed the
17
+ selection pipeline). Previously the name held only a reservation stub; the
18
+ crate now carries the released engine, starting at 1.12.0.
19
+ - Container image `ghcr.io/nikolay-e/diffctx` (linux amd64/arm64), built from
20
+ the release tag and smoke-tested against a real repository before the tag
21
+ moves: `docker run --rm -v "$PWD:/repo" ghcr.io/nikolay-e/diffctx . --diff HEAD~1`.
22
+ - Packaging manifests generated from the release checksums: Scoop
23
+ (`packaging/scoop/diffctx.json`), AUR (`packaging/aur/`) and an npm wrapper
24
+ (`packaging/npm/`) that downloads the platform binary and verifies its
25
+ SHA-256 against the published checksum.
26
+
27
+ ### Fixed
28
+
29
+ - The native binary silently emitted YAML for every unrecognized `--format`,
30
+ including `md` — the documented default of the Python CLI. It now accepts
31
+ only `yaml`/`json` and exits 2 on anything else.
32
+
10
33
  ## [1.12.0] - 2026-07-23
11
34
 
12
35
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: diffctx
3
- Version: 1.12.0
3
+ Version: 1.12.1
4
4
  Classifier: Development Status :: 5 - Production/Stable
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -64,6 +64,7 @@ Provides-Extra: tree-sitter
64
64
  License-File: LICENSE
65
65
  Summary: Export codebase structure and contents for AI/LLM context
66
66
  Keywords: ai,chatgpt,claude,code-analysis,code-context,code-review,code-to-prompt,codebase,context,context-selection,diff-context,directory-tree,export,git-diff,json,llm,llm-context,mcp,model-context-protocol,tree,yaml
67
+ Home-Page: https://github.com/nikolay-e/diffctx
67
68
  Author-email: Nikolay Eremeev <nikolay.eremeev@outlook.com>
68
69
  License-Expression: Apache-2.0
69
70
  Requires-Python: >=3.10
@@ -77,6 +78,7 @@ Project-URL: Repository, https://github.com/nikolay-e/diffctx
77
78
 
78
79
  [![CI](https://github.com/nikolay-e/diffctx/actions/workflows/ci.yml/badge.svg)](https://github.com/nikolay-e/diffctx/actions/workflows/ci.yml)
79
80
  [![PyPI](https://img.shields.io/pypi/v/diffctx)](https://pypi.org/project/diffctx/)
81
+ [![crates.io](https://img.shields.io/crates/v/diffctx)](https://crates.io/crates/diffctx)
80
82
  [![License](https://img.shields.io/pypi/l/diffctx)](https://pypi.org/project/diffctx/)
81
83
 
82
84
  **diffctx selects the minimum code an LLM needs to review a git diff.**
@@ -109,11 +111,23 @@ pip install diffctx # or: into an active environment
109
111
  pipx install 'diffctx[mcp]' # + MCP server for AI assistants
110
112
  ```
111
113
 
112
- A standalone binary (no Python required) is on the
113
- [releases page](https://github.com/nikolay-e/diffctx/releases/latest).
114
114
  The `[tree-sitter]` extra adds AST-level parsing for more accurate context
115
115
  selection across 30+ languages.
116
116
 
117
+ Without Python:
118
+
119
+ ```bash
120
+ cargo install diffctx # native CLI from crates.io
121
+ docker run --rm -v "$PWD:/repo" ghcr.io/nikolay-e/diffctx . --diff HEAD~1
122
+ ```
123
+
124
+ Prebuilt binaries for linux (x86_64/aarch64), macOS (arm64) and Windows (x64)
125
+ are attached to every [release](https://github.com/nikolay-e/diffctx/releases/latest).
126
+ The native binary covers diff mode with YAML/JSON output; tree mode, Markdown
127
+ output, the `graph` subcommand and the MCP server live in the Python package.
128
+ `cargo add diffctx` embeds the selection pipeline in a Rust project
129
+ ([docs.rs](https://docs.rs/diffctx)).
130
+
117
131
  ## Quick start
118
132
 
119
133
  ```bash
@@ -2,6 +2,7 @@
2
2
 
3
3
  [![CI](https://github.com/nikolay-e/diffctx/actions/workflows/ci.yml/badge.svg)](https://github.com/nikolay-e/diffctx/actions/workflows/ci.yml)
4
4
  [![PyPI](https://img.shields.io/pypi/v/diffctx)](https://pypi.org/project/diffctx/)
5
+ [![crates.io](https://img.shields.io/crates/v/diffctx)](https://crates.io/crates/diffctx)
5
6
  [![License](https://img.shields.io/pypi/l/diffctx)](https://pypi.org/project/diffctx/)
6
7
 
7
8
  **diffctx selects the minimum code an LLM needs to review a git diff.**
@@ -34,11 +35,23 @@ pip install diffctx # or: into an active environment
34
35
  pipx install 'diffctx[mcp]' # + MCP server for AI assistants
35
36
  ```
36
37
 
37
- A standalone binary (no Python required) is on the
38
- [releases page](https://github.com/nikolay-e/diffctx/releases/latest).
39
38
  The `[tree-sitter]` extra adds AST-level parsing for more accurate context
40
39
  selection across 30+ languages.
41
40
 
41
+ Without Python:
42
+
43
+ ```bash
44
+ cargo install diffctx # native CLI from crates.io
45
+ docker run --rm -v "$PWD:/repo" ghcr.io/nikolay-e/diffctx . --diff HEAD~1
46
+ ```
47
+
48
+ Prebuilt binaries for linux (x86_64/aarch64), macOS (arm64) and Windows (x64)
49
+ are attached to every [release](https://github.com/nikolay-e/diffctx/releases/latest).
50
+ The native binary covers diff mode with YAML/JSON output; tree mode, Markdown
51
+ output, the `graph` subcommand and the MCP server live in the Python package.
52
+ `cargo add diffctx` embeds the selection pipeline in a Rust project
53
+ ([docs.rs](https://docs.rs/diffctx)).
54
+
42
55
  ## Quick start
43
56
 
44
57
  ```bash
@@ -194,7 +194,7 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
194
194
 
195
195
  [[package]]
196
196
  name = "diffctx"
197
- version = "1.12.0"
197
+ version = "1.12.1"
198
198
  dependencies = [
199
199
  "anyhow",
200
200
  "clap",
@@ -1,8 +1,17 @@
1
1
  [package]
2
2
  name = "diffctx"
3
- version = "1.12.0"
3
+ version = "1.12.1"
4
4
  edition = "2024"
5
5
  rust-version = "1.85"
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"
7
+ license = "Apache-2.0"
8
+ repository = "https://github.com/nikolay-e/diffctx"
9
+ homepage = "https://github.com/nikolay-e/diffctx"
10
+ documentation = "https://docs.rs/diffctx"
11
+ readme = "README.md"
12
+ keywords = ["git", "diff", "llm", "context", "tree-sitter"]
13
+ categories = ["development-tools", "command-line-utilities"]
14
+ exclude = ["tests/**", "src/test_harness.rs"]
6
15
 
7
16
  [lib]
8
17
  name = "_diffctx"
@@ -12,10 +21,6 @@ crate-type = ["cdylib", "rlib"]
12
21
  name = "diffctx"
13
22
  path = "src/main.rs"
14
23
 
15
- [[example]]
16
- name = "diffctx-test"
17
- path = "src/test_harness.rs"
18
-
19
24
  [features]
20
25
  default = ["lang-core", "lang-extra"]
21
26
  python = ["pyo3"]
@@ -127,10 +132,6 @@ pyo3 = { version = "0.29", features = ["extension-module", "abi3-py310"], option
127
132
  tempfile = "3"
128
133
  libtest-mimic = "0.8"
129
134
 
130
- [[test]]
131
- name = "yaml_cases"
132
- harness = false
133
-
134
135
  [lints.clippy]
135
136
  pedantic = { level = "warn", priority = -1 }
136
137
  nursery = { level = "warn", priority = -1 }
@@ -0,0 +1,210 @@
1
+ # diffctx — smart diff context for LLM code review
2
+
3
+ [![CI](https://github.com/nikolay-e/diffctx/actions/workflows/ci.yml/badge.svg)](https://github.com/nikolay-e/diffctx/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/diffctx)](https://pypi.org/project/diffctx/)
5
+ [![crates.io](https://img.shields.io/crates/v/diffctx)](https://crates.io/crates/diffctx)
6
+ [![License](https://img.shields.io/pypi/l/diffctx)](https://pypi.org/project/diffctx/)
7
+
8
+ **diffctx selects the minimum code an LLM needs to review a git diff.**
9
+ Instead of pasting whole files, it walks the dependency graph from the changed
10
+ lines outward and stops as soon as additional context stops paying for itself.
11
+
12
+ > Coming from [`treemapper`](https://pypi.org/project/treemapper/)? That name is
13
+ > deprecated — it was a thin wrapper around diffctx. Every command, flag, and
14
+ > API call works unchanged: `treemapper` → `diffctx`, `treemapper-mcp` → `diffctx-mcp`.
15
+
16
+ ## Why not just use `tree` or repomix?
17
+
18
+ | | `tree` | repomix | Claude Code Review | **diffctx** |
19
+ |---|:---:|:---:|:---:|:---:|
20
+ | **Primary use case** | directory listing | full repo export | automated PR review | **diff context for code review** |
21
+ | Smart diff context | ✗ | ✗ | ✓ | ✓ |
22
+ | Works with any LLM | ✓ | ✓ | Claude only | ✓ |
23
+ | Free / local / offline | ✓ | ✓ | $15–25/review | ✓ |
24
+ | GitHub required | ✗ | ✗ | ✓ | ✗ |
25
+ | Multiple output formats | ✗ | limited | — | YAML/JSON/MD/txt |
26
+ | Python API | ✗ | ✗ | ✗ | ✓ |
27
+ | MCP server | ✗ | ✗ | ✗ | ✓ |
28
+
29
+ ## Install
30
+
31
+ ```bash
32
+ uvx diffctx . --diff HEAD~1 # zero-install, run once via uv
33
+ pipx install diffctx # recommended: isolated CLI, no venv needed
34
+ pip install diffctx # or: into an active environment
35
+ pipx install 'diffctx[mcp]' # + MCP server for AI assistants
36
+ ```
37
+
38
+ The `[tree-sitter]` extra adds AST-level parsing for more accurate context
39
+ selection across 30+ languages.
40
+
41
+ Without Python:
42
+
43
+ ```bash
44
+ cargo install diffctx # native CLI from crates.io
45
+ docker run --rm -v "$PWD:/repo" ghcr.io/nikolay-e/diffctx . --diff HEAD~1
46
+ ```
47
+
48
+ Prebuilt binaries for linux (x86_64/aarch64), macOS (arm64) and Windows (x64)
49
+ are attached to every [release](https://github.com/nikolay-e/diffctx/releases/latest).
50
+ The native binary covers diff mode with YAML/JSON output; tree mode, Markdown
51
+ output, the `graph` subcommand and the MCP server live in the Python package.
52
+ `cargo add diffctx` embeds the selection pipeline in a Rust project
53
+ ([docs.rs](https://docs.rs/diffctx)).
54
+
55
+ ## Quick start
56
+
57
+ ```bash
58
+ diffctx . --diff HEAD~1 # smart context for last commit → paste into Claude/ChatGPT
59
+ diffctx . -f md -c # full codebase export → clipboard in Markdown
60
+ ```
61
+
62
+ ![diffctx demo](https://raw.githubusercontent.com/nikolay-e/diffctx/main/docs/demo.gif)
63
+
64
+ *`diffctx . --diff HEAD~1` selects only the fragments — functions, imports,
65
+ type definitions — that an LLM actually needs to review the last commit,
66
+ instead of dumping every changed file in full.*
67
+
68
+ ## Diff context mode
69
+
70
+ Finds the minimal set of code fragments needed to understand a change —
71
+ imports, callers, type definitions, config dependencies — across 50+ file
72
+ types. It builds a code graph (imports, co-changes, type refs), propagates
73
+ relevance from the changed lines outward, and stops when relevance drops below
74
+ `--tau` or the `--budget` token cap is reached.
75
+
76
+ | Flag | Default | Description |
77
+ |-------------|---------|--------------------------------------------------------------------------|
78
+ | `--scoring` | `ego` | `ego` = bounded expansion around changed nodes (fast, predictable radius); `ppr` = Personalized PageRank (global, smoother decay, slower); `bm25` = lexical retrieval against the diff hunks (baseline for sparse graphs) |
79
+ | `--budget` | auto | Hard token cap: `N` enforces a fixed cap, `-1` disables it, `0` is a strict-zero floor (empty selection; use `--full` for changed files only) |
80
+ | `--alpha` | 0.60 | PPR damping; higher = context clusters tighter around changes (`--scoring ppr` only) |
81
+ | `--tau` | 0.12 | Relevance threshold for full fragment content; lower-scoring fragments are stubbed or dropped (lower = more context) |
82
+ | `--full` | false | Only the changed files, every fragment, no related-code context |
83
+ | `--timeout` | 300 | Wall-clock deadline in seconds; on expiry diffctx exits 124 instead of hanging |
84
+
85
+ Calibration of `--alpha`, `--tau`, and the edge-weight priors:
86
+ [`docs/parameter-strategy.md`](docs/parameter-strategy.md). Theory:
87
+ [Context-Selection for Git Diff (Zenodo, 2026)](https://doi.org/10.5281/zenodo.18824580).
88
+
89
+ ### `graph` subcommand
90
+
91
+ Explore the underlying dependency graph directly, without a diff:
92
+
93
+ ```bash
94
+ diffctx graph . # Mermaid graph of directory deps (default)
95
+ diffctx graph . --summary # cycles, hotspots, coupling metrics
96
+ diffctx graph . --level fragment -f json # fragment-level graph as JSON
97
+ diffctx graph . --level file -f graphml -o g.xml # file-level graph as GraphML
98
+ ```
99
+
100
+ ## Usage
101
+
102
+ <!-- BEGIN USAGE -->
103
+ ```bash
104
+ # full codebase export:
105
+ diffctx . # Markdown to stdout + token count
106
+ diffctx . -f md -c # Markdown → clipboard
107
+ diffctx . -f json -o tree.json # JSON → file
108
+ diffctx . --no-content # structure only, no file contents
109
+ diffctx . --max-depth 3 # limit depth
110
+ diffctx . -i custom.ignore # custom ignore patterns
111
+
112
+ # diff context mode (requires git repo):
113
+ diffctx . --diff # uncommitted changes (working tree vs HEAD)
114
+ diffctx . --diff HEAD~1 # context for last commit
115
+ diffctx . --diff main..feature # context for feature branch
116
+ diffctx . --diff HEAD~1 --budget 30000 # limit to ~30k tokens
117
+ diffctx . --diff HEAD~1 -c # diff context to clipboard
118
+ ```
119
+ <!-- END USAGE -->
120
+
121
+ Every run reports token count and size on stderr — `12,847 tokens
122
+ (o200k_base), 52.3 KB` (tiktoken, the GPT-4o tokenizer; `~`-prefixed
123
+ approximation above 1 MB). `-c/--copy` sends output to the clipboard via
124
+ `pbcopy` (macOS), `clip` (Windows), or `wl-copy`/`xclip`/`xsel` (Linux).
125
+ Unreadable files are replaced by placeholders such as `<binary file: N bytes>`,
126
+ `<file too large: N bytes>`, or `<unreadable content: not utf-8>`.
127
+
128
+ ## Python API
129
+
130
+ ```python
131
+ from pathlib import Path
132
+ from diffctx import build_diff_context, map_directory, to_json, to_markdown, to_text, to_yaml
133
+
134
+ ctx = build_diff_context(
135
+ Path("."),
136
+ "HEAD~1..HEAD",
137
+ budget_tokens=None, # None = auto; 0 = strict-zero floor (empty); -1 = uncapped; N = hard cap
138
+ alpha=0.6,
139
+ tau=0.12,
140
+ full=False,
141
+ scoring_mode="ego",
142
+ timeout=300,
143
+ )
144
+ print(to_markdown(ctx))
145
+
146
+ tree = map_directory(
147
+ ".",
148
+ max_depth=None,
149
+ no_content=False,
150
+ max_file_bytes=None,
151
+ ignore_file=None,
152
+ no_default_ignores=False,
153
+ whitelist_file=None,
154
+ )
155
+ print(to_yaml(tree))
156
+ ```
157
+
158
+ ## MCP server
159
+
160
+ diffctx includes an [MCP](https://modelcontextprotocol.io) server that lets AI
161
+ assistants (Claude Code, Cursor, Windsurf, etc.) call diff context analysis
162
+ automatically during code review. Install with `pip install 'diffctx[mcp]'`
163
+ and add to your MCP client config (e.g. `~/.claude/mcp.json` for Claude Code):
164
+
165
+ ```json
166
+ {
167
+ "mcpServers": {
168
+ "diffctx": {
169
+ "command": "diffctx-mcp"
170
+ }
171
+ }
172
+ }
173
+ ```
174
+
175
+ The server exposes a `get_diff_context` tool that assistants call when
176
+ reviewing PRs, explaining changes, or investigating broken tests. Configs for
177
+ Cursor, Continue, Windsurf, and Zed:
178
+ [`src/diffctx/mcp/README.md`](src/diffctx/mcp/README.md).
179
+
180
+ ## Ignore patterns
181
+
182
+ Respects `.gitignore` and `.diffctx/ignore` automatically — hierarchically at
183
+ every directory level, with gitignore semantics (negation `!important.log`,
184
+ anchored `/root_only.txt`). `.diffctx/whitelist` acts as an include-only
185
+ filter, and the output file is always auto-ignored. `--no-default-ignores`
186
+ disables the built-in patterns; `--no-ignores` disables all ignore rules
187
+ (tree mode only).
188
+
189
+ ## Exit codes
190
+
191
+ | Code | Meaning |
192
+ |------|---------|
193
+ | `0` | Success — output contains content |
194
+ | `1` | Runtime error (bad path, permission denied, etc.) |
195
+ | `2` | Usage error (invalid flags/arguments) |
196
+ | `3` | Environment error (`--diff` outside a git repo, git not installed, no commits yet) |
197
+ | `4` | `--diff` produced no semantic context (clean tree, binary-only, everything filtered); output is still emitted. Deletion/rename-only diffs list `deleted_files`/`renamed_files` and exit `0` |
198
+ | `130`| Interrupted (Ctrl-C) |
199
+ | `141`| Broken pipe (e.g. piping into `head`) |
200
+
201
+ ## License
202
+
203
+ Apache 2.0
204
+
205
+ ---
206
+
207
+ - [Changelog](CHANGELOG.md)
208
+ - [Security policy](SECURITY.md) — threat model and vulnerability reporting
209
+ - [Parameter strategy](docs/parameter-strategy.md) — how `--alpha`,
210
+ `--tau`, and edge weights are calibrated
@@ -27,7 +27,7 @@ struct Cli {
27
27
  #[arg(long, default_value_t = DEFAULT_BUDGET_TOKENS)]
28
28
  budget: u32,
29
29
 
30
- #[arg(long, default_value = "yaml")]
30
+ #[arg(long, default_value = "yaml", value_parser = ["yaml", "json"])]
31
31
  format: String,
32
32
 
33
33
  #[arg(long = "diff")]
@@ -113,10 +113,13 @@ fn main() -> Result<()> {
113
113
  let json = serde_json::to_string_pretty(&output)?;
114
114
  println!("{}", json);
115
115
  }
116
- _ => {
116
+ "yaml" => {
117
117
  let yaml = serde_yaml::to_string(&output)?;
118
118
  print!("{}", yaml);
119
119
  }
120
+ other => {
121
+ anyhow::bail!("diffctx: unsupported --format '{other}' (native binary: yaml, json)")
122
+ }
120
123
  }
121
124
 
122
125
  Ok(())
@@ -5,7 +5,7 @@ requires = [ "maturin>=1.10,<1.15" ]
5
5
 
6
6
  [project]
7
7
  name = "diffctx"
8
- version = "1.12.0"
8
+ version = "1.12.1"
9
9
  description = "Export codebase structure and contents for AI/LLM context"
10
10
  readme = "README.md"
11
11
  keywords = [
@@ -0,0 +1 @@
1
+ __version__ = "1.12.1"
@@ -1,274 +0,0 @@
1
- use std::collections::BTreeMap;
2
- use std::path::Path;
3
- use std::sync::OnceLock;
4
-
5
- use serde::Deserialize;
6
-
7
- use _diffctx::render::{DiffContextOutput, FragmentEntry};
8
-
9
- #[derive(Debug, Deserialize, Clone, Default)]
10
- pub struct TestCase {
11
- #[serde(default)]
12
- pub name: String,
13
- #[serde(default)]
14
- pub tags: Vec<String>,
15
- #[serde(default)]
16
- pub repo: Repo,
17
- #[serde(default)]
18
- pub fixtures: Fixtures,
19
- #[serde(default)]
20
- pub fragments: Vec<DeclaredFragment>,
21
- #[serde(default)]
22
- pub oracle: Oracle,
23
- #[serde(default)]
24
- pub accept: Accept,
25
- #[serde(default)]
26
- pub xfail: Option<XFail>,
27
- #[serde(default)]
28
- pub min_score: Option<f64>,
29
- }
30
-
31
- #[derive(Debug, Deserialize, Clone, Default)]
32
- pub struct Repo {
33
- #[serde(default)]
34
- pub initial_files: BTreeMap<String, String>,
35
- #[serde(default)]
36
- pub changed_files: BTreeMap<String, String>,
37
- #[serde(default = "default_commit_message")]
38
- pub commit_message: String,
39
- }
40
-
41
- #[derive(Debug, Deserialize, Clone, Default)]
42
- pub struct Fixtures {
43
- #[serde(default)]
44
- pub auto_garbage: bool,
45
- #[serde(default)]
46
- pub distractors: BTreeMap<String, String>,
47
- }
48
-
49
- #[derive(Debug, Deserialize, Clone)]
50
- pub struct DeclaredFragment {
51
- pub id: String,
52
- #[serde(default)]
53
- pub selector: Selector,
54
- }
55
-
56
- #[derive(Debug, Deserialize, Clone, Default)]
57
- pub struct Selector {
58
- pub path: Option<String>,
59
- pub symbol: Option<String>,
60
- pub kind: Option<String>,
61
- pub anchor: Option<String>,
62
- pub any_of: Option<Vec<Selector>>,
63
- }
64
-
65
- #[derive(Debug, Deserialize, Clone, Default)]
66
- pub struct Oracle {
67
- #[serde(default)]
68
- pub required: Vec<String>,
69
- #[serde(default)]
70
- pub allowed: Vec<String>,
71
- #[serde(default)]
72
- pub forbidden: Vec<String>,
73
- }
74
-
75
- #[derive(Debug, Deserialize, Clone)]
76
- pub struct Accept {
77
- #[serde(default = "default_symbol_match")]
78
- pub symbol_match: String,
79
- #[serde(default)]
80
- pub kind_must_match: bool,
81
- #[serde(default = "default_span_relation")]
82
- pub span_relation: String,
83
- }
84
-
85
- impl Default for Accept {
86
- fn default() -> Self {
87
- Self {
88
- symbol_match: default_symbol_match(),
89
- kind_must_match: false,
90
- span_relation: default_span_relation(),
91
- }
92
- }
93
- }
94
-
95
- #[derive(Debug, Deserialize, Clone)]
96
- pub struct XFail {
97
- #[serde(default)]
98
- pub category: Option<String>,
99
- #[serde(default)]
100
- pub reason: Option<String>,
101
- #[serde(default)]
102
- pub issue: Option<String>,
103
- }
104
-
105
- impl XFail {
106
- pub fn is_active(&self) -> bool {
107
- self.reason.is_some() || self.category.is_some()
108
- }
109
- }
110
-
111
- fn default_symbol_match() -> String {
112
- "exact".into()
113
- }
114
- fn default_span_relation() -> String {
115
- "exact_or_enclosing".into()
116
- }
117
- fn default_commit_message() -> String {
118
- "Update files".into()
119
- }
120
-
121
- pub fn match_path(candidate: &str, target: &str) -> bool {
122
- candidate == target
123
- || candidate.ends_with(&format!("/{target}"))
124
- || candidate
125
- .replace('\\', "/")
126
- .ends_with(&format!("/{target}"))
127
- }
128
-
129
- pub fn symbol_matches(frag_symbol: &str, expected: &str, mode: &str) -> bool {
130
- match mode {
131
- "prefix" => frag_symbol.starts_with(expected),
132
- "substring" => frag_symbol.contains(expected),
133
- _ => frag_symbol == expected,
134
- }
135
- }
136
-
137
- pub fn matches_selector(fragment: &FragmentEntry, selector: &Selector, accept: &Accept) -> bool {
138
- if let Some(any) = &selector.any_of {
139
- return any.iter().any(|s| matches_selector(fragment, s, accept));
140
- }
141
- if let Some(path) = &selector.path {
142
- if !match_path(&fragment.path, path) {
143
- return false;
144
- }
145
- }
146
- if let Some(symbol) = &selector.symbol {
147
- let frag_symbol = fragment.symbol.as_deref().unwrap_or("");
148
- if !symbol_matches(frag_symbol, symbol, &accept.symbol_match) {
149
- return false;
150
- }
151
- }
152
- if let Some(kind) = &selector.kind {
153
- if accept.kind_must_match && fragment.kind != *kind {
154
- return false;
155
- }
156
- }
157
- if let Some(anchor) = &selector.anchor {
158
- let content = fragment.content.as_deref().unwrap_or("");
159
- if !content.contains(anchor) && !fragment.path.contains(anchor) {
160
- return false;
161
- }
162
- }
163
- true
164
- }
165
-
166
- pub struct OracleResult {
167
- pub passed: bool,
168
- pub score: f64,
169
- pub recall: f64,
170
- pub forbidden_rate: f64,
171
- pub missing_required: Vec<String>,
172
- pub hit_forbidden: Vec<String>,
173
- }
174
-
175
- pub fn evaluate_oracle(case: &TestCase, output: &DiffContextOutput) -> OracleResult {
176
- let mut matched_ids: std::collections::HashSet<&str> = std::collections::HashSet::new();
177
- for entry in &output.fragments {
178
- for decl in &case.fragments {
179
- if matches_selector(entry, &decl.selector, &case.accept) {
180
- matched_ids.insert(decl.id.as_str());
181
- }
182
- }
183
- }
184
-
185
- let required_total = case.oracle.required.len();
186
- let missing_required: Vec<String> = case
187
- .oracle
188
- .required
189
- .iter()
190
- .filter(|id| !matched_ids.contains(id.as_str()))
191
- .cloned()
192
- .collect();
193
-
194
- let forbidden_total = case.oracle.forbidden.len();
195
- let hit_forbidden: Vec<String> = case
196
- .oracle
197
- .forbidden
198
- .iter()
199
- .filter(|id| matched_ids.contains(id.as_str()))
200
- .cloned()
201
- .collect();
202
-
203
- let recall = if required_total == 0 {
204
- 1.0
205
- } else {
206
- (required_total - missing_required.len()) as f64 / required_total as f64
207
- };
208
- let forbidden_rate = if forbidden_total == 0 {
209
- 0.0
210
- } else {
211
- hit_forbidden.len() as f64 / forbidden_total as f64
212
- };
213
- let score = 100.0 * recall * (1.0 - forbidden_rate);
214
- let passed = missing_required.is_empty() && hit_forbidden.is_empty();
215
-
216
- OracleResult {
217
- passed,
218
- score,
219
- recall,
220
- forbidden_rate,
221
- missing_required,
222
- hit_forbidden,
223
- }
224
- }
225
-
226
- pub fn calculate_budget(case: &TestCase) -> u32 {
227
- let overhead = _diffctx::config::limits::LIMITS.overhead_per_fragment;
228
- let mut all_files: BTreeMap<String, String> = case.repo.initial_files.clone();
229
- for (k, v) in &case.repo.changed_files {
230
- all_files.insert(k.clone(), v.clone());
231
- }
232
- for (k, v) in &case.fixtures.distractors {
233
- all_files.insert(k.clone(), v.clone());
234
- }
235
-
236
- let content_tokens: u32 = all_files
237
- .values()
238
- .map(|c| _diffctx::tokenizer::count_tokens(c))
239
- .sum();
240
- let n_frags = all_files.len().max(2) as u32;
241
- ((content_tokens + n_frags * overhead) * 5 / 2).max(500)
242
- }
243
-
244
- static GARBAGE_CACHE: OnceLock<BTreeMap<String, String>> = OnceLock::new();
245
-
246
- pub fn garbage_files() -> &'static BTreeMap<String, String> {
247
- GARBAGE_CACHE.get_or_init(|| {
248
- let mut m = BTreeMap::new();
249
- let dir = Path::new(env!("CARGO_MANIFEST_DIR"))
250
- .join("tests")
251
- .join("fixtures")
252
- .join("garbage");
253
- let entries = match std::fs::read_dir(&dir) {
254
- Ok(e) => e,
255
- Err(_) => return m,
256
- };
257
- for entry in entries.flatten() {
258
- let path = entry.path();
259
- if !path.is_file() {
260
- continue;
261
- }
262
- let name = match path.file_name().and_then(|n| n.to_str()) {
263
- Some(n) => n,
264
- None => continue,
265
- };
266
- let content = match std::fs::read_to_string(&path) {
267
- Ok(c) => c,
268
- Err(_) => continue,
269
- };
270
- m.insert(format!("unrelated_dir/{name}"), content);
271
- }
272
- m
273
- })
274
- }