v1.108.270 - A directory that declares itself a cache is not corpus
jCodeMunch now honours the Cache Directory Tagging Specification: a directory containing a CACHEDIR.TAG whose first 43 bytes are Signature: 8a477f597d28d172789f06886806bc55 is pruned from the walk, along with everything beneath it.
Why this exists
A sibling tool wrote a derived projection into a directory inside an indexed tree. jCodeMunch walked in and indexed its JSON as source, so content that was never project source came back from search_symbols and search_text. That tool then adopted CACHEDIR.TAG to declare the directory derived, and we ignored the declaration, because we had no notion of one. The containment it built did nothing for us.
Why a tag rather than another denylist entry
Three fixes were available and two are traps.
_SKIP_DIRECTORY_NAMESalready lists.git,.venv,.tox— every dotted directory somebody thought of in advance. Adding the offender re-arms the same trap for the next tool.- A dotted-directory rule is better, but keys on a naming convention and cannot see a cache that is not dotted.
- The tag is a declaration by the writer. It is the only one of the three that does not require every reader to know about every writer in advance.
The signature is verified
A file merely named CACHEDIR.TAG excludes nothing. A name-only check asserts one instance of the property instead of the property. Five parametrized lookalikes — empty, wrong hash, signature not first, truncated by one byte, wrong case — are controls, and a name-only implementation fails all five.
Scope
cache_diris an ordinary exclusion, not a withheld reason. A tagged directory holds regenerable derived data by its writer's own declaration, which puts it in the same class asgitignore: the corpus being defined, not a file we refused. Coverage stayscompleteand absence claims over the remainder stay citable.- Reaches the full walk and the watcher fast path.
resolve_explicit_pathsdeliberately bypasses it, with a test saying so: that route already opts pastgitignoreand skip-directory rules so a caller can name a generated file on purpose. - Local walks only.
index_repois deliberately uncovered, because validating the signature needs the blob's content and the GitHub tree listing carries only paths and sizes. A filename-only check is the one thing this release rejects, so the GitHub walk gets nothing rather than a lookalike. The gap is test-pinned.
Config key respect_cachedir_tag (default true; only an explicit false disables it) / JCODEMUNCH_RESPECT_CACHEDIR_TAG. Pruned directories are counted as cache_dir in discovery_skip_counts.
Verification
tests/test_v1_108_270.py, 31 tests. Non-vacuous against unmodified HEAD in an isolated worktree: 27 fail before the fix, and the 4 that pass on both sides are the controls.- Full suite 7501 passed, 7 skipped, 0 failed; clean-environment Python 3.13 run 7495 passed, 13 skipped, 0 failed. Both reconcile at 7508, and the delta from 1.108.269 is exactly the 31 new tests.
uv run ruff check src/clean, and all 9 CI jobs green on the released SHA.