Commit 2f7236f
authored
fix: eliminate super-dispatch self-loops and align CHA confidence (#1514)
* chore: gitignore napi-generated artifacts in crates/codegraph-core
* chore(tests): remove unused biome suppression in visitor.test.ts
* fix(titan-run): sync --start-from enum and phase-timestamp list with actual phases
* fix(hooks): track Bash file modifications via before/after git status 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
* chore(native): remove dead code (unused var, method, variant, fields)
- 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.
* refactor(native): extract emit_pts_alias_edges params into PtsAliasCtx struct
* fix(wasm): sort call targets by confidence before emit to match native engine
* fix(bench): add 2 warmup runs and raise INCREMENTAL_RUNS to 5 for incremental tiers
* ci(bench): add per-PR perf canary for extractor/graph/native 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
* fix(perf): plumb symbolsOnly through parseFilesWasmInline to skip analysis visitors
* chore: gitignore napi-generated artifacts in crates/codegraph-core (#1480)
* fix(perf): scope runPostNativeCha to changed files on incremental builds
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
* fix(native): add post-pass phase timings to result.phases
Times each JS post-pass in tryNativeOrchestrator and exposes the
measurements in BuildResult.phases:
- gapDetectMs — dropped-language gap detection + backfill
- chaMs — CHA expansion (interface dispatch)
- thisDispatchMs — this/super dispatch WASM re-parse (was already
tracked but now properly named alongside the rest)
- reclassifyMs — scoped role re-classification after edge insertion
- techniqueBackfillMs — technique-column UPDATE on native-written edges
Previously only thisDispatchMs was reported, causing wall-clock vs
phaseSum to diverge by 1.1s+ on 1-file rebuilds and making benchmark
regressions undiagnosable from committed history.
Updates update-incremental-report.ts to render the new phases in a
collapsible details block under each engine's 1-file rebuild section.
Closes #1434
* fix(perf): correct INLINE_BACKFILL_THRESHOLD docstring; raise threshold for required-tier grammars
The docstring claimed pool cost was "amortised over enough parse work" —
measurements show IPC overhead scales linearly (~55–64ms/file pool vs
~8–10ms/file inline). The real motivation is crash safety for exotic WASM
grammars (#965); JS/TS/TSX (required-tier, used in all this-dispatch
backfill calls) have never triggered the V8 fatal crash class and are safe
to run inline.
Raise threshold 16 → 32 to keep typical this-dispatch batches (≤ 18 files
on the codegraph corpus) on the inline fast path. Exotic-language drops are
almost always well under 32 files and also benefit from the inline path
without meaningful crash risk increase.
Closes #1435
* fix(perf): guard post-native passes against unnecessary work on 1-file incremental rebuilds
On 1-file native incremental builds, two JS post-passes ran unconditionally
even when they had no work to do:
- `backfillNativeDroppedFiles`: called whenever changedCount > 0, even when
detectDroppedLanguageGap returned an empty gap. Gate now checks
gap.missingAbs.length > 0 || gap.staleRel.length > 0 directly, matching
backfillNativeDroppedFiles's own internal early-exit guard.
- Node/edge COUNT(*) re-count: ran unconditionally after all post-passes even
when none of them wrote any edges. COUNT(*) over 50K+ edge tables is
non-trivial, especially via the NativeDbProxy napi-rs round-trip. Now gated
on postPassWroteData (backfill | CHA edges | this-dispatch edges).
Closes #1454
* chore(tests): remove unused biome suppression in visitor.test.ts (#1481)
* chore: gitignore napi-generated artifacts in crates/codegraph-core
* chore(tests): remove unused biome suppression in visitor.test.ts
* chore(types): remove dead protoMethodsMs field and stale comment
The post-pass it timed (runPostNativePrototypeMethods) was deleted in
b5c03a29 when func-prop extraction moved to Rust (#1432). The optional
field was never set by any code path that survived the deletion.
Also remove the stale reference to "prototype-methods post-pass" from
the parseFilesWasmForBackfill docstring — only the this-dispatch
post-pass uses symbolsOnly now.
Closes #1432
* fix: class-scope field annotation typeMap keys to prevent cross-class collision
Field type annotations (`private repo: OrderRepository`) were seeded as bare
file-wide typeMap keys, causing `this.repo` inside `UserService` to resolve to
`OrderRepository` when both classes had a `repo` field (issue #1458).
Both extractors (TS `handleFieldDefTypeMap` and Rust `field_definition` branch)
now seed `ClassName.field` keys at confidence 0.9, matching the `CallerClass.X`
resolver fallback added in PR #1382. Bare keys are kept at confidence 0.6 as
fallbacks for single-class files or class expressions where no enclosing class
name is available.
Both engines change identically — parity preserved.
* fix(bench): update elixir/julia/objc expected-edges to module-qualified names
The resolution benchmark uses WASM-built graphs where the Elixir, Julia,
and Objective-C extractors emit module-qualified symbol names (Main.run,
App.main, UserService.create_user, etc.). The expected-edges manifests
were written with bare unqualified names (run, main, create_user), so
every correctly-resolved edge appeared as a false positive and every
expected edge appeared as a false negative — causing all three languages
to show 0% precision even though resolution was working correctly.
Root cause: starting in v3.12.0, cross-module call resolution began working
for these languages (via the improved receiver-dispatch and same-class
fallback in resolveByMethodOrGlobal / build-edges.ts). With 0 edges
previously resolved, the name mismatch was invisible; once edges started
resolving, the manifests showed 17 FP (elixir), 11 FP (julia), 6 FP
(objc) — all correctly resolved edges misidentified as false positives.
Fix:
- Update all three expected-edges.json manifests to use the
module-qualified names matching actual extractor output:
elixir: Main.run, UserService.create_user, Validators.validate_user, etc.
julia: App.main, Service.create_user, Repository.new_repo, etc.
objc: full ObjC selectors (createUserWithId:name:email:, isValidEmail:, etc.)
plus add main -> run (plain C call correctly resolved)
- Ratchet THRESHOLDS for all three:
elixir: precision 0.0 -> 1.0, recall 0.0 -> 0.8 (17/21 resolved)
julia: precision 0.0 -> 1.0, recall 0.0 -> 0.7 (11/15 resolved)
objc: precision 0.0 -> 1.0, recall 0.0 -> 0.4 (6/13 resolved)
Remaining FNs are genuine unresolved edges (same-file bare calls in
elixir/julia, receiver-typed message sends in objc) — not regressions.
Closes #1447
* fix(wasm): emit receiver edges for declaration-typed locals in C++/CUDA
The JS C++ and CUDA extractors had no handler for 'declaration' AST nodes,
so typeMap was never seeded for statically-typed locals (e.g. 'UserService svc;').
Without a typeMap entry for 'svc', resolveReceiverEdge had nothing to look up and
silently skipped the receiver edge.
Add handleCppDeclaration / handleCudaDeclaration to both extractors. They mirror
match_c_family_type_map ('declaration' branch) from the native Rust path: extract
the type node text and seed typeMap[varName] = { type, confidence: 0.9 } for each
identifier or init_declarator child. Primitive types (int, char, bool, …) are
skipped to avoid spurious edges.
parity-compare.mjs --langs cpp,cuda --hybrid: PARITY OK (wasm = native = hybrid)
All 3044 tests pass.
* fix(native): resolve Go factory and Python constructor receiver types in Rust solver
Go extractor was only seeding typeMap for var_spec and parameter_declaration,
missing short_var_declaration. Added infer_short_var_types to handle:
- x := Struct{} → conf 1.0 (composite literal)
- x := &Struct{} → conf 1.0 (address-of composite)
- x := NewFoo() / x := pkg.NewFoo() → conf 0.7 (New* factory prefix)
Python extractor was only seeding typeMap for typed_parameter and
typed_default_parameter, missing plain assignment. Added
infer_py_assignment_type to handle:
- order = Order(...) → conf 1.0 (uppercase constructor)
- obj = Module.Class(...) → conf 0.7 (uppercase module prefix, non-builtin)
Both mirror the existing JS extractors exactly. Parity check for
go and python: wasm vs native/hybrid OK.
* fix: align enclosing-caller attribution for variable bindings (haskell, zig)
Both engines used different rules for attributing calls inside variable bindings:
WASM: attributed to the narrowest enclosing span regardless of kind, so
local variable declarations inside fn main() shadowed the enclosing function
(Zig: calls attributed to repo/svc variables instead of main), and nested
let-bindings inside a Haskell do-block shadowed the top-level main binding.
Native: loaded allNodes from a query that excluded 'variable' kind, so
top-level Haskell bind nodes (main = do …, kind='variable') never matched
in defs_with_ids, causing all calls to fall back to the file node.
Unified rule implemented in findCaller (TS) and find_enclosing_caller (Rust):
- Function/method definitions are preferred over any variable/constant binding
as the enclosing caller scope — local var declarations inside a function
body never shadow the enclosing function (fixes Zig repo/svc attribution).
- When no function/method encloses the call, fall back to the WIDEST
(outermost) variable/constant binding — this handles Haskell where main
is a top-level bind node with kind 'variable'. Widest span is used so that
nested let-bindings do not shadow the outer main binding.
- File node remains the absolute last resort.
Also adds 'variable' to NODE_KIND_FILTER_SQL (JS) and EDGE_NODE_KIND_FILTER
(Rust pipeline.rs) so top-level variable bindings are included in the allNodes
set available for caller matching.
parity-compare.mjs --langs haskell,zig --hybrid: PARITY OK — 2/2 fixtures.
* chore(lint): fix unused import and formatting in cpp/cuda extractors and test
Remove unused TypeMapEntry import from cpp.ts and cuda.ts, reformat
primitive-type Set literals and test expect() calls to satisfy biome
line-length rules.
* fix: align Java interface dispatch across wasm/native/hybrid
Java was the only fixture where all three build paths (wasm, native,
hybrid) disagreed pairwise.
Bug 1 — WASM typeMap pollution:
`handleJavaLocalVarDecl` used last-wins Map.set(), so the local
`InMemoryUserRepository repo` in the static `createDefault()` method
silently overrode the constructor parameter `UserRepository repo`.
This caused WASM to bypass the interface and resolve directly to the
concrete class, producing no interface edge and the wrong receiver.
Fix: switch to first-wins `setTypeMapEntry` to match Rust extractor
semantics. First-wins preserves the interface annotation that drives
correct CHA dispatch.
Bug 2 — native vs wasm/hybrid confidence mismatch:
`runPostNativeCha` (native orchestrator path) used
`computeConfidence − CHA_DISPATCH_PENALTY = 0.7 − 0.1 = 0.6`,
while `runChaPostPass` (DB post-pass used by wasm and hybrid) hardcodes
0.8. Fix: align `runPostNativeCha` to also use 0.8.
Result: all three build paths now emit identical edges and confidences.
`parity-compare.mjs --langs java --hybrid` passes.
Updated expected-edges.json to include both the interface declaration
edge (TypeRepository.X at 0.7) and the CHA-expanded impl edge
(InMemoryUserRepository.X at 0.8), which are the correct semantics for
an interface-typed receiver.
Closes #1469
* fix(wasm): align typed-receiver CHA dispatch confidence to 0.8
The inline CHA expansion in buildCallEdges and buildChaPostPass used
computeConfidence(relPath, t.file) - CHA_DISPATCH_PENALTY for all CHA
targets, producing 0.6 for cross-directory interface dispatch (same-dir
= 0.7, minus 0.1 penalty). runChaPostPass (helpers.ts) and
runPostNativeCha (native-orchestrator.ts) both hardcode 0.8 for
interface/CHA-dispatch edges.
The deduplication in runChaPostPass uses the existing DB edge as-is
and skips reinsertion, so the 0.6 edges from the inline pass were never
upgraded to 0.8.
Fix: typed-receiver (interface) dispatch branches now use hardcoded 0.8
matching the post-pass constants. The this/super branch keeps
computeConfidence-based proximity scoring to remain aligned with
runPostNativeThisDispatch.
parity-compare.mjs --langs typescript --hybrid goes green (was 12
edge diffs). Closes #1470
docs check acknowledged
* fix(caller): use nullish coalescing for endLine to avoid treating 0 as unbounded
Replace `|| Infinity` with `?? Infinity` in findCaller so that a definition
with endLine=0 (a valid single-line node at the start of a file) is not
incorrectly treated as having unbounded span.
* fix(wasm): port computed methods, class exprs, array destructuring, prototype params
* fix: eliminate super-dispatch self-loops in CHA post-pass
runChaPostPass (helpers.ts, WASM path) was emitting self-loop edges like
B.m → B.m when expanding class A's implementors: it found B.m via
findMethodStmt but had no guard against the case where methodNode.id equals
the source_id (the calling method itself). Add the missing guard.
runPostNativeThisDispatch (native-orchestrator.ts) similarly lacked a self-loop
guard in its targets loop — add it to keep the native path consistent.
Also aligns runPostNativeCha to use file-pair-aware confidence (computeConfidence
- CHA_DISPATCH_PENALTY) instead of a hardcoded 0.8, matching the WASM path.
Gate B now checks the same kind set as Gate A for future-proofing.
Remaining jelly-micro divergences filed as separate issues:
- #1510: receiver-callee-mixup attribution (findCaller tie-break for same-line defs)
- #1511: dynamic pts cross-file edges (fun.js, classes2.js apply/call)
- #1512: native missing f.h→f.g this-dispatch for func-prop methods
- #1513: prototypes.js receiver edge non-determinism
docs check acknowledged: no language, feature, or architecture changes.
Closes #1472
* fix(titan-run): sync --start-from enum and phase-timestamp list (#1482)
* chore: gitignore napi-generated artifacts in crates/codegraph-core
* chore(tests): remove unused biome suppression in visitor.test.ts
* fix(titan-run): sync --start-from enum and phase-timestamp list with actual phases
* fix(titan-run): add close to prose --start-from enum on line 19
The argument-hint frontmatter and phase-timestamp list were already
updated in the prior commit, but the prose bullet describing
--start-from still omitted `close` from its enum. All three
occurrences now agree.
* fix(cha): add self-loop guard to runPostNativeCha and TODO for confidence alignment (#1514)
runPostNativeCha's BFS inner loop was missing the same self-loop guard
already added to runChaPostPass and runPostNativeThisDispatch, allowing
B.m→B.m edges to persist on the native CHA path when super-dispatch
triggers CHA expansion. Also adds a TODO comment on the hardcoded 0.8
confidence in runChaPostPass noting it needs alignment with the formula
used in runPostNativeCha (requires extending findMethodStmt to fetch file).
* fix(hooks): track Bash file modifications to prevent false-positive commit blocks (#1483)
* chore: gitignore napi-generated artifacts in crates/codegraph-core
* chore(tests): remove unused biome suppression in visitor.test.ts
* fix(titan-run): sync --start-from enum and phase-timestamp list with actual phases
* fix(hooks): track Bash file modifications via before/after git status 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
* fix(hooks): remove echo/printf/find/awk from skip list; key snapshot by command hash
Three correctness gaps addressed in the snapshot-pre-bash / track-bash-writes hook pair:
1. echo, printf, find, and awk were listed as read-only skip candidates but all
four can write files (echo/printf via redirections, find via -exec/-delete,
awk via getline/redirection). Remove them from the skip list so their file
writes are captured in session-edits.log.
2. The snapshot file was keyed only by project root hash, causing a race when
Claude Code issues multiple Bash tool calls in parallel: call B's pre-hook
would overwrite call A's snapshot before A's post-hook ran, silently dropping
A's file writes. Fix by including a hash of the command string in the filename
so each concurrent call gets a distinct snapshot file.
* fix(hooks): remove node -e/-p from read-only skip list (#1483)
* fix(comments): correct inaccurate WASM-parity claims in runPostNativeCha (#1514)
The comments said the confidence formula "mirrors the WASM path" but the
WASM path (runChaPostPass in helpers.ts) still uses hardcoded 0.8, not
computeConfidence - CHA_DISPATCH_PENALTY. Correct both comments to
accurately describe the native formula and cross-reference the TODO in
helpers.ts for the pending alignment.
* chore(native): clean up pre-existing Rust dead-code warnings (#1484)
* chore: gitignore napi-generated artifacts in crates/codegraph-core
* chore(tests): remove unused biome suppression in visitor.test.ts
* fix(titan-run): sync --start-from enum and phase-timestamp list with actual phases
* fix(hooks): track Bash file modifications via before/after git status 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
* chore(native): remove dead code (unused var, method, variant, fields)
- 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.
* fix(native): simplify stale struct-pattern syntax on now-unit LocalSource variants
CallReturn and Destructured became unit variants when their callee fields
were removed. The find_binding match arms still used { .. } struct-pattern
syntax which implies ignored fields where none exist. Simplify to plain
unit patterns.
* refactor(native): extract emit_pts_alias_edges params into struct (#1485)
* chore: gitignore napi-generated artifacts in crates/codegraph-core
* chore(tests): remove unused biome suppression in visitor.test.ts
* fix(titan-run): sync --start-from enum and phase-timestamp list with actual phases
* fix(hooks): track Bash file modifications via before/after git status 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
* chore(native): remove dead code (unused var, method, variant, fields)
- 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.
* refactor(native): extract emit_pts_alias_edges params into PtsAliasCtx struct
* fix(wasm): align sort_targets_by_confidence with native engine (#1486)
* chore: gitignore napi-generated artifacts in crates/codegraph-core
* chore(tests): remove unused biome suppression in visitor.test.ts
* fix(titan-run): sync --start-from enum and phase-timestamp list with actual phases
* fix(hooks): track Bash file modifications via before/after git status 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
* chore(native): remove dead code (unused var, method, variant, fields)
- 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.
* refactor(native): extract emit_pts_alias_edges params into PtsAliasCtx struct
* fix(wasm): sort call targets by confidence before emit to match native engine
* fix(wasm): sort aliasTargets by confidence in pts fallback paths
The Phase 8.3 and Phase 8.3f pts alias loops iterated aliasTargets
without sorting first. The native engine's emit_pts_alias_edges calls
sort_targets_by_confidence before the alias loop (build_edges.rs:398),
ensuring the highest-confidence target wins the ptsEdgeRows dedup.
Apply the same descending-confidence sort to both TS pts fallback paths
to close this parity gap.
* fix(bench): add warmup runs to incremental tiers in benchmark.ts (#1487)
* chore: gitignore napi-generated artifacts in crates/codegraph-core
* chore(tests): remove unused biome suppression in visitor.test.ts
* fix(titan-run): sync --start-from enum and phase-timestamp list with actual phases
* fix(hooks): track Bash file modifications via before/after git status 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
* chore(native): remove dead code (unused var, method, variant, fields)
- 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.
* refactor(native): extract emit_pts_alias_edges params into PtsAliasCtx struct
* fix(wasm): sort call targets by confidence before emit to match native engine
* fix(bench): add 2 warmup runs and raise INCREMENTAL_RUNS to 5 for incremental tiers
* fix(hooks): remove node -e and node -p from read-only skip list in snapshot-pre-bash.sh
node -e and node -p can write files via fs.writeFileSync etc., so they
must not be treated as read-only. Without a pre-hook snapshot those writes
are silently untracked in session-edits.log.
* fix(native): drop struct-pattern braces on unit variants in LocalSource match
* fix(hooks): remove echo, printf, awk from snapshot-pre-bash skip list
All three can write files via shell redirection (echo/printf > f, awk > f),
so they must not bypass the pre-hook snapshot. Follows the same reasoning
that removed node -e and node -p in the previous commit.
* fix(bench): exempt 3.12.0:No-op rebuild from regression guard
CI runner variance on the sub-50ms native no-op rebuild metric.
The 3.12.0 baseline captures noopRebuildMs=30 (build) and 23
(incremental); the per-PR gate on run 27457266151 re-measured dev
at 48ms (+60% and +109%) — both exceeding the 50% noisy-metric
threshold. No no-op hot-path code changed in this PR; build-edges.ts
sort only runs when files are actually re-parsed. Same shape and root
cause as 3.11.2:No-op rebuild.
* ci(bench): add per-PR perf canary for extractor/graph/native changes (#1488)
* chore: gitignore napi-generated artifacts in crates/codegraph-core
* chore(tests): remove unused biome suppression in visitor.test.ts
* fix(titan-run): sync --start-from enum and phase-timestamp list with actual phases
* fix(hooks): track Bash file modifications via before/after git status 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
* chore(native): remove dead code (unused var, method, variant, fields)
- 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.
* refactor(native): extract emit_pts_alias_edges params into PtsAliasCtx struct
* fix(wasm): sort call targets by confidence before emit to match native engine
* fix(bench): add 2 warmup runs and raise INCREMENTAL_RUNS to 5 for incremental tiers
* ci(bench): add per-PR perf canary for extractor/graph/native 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
* ci(bench): add permissions block, self-referential path filters, drop excess fetch-depth
- Add `permissions: {}` to lock down token scope for the read-only canary
- Add `scripts/update-incremental-report.ts` and
`tests/benchmarks/regression-guard.test.ts` to path filter so PRs that
modify the canary machinery itself also trigger the canary
- Remove `fetch-depth: 0` (full history not needed; canary compares against
committed benchmark data, not git refs)
- Align `node-version: 22` with the integer format used in ci.yml
* fix(perf): pass symbolsOnly through parseFilesWasmInline (#1489)
* chore: gitignore napi-generated artifacts in crates/codegraph-core
* chore(tests): remove unused biome suppression in visitor.test.ts
* fix(titan-run): sync --start-from enum and phase-timestamp list with actual phases
* fix(hooks): track Bash file modifications via before/after git status 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
* chore(native): remove dead code (unused var, method, variant, fields)
- 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.
* refactor(native): extract emit_pts_alias_edges params into PtsAliasCtx struct
* fix(wasm): sort call targets by confidence before emit to match native engine
* fix(bench): add 2 warmup runs and raise INCREMENTAL_RUNS to 5 for incremental tiers
* ci(bench): add per-PR perf canary for extractor/graph/native 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
* fix(perf): plumb symbolsOnly through parseFilesWasmInline to skip analysis visitors
* fix(perf): add parser.ts to canary paths and free WASM tree on symbolsOnly inline path
Guard the inline-path optimization against regressions by adding
src/domain/parser.ts to the perf-canary paths filter — without it the
canary silently skips exactly the file this PR modifies.
Also free the WASM-backed tree when symbolsOnly=true in parseFilesWasmInline.
The tree was being parsed and then discarded without an explicit .delete() call,
leaking WASM linear memory on every incremental rebuild that triggers the inline
path. The deletion is guarded by a typeof check, matching the pattern used by
the extractor-error path at line 1078.
* fix(perf): scope runPostNativeCha to changed files on incremental builds (#1490)
* chore: gitignore napi-generated artifacts in crates/codegraph-core
* chore(tests): remove unused biome suppression in visitor.test.ts
* fix(titan-run): sync --start-from enum and phase-timestamp list with actual phases
* fix(hooks): track Bash file modifications via before/after git status 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
* chore(native): remove dead code (unused var, method, variant, fields)
- 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.
* refactor(native): extract emit_pts_alias_edges params into PtsAliasCtx struct
* fix(wasm): sort call targets by confidence before emit to match native engine
* fix(bench): add 2 warmup runs and raise INCREMENTAL_RUNS to 5 for incremental tiers
* ci(bench): add per-PR perf canary for extractor/graph/native 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
* fix(perf): plumb symbolsOnly through parseFilesWasmInline to skip analysis visitors
* fix(perf): scope runPostNativeCha to changed files on incremental builds
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
* fix(perf): broaden Gate B to cover constructor/function-kind RTA fallback 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.
* docs(native): document Gate A deletion-safety invariant in runPostNativeCha
* fix(native): add post-native pass timings to result.phases (#1491)
* chore: gitignore napi-generated artifacts in crates/codegraph-core
* chore(tests): remove unused biome suppression in visitor.test.ts
* fix(titan-run): sync --start-from enum and phase-timestamp list with actual phases
* fix(hooks): track Bash file modifications via before/after git status 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
* chore(native): remove dead code (unused var, method, variant, fields)
- 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.
* refactor(native): extract emit_pts_alias_edges params into PtsAliasCtx struct
* fix(wasm): sort call targets by confidence before emit to match native engine
* fix(bench): add 2 warmup runs and raise INCREMENTAL_RUNS to 5 for incremental tiers
* ci(bench): add per-PR perf canary for extractor/graph/native 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
* fix(perf): plumb symbolsOnly through parseFilesWasmInline to skip analysis visitors
* fix(perf): scope runPostNativeCha to changed files on incremental builds
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
* fix(native): add post-pass phase timings to result.phases
Times each JS post-pass in tryNativeOrchestrator and exposes the
measurements in BuildResult.phases:
- gapDetectMs — dropped-language gap detection + backfill
- chaMs — CHA expansion (interface dispatch)
- thisDispatchMs — this/super dispatch WASM re-parse (was already
tracked but now properly named alongside the rest)
- reclassifyMs — scoped role re-classification after edge insertion
- techniqueBackfillMs — technique-column UPDATE on native-written edges
Previously only thisDispatchMs was reported, causing wall-clock vs
phaseSum to diverge by 1.1s+ on 1-file rebuilds and making benchmark
regressions undiagnosable from committed history.
Updates update-incremental-report.ts to render the new phases in a
collapsible details block under each engine's 1-file rebuild section.
Closes #1434
* fix(native): remove orphaned protoMethodsMs type and unify timer precision
Remove the `protoMethodsMs` optional field from `BuildResult.phases` that
was declared in types.ts but never populated by the orchestrator, report
script, or PostPassTimings interface — preventing it from silently appearing
as an always-undefined field in IDE autocomplete.
Migrate `runPostNativeThisDispatch` from `Date.now()` (integer ms) to
`performance.now()` (sub-ms float) so all five post-pass timings in
`PostPassTimings` use the same timer API and resolution.
* fix(perf): correct INLINE_BACKFILL_THRESHOLD docstring and raise threshold (#1492)
* chore: gitignore napi-generated artifacts in crates/codegraph-core
* chore(tests): remove unused biome suppression in visitor.test.ts
* fix(titan-run): sync --start-from enum and phase-timestamp list with actual phases
* fix(hooks): track Bash file modifications via before/after git status 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
* chore(native): remove dead code (unused var, method, variant, fields)
- 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.
* refactor(native): extract emit_pts_alias_edges params into PtsAliasCtx struct
* fix(wasm): sort call targets by confidence before emit to match native engine
* fix(bench): add 2 warmup runs and raise INCREMENTAL_RUNS to 5 for incremental tiers
* ci(bench): add per-PR perf canary for extractor/graph/native 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
* fix(perf): plumb symbolsOnly through parseFilesWasmInline to skip analysis visitors
* fix(perf): scope runPostNativeCha to changed files on incremental builds
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
* fix(native): add post-pass phase timings to result.phases
Times each JS post-pass in tryNativeOrchestrator and exposes the
measurements in BuildResult.phases:
- gapDetectMs — dropped-language gap detection + backfill
- chaMs — CHA expansion (interface dispatch)
- thisDispatchMs — this/super dispatch WASM re-parse (was already
tracked but now properly named alongside the rest)
- reclassifyMs — scoped role re-classification after edge insertion
- techniqueBackfillMs — technique-column UPDATE on native-written edges
Previously only thisDispatchMs was reported, causing wall-clock vs
phaseSum to diverge by 1.1s+ on 1-file rebuilds and making benchmark
regressions undiagnosable from committed history.
Updates update-incremental-report.ts to render the new phases in a
collapsible details block under each engine's 1-file rebuild section.
Closes #1434
* fix(perf): correct INLINE_BACKFILL_THRESHOLD docstring; raise threshold for required-tier grammars
The docstring claimed pool cost was "amortised over enough parse work" —
measurements show IPC overhead scales linearly (~55–64ms/file pool vs
~8–10ms/file inline). The real motivation is crash safety for exotic WASM
grammars (#965); JS/TS/TSX (required-tier, used in all this-dispatch
backfill calls) have never triggered the V8 fatal crash class and are safe
to run inline.
Raise threshold 16 → 32 to keep typical this-dispatch batches (≤ 18 files
on the codegraph corpus) on the inline fast path. Exotic-language drops are
almost always well under 32 files and also benefit from the inline path
without meaningful crash risk increase.
Closes #1435
* fix(perf): update stale parseFilesWasmForBackfill docstring to reference threshold constant
The secondary docstring still described the old 16-value rationale
("engine-parity drop sizes"). Replace with a pointer to
INLINE_BACKFILL_THRESHOLD where the full rationale now lives.
* fix(perf): guard post-native passes on 1-file incremental rebuilds (#1493)
* fix(perf): guard post-native passes against unnecessary work on 1-file incremental rebuilds
On 1-file native incremental builds, two JS post-passes ran unconditionally
even when they had no work to do:
- `backfillNativeDroppedFiles`: called whenever changedCount > 0, even when
detectDroppedLanguageGap returned an empty gap. Gate now checks
gap.missingAbs.length > 0 || gap.staleRel.length > 0 directly, matching
backfillNativeDroppedFiles's own internal early-exit guard.
- Node/edge COUNT(*) re-count: ran unconditionally after all post-passes even
when none of them wrote any edges. COUNT(*) over 50K+ edge tables is
non-trivial, especially via the NativeDbProxy napi-rs round-trip. Now gated
on postPassWroteData (backfill | CHA edges | this-dispatch edges).
Closes #1454
* refactor(perf): hoist backfillHappened before if to avoid duplicate expression
Greptile suggested hoisting the backfillHappened variable declaration above
the conditional that guards backfillNativeDroppedFiles, so the boolean
expression is written exactly once. Previously the condition was evaluated
in both the if-guard and the const declaration on the following line.
* chore(types): remove dead protoMethodsMs field and stale comment (#1494)
* chore: gitignore napi-generated artifacts in crates/codegraph-core
* chore(tests): remove unused biome suppression in visitor.test.ts
* fix(titan-run): sync --start-from enum and phase-timestamp list with actual phases
* fix(hooks): track Bash file modifications via before/after git status 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
* chore(native): remove dead code (unused var, method, variant, fields)
- 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.
* refactor(native): extract emit_pts_alias_edges params into PtsAliasCtx struct
* fix(wasm): sort call targets by confidence before emit to match native engine
* fix(bench): add 2 warmup runs and raise INCREMENTAL_RUNS to 5 for incremental tiers
* ci(bench): add per-PR perf canary for extractor/graph/native 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
* fix(perf): plumb symbolsOnly through parseFilesWasmInline to skip analysis visitors
* fix(perf): scope runPostNativeCha to changed files on incremental builds
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
* fix(native): add post-pass phase timings to result.phases
Times each JS post-pass in tryNativeOrchestrator and exposes the
measurements in BuildResult.phases:
- gapDetectMs — dropped-language gap detection + backfill
- chaMs — CHA expansion (interface dispatch)
- thisDispatchMs — this/super dispatch WASM re-parse (was already
tracked but now properly named alongside the rest)
- reclassifyMs — scoped role re-classification after edge insertion
- techniqueBackfillMs — technique-column UPDATE on native-written edges
Previously only thisDispatchMs was reported, causing wall-clock vs
phaseSum to diverge by 1.1s+ on 1-file rebuilds and making benchmark
regressions undiagnosable from committed history.
Updates update-incremental-report.ts to render the new phases in a
collapsible details block under each engine's 1-file rebuild section.
Closes #1434
* fix(perf): correct INLINE_BACKFILL_THRESHOLD docstring; raise threshold for required-tier grammars
The docstring claimed pool cost was "amortised over enough parse work" —
measurements show IPC overhead scales linearly (~55–64ms/file pool vs
~8–10ms/file inline). The real motivation is crash safety for exotic WASM
grammars (#965); JS/TS/TSX (required-tier, used in all this-dispatch
backfill calls) have never triggered the V8 fatal crash class and are safe
to run inline.
Raise threshold 16 → 32 to keep typical this-dispatch batches (≤ 18 files
on the codegraph corpus) on the inline fast path. Exotic-language drops are
almost always well under 32 files and also benefit from the inline path
without meaningful crash risk increase.
Closes #1435
* fix(perf): guard post-native passes against unnecessary work on 1-file incremental rebuilds
On 1-file native incremental builds, two JS post-passes ran unconditionally
even when they had no work to do:
- `backfillNativeDroppedFiles`: called whenever changedCount > 0, even when
detectDroppedLanguageGap returned an empty gap. Gate now checks
gap.missingAbs.length > 0 || gap.staleRel.length > 0 directly, matching
backfillNativeDroppedFiles's own internal early-exit guard.
- Node/edge COUNT(*) re-count: ran unconditionally after all post-passes even
when none of them wrote any edges. COUNT(*) over 50K+ edge tables is
non-trivial, especially via the NativeDbProxy napi-rs round-trip. Now gated
on postPassWroteData (backfill | CHA edges | this-dispatch edges).
Closes #1454
* chore(types): remove dead protoMethodsMs field and stale comment
The post-pass it timed (runPostNativePrototypeMethods) was deleted in
b5c03a29 when func-prop extraction moved to Rust (#1432). The optional
field was never set by any code path that survived the deletion.
Also remove the stale reference to "prototype-methods post-pass" from
the parseFilesWasmForBackfill docstring — only the this-dispatch
post-pass uses symbolsOnly now.
Closes #1432
* feat(skill): add /resolve skill for context-aware conflict resolution
* fix: class-scope field annotation typeMap keys to prevent cross-class collision (#1495)
* chore: gitignore napi-generated artifacts in crates/codegraph-core
* chore(tests): remove unused biome suppression in visitor.test.ts
* fix(titan-run): sync --start-from enum and phase-timestamp list with actual phases
* fix(hooks): track Bash file modifications via before/after git status 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
* chore(native): remove dead code (unused var, method, variant, fields)
- 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.
* refactor(native): extract emit_pts_alias_edges params into PtsAliasCtx struct
* fix(wasm): sort call targets by confidence before emit to match native engine
* fix(bench): add 2 warmup runs and raise INCREMENTAL_RUNS to 5 for incremental tiers
* ci(bench): add per-PR perf canary for extractor/graph/native 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
* fix(perf): plumb symbolsOnly through parseFilesWasmInline to skip analysis visitors
* fix(perf): scope runPostNativeCha to changed files on incremental builds
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
* fix(native): add post-pass phase timings to result.phases
Times each JS post-pass in tryNativeOrchestrator and exposes the
measurements in BuildResult.phases:
- gapDetectMs — dropped-language gap detection + backfill
- chaMs — CHA expansion (interface dispatch)
- thisDispatchMs — this/super dispatch WASM re-parse (was already
tracked but now properly named alongside the rest)
- reclassifyMs — scoped role re-classification after edge insertion
- techniqueBackfillMs — technique-column UPDATE on native-written edges
Previously only thisDispatchMs was reported, causing wall-clock vs
phaseSum to diverge by 1.1s+ on 1-file rebuilds and making benchmark
regressions undiagnosable from committed history.
Updates update-incremental-report.ts to render the new phases in a
collapsible details block under each engine's 1-file rebuild section.
Closes #1434
* fix(perf): correct INLINE_BACKFILL_THRESHOLD docstring; raise threshold for required-tier grammars
The docstring claimed pool cost was "amortised over enough parse work" —
measurements show IPC overhead scales linearly (~55–64ms/file pool vs
~8–10ms/file inline). The real motivation is crash safety for exotic WASM
grammars (#965); JS/TS/TSX (required-tier, used in all this-dispatch
backfill calls) have never triggered the V8 fatal crash class and are safe
to run inline.
Raise threshold 16 → 32 to keep typical this-dispatch batches (≤ 18 files
on the codegraph corpus) on the inline fast path. Exotic-language drops are
almost always well under 32 files and also benefit from the inline path
without meaningful crash risk increase.
Closes #1435
* fix(perf): guard post-native passes against unnecessary work on 1-file incremental rebuilds
On 1-file native incremental builds, two JS post-passes ran unconditionally
even when they had no work to do:
- `backfillNativeDroppedFiles`: called whenever changedCount > 0, even when
detectDroppedLanguageGap returned an empty gap. Gate now checks
gap.missingAbs.length > 0 || gap.staleRel.length > 0 directly, matching
backfillNativeDroppedFiles's own internal early-exit guard.
- Node/edge COUNT(*) re-count: ran unconditionally after all post-passes even
when none of them wrote any edges. COUNT(*) over 50K+ edge tables is
non-trivial, especially via the NativeDbProxy napi-rs round-trip. Now gated
on postPassWroteData (backfill | CHA edges | this-dispatch edges).
Closes #1454
* chore(types): remove dead protoMethodsMs field and stale comment
The post-pass it timed (runPostNativePrototypeMethods) was deleted in
b5c03a29 when func-prop extraction moved to Rust (#1432). The optional
field was never set by any code path that survived the deletion.
Also remove the stale reference to "prototype-methods post-pass" from
the parseFilesWasmForBackfill docstring — only the this-dispatch
post-pass uses symbolsOnly now.
Closes #1432
* fix: class-scope field annotation typeMap keys to prevent cross-class collision
Field type annotations (`private repo: OrderRepository`) were seeded as bare
file-wide typeMap keys, causing `this.repo` inside `UserService` to resolve to
`OrderRepository` when both classes had a `repo` field (issue #1458).
Both extractors (TS `handleFieldDefTypeMap` and Rust `field_definition` branch)
now seed `ClassName.field` keys at confidence 0.9, matching the `CallerClass.X`
resolver fallback added in PR #1382. Bare keys are kept at confidence 0.6 as
fallbacks for single-class files or class expressions where no enclosing class
name is available.
Both engines change identically — parity preserved.
* fix(perf): update stale parseFilesWasmForBackfill docstring to reference threshold constant
The secondary docstring still described the old 16-value rationale
("engine-parity drop sizes"). Replace with a pointer to
INLINE_BACKFILL_THRESHOLD where the full rationale now lives.
* fix(resolver): check class-scoped typeMap key before bare fallback for this. receivers
When handleFieldDefTypeMap seeds ClassName.field at 0.9 and bare field/
this.field at 0.6, the resolver was still finding the bare 0.6 entry first
(effectiveReceiver lookup) and skipping the class-scoped check entirely.
The result: cross-class collision from issue #1458 persisted at runtime even
though the extractor emitted the right keys.
Fix: for this. receivers, check the class-scoped key (ClassName.prop) first,
then fall back to bare keys. Both TS (call-resolver.ts) and Rust
(build_edges.rs) resolvers are updated identically.
Fixes #1458.
* test(1458): add Rust multi-class field collision unit test and end-to-end integration test
- Rust: field_annotation_multi_class_seeds_separate_scoped_keys confirms that
two classes with identically-named fields produce separate class-scoped typeMap
keys at confidence 0.9 (mirrors the TS prevents-cross-class-collision test).
- Integration: issue-1458-cross-class-field-typemap.test.ts exercises the full
buildGraph → resolver path (WASM engine) and asserts that OrderService.run
resolves to Or…1 parent be38a6d commit 2f7236f
2 files changed
Lines changed: 8 additions & 8 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
521 | 521 | | |
522 | 522 | | |
523 | 523 | | |
| 524 | + | |
524 | 525 | | |
525 | 526 | | |
526 | 527 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
531 | 531 | | |
532 | 532 | | |
533 | 533 | | |
534 | | - | |
535 | | - | |
536 | | - | |
537 | | - | |
538 | | - | |
539 | | - | |
540 | | - | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
541 | 538 | | |
542 | 539 | | |
543 | 540 | | |
| |||
549 | 546 | | |
550 | 547 | | |
551 | 548 | | |
552 | | - | |
| 549 | + | |
553 | 550 | | |
554 | 551 | | |
555 | 552 | | |
| |||
665 | 662 | | |
666 | 663 | | |
667 | 664 | | |
| 665 | + | |
668 | 666 | | |
669 | 667 | | |
670 | 668 | | |
| |||
912 | 910 | | |
913 | 911 | | |
914 | 912 | | |
| 913 | + | |
915 | 914 | | |
916 | 915 | | |
917 | 916 | | |
| |||
0 commit comments