Skip to content

feat: surface file:line anchors for symbols (find, grep --symbols, show, for/context) - #16

Merged
scodge-24 merged 6 commits into
mainfrom
feat/symbol-surfacing
Jun 5, 2026
Merged

scodge-24 merged 6 commits into
mainfrom
feat/symbol-surfacing

Conversation

@scodge-24

Copy link
Copy Markdown
Owner

Four focused, additive enhancements that let agents get a concrete file:line
anchor for a symbol straight from the slice CLI, instead of re-deriving spans by
hand after a grep hit. Each is an isolated commit; no breaking changes.

Changes

  • find multi-term matching — a multi-word query now matches per-term across
    a slice's description/abstractions/body/tags, rather than as one rigid substring.
  • grep --symbols (opt-in) — annotates each hit with its enclosing
    function/class line span via a hand-rolled, reject-on-ambiguity heuristic
    (decorators, nested functions, multi-line signatures, tab indentation, and
    braces-in-strings are left unannotated rather than guessed). New symbols.rs
    module behind two small functions, so an AST backend can replace it later
    without touching callers.
  • show abstraction anchoring — abstractions are annotated with their
    definition file:line in human output.
  • for / context test resolution — test files resolve to their owning
    slices via verification links.

Test plan

  • cargo fmt --check — clean
  • cargo clippy --all-targets -- -D warnings — clean
  • cargo test — 56 passed, 0 failed (incl. new symbols.rs units + CLI snapshots)

🤖 Generated with Claude Code

test and others added 4 commits June 5, 2026 16:41
slice find matched the whole needle as one literal substring, so any
multi-word query (e.g. "Variable setitem values") silently returned no
matches. Split the needle into whitespace terms and require every term to
hit some field (per-term AND). Empty/whitespace needle preserves the prior
match-all. Per-field lowercase is cached once per doc, not once per term.
On an empty multi-term result, name the term(s) that hit nothing anywhere.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…spans

slice grep returned file:line:text with no extent, so an agent had to guess
a read window around each hit. Add a new src/symbols.rs heuristic (hand-rolled
std scanning, no parser dependency) exposing enclosing_span/definition_span,
and a --symbols flag on grep that appends a stable, uncolored
'\t[span <Name> <start>-<end> approx]' suffix per hit (via rg --json, so the
path/line come structurally).

The flag is opt-in: the default streaming path, exit codes (0/1/2), and stderr
are byte-for-byte unchanged. The heuristic is reject-on-ambiguity — decorators,
nested functions, multi-line signatures, tab indent, and braces in
strings/comments yield no span rather than a wrong one (a wrong span is worse
than none on this surface).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
slice show listed abstractions by name only, so an agent that wanted the
definition had to grep for it (the card already named the symbol). Resolve
each abstraction's symbol to its definition site via symbols::definition_span
and append a dim '(path:line)' in the human render. Reuses normalize_abstraction
(now pub(crate)) so the name is parsed exactly as check does.

Human output only: the --json contract is unchanged (no new field), and a
symbol defined in zero or multiple of the slice's files is left unannotated
rather than guessed. Scoped to show; context has no abstractions block to
annotate, so it is left unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
slice for <testfile> returned 'no owning slice' even when the file was a
Verification target of a slice, so test files (legitimate context) were
navigationally unreachable. Add a verification-link fallback for for/context:
when no files: owner matches, resolve via the slice's Verification refs.

Surgical and bounded: owners_for_path is untouched (affected-docs and
staleness are unaffected); the fallback is a separate helper
(verification_associations) reusing parse_verification (now pub(crate)). It is
human-output only — the --json contract is unchanged. Verification refs only;
exclusions: is a disown signal, not ownership. Empty result now reports
'no slice association for: X (checked files and verification links)'.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 5, 2026 15:43

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds opt-in symbol anchoring and improved slice resolution behaviors so CLI users (and agents) can get concrete file:line anchors and better for/context results without re-deriving spans manually.

Changes:

  • Update find to support multi-term AND matching across slice fields, with improved “unmatched term” diagnostics.
  • Add grep --symbols (opt-in) and a new symbols module to heuristically annotate grep hits with enclosing symbol spans.
  • Annotate show abstractions with (path:line) in human output and add a human-only verification-link fallback for for/context when no files: owner matches.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
rust/slice-rs/tests/cli.rs Adds CLI tests covering multi-term find, grep --symbols, abstraction location rendering, and verification-link fallback behavior.
rust/slice-rs/src/symbols.rs Introduces heuristic symbol span detection utilities for Python and brace-based languages.
rust/slice-rs/src/lib.rs Exposes the new symbols module.
rust/slice-rs/src/commands.rs Implements multi-term find, grep --symbols output, abstraction (path:line) rendering, and verification-link fallback for for/context.
rust/slice-rs/src/cli.rs Adds the --symbols flag and help text for slice grep.
rust/slice-rs/src/check.rs Makes verification parsing and abstraction normalization reusable within the crate.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread rust/slice-rs/src/symbols.rs Outdated
Comment on lines +244 to +246
if line_has_string_or_comment(line) {
return None; // a brace could be inside a string/comment → ambiguous
}
Comment thread rust/slice-rs/src/commands.rs Outdated
Comment on lines +858 to +874
write!(lock, "{path}:{lineno}:{text}")?;
if let Some(lang) = crate::symbols::lang_for_path(path) {
let content = file_cache
.entry(path.to_owned())
.or_insert_with(|| std::fs::read_to_string(ctx.repo_root().join(path)).ok());
if let Some(content) = content
&& let Some(sym) = crate::symbols::enclosing_span(content, lineno, lang)
{
write!(
lock,
"\t[span {} {}-{} approx]",
sym.name, sym.start, sym.end
)?;
}
}
writeln!(lock)?;
}
Comment thread rust/slice-rs/src/commands.rs Outdated
Comment on lines +251 to +252
let file = reference.split("::").next().unwrap_or(&reference).trim();
matches_path(&normalized, file)
Comment thread rust/slice-rs/src/commands.rs Outdated
Comment on lines +427 to +432
let ids: Vec<String> = verification_associations(ctx, selector)?
.into_iter()
.map(|(slice_id, _)| slice_id)
.collect();
targets = docs.iter().filter(|d| ids.contains(&d.slice_id)).collect();
if targets.is_empty() {
Comment thread rust/slice-rs/src/commands.rs Outdated
Comment on lines +1858 to +1866
for pattern in files {
for rel in expand_literal_or_existing(pattern, ctx) {
let Some(lang) = crate::symbols::lang_for_path(&rel) else {
continue;
};
let Ok(content) = std::fs::read_to_string(repo_join(ctx, &rel)) else {
continue;
};
if let Some((start, _)) = crate::symbols::definition_span(&content, &name, lang) {
@greptile-apps

greptile-apps Bot commented Jun 5, 2026 •

Copy link
Copy Markdown

Greptile Summary

This PR adds four related features to the slice CLI: multi-term find matching (splits the query on whitespace and ANDs each term across fields), grep --symbols (annotates each hit with its enclosing function/class span via a conservative hand-rolled heuristic), show abstraction anchoring (resolves each abstraction to its file:line definition site), and for/context verification fallback (test files resolve to their owning slices through Verification links rather than files: ownership).

  • symbols.rs is a new module exporting enclosing_span and definition_span behind a two-language heuristic (Python indentation-based, brace-counting for C/Rust/Go/TS/JS); the previously-flagged early-exit and Rust-lifetime bugs are fixed and regression-tested.
  • commands.rs wires grep_with_symbols, verification_associations, print_abstractions/abstraction_location, and the expanded find_matches multi-term logic; JSON contracts are deliberately unchanged for all new fallback paths.
  • check.rs promotes parse_verification and normalize_abstraction to pub(crate) for reuse.

Confidence Score: 4/5

The verification fallback, find multi-term, and show abstraction anchoring are correct and safe; the grep --symbols and show abstraction annotation features work as advertised for Python but are silently ineffective for most real Rust code because pub fn, pub(crate) fn, and async fn are never matched by brace_def_name.

brace_def_name does not strip visibility or qualifier prefixes before matching keywords — a Rust function declared as pub fn handle or pub async fn fetch falls through all keyword checks and returns None. Neither grep --symbols annotations nor show abstraction file:line locations appear for those symbols, even though Rust is listed as supported. All tests pass because the fixtures use Python source files, masking the gap entirely.

rust/slice-rs/src/symbols.rs — specifically brace_def_name and the BRACE_KEYWORDS constant

Important Files Changed

Filename Overview
rust/slice-rs/src/symbols.rs New heuristic symbol-span module. Previously-flagged early-exit and Rust-lifetime bugs are fixed and tested. However, brace_def_name does not strip visibility/qualifier prefixes, so most real Rust library functions (pub fn, pub(crate) fn, etc.) are silently unannotated.
rust/slice-rs/src/commands.rs Core command changes look correct. grep_with_symbols uses rg --json for structured output, caches file reads per path, and handles BrokenPipe gracefully. verification_associations and its callers correctly keep JSON contracts unchanged. find_matches multi-term logic is sound.
rust/slice-rs/src/check.rs Minimal change: parse_verification and normalize_abstraction promoted to pub(crate). No behavioral change.
rust/slice-rs/src/cli.rs Adds --symbols flag to grep with accurate after_help text. CLI wiring is clean.
rust/slice-rs/src/lib.rs Adds pub mod symbols declaration. Trivial change.
rust/slice-rs/tests/cli.rs New tests cover grep --symbols (Python defs), for/context verification fallback, and multi-term find. No test exercises the Rust/brace path with visibility modifiers, leaving that gap untested.

Fix All in Claude Code Fix All in Codex

Reviews (2): Last reviewed commit: "fix(commands): broken-pipe, path-norm, s..." | Re-trigger Greptile

Comment thread rust/slice-rs/src/symbols.rs Resolved
Comment thread rust/slice-rs/src/symbols.rs Resolved
test and others added 2 commits June 5, 2026 17:09
…times

Two correctness fixes to the brace-language span heuristic, from PR review:

- brace_enclosing aborted the backward scan on the first preceding def (via `?`
  and `return None`), so a line sitting *after* a nested function but still inside
  the outer one got no span instead of the outer's. It now skips non-enclosing /
  unparseable defs and keeps climbing to the real encloser.
- The brace-hazard check rejected any line containing a quote/comment marker, even
  braceless ones, and treated a bare `'` as a string — which nuked annotation for
  most Rust fns (lifetimes like `&'a T`). It now only fires when a line actually
  carries a brace, and treats single quotes as a hazard only as `'{'`/`'}'`.

Adds regression tests for all three cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ions

Four PR-review fixes, all preserving existing JSON contracts:

- grep --symbols: a closed consumer (`… | head`) surfaced BrokenPipe as exit 2;
  now treated as a clean early exit (Ok(0)), matching rg's behavior.
- verification_associations: normalize the verification ref's file path before
  matching, the same way the query path is, so `./`-style forms line up.
- context verification fallback: look up slice ids via FxHashSet, not Vec::contains.
- abstraction_location: read each slice file once per invocation instead of once
  per abstraction; the dedup also fixes a latent false-ambiguity when one file
  matched two patterns.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@scodge-24

Copy link
Copy Markdown
Owner Author

Thanks for the reviews — all 7 points addressed in 7322961 (symbols) + fb3f523 (commands). fmt + clippy clean; 47 lib + 56 CLI tests green (3 new regression tests). JSON contracts unchanged throughout.

symbols.rs

  • Greptile P1 — brace_enclosing early exit. The backward scan no longer aborts on the first preceding def; it skips non-enclosing / unparseable defs and keeps climbing to the real encloser. Test: brace_climbs_to_outer_after_nested_fn.
  • Greptile P2 + Copilot (over-rejection in brace_span_from_def). Unified into one fix: the brace-hazard check now only fires when a line actually carries a {/}, and treats single quotes as a hazard only as '{'/'}'. Rust lifetimes (&'a T) and braceless string/comment lines no longer suppress annotation. Tests: brace_rust_lifetime_on_def_line_is_ok, brace_braceless_string_line_does_not_suppress.

commands.rs

  • BrokenPipe (grep --symbols … | head) → clean Ok(0) early exit instead of exit 2, matching rg.
  • verification_associations normalizes the verification ref's file path before matches_path.
  • context verification fallback uses FxHashSet instead of Vec::contains.
  • abstraction_location reads each slice file once per invocation rather than once per abstraction; the dedup also fixes a latent false-ambiguity when one file matched two patterns.

@scodge-24
scodge-24 merged commit 8faf772 into main Jun 5, 2026
1 check passed
@scodge-24
scodge-24 deleted the feat/symbol-surfacing branch June 5, 2026 16:19
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