Skip to content

feat(guard): guard Rust, and stop conflating packages in a workspace - #787

Merged
mick-gsk merged 1 commit into
mainfrom
feat/guard-rust
Jul 29, 2026
Merged

mick-gsk merged 1 commit into
mainfrom
feat/guard-rust

Conversation

@mick-gsk

Copy link
Copy Markdown
Owner

Five languages in one index. Rust follows Go's shape — but the interesting part is what the noise gate rejected on the way.

The gate did its job

The first version measured 23.6 % on ripgrep, over the 10 % budget, and measure_noise.py exited non-zero.

The cause was not Rust. ripgrep is a Cargo workspace: crates/cli and crates/globset both define escape. Those are two published libraries with their own namespaces, and no author would call that duplication.

The index had no idea packages existed — so every monorepo had been getting this wrong, in every language, since the beginning. Node workspaces, Go modules and Python source trees split exactly the same way.

files.package_root now records the nearest ancestor holding a Cargo.toml, package.json, go.mod, pyproject.toml or setup.py, and duplicates are reported only within one package. Observed rather than configured, like every other fact this index keeps.

before after
ripgrep 23.6 % 7.3 %
serde 4.8 % 2.9 %

No regression elsewhere: requests 0.0 %, gin 2.0 %, flask 4.8 %, axios 5.6 %.

A schema bump that had to fix itself

Schema goes to 2. That exposed a second problem: an index written by an older schema is unusable, and nothing would ever replace it — the file exists, so the missing-index path never runs, and the guard would sit silent forever behind an index it cannot read. SessionStart now treats an unusable schema as stale.

Rust specifics

  • pub fn, struct, enum, trait, union, type at column zero.
  • Methods inside impl blocks are excluded: new, fmt and from sit on half the types in any crate — the same rule as Go and for the same measured reason.
  • use crate::db::client becomes db/client; super:: and self:: become the relative forms Python and TypeScript already emit. use crate::api::{routes, schemas} keeps its prefix, because the braces name children of one directory.
  • mod x; is not an import — it declares a child module in the same directory.
  • suffix_to_dir had to learn a second shape: Go carries the full module path and the repository holds its tail, while Rust drops the head (crate is src/), so the repository holds the tail of the specifier. Ambiguous matches are discarded rather than guessed.

config and options join the stopwords

Not corpus fitting. config was the loudest false positive in axios, in JavaScript, before Rust existed here — and it came back as the dominant one in ripgrep, where nearly every module keeps a private Config.

Verification

148 guard tests (14 new), all nine gates, six repositories across five languages within the noise budget, ruff and mypy clean, markdownlint clean.

🤖 Generated with Claude Code

Five languages in one index now. Rust follows Go's shape: `pub fn` and
`struct`/`enum`/`trait` at column zero, methods inside `impl` blocks excluded
because `new`, `fmt` and `from` sit on half the types in any crate. `use`
clauses become the specifiers the resolver already speaks — `crate::db::client`
as `db/client`, `super::` and `self::` as the relative forms Python and
TypeScript already produce. `mod x;` is not an import: it declares a child in
the same directory.

The noise gate rejected the first version at 23.6 % on ripgrep, which is what
it exists for.

The cause was not Rust. ripgrep is a Cargo workspace, and `crates/cli` and
`crates/globset` both define `escape` — two published libraries with their own
namespaces, which no author would call duplication. The index had no idea
packages existed, so every monorepo had been getting this wrong in every
language since the beginning.

`files.package_root` now records the nearest ancestor holding a `Cargo.toml`,
`package.json`, `go.mod`, `pyproject.toml` or `setup.py`, and duplicates are
only reported within one package. Observed rather than configured, like every
other fact this index keeps. Schema goes to 2, and SessionStart now treats an
unusable schema as stale — otherwise an upgraded guard would sit silent forever
behind an index it cannot read, since the file exists and the missing-index
path never runs.

`config` and `options` join the stopwords. Not corpus fitting: `config` was the
loudest false positive in axios months of JavaScript ago and again in ripgrep,
where nearly every Rust module keeps its own private `Config`.

    ripgrep  23.6 % -> 7.3 %      serde  4.8 % -> 2.9 %

No regression elsewhere: requests 0.0 %, gin 2.0 %, flask 4.8 %, axios 5.6 %.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@mick-gsk
mick-gsk merged commit eecc06b into main Jul 29, 2026
12 of 37 checks passed
@mick-gsk
mick-gsk deleted the feat/guard-rust branch July 29, 2026 23:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants