- Added `NodeSpan` struct and `nodeChainAt` function in `pi-ast` with native bindings to query AST node chains by line.
- Replaced regex-based annotation parsing with tree-sitter node queries and addedconstruct relocation validation.
- Implemented opener-escape landing correction for inserts anchored on block openers along with warning messages.
- Added comprehensive unit and integration tests covering node chain resolution and escape behavior.
bazel test sandboxes stage only the crate's declared inputs, so the repository sweep found zero sources and tripped its own evidence guard ('corpus sample too small to be evidence: 0'). Skip on a missing or empty corpus; any non-empty scan still enforces the >40-file evidence floor.
collect_boundaries walked every node in the file even though the answer is
bounded by the visible window, which cost roughly twice the parse: on an
81KB source the walk was 8.95ms against a 4.32ms parse, and on 1MB it was
138.8ms against 87.6ms.
A node contributes a boundary only when one of its own endpoint lines is
visible, and both of those lines lie inside its raw row span. Every
descendant's span is contained in its ancestor's, so a span holding no
visible line rules out that node and everything beneath it. Skip such
subtrees with a binary search over the merged visible ranges.
The test is the raw span, not endpoint visibility: a node whose span merely
straddles the window has both endpoints outside it yet can contain a child
that opens exactly on a visible line. The raw span is also conservative
relative to node_content_end_line, so the prune needs no reasoning about
that newline adjustment.
Equivalence is proven differentially rather than argued: the pre-prune walk
is retained under cfg(test) and compared for exact Option<Vec<u32>> equality
across 4827 .ts/.py/.rs files and 38,616 comparisons over eight window
shapes, including whole-file-visible, past-EOF, disjoint ranges, empty range
lists and files that fail to parse. Zero mismatches. root.has_error() is
still evaluated on the whole tree before the walk, so pruning cannot change
a None verdict.
Measured on the built addon with a mid-file 40-line window, medians of 20,
against the parse cache alone: 81KB 13.4ms -> 4.45ms cold and 9.04ms ->
0.149ms warm; 1.06MB 188.1ms -> 55.8ms cold and 131.7ms -> 0.440ms warm.
`enclosing_block_boundaries`, `block_range_at` and `summarize_code` each
re-parsed the whole file on every call. The results are not cacheable —
boundaries depend on the caller's visible ranges, which differ per call —
but the `tree_sitter::Tree` is, so cache that instead and hand out
`ts_tree_copy` clones.
Keyed on (xxh64 of the source, source length, language). The hash is a
bucket selector only: a hit re-verifies the stored source against the
request byte-for-byte before returning the tree, so a collision costs a
re-parse and can never yield a tree built from other content. Language is
in the key because the same bytes parsed as TypeScript and as Python are
different trees.
Bounded at 12 slots and 4 MiB of retained source with LRU eviction;
sources above 4 MiB are parsed but never retained. `Tree` is `Send` but
not `Sync`, so entries sit behind a `Mutex` that is held only for a map
probe, a byte compare and a refcount bump, never across a parse or walk.
Error trees are cached like any other: `has_error()` is a property of the
tree, so the callers' own checks reach an identical verdict from a cached
tree, and repeated "does this parse" probes get the speedup too.
Measured (M4 Max, bazel-built .node, median of 20, 1-40 visible):
read.ts 81 KB 13.34 ms -> 8.86 ms on repeat; 1 MB synthetic 225.7 ms ->
138.3 ms. Parser::new + set_language measured at 0.30 us against a
3.91 ms parse, so no parser pooling.
- Updated the nightly rust toolchain channel in rust-toolchain.toml to nightly-2026-07-28.
- Adopted slice chunking and multiple-of helper methods across native and vendor crates.
- Replaced option map adapters and conditional patterns with idiomatic combinators.
- Replaced the napi-cli/cargo-zigbuild/cargo-xwin/sccache build path with
Bazel: rules_rust + crate_universe over Cargo.lock, hermetic zig cc
toolchains (linux-gnu pinned to glibc 2.17, linux-musl), host Xcode for
darwin, and a repo-local hermetic clang-cl + llvm-ml + xwin toolchain for
windows-msvc (bazel/toolchains/msvc).
- All eight shipped addons build as //:natives-<target> via the release
transition in bazel/defs.bzl (opt, thin LTO, cgu=16, stripped, canonical
.node naming); scripts/bazel-natives.ts is the single driver for local
dev and CI.
- Rust validation moved to bazel test + clippy aspects (strict workspace
policy for opted-in crates, default lints elsewhere, mirroring cargo
semantics) and the rustfmt aspect; cargo stays as the dev-iteration
surface, with brush-core/brush-builtins promoted to workspace members
and excluded from cargo dev tasks to keep their historical scope.
- CI caches through an in-cluster bazel-remote action cache (TLS + basic
auth, cluster-internal only); GitHub-hosted runners never touch the
infrastructure and use an actions/cache-backed disk cache instead.
- Deleted the hand-rolled caching machinery: ci-target-cache,
ci-native-artifact-cache, ci-build-native, native-source-hash,
find-native-artifacts, restore-linux-native, native-prewarm workflow,
ensure-* toolchain actions, and all sccache/Swatinem wiring.
- Warm native rebuilds drop from ~20 minutes to seconds; a cold client
with a warm remote cache rebuilds the linux x64 pair in ~2.5 minutes.
- Reflowed compile_pattern/wrapper_template comments and while-condition to rustfmt's preferred layout.
- Made wrapper_template a const fn to satisfy clippy::missing_const_for_fn under -D warnings.
A pattern like `"@types/bun": $V` parses to multiple root AST nodes and was
rejected with "Multiple AST nodes are detected". When no explicit selector is
given, retry the fragment wrapped in a minimal single-node context (JSON: `{ <frag> }`
selecting `pair`), quoting bare metavars so JSON parses. Falls back to the
original error if the wrapped form still fails, so bad patterns are unchanged.
Applies to both ast_edit (compile_pattern) and ast_grep (compile_search_patterns).
- Enabled deduplication of byte-identical edits in `apply_edits` to prevent false overlap errors.
- Added logic in `ast_edit_blocking` to filter out duplicate rewrite matches before staging.
- Updated `apply_edits` to collapse multiple identical replacements into a single deterministic operation.
- Added regression tests for both `apply_edits` and `ast_edit_blocking` to ensure identical matches resolve cleanly.
- Added tree-sitter markdown support to resolve headings into full sections in `pi-ast`.
- Enabled block operations (`SWAP.BLK`, `DEL.BLK`, `INS.BLK.POST`) on markdown headings so they encompass the entire section, including nested deeper headings.
- Updated system prompt to guide agents in using structured markdown heading edits for plans.
- Fixed `plan-mode-guard` to correctly resolve local protocol options for subagents.
- Changed `select_folded_spans` to skip an unfold candidate that exceeded `unfold_limit` and continue processing the remaining BFS queue instead of breaking.
- Added a regression test in `summary.rs` proving a large `<style>` raw-text span no longer starves sibling HTML sections from unfolding.
- Updated the summary unfold-ceiling setting description to document that overflowed candidates stay folded while BFS continues.
Register tree-sitter-elisp in the shared AST language registry so
.el files infer the emacs-lisp grammar across blockRangeAt,
summarizeCode, astGrep/astEdit, and native aliases.
This fixes edit-tool block operations on top-level Emacs Lisp forms
instead of returning unsupported-language block errors.
- Add canonical emacs-lisp aliases and .el extension inference.
- Teach summaries to fold Lisp forms without grouping arbitrary lists.
- Map .el rendering and highlighting aliases through coding-agent/native.
- Cover defun, ERT, use-package, with-eval-after-load, pcase,
summary, astMatch, astEdit, and edit-tool insertion paths.
- Document the language and update package changelogs.
- Updated `block_range_at` to query a one-column span from `col` to `col + 1` instead of a zero-width point, preventing zero-width separator nodes from trapping resolution to enclosing scopes.
- Added regression tests for Swift computed properties and top-level declarations after blank lines so the resolver now returns the expected block boundaries.
- Documented the fix in the package changelog under the `Fixed` section for native tooling block-range operations.
- Running job labels now shimmer in the TUI to provide a dynamic visual indicator of activity.
- Adjusted cache key to account for shimmer animation, ensuring it updates at 30fps instead of the 12.5fps spinner cadence.
- Suppressed job ID display when the job label is identical to its ID, avoiding redundant information.
blockRangeAt inferred no language for files like `zshrc`/`.zshrc`/`.bashrc`
because `Path::extension` returns `None` for bare and dotfile names alike,
so the edit tool's `replace block` / `insert after block` ops were
permanently unresolvable on them — an agent retrying the block op would
loop on the same "could not resolve a syntactic block" error.
from_extension now maps known shell rc/profile basenames (zshrc, zshenv,
zprofile, zlogin, zlogout, zsh_aliases, bashrc, bash_profile, bash_login,
bash_logout, bash_aliases, profile, kshrc, mkshrc, shrc; with or without a
leading dot) to the bash grammar, alongside the existing Makefile/Dockerfile
basename handling. Adds a block-resolution regression test over a zsh
`if [[ … ]]; then … fi` block in `zshrc`/`.zshrc`/`.bashrc`.
- Added tree-sitter `enclosing_block_boundaries` API with line range models.
- Added N-API `enclosingBlockBoundaries` bridge and exported JS declarations.
- Replaced matching-bracket context resolution with source-aware block context in read and diff flows.
- Passed source path through diff/read generators to surface native block boundary previews.
- Added `replace block N:` syntax in grammar/parser/tokenizer/types, emitting `kind: "block"` edits with empty-body checks.
- Added block-resolution in hashline apply/recovery, wiring `BlockResolver` to resolve edits with throw/drop unresolved behavior.
- Added native `blockRangeAt` support and exported block range/options types through pi-ast, pi-natives, and JS bindings.
- Added coding-agent resolver wiring plus internal visibility/exports updates and new parser, patcher, and setup-wizard tests.
- Added `unfoldUntilLines`/`unfoldLimitLines` options to progressively reveal nested elidable spans breadth-first instead of collapsing everything behind the outermost elision.
- Added `minTotalLines` setting to skip summarization for short files, returning verbatim content instead.
- Added `:` selector support to `search` paths for constraining matches to specific line ranges.
- Extracted `parseLineRanges`/`parseLineRangeChunk`/`isLineInRanges` from `read.ts` into shared `path-utils.ts`.
- Added a workspace dependency catalog in `Cargo.toml` for internal and external crates.
- Replaced explicit dependency specs and pinned versions with workspace references in crate manifests.
- Removed inline dependency overrides such as feature flags, renames, and package metadata.
- Consolidated platform-specific target deps (`libc`, `winreg`, `windows-sys`, `parking_lot`) to workspace entries.
- Added `pi-ast` as a new crate with public `language`, `ops`, and `summary` modules and exported `SupportLang`.
- Added AST summary and rewrite APIs in `pi-ast`, including language inference, overlap-safe edits, and span normalization.
- Added `pi-shell` crate with new `Shell` and cross-platform `Process` APIs, session execution, and cancellation-aware runs.
- Updated `pi-natives` to use shared `pi_ast`/`pi_shell` APIs, switched to local path deps, and moved minimizer assets to `pi-shell`.