v0.49.4
v0.49.4
Patch release: callgraph precision and disk-write reduction, daemon-outage hardening, edit-failure recovery steering, and log hygiene. No agent-visible schema changes.
Callgraph
- More precise Rust method dispatch: calls through
self.fieldnow resolve to the field's actual type when it is provably a local concrete struct, instead of name-matching any same-named method in the workspace — and when the field's type is known but not locally provable, the guess is suppressed rather than wrong. Measured on this repository: 331 wrong-by-mechanism approximate edges removed, 177 upgraded to precise edges. Contributed by @onedotmint (#196). - Callgraph stores write far less: SQLite WAL tuning, position-aware row diffing, and checkpoint policy cut disk writes by ~85% per edit burst. Combined with the borrow-only worktree fix and repair-loop fixes from this line's earlier patches, steady-state daemon disk writes dropped from ~100 MB/min to ~2 MB/min on a busy multi-project machine.
- Worktrees no longer trigger callgraph rebuild storms: linked worktrees are strictly borrow-only readers of the parent's store, and a denied writer path now reports a terminal
callgraph_unavailablestatus instead of retrying forever.
Edit failures now steer recovery
match not founderrors include the nearest near-miss candidate: its line range, a bounded excerpt, and the first diverging line as a- expected/+ actualpair — plus a pointer to the line-range escape hatch ({startLine, endLine, content}) when a candidate is found. Below the similarity floor, the error reports the file's line count instead of a misleading candidate.ambiguous matcherrors list the 1-based occurrences with their line numbers (capped at 8), so picking anoccurrenceno longer requires a re-read.- Interrupted foreground bash calls now state their disposition explicitly ("no background task exists for this call — re-run; don't poll"), and unknown-task-ID status checks steer to re-execution instead of polling placeholder IDs.
Daemon reliability
- The health reply path never spawns subprocesses anymore. Per-root repair annotations previously derived cache keys with a git probe per unmemoized root on the channel-0 health path; under host exec stalls this pushed replies past the supervisor deadline and read as module death. Keys now come from an in-process derivation map (try-read only), with
callgraph_repair_roots_annotated/callgraph_repair_roots_totalmaking the annotation coverage explicit. - Plugins stop hammering dead roots: bind attempts against a reclaimed project directory (rejected by the daemon as nonexistent) now suspend further attaches for that root — a
.reclaimedmarker or repeated rejection makes the root dormant until the directory reappears. This ends the 40/sec rejection loops that flooded shared daemon logs. - The daemon-side bind-rejection log line is rate-limited per distinct message (repeats within 60s are summarized as
(repeated Nx in last 60s)), keeping shared logs readable during a loop without losing the rate signal. - AFT log files rotate and expire properly: 32 MiB rotation for the plugin log, liveness-aware reaping of dead-process logs, and noisy per-lock/per-repair lines demoted or rate-limited. A busy day of daemon logs now measures in tens of MB, not GB.
Fixes
- Inspect scope caches for deleted checkouts (reclaimed worktrees) are garbage-collected automatically; a stale-cache sweep reclaimed 21.5 GB on a heavy mason machine.
- External
aft_searchscope paths preserve their requested form in results. - Interactive search waits are bounded while background index builds run.
- Empty placeholder items inside
edits[]arrays are stripped (GPT sentinel-item shape, second round of #171). - Background completion nudges use opaque references end-to-end, fixing a replay edge after daemon restarts.
- JSON named-import handling in the dead-code scanner no longer marks
.jsondefault imports as unresolved (#1126 class). AFT_CACHE_DIRis honored consistently across all cache-root resolution sites (previously silently ignored in two of four).
Test infrastructure
- Four flake classes fixed at root: an LSP test helper that discarded drained events (losing back-to-back notifications), a completion-push latency bound with zero headroom over the push mechanism's own coalescing tick, the keychain-sensitive TLS test isolated into its own serial gate phase, and Windows parity envelopes masking live counters and 8.3-vs-long path spellings.