The runtime matcher (`ignore` in ignore-filter.ts) runs with its default
`ignorecase: true`, so the previously proposed file-suffix globs were far
broader than they read:
**/*Test.swift -> Contest.swift, Latest.swift, Backtest.swift, Protest.swift
**/*Tests.swift -> Contests.swift
**/*Spec.swift -> Inspec.swift
This is not recoverable by writing the glob more carefully -- `ignore`
compiles patterns to a RegExp with the `i` flag, so a [Tt] character class
buys nothing. Since these starter lines exist to be uncommented wholesale,
silently dropping production source is the worst available failure mode.
Replaced with exact-name directory globs (**/Tests/**/*.swift,
**/Specs/**/*.swift) which carry no false-positive risk at all and, unlike
detectDirectories(), reach nested layouts such as Modules/Feature/Tests/.
Cross-language change, called out for review: SUFFIX_DIR_GLOBS is broadened
from [".tests", ".unittests", ".integrationtests"] to ["tests", "specs"].
The dotted C# forms all end in "tests", so three entries collapse to one
with no behaviour lost. This is what recovers Xcode's MyAppTests/ and
MyAppUITests/ targets. Suffix matching is safe *here* in a way the
equivalent file glob is not: it only ever runs against directories that
exist on disk, and emits them commented-out under their real name -- a repo
with a genuine Contests/ dir sees a literal `# Contests/` line it can
decline to uncomment. Note this now also applies outside Swift projects.
Accepted gap: nested Xcode-style dirs (Modules/Feature/FeatureTests/) are
reached by neither rule. Under-matching costs tokens; over-matching drops
source. Only one of those is a correctness bug.
Tests assert on real matcher behaviour rather than emitted text alone --
the gap that let the original patterns through review.
The earlier token-savings figures are removed rather than restated: they
were measured against the file-suffix shape and do not carry over. The
directory-scoped shape still needs re-measuring.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The initial comment speculated about Xcode `<AppName>Tests/` folders
being uncaught. Measurement across 10 major Swift repos showed the
picture is the opposite of Ruby: the existing case-insensitive `tests`
dir rule is already load-bearing, and the file globs earn only ~1% on
the weighted total.
Hero: signalapp/Signal-iOS at 1% (−0.07M tok, 31 inline-leaked
*Tests.swift file hits under production modules like
SignalServiceKit/Cryptography/).
All 9 other repos measured at 0% because SPM funnels tests into
`Tests/` (dir-caught) and even Xcode-authored consumer apps nest
their unit tests under `test/`/`tests/`.
Also record why no dir rules were added:
- Xcode-style `*tests/` suffix (Scenario B) delivered ZERO
additional bytes across the sample while carrying real false-
positive risk on Contests/, Requests/, Interests/, Manifests/.
- `uitests` exact rule (Scenario C) had zero hits — the codebases
that use UI tests keep them under a nested `Tests/` folder that
the existing rule already handles.
Update the block comment so a future reader understands why Swift's
file-glob group is minimal and why they shouldn't reach for dir rules
to try to make it larger.
Quick is the Swift ecosystem's RSpec analogue — the dominant BDD-style
testing framework before Apple shipped Swift Testing (Xcode 16, 2024).
Files follow the `<Subject>Spec.swift` convention and typically live
under `<Package>Tests/` (SPM) or `<AppName>Tests/` (Xcode) alongside
XCTest files, so the dir rules do catch most of them — but small
gem-style packages and CLI tools sometimes keep `*Spec.swift` at the
`Sources/` root, which the dir rules miss.
Kept as a separate opt-in glob from `*Tests.swift` so users on
XCTest-only projects (Apple's official Swift repos, Alamofire) aren't
carrying a comment for a framework they don't use. Mirrors the split
in the Ruby group between RSpec `*_spec.rb` and Minitest `*_test.rb`.
Apple's XCTest and the newer Swift Testing framework (introduced with
Xcode 16) both use the dominant `<ClassName>Tests.swift` naming
convention — a single test file per unit-under-test. SPM already
funnels those into a `Tests/` package directory (caught by the
case-insensitive `tests` exact rule), but Xcode-authored codebases
frequently keep them in `<AppName>Tests/` folders (no leading dot,
so the C#-style suffix rule misses them) and mixed SPM/Xcode repos
sometimes colocate `*Tests.swift` next to `Sources/` files.
Add:
**/*Tests.swift — dominant Apple / Swift Testing convention
**/*Test.swift — singular variant seen in some older codebases
Kept minimal for the baseline commit; the BDD `*Spec.swift` glob
(Quick/Nimble) follows separately for opt-in symmetry with the Ruby
group's RSpec vs Minitest split.
Tests: assert Swift sub-header + both globs; extend the stable-order
invariant to place Swift after Ruby.
The PostToolUse auto-update hook read "$TOOL_INPUT" to detect git
commit/merge/cherry-pick/rebase commands, but Claude Code never sets a
TOOL_INPUT environment variable for hook processes — hook input is
delivered as JSON on stdin (fields tool_name, tool_input, cwd, ...).
The grep therefore always tested an empty string, the && chain
short-circuited, and the trailing || true swallowed the failure: the
per-commit auto-update silently never fired, on any OS.
Capture stdin into INPUT, then extract tool_input.command via a small
node -e script (matching the JSON-parsing style already used by the
SessionStart hook's node -p call, and requiring no new dependency —
jq isn't guaranteed present on Windows/Git Bash hosts). The extracted
command is piped into the same grep check as before; everything
downstream (UA_DIR resolution, config.json/knowledge-graph.json gating)
is unchanged.
Verified by piping a realistic PostToolUse JSON payload into both the
old and new hook commands: the old command produces no output (bug
reproduced), the new one prints the auto-update instruction. Also
checked git merge/cherry-pick/rebase trigger correctly, non-matching
commands and non-Bash tool payloads (no tool_input.command field) stay
silent, embedded quotes in commit messages don't break JSON parsing,
and the autoUpdate:false / missing knowledge-graph.json gates still
suppress the hook as before.
Fixes#594.
The initial comment was speculative ("individual files sometimes leak
elsewhere"). Measurement across 10 major Ruby repos showed the picture
is much stronger and closer to C++ than to Rust:
Weighted total reduction across the sample: 51% (−11.80M tokens on
92 MB of Ruby source), the highest of any language group so far.
spec/ dir rule alone drives 46 pp; file globs add the remaining 5 pp
by catching *_spec.rb in gem lib/ trees, Minitest files leaking
outside test/ in Rails engines, and helper bootstraps referenced
via require_relative.
Hero projects:
rubocop/rubocop 67% (−1.75M tok) highest percent
discourse/discourse 60% (−5.53M tok) highest absolute
ruby/ruby 59% (−3.66M tok)
rspec/rspec-rails 61% (−0.07M tok)
fastlane/fastlane 42% (−0.73M tok)
Update the block comment so a future reader lands on the right mental
model — Ruby is a big win precisely because the ecosystem clusters
tests but under a directory (`spec/`) that the starter had previously
missed entirely.
Test harness bootstrap files loaded by every test in the suite:
spec_helper.rb — RSpec baseline configuration
test_helper.rb — Minitest baseline configuration
rails_helper.rb — Rails-specific RSpec bootstrap (require rails
env + all initialisers)
These sit under `spec/` or `test/` in canonical layouts (already
caught by dir rules), but Rails engines and multi-app monorepos
sometimes reference them via `require_relative` from other paths.
Kept as separate opt-in globs so projects that share helpers with
production code paths (rare, but legal) can leave them commented.
Ruby ecosystems split between two test frameworks with different
file-naming conventions:
RSpec — `*_spec.rb`, canonically under `spec/`
(discourse, homebrew, gitlab, fastlane, rubocop)
Minitest — `*_test.rb` / `test_*.rb`, canonically under `test/`
(rails/rails, activerecord, ruby/ruby)
Both top-level dirs are now caught by EXACT_DIR_NAMES (`spec` was
added in the previous commit), but individual files sometimes leak
elsewhere — engine test files under `app/**/*_test.rb` in Rails
engines, gem repos that colocate small `*_spec.rb` beside
`lib/**/*.rb`. The file globs are what catch that shape.
Tests: assert Ruby sub-header and all three globs; extend the
stable-order invariant to place Ruby after Rust.
Ruby's second-most-common testing tool (arguably first in Rails-adjacent
projects — discourse, gitlab, homebrew all use RSpec) organises tests
under a top-level `spec/` directory rather than `test/`. The existing
list caught Minitest / Rails default `test/` but silently ingested
every `spec/**/*.rb` file for RSpec-based projects.
Add `spec` as a case-insensitive exact-name match. Kept separate from
the Ruby file-pattern group added in follow-up commits so users on
Minitest-only projects (rails/rails core, ruby/ruby) aren't affected.
The initial comment implied `tests.rs` was the load-bearing pattern.
Measurement across 10 major public Rust repos (rust-lang/rust, tokio,
cargo, ripgrep, alacritty, helix, deno, solana, foundry, polkadot-sdk)
showed the actual distribution is bimodal:
- Library-scale crates keep tests inline via `#[cfg(test)] mod`,
which no file-pattern rule can touch → group is a near-noop.
- Workspace monorepos colocate `foo_test.rs` beside `foo.rs` at
scale → `*_test.rs` alone accounts for the vast majority of hits
(232 in polkadot-sdk, 253 in rust-lang/rust even with truncated
tree).
Rewrite the block comment so a future reader lands on the right mental
model — the extracted `tests.rs` form is legitimate but rare; the win
comes from the colocated shape in workspace-heavy repos.
Most Rust benchmarks live under `benches/` (already covered by the
directory rule added earlier in this branch), so these file-name
patterns are mostly a defensive belt-and-braces for the small set of
Cargo workspaces that keep bench targets alongside library code
rather than under the canonical `benches/` tree.
Kept commented-out; contributes negligibly to the token savings for
mainstream layouts but preserves symmetry with the C++ group, which
already ships `*_benchmark.cc` and `*Benchmark.cpp`.
Measurement across 10 large public Rust repos showed the tests.rs-only
group carries almost no weight — the majority of file-glob hits come
from workspaces that colocate a `foo_test.rs` next to `foo.rs` rather
than using extracted `tests.rs` modules. In paritytech/polkadot-sdk
this shape accounts for 232 files and a 15% reduction on the analysed
budget; in rust-lang/rust it accounts for 253 files (3% reduction on
the truncated tree, i.e. a lower bound).
Add:
**/test_*.rs — pytest / unittest style (less common in Rust)
**/*_test.rs — dominant convention in Rust workspace monorepos
Kept as separate globs from `**/tests.rs` so users on projects that
follow the Rust Book pattern (single sibling tests.rs per module)
aren't forced to also strip a naming shape they never use.
Rust's dominant unit-test convention is inline `#[cfg(test)] mod tests
{ ... }` blocks that cannot be excluded by filename — anyone wanting to
strip those has to post-process the source. The one file-pattern the
Rust Book (chapter 11.3) does describe is extracting that block into a
sibling `tests.rs` file next to `mod.rs` or `lib.rs`; that extracted
form is what a `**/tests.rs` rule can catch.
Integration tests already live under `tests/` (existing dir rule) and
benchmarks under `benches/` (added in the previous commit), so the
new file-pattern group is deliberately minimal — just the one glob
the community has actually converged on.
Tests: assert the Rust sub-header + pattern emission, and extend the
stable-order invariant to place Rust after Python.
Rust's official Cargo convention is `benches/` (plural, trailing `s`),
matching the target type documented at
https://doc.rust-lang.org/cargo/reference/cargo-targets.html — every
`.rs` file under that directory compiles as a separate benchmark
binary. The existing list picked up the C++-style `bench` / `benchmark`
/ `benchmarks` variants but missed the Rust one, so Cargo workspaces
were silently getting their criterion / test::Bencher sources indexed.
Add the plural form as a case-insensitive exact-name match. Also useful
to any language that adopts the naming (some Go and Zig projects use
`benches/` too).
The initial comment implied the file-pattern rules matter for pandas /
numpy / scikit-learn / tensorflow uniformly. Measurement across 17
public Python repos showed the picture is bimodal: cluster-in-tests/
projects (django, flask, pandas, numpy, scikit-learn) see near-zero
extra savings because existing directory rules already exclude those
paths, while Google-style interleaved projects (tensorflow at 47%,
plus jax / pytorch / cpython lower down) account for essentially all
the token reduction.
Update the block comment so a future reader lands on the right mental
model when deciding whether to activate these suggestions.
conftest.py hosts pytest fixtures and hook implementations — logically
test-adjacent rather than test-under-test, but from an ingestion budget
perspective it's still test scaffolding a user analysing production code
rarely wants indexed.
Kept as a separate opt-in suggestion so users who share module-level
fixtures across production and test paths (rare, but possible) can leave
it commented and skip only the *_test.py / test_*.py globs above.
Django's per-app single-file test module — historically the tutorial's
default before pytest-django popularised the tests/ package layout — is
still ubiquitous in Django and Django-derived projects (e.g. odoo,
django/contrib/admin/tests.py).
Add **/tests.py so users who opt-in also catch that convention. Kept
distinct from **/test_*.py because it's a framework idiom, not a pytest
discovery rule.
Introduce a Python entry in TEST_PATTERN_GROUPS with the two dominant
discovery conventions:
**/test_*.py — pytest / unittest default (Django, Flask, requests, …)
**/*_test.py — Google/TensorFlow style (tensorflow, jax, some Meta libs)
Python testing tools rely on filename-based discovery, so large codebases
routinely co-locate test files with the module under test rather than
funnelling everything into a single tests/ tree — mirroring the C++
rationale for file-pattern rules introduced in #480.
Tests: assert Python sub-header + both pattern emissions and extend the
stable-order invariant to place Python after C++.
The knowledge-graph.json load was the only data fetch in App.tsx that
called res.json() without first checking res.ok, unlike the sibling
meta.json / config.json / diff-overlay.json / domain-graph.json fetches
in the same file.
When the dev server returns a 404 JSON error body (e.g. the graph file is
not found because GRAPH_DIR is unset), that error object was parsed and
passed to validateGraph(), which then failed project-metadata validation
and surfaced the misleading "Invalid knowledge graph: Missing or invalid
project metadata" instead of the real cause.
Guard res.ok and surface the server's actual error message
("No knowledge graph found. Run /understand first."), routing to the
existing .catch so the user sees an actionable message.
Refs #288, #406.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>