Scan speed: repo-complexity gate + evidence-driven read set (halve runtime on complex repos) - #34
Scan speed: repo-complexity gate + evidence-driven read set (halve runtime on complex repos)#34serenakeyitan wants to merge 4 commits into
Conversation
…on complex repos) The dominant scan cost is the agent reading+analyzing files (seconds each; the module greps are ms and sweep the whole tree regardless). Depth was decided by file COUNT alone, so a 400-file / 200k-LOC repo read all 200k lines under DEEP. Add a complexity gate at the very start and read the important + evidence-flagged files on demand (top-down) for complex repos. STAGE 1 changes (SKILL.md only — no behavior for small/normal repos): - 1.1b: depth = max(count_tier, loc_tier). New LOC axis (wc -l over the already-sorted source_list; thresholds 40k/250k). max() of two closed-range total orders is deterministic and monotone (can only read MORE conservatively). A 400-file/200k-LOC repo now tiers DEEP -> SHALLOW. - Expand MUST_SCAN dirs to the READ-DEPENDENT service surface (services/jobs/workers/tasks/lib/domain/usecases/processors/core) so error-handling/auth/perf still fully read handler+service bodies even when the rest is sampled — the guardrail that makes tiering-down safe. - Read set redefined (holds at every depth): MUST_SCAN UNION grep-hits UNION a depth-scaled deterministic sample of the remainder. Grep-hit files are promoted into the full-read set at every depth (evidence-driven / on-demand "top-down" read); must + hits are NEVER sampled out. - Sample floors: DEEP all / SHALLOW 1-in-5 / MINIMAL 1-in-20 (was zero) — MINIMAL no longer reads the tail blind. - Transparency: coverage line + "Partial coverage" warning whenever below DEEP. Determinism + cache intact (scan_depth already gates the key; hits are a pure function of content the WT fingerprint already covers). Vetted by an adversarial workflow: the naive "MUST_SCAN + grep-hit only" version would silently drop read-dependent confirmed fatals (swallowed catch, IDOR dataflow) living in service/worker files — hence guardrail (2) and the "never sample below the flagged set" invariant. Bash logic validated end-to-end on a fixture repo; complexity tiering verified across 6 (count, LOC) cases; render-report.test.mjs 17/17 still pass. Refs #33. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…urity filenames) Live before/after test on SillyTavern (358 files / 193k LOC, DEEP→SHALLOW) caught two holes the file-list math + design review missed: 1. Backend routes in `src/endpoints/` were NOT in MUST_SCAN — only 10 of 47 route files were read (rest sampled). `endpoints` wasn't in the dir regex. Added endpoints/resolvers/graphql/rpc/views/functions (Django views, GraphQL resolvers, serverless functions, the endpoints convention). Now 47/47 read. 2. Security files named by FUNCTION at the top of src/ (recover-password.js, users.js, private-request-filter.js [SSRF], request-proxy.js) were dropped — dir matching misses them. Added a security-noun BASENAME rule to MUST_SCAN. After both fixes on SillyTavern: 367→203 files read (45% fewer, still ~half), with 0 grep-hit files dropped and 0 route/read-dependent-dir files dropped — every backend route, every security-named file, and every grep-flagged file is fully read; only util/parser/vector-provider/frontend-UI boilerplate is sampled. This is why the live test mattered: the design was sound but the concrete dir/filename lists had real holes only a real repo exposed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Live before/after verification — SillyTavern (358 files / 193k LOC, real complex app)Ran the actual old-vs-new read-set computation on a real 3rd-party app that tiers DEEP → SHALLOW. This surfaced two real coverage gaps the design/file-math missed (now fixed in
After both fixes:
So on a genuinely complex repo the optimization reads ~half the files, and every backend route, every security-named file, and every mechanically-flagged file is still fully read — only boilerplate/UI is sampled. The disclosed residual (a swallow-and-continue in a hit-free non-security util file) is the same class SHALLOW already accepts, now warned via the partial-coverage line. |
The report's arch summary must state what a project IS (stack, deploy shape), which lives in README/docs — but .md was entirely outside the file universe (not in EXT_RE). And for a prompt-native project (tdoc, Claude Code skills) the real logic + risk lives in SKILL.md / *.prompt.md / .claude/ — a prompt file can carry a hardcoded key OR an embedded injection, yet was never scanned. Changes (all in MUST_SCAN — docs/prompts are read at EVERY depth; EXT_RE is deliberately untouched so md never enters count/LOC/complexity-tier/sample — a big docs/ or CHANGELOG must not inflate LOC and tier the scan DOWN): - MUST_SCAN grep line 4: project-doc basenames (README/ARCHITECTURE/CONTRIBUTING/ SECURITY/AGENTS/CLAUDE + docs/ architecture/deploy/design/setup files), off full_list.txt like manifests. Tight allow-list — CHANGELOG/generated/blog excluded. - MUST_SCAN grep line 5: prompt/skill/agent files (SKILL.md, *.prompt.md, .claude/, agents/, prompts/, .cursorrules, copilot-instructions) as ATTACK SURFACE. - secrets.md: prompt/skill/config files are secret-finding surface — a hardcoded key in SKILL.md is as fatal as one in db.ts. - ai-integration.md: now ALSO applies to prompt-native repos with no SDK call — checks the prompt content for embedded injection / guardrail-override / dangerous-sink instructions. - Doc/prompt read cap: first 2000 lines (a generated 10k-line README can't blow up the read); 10 MiB byte cap still the outer backstop. - Coverage numerator X filtered to EXT_RE so read docs don't corrupt the "X of Y source files" ratio. Validated on tdoc: README/SECURITY/CONTRIBUTING/docs-design + SKILL.md (x2) captured; CHANGELOG/generated/blog/LICENSE correctly excluded. render-report.test.mjs 17/17 pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Scope addition — read project docs + scan prompt/skill files as attack surface
Design (matches the complexity-gate philosophy — read via MUST_SCAN, NOT via EXT_RE):
Validated on tdoc: |
Measured DEEP-vs-SHALLOW read counts on 8 real repos: a ~30k-LOC project like first-tree (134 files) was still fully read under the 40k threshold but would save ~77% of file-reads if tiered to SHALLOW (few MUST_SCAN, many sample-able leaves). 25k pulls the 25k–40k band (first-tree) into the fast path while keeping genuinely small projects fully read (tdoc 9k, ellie 20k stay DEEP — full read of a few dozen files is already fast, zero miss risk). Kept ≥20k floor: below that, sampling's coverage (MUST_SCAN) is a smaller share so the finding-miss risk rises faster than the time saved. loc_tier: LOC ≤ 25000 → 0 (was 40000) · ≤ 250000 → 1 · else → 2. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Refs #33.
Problem
The dominant scan cost is the agent reading+analyzing files (seconds each). The module greps are milliseconds and sweep the whole tree regardless of depth. But scan depth was decided by file COUNT alone — so a 400-file / 200k-LOC repo read all 200k lines under DEEP. The user asked to (a) add a complexity judgment at the very start of the run, (b) read only important files top-down / on-demand for complex repos, (c) aim to halve runtime.
Design (adversarially vetted)
A workflow pressure-tested the naive idea ("read only MUST_SCAN + grep-hits") and proved it unsafe — error-handling (swallowed catch, floating promise), auth (IDOR / guard-dominance dataflow), and performance are read-dependent verticals: their fatal-eligible findings need the agent to READ a handler/service body, not match a grep. Dropping those files would silently drop confirmed fatals. The final design keeps the speed win with guardrails:
scan_depth = max(count_tier, loc_tier). New LOC axis (wc -lover the already-sortedsource_list; thresholds 40k / 250k). Deterministic:max()of two closed-range total orders is one pure, monotone function (can only ever read more conservatively). A 400-file / 200k-LOC repo now tiers DEEP → SHALLOW; a 400-file / 20k-LOC repo stays DEEP.services/jobs/workers/tasks/lib/domain/usecases/processors/core) so handler+service bodies are always fully read even when the rest is sampled. This is the guardrail that makes tiering-down safe.MUST_SCAN ∪ grep-hits ∪ depth-scaled sample. Every grep-flagged file is promoted into the full-read set — the "follow the evidence / on-demand top-down" read.must+hitsare never sampled out.⚠️ Partial coveragewarning whenever below DEEP. Cache/determinism intact (scan_depthalready gates the key).Net effect
~50% fewer file-reads on complex repos; normal/small repos unchanged; two pre-existing coverage gaps fixed as a side effect (MINIMAL zero-sample; grep-hits could be sampled out in SHALLOW).
Verification
max()tiering, MUST_SCAN dirs, two-phase read set).(count, LOC)cases (300/15k→DEEP, 400/200k→SHALLOW, 400/20k→DEEP, 3000/100k→SHALLOW, 400/300k→MINIMAL, 8000/50k→MINIMAL).render-report.test.mjs17/17 still pass (no collateral).🤖 Generated with Claude Code