fix(perf): scope runPostNativeCha to changed files on incremental builds#1490
fix(perf): scope runPostNativeCha to changed files on incremental builds#1490carlos-alm wants to merge 15 commits into
Conversation
… diff Adds snapshot-pre-bash.sh (PreToolUse Bash) + track-bash-writes.sh (PostToolUse Bash): the pre-hook captures git status --porcelain to a per-worktree temp file before each Bash call; the post-hook diffs the before/after state and appends newly modified or created files to .claude/session-edits.log. This closes the gap where files written by sed -i, printf redirects, tee, heredocs, or build tools (Cargo.lock, lockfiles) were never recorded, causing guard-git.sh to emit false-positive BLOCKED errors. Closes #1457
- clojure.rs: annotate lifetime-anchor assignment to silence false-positive - cfg.rs: remove never-called start_line_of method - complexity.rs: remove never-constructed NotHandled variant; convert irrefutable if-let patterns to plain let destructures - dataflow.rs: remove never-read callee fields from CallReturn/Destructured - incremental.rs: remove never-read lang field from CacheEntry cargo check and cargo clippy both clean after these changes.
Adds .github/workflows/perf-canary.yml — a path-filtered workflow that fires on PRs touching src/extractors/, src/domain/graph/, or crates/** and runs only the incremental-benchmark suite (full build + no-op + 1-file rebuild, both engines). Catches the class of regressions that accumulated invisibly across the Phase 8.x PRs and were only detected at v3.12.0 publish time. The regression guard gains BENCH_CANARY=1 mode: raises thresholds to 50%/100%/150% (standard/noisy/WASM) and skips the build, query, and resolution suites — only incremental checks run. This absorbs shared- runner timing variance while still blocking catastrophic regressions (+98% full build, +1827% 1-file rebuild from v3.12.0). Closes #1433
On incremental builds, runPostNativeCha previously scanned all call→qualified-method edges in the DB (~12ms flat, O(graph size)), even for 1-file changes where no hierarchy or RTA evidence changed. Add two cheap indexed gate queries. Gate A checks whether any changed file introduced a class/interface/trait/struct/record node (hierarchy may have new implementors reachable from unchanged call sites). Gate B checks whether any changed file added a call edge to a class-kind target (RTA set may have grown, enabling previously filtered expansions in unchanged callers). If neither gate fires, restrict the candidate query to src.file IN changedFiles — safe because the hierarchy and instantiated set are unchanged for all other files. Full builds (isFullBuild=true) and cases where either gate fires retain the existing full-scan behaviour. Mirrors the changed-files scoping pattern of runPostNativeThisDispatch. Closes #1441
|
Claude finished @carlos-alm's task —— View job PR Review
|
Greptile SummaryThis PR scopes
Confidence Score: 5/5Safe to merge — the optimization is conservative (any class node or any constructor call in a changed file forces a full scan) and full builds are untouched. The gate logic is sound: Gate A and Gate B provide provably safe conditions under which restricting the CHA scan to changed-file call sites cannot miss any new edges. The previously flagged schema-fallback gap in Gate B (missing constructor/function kinds) is now fixed. Full-build and gate-fired paths follow the pre-existing code path unchanged. All ancillary Rust changes remove dead code with no behavioural impact. No files require special attention. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[runPostNativeCha called] --> B{isFullBuild?}
B -- yes / changedFiles=null --> C[Full scan\nall call→method edges]
B -- no / changedFiles=array --> D{changedFiles.length > 0?}
D -- no --> C
D -- yes --> E[Gate A: any class/interface/\ntrait/struct/record nodes\nin changed files?]
E -- yes --> F[gateAFired = true\nFull scan]
E -- no --> G[Gate B: any calls edges\nfrom changed-file sources to\nclass/constructor/function targets?]
G -- yes --> H[gateBFired = true\nFull scan]
G -- no --> I[scopeToChangedFiles = true\nRestrict callToMethods\nto src.file IN changedFiles]
C --> J[BFS expand implementors\nadd CHA edges]
F --> J
H --> J
I --> J
J --> K[Return newEdgeCount\n+ affectedFiles]
Reviews (5): Last reviewed commit: "chore: merge origin/main into fix/cha-in..." | Re-trigger Greptile |
Codegraph Impact Analysis13 functions changed → 153 callers affected across 44 files
|
…back schema
Gate B previously checked only `tgt.kind = 'class'`, but the RTA seed has a
fallback that matches `tgt.kind IN ('constructor', 'function')` when no
class-kind constructor edges exist (older native engine schemas). On codebases
where the fallback path is always active, Gate B would never fire, causing
scopeToChangedFiles to be set incorrectly and silently dropping CHA edges for
unchanged callers whose RTA evidence lives in the fallback-schema rows.
Broaden Gate B to `tgt.kind IN ('class', 'constructor', 'function')` to mirror
the full two-shape RTA seed. Also fix formatter violation on the .all() cast.
|
Addressed all three concerns from the Claude review:
|

runPostNativeChascanned all call→qualified-method edges on every native build including 1-file incrementals (~12ms flat, grows with graph size).Adds two cheap indexed gate queries: if no class hierarchy changes and no new RTA evidence landed in the changed files, scopes the candidate scan to
src.file IN changedFiles. Falls back to the full scan when either gate fires (hierarchy or RTA set may have changed for callers in other files).Gate A: any
class/interface/trait/struct/recordnodes in changed files → hierarchy may have new implementors reachable from unchanged call sites → full scan.Gate B: any
callsedges from changed-file sources toclass-kind targets → RTA set may have grown, enabling previously filtered expansions in unchanged callers → full scan.If neither gate fires, the hierarchy and instantiated set are provably unchanged outside the changed files, so restricting candidates to
src.file IN changedFilesis safe.Full builds (
isFullBuild=true) and gate-fired paths retain the existing full-scan behaviour unchanged.Mirrors the changed-files scoping pattern of
runPostNativeThisDispatch.Closes #1441