Skip to content

fix(graph): skip .git by path component, not substring, in _walk_md#4

Merged
sturlese merged 1 commit into
mainfrom
fix/bughunt-graph-git-component
Jul 12, 2026
Merged

fix(graph): skip .git by path component, not substring, in _walk_md#4
sturlese merged 1 commit into
mainfrom
fix/bughunt-graph-git-component

Conversation

@sturlese

Copy link
Copy Markdown
Owner

Bug

_walk_md skipped directories with if os.sep + ".git" in d — a substring test over the full directory path, not a path-component test. It over-matches:

  • Sibling directories like .github/ and .gitdata/ are silently skipped (their path contains /.git).
  • Worst: if the --in root lives under a .git-prefixed ancestor (e.g. --in /srv/.gitdata/brain-md), then every directory os.walk yields contains /.git, so all of them are skipped. build_graph collects zero docs, writes nothing, and stale-removal likewise skips everything — a silent total-data-loss failure that returns {"docs": 0} with no error.

Fix

Prune the walk in-place by exact directory name (dirs[:] = [sub for sub in dirs if sub != ".git"]), the canonical os.walk idiom. This skips only real .git subtrees (VCS internals — the stated intent) and never inspects the root/ancestor path, so a .git-prefixed ancestor can't empty the tree.

Test

Two regression tests in test_build.py:

  • test_build_graph_walks_root_under_git_prefixed_ancestor — a brain-md dir under .gitdata/ is processed (docs == 1); verified to fail on the pre-fix substring check.
  • test_build_graph_still_skips_real_git_subtree — a directory literally named .git is still pruned (intent preserved; passes before and after).

Full graph suite: 23 passed, coverage 98% (gate 75%). Offline evals: 8/8 green (graph metric included). ruff check pipeline evals clean.


Found by the autonomous bughunt loop (iteration 4), from a lead surfaced during iteration 1's sweep. Dry-run mode — human merges.

🤖 Generated with Claude Code

_walk_md skipped any directory whose path contained the substring os.sep+'.git',
which over-matches: sibling dirs like .github/.gitdata, and — worst — a root
whose ancestor has a '.git'-prefixed segment (e.g. --in /x/.gitdata/brain) made
EVERY walked dir match, so build_graph silently collected zero docs, wrote
nothing, and stale-removal skipped everything. Prune dirs in-place by exact
name so only real .git subtrees are skipped and the root/ancestor path is never
matched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sturlese

Copy link
Copy Markdown
Owner Author

Gate: adversarial verify → clean (SAFE)

The verifier tried five refutation angles; all failed to find a defect:

  • os.walk semantics: default topdown=True, so the in-place dirs[:] = [...] slice-assignment genuinely prunes descent into .git (verified the real .git subtree is not yielded).
  • Stale-removal walk: both passes use the same _walk_md, so pass-1 (write) and the stale sweep stay consistent — no foreign deletion regression; brain-md-graphed is a documented regenerable mirror.
  • .github inclusion: confirmed behavior change but inert — evals/out/brain-md and examples/out/brain-md contain no dot-dirs, so the graph eval metric is unaffected (no golden shift).
  • project.git-style names: the old substring /.git never matched these either, so exact-name matching loses no skip; it only removes the false skips (.git*-prefixed) and the actual bug (root/ancestor containing /.git emptying the whole tree).
  • Determinism: retained-dir order and file yield order unchanged; downstream aggregates order-independently.

All 5 tests pass, 100% coverage of build.py. Gate verdict: clean. Dry-run mode → open for human merge.

@sturlese
sturlese merged commit 2135150 into main Jul 12, 2026
7 checks passed
@sturlese
sturlese deleted the fix/bughunt-graph-git-component branch July 12, 2026 07:14
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.

1 participant