Skip to content

feat(stella-graph): the code-graph walk honors the repository's own ignore rules (#2360) - #2366

Merged
macanderson merged 4 commits into
mainfrom
fix/2360-graph-walk-gitignore
Aug 8, 2026
Merged

feat(stella-graph): the code-graph walk honors the repository's own ignore rules (#2360)#2366
macanderson merged 4 commits into
mainfrom
fix/2360-graph-walk-gitignore

Conversation

@macanderson

@macanderson macanderson commented Aug 8, 2026

Copy link
Copy Markdown
Owner

What & why

Closes #2360.

crates/stella-graph/src/walk.rs has carried this in its own module doc for two releases:

Documented gap (task brief, ignore-discipline clause): custom .gitignore patterns (per-directory ignore files, negations, glob rules) are not honored — only the built-in deny-list is.

So a generated directory whose name the deny-list has never heard of — generated-sdk/, a sibling checkout, anything project-specific — was indexed into the code graph and paid for on every recall. #2344 closed the same gap for the workspace probe; this closes it for the index, and makes the two share one answer instead of two approximations.

The design: ask git, don't reimplement it

New stella-graph::workspace_ignore resolves the rules once per walk with git ls-files -z --others --ignored --directory --exclude-standard. Per-directory ignore files, negations, globs, .git/info/exclude, and the user's global excludesfile therefore behave exactly as they do at the command line — because the answer is git's. A hand-rolled matcher would be a second ignore engine to keep correct forever, and its failure mode is silent: a file quietly indexed, or quietly not.

Where it lives, and why that is not arbitrary. stella-tools already depends on stella-graph and not the reverse, so putting the resolver here lets one implementation serve both walkers with no new crate and no new dependency edge — and it costs stella-graph nothing but std. The alternative considered and rejected was pulling ripgrep's ignore crate: it would give this crate a dependency and leave stella-tools on a different mechanism, so the two could disagree about the same tree. stella-tools' private copy from #2344 is deleted in this PR.

The deny-list survives beside the rules, not under them. They answer different questions: the rules say what this repository does not track, the deny-list says what is never worth parsing. A Terminal-Bench task directory is not a repository, so it has no rules to consult — and there the deny-list is the only thing keeping a vendored bundle out of the index. Witnessed both ways.

The watcher resolves once at construction, not per event. The relevance filter runs on every saved file; resolving per call would be one git invocation per write. The window that opens is a .gitignore edited while a session is live, whose new rules are not seen until the next full index pass — one wasted re-index, never a wrong row, and the same direction every other bound in this subsystem fails.

A defect #2344 shipped, fixed here

HostDataIsolation::ProcessFree promises that the registry "omit[s] every built-in tool that launches a child process". #2344's probe spawned git unconditionally to resolve ignore rules — including under that attestation. It now resolves to IgnorePolicy::WalkAll whenever the registry is process-free, whatever the ignore_gitignore setting says. Losing the filter there costs walk time, which MAX_RECORDED_TOUCHES already bounds; honoring the setting instead would cost the guarantee.

This is the "a field's second consumer arms what the first never checked" shape: process_free was consulted at construction for tools and for exploration coverage, and the probe's new subprocess simply never asked.

The witness

  • This PR includes witness tests (fail on main, pass here)
Test File Proves
a_repositorys_own_ignore_rules_are_honored_including_globs_and_negations stella-graph/src/walk.rs the closed gap — an ignored dir, a glob, and a negation re-admitting one file
the_deny_list_still_applies_when_there_are_no_rules_to_consult stella-graph/src/walk.rs the non-repository posture is unchanged
a_watcher_event_for_an_ignored_path_is_not_relevant stella-graph/src/walk.rs live re-index honors the same answer
a_repositorys_own_rules_are_resolved_including_a_collapsed_directory stella-graph/src/workspace_ignore.rs --directory collapsing, and excludes vs excludes_dir
a_non_repository_ignores_nothing_even_inside_a_repository stella-graph/src/workspace_ignore.rs the repo-root gate: a $HOME dotfiles repo ignoring * cannot blind a scratch dir under it
a_process_free_registry_never_consults_the_repositorys_ignore_rules stella-tools/src/registry/tests/file_change.rs the isolation fix, with a control asserting the same option is honored outside isolation

The pre-existing dist_standalone_and_next_directories_are_never_walked is unchanged and still green — that is the non-repository posture stated as a test that predates this PR.

God-file accounting

registry.rs sat at its exact ratchet ceiling again, so the process-free resolution went to RegistryOptions::effective_probe_ignore_policy in registry/options.rs and the accessor to registry/turn_probe.rs. registry.rs nets −7; shell_touch.rs nets −42 from deleting the duplicated resolver. file-size is green and reports "none grew by this change".

Docs

crates/stella-graph/README.md's "Gotchas" entry claimed the gap; it now describes the two-filter arrangement. The .gitattributes half of that entry is genuinely still unresolved and is now stated separately rather than bundled with a gap that is closed.

The gate

Not run locally — a Terminal-Bench match is executing on this machine (harbor run … sqlite-with-gcov) and a workspace build would contend for CPU, which is how a trial acquires a false timeout. make guards-fast is green, including file-size, god-files, module-reachability and fmt --check. The compile tiers are deliberately CI's.

Opened as a draft on purpose: it is unverified until CI compiles it, and this repository merges PRs fast. Marking it ready is the signal that the build is green.

  • cargo fmt --check · [ ] cargo clippy --workspace --all-targets -- -D warnings · [ ] cargo test --workspace
  • Docs updated where behavior changed

Depends on

#2365 unbreaks main (a public item intra-doc-linking a private one, landed by #2344). Until that merges, this PR is red for a reason that has nothing to do with it. This branch already carries the equivalent fix, so expect a one-line conflict on rebase.

Ground-rule check

  • No I/O added to stella-core; no new dependencies in any crate
  • No new outbound network calls
  • No new cross-boundary types (WorkspaceIgnore is internal to the tool/graph layer, not a protocol type)

Nothing left behind

  • .gitattributes per-directory merging remains unresolved and is now named on its own in the README gotcha rather than hidden inside a gap that is closed. It is pre-existing, unrelated to the walk, and already visible to a reader of that section.

Summary by Sourcery

Unify and harden workspace ignore handling by introducing a shared git-backed WorkspaceIgnore resolver used by both the code-graph walk and workspace probes, ensuring repository .gitignore rules are honored alongside the existing deny-list while preserving process-free isolation guarantees.

New Features:

  • Honor repository-specific .gitignore rules when walking and indexing the code graph, including per-directory files, globs, negations, and standard git exclude sources.
  • Expose a shared WorkspaceIgnore resolver in stella-graph for reuse by both the code-graph walk and stella-tools' workspace probe.

Bug Fixes:

  • Ensure process-free tool registries never spawn git by forcing an unfiltered walk policy regardless of the configured ignore setting, restoring HostDataIsolation::ProcessFree guarantees.

Enhancements:

  • Combine repository ignore rules with the existing deny-list so non-repository workspaces still avoid indexing vendored or build output while repositories respect their own tracked/untracked state.
  • Apply repository ignore rules to filesystem watcher events so live re-indexing uses the same exclusion semantics as full walks.
  • Refactor workspace probe ignore handling to use WorkspaceIgnore instead of an ad hoc path set, simplifying diff logic and making ignore behavior consistent across tools.

Documentation:

  • Update stella-graph README gotchas to describe the new dual-filter behavior for ignores and to clarify that .gitattributes handling remains root-level only.

Tests:

  • Add integration tests validating that repository ignore rules, globs, and negations are honored by the index and watcher, that the deny-list still applies in non-repository workspaces, that WorkspaceIgnore resolution behavior is correct, and that process-free registries use an unfiltered ignore policy.

@vercel

vercel Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
stella-cli-docs Ignored Ignored Preview Aug 8, 2026 11:29pm

@sourcery-ai

sourcery-ai Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

This PR centralizes repository ignore resolution in a new stella-graph::workspace_ignore module that shells out to git once per walk, wires it into both the code-graph index walk and the stella-tools workspace probe (including watcher events), preserves the deny-list for non-repo workspaces, and fixes process-free registry behavior so it never spawns git despite IgnorePolicy settings.

Sequence diagram for process-free registry and ignore resolution

sequenceDiagram
    actor Host
    participant RegistryOptions
    participant ToolRegistry
    participant WorkspaceProbe
    participant WorkspaceIgnore
    participant git

    Host->>RegistryOptions: configure probe_ignore_policy
    Host->>ToolRegistry: new(process_free, options)
    ToolRegistry->>RegistryOptions: effective_probe_ignore_policy(process_free)
    RegistryOptions-->>ToolRegistry: IgnorePolicy (WalkAll if process_free)
    Note over ToolRegistry,RegistryOptions: process_free forces WalkAll, no git

    Host->>WorkspaceProbe: capture_with(root, ToolRegistry.probe_ignore_policy())
    WorkspaceProbe->>WorkspaceIgnore: resolve(root)
    alt workspace_is_repo and not process_free
        WorkspaceIgnore->>git: ls-files -z --others --ignored --directory --exclude-standard
        git-->>WorkspaceIgnore: ignored paths
    else non_repo or process_free
        WorkspaceIgnore-->>WorkspaceProbe: none()
    end

    WorkspaceProbe-->>Host: WorkspaceProbe snapshot with ignored: WorkspaceIgnore
Loading

File-Level Changes

Change Details Files
Introduce WorkspaceIgnore in stella-graph to resolve and query git-based ignore rules for a workspace.
  • Add workspace_ignore.rs implementing WorkspaceIgnore, using git ls-files --others --ignored --directory --exclude-standard once per resolution.
  • Provide helpers excludes, excludes_dir, is_empty, and none for workspace-relative path filtering.
  • Gate resolution on the presence of a .git directory at the workspace root, falling back to an empty rule set on any failure.
  • Add unit tests covering collapsed directories, ancestor-repo isolation, and the none behavior.
crates/stella-graph/src/workspace_ignore.rs
Wire WorkspaceIgnore into the code-graph index walk and live watcher so repository ignore rules are honored while keeping the deny-list semantics.
  • Update walk.rs docs to describe the closed ignore-gap and the two-filter arrangement (git rules plus deny-list).
  • Thread WorkspaceIgnore into rel_is_ignored and the watcher relevance filter, resolving rules once at watch construction.
  • Refactor walk_indexable into walk_indexable_with to accept a pre-resolved WorkspaceIgnore and use it to prune dirs and files via excludes/excludes_dir.
  • Add tests ensuring gitignore rules (including globs and negations) are honored, deny-list still applies in non-repo workspaces, and watcher events for ignored paths are filtered out.
crates/stella-graph/src/walk.rs
crates/stella-graph/src/watch.rs
Refactor the stella-tools workspace probe to use WorkspaceIgnore instead of its own git ls-files implementation and simplify ignore handling.
  • Replace the probe-local repo_ignored_paths (BTreeSet) with a WorkspaceIgnore field on WorkspaceProbe.
  • Use WorkspaceIgnore::resolve/none based on IgnorePolicy when capturing probes.
  • Update probe traversal to call excludes/excludes_dir and simplify ignores to delegate to WorkspaceIgnore.
  • Adjust diff logic comments to reference WorkspaceIgnore and ensure ignored-vs-seen paths are treated as unknowable rather than fabricated changes.
crates/stella-tools/src/shell_touch.rs
Ensure process-free ToolRegistry instances never spawn git by overriding probe ignore policy based on host isolation attestations and test it.
  • Add RegistryOptions::effective_probe_ignore_policy that forces IgnorePolicy::WalkAll when the registry is process-free, otherwise returns the requested policy.
  • Wire ToolRegistry construction to use effective_probe_ignore_policy instead of the raw option field.
  • Expose ToolRegistry::probe_ignore_policy to read the effective policy for tests.
  • Add a regression test asserting process-free registries always use WalkAll and that non-isolated registries honor the SkipIgnored setting.
crates/stella-tools/src/registry/options.rs
crates/stella-tools/src/registry.rs
crates/stella-tools/src/registry/turn_probe.rs
crates/stella-tools/src/registry/tests/file_change.rs
Update stella-graph public API and documentation to reflect the new ignore behavior and remaining .gitattributes limitation.
  • Export the workspace_ignore module from stella-graph's lib.rs so stella-tools can depend on it without a new crate edge.
  • Revise the README "Gotchas" section to describe the two-filter behavior (git-based rules plus deny-list) and clarify that .gitattributes merging remains unresolved and root-only.
crates/stella-graph/src/lib.rs
crates/stella-graph/README.md

Assessment against linked issues

Issue Objective Addressed Explanation
#2360 Make the stella-graph code-graph walker (walk_indexable and its watcher) consult the repository's real ignore rules (via git ls-files), skipping ignored paths while preserving a non-repo posture, with tests that demonstrate both behaviors.
#2360 Make the exploration coverage walks in crates/stella-tools/src/exploration.rs consult the repository's real ignore rules and skip ignored build trees, with tests that pin the non-repo behavior. The PR does not modify crates/stella-tools/src/exploration.rs or introduce ignore-rule consultation into the exploration coverage walks. All changes are in stella-graph (walk, watch, workspace_ignore), shell_touch, registry, and README; exploration coverage logic remains unchanged.
#2360 Unify ignore policy handling across walkers so that the ignore_gitignore / IgnorePolicy setting governs whether repository ignore rules are consulted, while carrying the ignored set on snapshots to treat pruned paths as unknowable in diffs. The PR does integrate WorkspaceIgnore into WorkspaceProbe and uses IgnorePolicy there, including a process-free override via RegistryOptions::effective_probe_ignore_policy, and it carries the ignored set on snapshots. However, the stella-graph walkers (walk_indexable and watcher) unconditionally resolve and consult WorkspaceIgnore::resolve(root) and are not wired to the ignore_gitignore / IgnorePolicy setting. Thus ignore policy is not unified across all walkers as described in the issue.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

…gnore rules (#2360)

Closes #2360

walk.rs carried a documented gap for two releases: custom .gitignore
patterns — per-directory files, negations, globs — were not honored, only
a hardcoded deny-list was. A generated directory with a project-specific
name was indexed, and recall paid for it.

Closed by asking git rather than reimplementing it. The new
stella-graph::workspace_ignore resolves the rules once per walk
(git ls-files --others --ignored --directory --exclude-standard) and is
the ONE answer two walkers now share: this crate's index walk and
stella-tools' workspace probe, which had its own copy from #2344.
It lives in stella-graph because stella-tools already depends on it and
not the reverse, so one implementation serves both with no new crate and
no new dependency edge.

The deny-list survives beside the rules rather than under them: it is what
still prunes a vendored bundle in a workspace that is NOT a repository,
where no ignore file says anything. Two filters, two jobs.

The watcher resolves once at construction rather than per event — the
filter runs on every saved file, and resolving per call would be one git
invocation per write. A .gitignore edited mid-session is not seen until
the next full index pass, which costs one wasted re-index, never a wrong
row.

Also fixes a defect #2344 shipped: the probe spawned git unconditionally,
including under HostDataIsolation::ProcessFree, whose whole promise is
that no built-in launches a child process. An isolated registry now walks
unfiltered — the cost is walk time, already bounded by
MAX_RECORDED_TOUCHES, rather than the guarantee.

Refs #2344, #2336
@macanderson
macanderson force-pushed the fix/2360-graph-walk-gitignore branch from cb72ace to 4d81dcb Compare August 8, 2026 23:03
macanderson and others added 2 commits August 8, 2026 23:24
…licy accessor

`probe_ignore_policy()` was added beside four call sites that all still read
the field directly, so it had no non-test caller and the lib build reported
`method `probe_ignore_policy` is never used`. Under the required job's
`cargo clippy --workspace --all-targets -- -D warnings` that warning is an
error, which is why `fmt + clippy + test` was red.

Routing the four captures through the accessor removes the dead code without
an `#[allow]` — the lint is right here, the method really was unreachable —
and makes its doc comment ("the ignore policy every capture this registry
takes will use") literally true: one chokepoint for the ProcessFree
attestation override instead of four independent field reads.

`registry.rs` nets zero lines, so its god-file ceiling is untouched.

Refs #2360

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`cargo doc -D warnings` fails this branch:

    error: public documentation for `workspace_ignore` links to private
           item `crate::walk`
      --> crates/stella-graph/src/workspace_ignore.rs:4
    error: could not document `stella-graph`

`workspace_ignore` is `pub mod`; `walk` is a private `mod`.
`rustdoc::private_intra_doc_links` denies that under `-D warnings`, and
`--document-private-items` does not silence it for a public → private
link — the same rule and the same fix shape as #2365.

The link becomes prose. No API change, no behavior change.
@macanderson
macanderson marked this pull request as ready for review August 8, 2026 23:31

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Sorry @macanderson, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@macanderson
macanderson enabled auto-merge (squash) August 8, 2026 23:33
@macanderson
macanderson disabled auto-merge August 8, 2026 23:33
@macanderson
macanderson merged commit 47b7bab into main Aug 8, 2026
15 checks passed
@macanderson
macanderson deleted the fix/2360-graph-walk-gitignore branch August 8, 2026 23:33
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.

Extend real-gitignore consultation (ignore_gitignore, #2344) to the remaining tree walkers: stella-graph walk_indexable and exploration coverage

1 participant