Repository navigation
feat(ingest): configurable size cap, graftignore and minified-bundle skip - #586
webmaniabr wants to merge 3 commits into
Conversation
Add src/ingest/filters.ts and thread one per-walk FileFilter through the three enumeration paths in walkDir (shallow git, recursive git, filesystem): - GRAFT_MAX_FILE_BYTES overrides the 1 MB per-file cap; an invalid value warns once and keeps the default. - .graftignore (repo root), $GIT_COMMON_DIR/info/graftignore (clone-local, shared by worktrees) and ~/.graft/ignore (user-wide) exclude paths with gitignore-style patterns. Unlike .gitignore they apply to tracked files. - GRAFT_SKIP_MINIFIED=1 skips .js/.mjs/.cjs files of at least 100 KB whose longest line exceeds 20,000 characters (memoized by size+mtime). Everything is read at walk time, so build, the fingerprint probe, the pre-query refresh, the MCP server and the sync hook agree on the file set. Co-Authored-By: Claude Code <noreply@anthropic.com>
Includes an end-to-end build followed by the freshness probe, so a filter that the refresh path saw differently would show up as drift. Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
🌱 graft blast radius1 area changed → 4 areas can be affected. 12 dependent symbols, depth 2. flowchart TB
A0(("Dependency Graph<br/>6 symbols"))
A1(("Context Analysis<br/>3 symbols"))
A2(("Pull Request Review<br/>2 symbols"))
A3(("Graph Engine<br/>1 symbol"))
classDef reached fill:#D9EDF3,stroke:#3AA7C9,stroke-width:1.5px,color:#0E313C;
class A0,A1,A2,A3 reached;
Who knows this code — 3 people across 5 areas
Ownership is git history over each area's own files, weighted towards recent work (120-day half-life). Merge commits and bots are dropped, and you are dropped from your own PR. A name with no All 12 dependent symbols, grouped by areaDependency Graph — 6 symbols in 5 files
Context Analysis — 3 symbols in 2 files
Pull Request Review — 2 symbols in 2 files
Graph Engine — 1 symbol in 1 file
Test signal per changed area — 1 ✓Reached = a node under a test path has a resolved edge into the changed symbol. It undercounts anything called indirectly — through a CLI, a spawned process or a dynamic import — so read a low ratio as “look here”, never as a coverage gate.
34 test suites also reference this code38 symbols, kept out of the diagram and the table so they cannot crowd out the areas a reviewer has to look at.
Open the interactive graph → — click an area to see its dependent symbols at file:line. |
|
Withdrawing this PR — we're consolidating our changes in an internal build and won't be able to maintain this branch. #575 covers the |
Two problems with the current file selection, both hit on a real PHP monorepo:
.gitignorecannot help. Git's ignore rules only gate untracked files, so committed*.min.jsfiles and build bundles are always indexed and crowd query results.This PR makes both configurable, with the defaults left exactly as they are.
Related: #370 (the silent 1 MB drop; this adds the override that issue suggests, and is complementary to #399, which adds the notice but keeps the cap fixed) and #575 (also adds
.graftignore; the two overlap there, and I'm happy to rebase onto whichever approach you prefer).What changes
GRAFT_MAX_FILE_BYTES=<n>overrides the per-file size cap. Default is still1000000. A value that is not a positive integer is ignored with a single warning and the default is kept.~/.graft/ignore: user-wide.$GIT_COMMON_DIR/info/graftignore(usually.git/info/graftignore): this clone only, never committed, and shared by all worktrees of the clone..graftignoreat the repo root: committed with the code.GRAFT_SKIP_MINIFIED=1(opt-in, off by default) skips.js/.mjs/.cjsfiles of at least 100 KB whose longest line is over 20,000 characters. The check is memoized by size+mtime.Supported syntax:
#comments,*,?,[a-z],**,/anchoring (a leading or inner slash), trailing/for directories only,!negation, last match wins. As in Git, a file can't be re-included once a parent directory is excluded.Limitations: matching is case-sensitive, and a submodule's own
.graftignoreis not read. Submodule paths go in the superproject's file.How
src/ingest/filters.ts.walkDirbuilds oneFileFilterper walk and passes it through all three enumeration paths: shallow git, recursive git (submodules/nested repos) and the filesystem fallback.graft build, the freshness probe, the pre-query refresh, the MCP server and the hooks always see the same file set. Changing a rule shows up as drift on the next query.RegExp, so a pathological pattern in a committed.graftignorecan't cause catastrophic backtracking (ReDoS).Compatibility
MAX_FILE_BYTESis still exported fromsrc/ingest/fs.tswith the same value.Tests
npm test: 1357 pass onmain, 1373 pass on this branch (+16, intest/ingest-filters.test.ts), 0 fail.tsc --noEmitis clean andnpm run buildpasses.!overrides, tracked files being excluded, directory pruning, minified detection, and an end-to-end build followed by the freshness probe, so a filter seen differently by the refresh path would show up as drift.**/**/**/**/a*a*a*a*b,a*a*a*a*…*b, 500*s,*?*?…, a 20,000-char pattern) are matched against a 10,000-char segment and a 200-level-deep path. Locally a full round takes about 0.3 ms (median of 20) and about 4 ms on a cold first run. The test asserts under 50 ms so it stays stable on a loaded CI runner.Real-world check (PHP repo)
GRAFT_MAX_FILE_BYTES=2000000, a 1.5 MB tracked PHP file went from 0 to 383 indexed methods..graftignore+GRAFT_SKIP_MINIFIED=1, about 300 tracked.min.jsfiles and bundles dropped out of the graph while staying tracked in Git.🤖 Generated with Claude Code