An extensionless script with a #!/usr/bin/env ruby shebang is indexed as text only, because every language claim is path-based #298

Open
opened 2026-09-23 18:13:17 +02:00 by buildagent · 0 comments
Member

From the Ruby dogfood behind #294 (§3.3 of the report): bin/zammad-mcp-server is the project's entry point, a Ruby script with no extension. It is indexed as TEXT — symbol-blind — so project_overview's entry-point list was empty and nothing it calls is reachable from it. index_coverage reports this correctly, so it is disclosed; it is not a lie, it is a gap.

Why it is not a one-line fix

Every language claim in this index is decided by PATH, by design:

  • code_index_package::builtin::BUILTIN_CLAIMS is a static extension/filename table, and builtin_claim_parity requires it to equal what all_plugins().find(|p| p.detect(path)) answers.
  • LanguagePlugin::detect(path, head) takes the file's first bytes, but the indexer calls it as p.detect(path, &[]) (crates/indexer/src/index.rs), and no plugin reads head today.
  • Packages claim by extension only ([claims.include] extensions = [...]); the package ABI has no way to claim by content.

A shebang-sniffed claim makes a file's LANGUAGE depend on its CONTENT. That has consequences the path model never had to face:

  1. An edit can reclassify a file. Delete the shebang line and a code file becomes text; the watcher, reclassified, generation identity and every symbol id in it are involved.
  2. Claim conflicts. The builtin-vs-package conflict rule (package.claim_conflicts_builtin) is keyed on paths; a content claim needs a precedence story against a package that claims the same extensionless name.
  3. Coverage explanations. index_coverage(path) names "the rule that decided it"; a content rule needs its own, stable reason code.
  4. One rule, not a Ruby patch. #!/usr/bin/env python3, node, php and ruby are the same fact; it should be one table in lang_profile (interpreter name -> profile), read through profile_name, not per-plugin detect bodies.

What would close it

  • A shebang table (ruby, python/python3, node, php, …) mapping the interpreter to a profile, consulted ONLY for a file that no path rule claims and that has no extension.
  • The walker reads the first line (it reads the file anyway) and records the claim with its own reason code, so index_coverage can say why.
  • Reclassification on edit handled as the existing reclassified path does, with a test that removing the shebang takes the file back to text and drops its symbols.
  • Measured on the corpus (none of the seven pinned repos has one today, so a fixture is needed, per language).

Related: #294, #297.

From the Ruby dogfood behind #294 (§3.3 of the report): `bin/zammad-mcp-server` is the project's entry point, a Ruby script with no extension. It is indexed as TEXT — symbol-blind — so `project_overview`'s entry-point list was empty and nothing it calls is reachable from it. `index_coverage` reports this correctly, so it is disclosed; it is not a lie, it is a gap. ## Why it is not a one-line fix Every language claim in this index is decided by PATH, by design: * `code_index_package::builtin::BUILTIN_CLAIMS` is a static extension/filename table, and `builtin_claim_parity` requires it to equal what `all_plugins().find(|p| p.detect(path))` answers. * `LanguagePlugin::detect(path, head)` takes the file's first bytes, but the indexer calls it as `p.detect(path, &[])` (`crates/indexer/src/index.rs`), and no plugin reads `head` today. * Packages claim by extension only (`[claims.include] extensions = [...]`); the package ABI has no way to claim by content. A shebang-sniffed claim makes a file's LANGUAGE depend on its CONTENT. That has consequences the path model never had to face: 1. **An edit can reclassify a file.** Delete the shebang line and a `code` file becomes `text`; the watcher, `reclassified`, generation identity and every symbol id in it are involved. 2. **Claim conflicts.** The builtin-vs-package conflict rule (`package.claim_conflicts_builtin`) is keyed on paths; a content claim needs a precedence story against a package that claims the same extensionless name. 3. **Coverage explanations.** `index_coverage(path)` names "the rule that decided it"; a content rule needs its own, stable reason code. 4. **One rule, not a Ruby patch.** `#!/usr/bin/env python3`, `node`, `php` and `ruby` are the same fact; it should be one table in `lang_profile` (interpreter name -> profile), read through `profile_name`, not per-plugin `detect` bodies. ## What would close it * A shebang table (`ruby`, `python`/`python3`, `node`, `php`, …) mapping the interpreter to a profile, consulted ONLY for a file that no path rule claims and that has no extension. * The walker reads the first line (it reads the file anyway) and records the claim with its own reason code, so `index_coverage` can say why. * Reclassification on edit handled as the existing `reclassified` path does, with a test that removing the shebang takes the file back to `text` and drops its symbols. * Measured on the corpus (none of the seven pinned repos has one today, so a fixture is needed, per language). Related: #294, #297.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
h-dv/code-index#298
No description provided.