No reviewers
Labels
No labels
code-review
correctness
dos
performance
security
severity/high
severity/low
severity/medium
tech-debt
Kind/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
h-dv/code-index!299
Loading…
Reference in a new issue
No description provided.
Delete branch "fix-ruby-recall-294"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fixes from a live Ruby repository's review of code-index. Every item was reproduced here before any code changed.
#294 — a bare zero-argument Ruby call produced no reference at all
forbidden_response— no parentheses, arguments or receiver — is how Ruby normally calls an attribute reader, predicate or memoized helper, and it parses to the sameidentifiernode as a local read. No row was emitted, so a method called only that way showedref_count: 0,name_fallback_count: 0,name_fallback_shape_excluded: 0and noname_fallback_unmeasured: three disclosures agreeing that nothing referenced it.The fix applies Ruby's own lexical rule: a name is a local from the point an assignment, parameter, block parameter,
forvariable,rescue => e, pattern binding or named regex capture binds it. Otherwise it is a call. Identifiers hung off anERRORnode are recovery debris (thebroken.rb.rbxdecoy caught that).What the row may bind to took five measured encodings. Each was diffed bind-for-bind on ruby-sinatra and every new bind outside the call site's own class read at source.
precision_gatewas 7/7 for all five, because none of these sites is a declared decoy:foo()'s shapeparams→PatternLookAlike#params)self.fooeverywhereself.fooin method bodiesselfqualifiedShipped: a bare call is
UNCAPTUREDunless its own class defines the name in the same file; a proven call binds only throughTIER1Q_REL_SAMEFILE. No existing bind changed. Thede.h-dv.rubywasm package got the same logic, andruby_package_parityholds builtin and package equal row for row (2966 resolved each). Package 0.6.0 → 0.7.0.Corpus: ruby-sinatra refs +2091, resolved +105; rust-ripgrep refs +8 (a Homebrew formula). The +11.2% cost was attributed per statement before blessing: it tracks the rows, nothing disproportionate. Full account:
_prdoc/records/I070-ruby-bare-calls.md.#295 —
check_renamerefusedfoo?and its text scan went darkThe index stores
issued_for_us?with its?, then refused to rename to such a name, and — the worse half — switched off the text channel that catches unresolved call sites. That left it off exactly where Ruby resolution is weakest. Now one table,lang_profile::IDENTIFIER_SUFFIXES, is read through the profile gate, so the packaged Ruby id gets it too (the #112 trap). A Rust symbol still refusesvalid?, with a message naming its language.Minified code ranked above the project's own symbol
search_symbols("error")put minifiedpublic/assets/*.jscopies above the project'sTools.error. Rather than a path list, this uses the structural fact already stored: no human-written declaration in the seven corpus repos or this one sits past column 105, and minified code declares in the thousands. Declarations past column 200 rank below the rest. It demotes and never hides.Precision is not recall
"1.00 precision where ripgrep scored 0.52" is true and read as general superiority. All five copies now say that a reference the index did not record or resolve is simply absent from callers, impact and dead-code answers.
Deferred, with measurements
Auth.resolve_user: the obvious encoding measured ~60 phantoms via tier 40's package anchoring.autoload: the one-line fix measured 17 phantoms through segment-run require matching. Reverted.Gates
fmt, clippy, rustdoc; 375 workspace suites / 4017 tests; 840 e2e; guest gates under
--locked; corpus ratchet; precision 7/7;ruby_package_parity; the version-bump suites. Mutations run on final code for every new test.Closes #294, #295.
🤖 Generated with Claude Code
https://claude.ai/code/session_0126PDDLB4wNHxKXvWM1VNmu
`forbidden_response` -- no parentheses, no arguments, no receiver -- is how Ruby normally calls an attribute reader, predicate or memoized helper. It parses to the same `identifier` node as a local read, and the extractor emitted NO ROW for it. A method called only that way then carried `ref_count: 0`, `name_fallback_count: 0`, `name_fallback_shape_excluded: 0` and no `name_fallback_unmeasured`: three disclosures agreeing nothing referenced a symbol with call sites. Reported from a live Ruby repository and reproduced before any change. THE FIX IS RUBY'S OWN RULE, APPLIED LEXICALLY. A name is a local from the point an assignment, parameter, block parameter, `for` variable, `rescue => e`, pattern binding or named regex capture binds it; `def`, `class`, `module` and `class << self` are hard scopes, blocks and lambdas soft. `identifier_role` reads which slot an identifier sits in -- every slot measured against a tree-sitter-ruby parse -- and a value that is not a bound local becomes a row. Implicit block params (`it`, `_1`) are not calls; an identifier whose parent is `ERROR` is recovery debris (the `broken.rb.rbx` decoy caught `method_call bad` / `method_call end`). WHAT THE ROW MAY BIND TO TOOK FIVE MEASURED ENCODINGS. Each was diffed bind-for-bind against the clean build on ruby-sinatra, every bind outside the call site's own class read at source. `precision_gate` was 7/7 for all five -- none of these sites is a declared decoy: foo()'s shape (unqualified CALL) 767 new, 59+ wrong (tier 2 bound route-block `params` to `PatternLookAlike#params`) `self.foo` everywhere 538 new, wrong via tier 3 qualified by container path 356 new, 85 wrong: tier 1Q reads a qualifier as a MODULE path `self.foo` in method bodies 114 new, 12 wrong: an inherited GEM method (`Rack::Response#headers`) was taken as the unique project one proven, relative-`self` qualified 105 new, 3 via documented tier 1b Shipped: every bare call is `UNCAPTURED` (refused by tiers 1a/2/3) unless self's own class DEFINES the name in the same file -- decided after the whole file -- and a proven call is qualified by the relative path `self`, binding only through `TIER1Q_REL_SAMEFILE`. Of the 105: 101 own-class (six sampled at source), 1 via `include Helpers`, 3 through the I023 same-directory/file-key policy (#194). No existing bind changed. A path qualifier rather than a receiver because the package ABI can only say `self` without a source token via `TAG_REF_REL_QUALIFIER`, which `validate` refuses unless the ref is qualified. THE PACKAGE MOVED WITH IT. `ruby_package_parity` holds builtin and the `de.h-dv.ruby` wasm guest equal row for row: the guest got the same logic in fixed buffers (`LOCALS`, `BARE`), 19 kinds appended to its kind table (`KIND_TABLE_DIGEST` unchanged -- it is the grammar's), extractor rebuilt after first reproducing the committed 807e0485... byte for byte. Both legs 2966 resolved. Package 0.6.0 -> 0.7.0, both digests moved. CORPUS: ruby-sinatra refs +2091, resolved +105; rust-ripgrep refs +8 (`pkg/brew/ripgrep-bin.rb`'s Homebrew DSL, resolved unchanged). Cost fired at +11.2% vm_step and was attributed per statement against the clean tree before blessing: it tracks the rows (INSERT INTO refs 32% of the delta), nothing disproportionate. SKILL.md now says both counters count ROWS, not source, and names field destructuring (#125) as a use still unrecorded. Constant-receiver calls (`Auth.resolve_user`) stay unresolved-and-disclosed: the naive encoding measured ~60 phantoms via tier 40's package anchoring -- #296. Mutations run on the final code, six, each RED on its named test. Gates: fmt, clippy, rustdoc, 374 workspace suites (the two re-pinned abi_projection counts are the 5 new fixture rows, each read), 840 e2e, guest gates, corpus ratchet, precision 7/7. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0126PDDLB4wNHxKXvWM1VNmu`issued_for_us?` is an ordinary Ruby method name, and the index stores it with the `?`. `check_rename` refused to rename TO such a name ("new_name must be a plain identifier"), and -- the worse half -- its text scan went DARK on one, because both used the language-blind `[A-Za-z0-9_$]` set. The text channel is what catches the call sites the index did not resolve, so it was off on exactly the methods where Ruby resolution is weakest, and `clean` became unreachable for every predicate, bang and setter method. ONE TABLE, READ THROUGH THE PROFILE GATE. `lang_profile:: IDENTIFIER_SUFFIXES` grants Ruby a single trailing `?`, `!` or `=`, and `is_plain_identifier(lang, name)` reads it through `profile_name` -- so the packaged `de.h-dv.ruby/ruby` gets Ruby's answer, which a bare `lang == "ruby"` would silently deny (the #112 trap). Every language without a row is unchanged. * The MCP layer checks SHAPE first (an identifier in SOME language, before any index call), then asks the symbol's own language. A Rust symbol still refuses `valid?`, now with a message naming the language and what it allows. * `text_occurrences` accepts the same names. `?`, `!` and `=` are literal inside an FTS5 phrase; only `"` is special, and it is still refused. Verified on the reporter's case: `issued_for_us?` -> `mcp_client_token?` finds both edit sites and every evidence channel reads `clear`, the text channel included. Mutations run, each RED on its named test: the bare charset gate restored in `text_occurrences`; the Ruby row dropped; the table read by bare language id instead of through the profile gate. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0126PDDLB4wNHxKXvWM1VNmuReported from a live Rails repository: `search_symbols("error")` ranked `error` functions from minified upstream assets (`public/assets/*.js`) above the project's own `Tools.error`, which missed the top five of 95. Exact matches are ordered by `ref_count`, and minified code's internal calls resolve densely inside one enormous line -- so the vendored copy out-counts the project's. A PATH RULE WOULD BE A LIST OF CASES (`assets/`, `.min.js`, `vendor/`). The structural fact is already stored: minified code declares symbols at huge COLUMN offsets, because the file is a handful of very long lines, and human-written code does not. MEASURED, max `start_col` over every non-import symbol: this repository 49 (19,187 symbols), cs-dapper 105, ruby-sinatra 79, python-flask 66, php-guzzle 35, ts-zod 33, js-express 18 -- none past 200 anywhere. `MINIFIED_DECL_COL = 200` is twice the widest real declaration measured. The tier sits UNDER exact match and above everything else, in both orderings (the SQL fast path and its Rust mirror). It demotes and never hides: the row stays in `results`, in `total`, and on a later page through `next_cursor`. Registered in `bounding_site_registry` as `Intentional` for exactly that reason. `search_ranking_minified_e2e` reproduces the report against a real daemon: the project's `error` with no refs, a minified copy past column 400 called three times. Two preconditions pin that the fixture really is the reported shape. MUTATION (run): drop the tier from both orderings -- RED, the minified `error` (col 421, ref_count 3) ranks first. Part of the same ranking complaint is downstream of #296: the project's `Tools.error` had `ref_count: 0` because its constant-receiver calls do not resolve. Ranking is only as good as resolution. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0126PDDLB4wNHxKXvWM1VNmuA Ruby user's review of this tool put it plainly: the index's precision held, but it missed most callers, and grep was complete. Our own line -- "a measured 1.00 precision where ripgrep scored 0.52" -- is true and reads as general superiority, which is the part that was not. Every copy now says what the figure measures and what it does not: a call the index did not record or resolve is simply ABSENT from callers, impact and dead-code answers, so where a language's `resolution` is low, confirm those with `search_text` before acting on them. * crates/core/src/skills/code-index/SKILL.md the served skill * crates/mcp-server/src/server.rs the MCP instructions * crates/cli/src/rules.rs the `rules` template, and this repository's own generated rule files refreshed from it * AGENTS.md hand-maintained, no marker block, so `rules` leaves it alone -- updated by hand Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0126PDDLB4wNHxKXvWM1VNmucheck_renamerefuses a Ruby predicate/bang name it indexes happily:new_name must be a plain identifierrejectsfoo?#295