Skip to content

v0.1.7

Choose a tag to compare

@github-actions github-actions released this 21 May 16:47
· 69 commits to main since this release

[0.1.7] — 2026-05-21

Post-0.1.6 cleanup pass: targeted perf wins across the structural,
scripted, and commit pipelines plus a round of error-schema and DRY
hardening surfaced by an internal review.

Performance

  • Structural mode is no longer per-file recompiled. The tree-sitter
    Query, capture-index table, and rewrite template are built once
    per invocation as a shared CompiledStructural; only the
    Parser + QueryCursor are per-thread. plan_structural_rewrite
    now drives per-file work via rayon par_iter().map_init(...). The
    rewrite template is pre-parsed into Vec<TemplatePart::{Literal, Capture { index, name }}> so capture-name lookup at match time is
    an index hit, not an O(N) byte scan.
  • commit::stage_all parallel. Per-file write + sync_all runs
    on rayon workers so kernel fsync overlaps; commit phase stays serial
    for deterministic rollback. Local measurement: 500-file apply ~9ms,
    1000-file ~15ms (NVMe, ext4).
  • plan_rewrite_scripted parallel. Each rayon worker gets a fresh
    sandboxed Rhai Engine via ScriptRewriter::fresh(); the compiled
    AST is shared. Enables sync feature on the rhai dep (Rc → Arc
    internally).
  • Single-pass match count in rewrite_text. Drops the redundant
    find_iter().count() scan that ran before replace_all and the
    before.to_owned() clone on every changed file.
  • Cheaper regex convergence probe. The per-file idempotency check
    uses Regex::find_iter().count() on the post-image instead of a
    full second replace_all round.
  • HashSet parent-dir dedup in fsync. best_effort_fsync_parents
    was O(N²) via Vec<PathBuf>::iter().any(...); now HashSet<&Path>.
  • Planner peak memory ~halved. FileChange::before (full
    pre-image per changed file) is dropped — it was set during diff
    rendering and never read afterward. Worst-case (max_bytes 10 MiB ×
    max_files 1000) drops ~10 GiB from peak resident memory.
  • Hot-path micro-allocations trimmed. label_for_path skips the
    \/ scan on Unix where it can't matter; structural splice
    pre-reserves source.len() + (replacement - range) delta so the
    output String doesn't realloc when matches grow text;
    emit_node writes literal-terminal predicates with write!
    instead of three intermediate format! Strings per terminal.

Changed

  • Wire JSON field order shifted. Plan / Apply / Check reports now
    emit outcome, files_scanned, total_matches as the shared
    prefix (extracted into JsonHeader with #[serde(flatten)])
    followed by the mode-specific count. JSON members are unordered
    per spec so this is semantically identical, but the wire bytes do
    reorder. Snapshot fixtures updated.
  • Error::kind() returns ErrorKind directly. The
    Error → ErrorKind mapping lives on impl Error instead of in
    json::error_kind(); the JSON module re-exports ErrorKind from
    error for back-compat.
  • Language::from_name returns Result<Self, Error> instead of
    Option<Self>. Unknown names surface as Error::UnknownLanguage
    from the library boundary.
  • RewriteOutcome shape. Dropped the before field — the caller
    already owns the pre-image. changed() method removed; compare
    outcome.after != before at the call site if needed.
  • FileChange.before field removed. Was #[serde(skip)] and
    only used to render the diff during planning; apply_changes has
    always read from after. Pre-1.0 break for anyone reading the
    field directly; FileChange.diff carries the rendered diff.
  • handle_plan_error returns Result<u8> instead of u8 so a
    failed JSON serialize during error reporting is now observable
    rather than silently dropped.
  • CLI options grouped into substructs. Output (--diff /
    --json / --quiet / --verbose), guards (--at-least /
    --at-most / --allow-non-convergent / --max-bytes /
    --max-files), and structural (--lang / --query / --ast)
    are now #[command(flatten)]-ed substructs. CLI surface
    byte-identical; only the in-code access path changes
    (cli.output.json, cli.guard.max_files, etc.).
  • SiblingKind enum drives recast-sibling filenames. The
    .{target}.recast.{token}.{nonce} token (bak / tmp) is now
    emitted and parsed through one SiblingKind::as_str /
    SiblingKind::from_token pair so the two sides cannot drift.

Added

  • Error::InvalidThreads + Error::ThreadPool variants replace
    the synthetic Error::Io { path: empty } wrappers that
    parallel::build_pool used to emit for non-IO failures. JSON
    surfaces them as invalid_threads / thread_pool error kinds.
  • ScriptRewriter::fresh() — sibling rewriter, new sandboxed
    Engine, shared AST. Designed for per-rayon-worker construction.
  • template_scan module — shared $NAME / ${NAME} / $$$NAME
    placeholder scanners. The regex convergence probe and the
    structural pattern preprocess + template parser now share one
    identifier grammar so the three byte walkers can't drift.
  • plan::read_text_or_skip_binary — single helper that wraps the
    metadata + max-bytes check + read_to_string + UTF-8 skip. Used by
    both plan_rewrite and plan_structural_rewrite.
  • Multi-file structural criterion bench
    (bench_plan_structural_rewrite) covering 10 / 100 / 500 file
    fixtures.

Internal

  • commit::recover_sweep reports ignore::Error via the existing
    Error::Walk variant instead of building a synthetic
    Error::Io { path: empty }.
  • commit extracted parent_dir / remove_nonced helpers; rollback
    loops collapsed.
  • Binary's Language::from_name / dispatch(plan) helpers consolidate
    the apply / check / diff trailer duplicated between run and
    run_structural.
  • Binary Cli adds min_matches(), paths_as_pathbufs(),
    recover_paths(), and acquire_workspace_lock_for(&cli) helpers
    so the recurring Some(at_least.unwrap_or(1)), path conversions,
    the --recover clap-workaround fold, and the lock-root probe
    each live in one place.
  • CompiledStructural::apply collects matches into a named
    struct Hit { start, end, replacement } instead of the positional
    (usize, usize, String) tuple it had before — readability only.

Documentation

  • CHANGELOG.md + PLAN.md §7.1 + docs/src/json-schema.md
    brought back in sync with the wire format (shared-header field
    order; full ErrorKind vocabulary including
    invalid_threads / thread_pool).
  • AGENTS.md §11 records the harness-classifier workaround for
    git commit: split git add and git commit into separate
    shell calls; do not chain them with && + heredoc.