Skip to content

Releases: bermudi/stitch

v0.16.0 — templates can include repo files via include()

Choose a tag to compare

@github-actions github-actions released this 17 Sep 00:44
Immutable release. Only release title and notes can be modified.
d42d3a5

Added

  • Templates can include repo files: {{ include("path/from/repo/root") }}.
    The motivating case is the AGENTS.md fan-in (one shared "hub" file linked
    into many agent config dirs): a tool that reads exactly one file and has no
    import syntax previously forced a choice between linking the hub verbatim
    or forking it — and a fork silently stops receiving hub edits. A .tmpl
    source can now compose hub + delta ({{ include("agents/AGENTS.md") }}
    followed by tool-specific lines), rendered into the staged file the target
    links. Include semantics are deliberately narrow: the argument must be a
    safe repo-relative fragment (no .., no leading /), must not live under
    .stitch/ or .git/, must reach a regular file without passing through a
    symlink (one hop only, the same rule as add --source), and must be valid
    UTF-8. Included text is embedded verbatim — never rendered — so recursion
    cannot occur and .tmpl arguments are rejected outright. Fresh renders
    re-read included files on every apply/diff/doctor, so editing an
    included hub shows up as a content diff and a doctor staging-drift
    warning until the next apply converges it.

v0.15.2 — self-update no longer fails with Text file busy

Choose a tag to compare

@github-actions github-actions released this 12 Sep 18:36
Immutable release. Only release title and notes can be modified.
976b658

Fixed

  • self-update no longer aborts with Text file busy when verifying the
    staged binary.
    The installer held its own O_RDWR staging handle open
    while trying to execute the staged file, and Linux refuses execve on any
    file with an open write descriptor (ETXTBSY) — so every install failed
    with staged binary cannot start on this host: Text file busy, left the
    previous binary untouched, and suggested retrying, which could never help.
    The write handle is now dropped and the staged file reopened read-only
    before the probe runs. Production always used the real exec-based probe,
    but the tests stubbed it out, which is why the bug survived review; a new
    regression test mirrors the ETXTBSY precondition by rejecting the install
    while any write descriptor still points at the staged file.

v0.15.1 — doctor sources-only suppression and add --source files→sources migration

Choose a tag to compare

@github-actions github-actions released this 26 Aug 04:17
Immutable release. Only release title and notes can be modified.
9ea39e0

Fixed

  • doctor no longer warns "directory is empty" on sources-only stores. A
    sources-only store (the v0.14 fan-in consumer shape) has an intentionally
    empty store dir — every file it links lives elsewhere in the repo, declared
    via sources. The empty-store check keyed off directory geometry, so it
    fired a false-positive warning whose hint ("add files or remove the store")
    pointed at the wrong remediation. It now suppresses when the store (or any
    named target) declares sources, and the hint for a genuinely empty store
    mentions sources as a third option.
  • add --source now migrates an existing files entry in one atomic write.
    Adding a sources entry for a target name that was previously a files
    entry used to error with "sources key is also listed in files; remove one" —
    the only path was hand-editing state.toml or a remove + add --source
    round-trip that briefly unlinks the live target link. add --source now
    drops the matching files entry and inserts the sources entry in a single
    state.toml write (the files→sources fan-in conversion the v0.14 plan
    envisioned), prints a "migrated from files entry" notice, and reports
    migrated_from_files: true in JSON. The filesystem link is untouched; the
    next apply repoints it.

v0.15.0 — add stitch self-update command

Choose a tag to compare

@github-actions github-actions released this 25 Aug 02:55

Added

  • stitch self-update — checks the latest stable GitHub release, verifies
    the matching SHA-256 sidecar, narrowly validates the archive and ELF target,
    runs the staged binary's --version to catch incompatible builds, then
    atomically replaces the running executable without dropping ordinary
    ownership/mode metadata. --check is metadata-only, and JSON output reports
    up-to-date, newer, update-available, or updated.

Fixed

  • Post-rename directory-sync failure no longer reports a failed update.
    When the atomic rename succeeds but fsync on the parent directory fails,
    the command now exits 0 with status: "updated" and a warning in the JSON
    envelope (and warning: on stderr in text mode). Previously this returned a
    non-zero exit with no success JSON, which could mislead an agent into
    retrying a no-op or reporting a failed update despite the binary being
    installed.

v0.14.2 — harden $HOME displacement, FIFO hang, and quadratic config checks

Choose a tag to compare

@github-actions github-actions released this 25 Aug 03:02
3fcb68c

Fixed

  • stitch add ~ can no longer relocate $HOME. add now rejects the resolved home directory (and its ~/-spelled variants) before any filesystem mutation, with a clear usage error. Previously the adopt path moved $HOME and then failed an internal home-identity check whose rollback was provably unable to restore it, leaving the entire home stranded inside the repo.
  • apply --force can no longer displace $HOME. A whole-directory store with target = "~" under --force previously renamed $HOME to $HOME.bak and replaced it with a repo symlink, reporting success. Direct apply and plan execution now refuse to back up and link the resolved home directory.
  • apply no longer hangs on a FIFO config/state file. The snapshot loader opens with O_NONBLOCK, so a no-writer FIFO fails fast with the same non-regular-file error other commands already produce, instead of blocking forever on open(2).
  • Link-collision and plan-source resolution are no longer quadratic. check_link_path_collisions, check_link_name_collisions, and same-store target-overlap validation now use index/trie-style ancestor lookups; plan construction resolves each store's link sources once. A 20,000-file patterns = ["**"] store now resolves in ~1.4s (was >120s).

v0.7.1 — 2026-06 trust-review safety fixes (P0/P1/P2)

Choose a tag to compare

@github-actions github-actions released this 25 Aug 02:55
2bc8aa2

Trust & safety

Closes all seven remaining items from the 2026-06 trust review. Each
upholds the core contract: never surprise the user with data movement,
exposure, or silent replacement.

  • P0 — nested ignore promotion no longer writes through foreign parent
    symlinks.
    A foreign_ancestor guard in apply_file_entry rejects
    nested link creation when a parent symlink resolves outside the repo,
    for both dry-run and real operations.
  • P1 — edit rejects path traversal. match_target_to_source
    validates relative target fragments with is_safe_fragment, rejecting
    . and .. components before joining onto the store directory.
  • P1 — apply --plan rejects parent symlinks that resolve into the
    repo.
    resolved_target_points_into_repo in plan_exec blocks
    executable plans from clobbering repository content through symlinked
    parents, closing the TOCTOU gap from the P0 fix.
  • P1 — ignore promotion preserves source symlinks. resolve_files
    now accepts is_symlink() entries, and create_link_to_entry links
    to the source symlink path without canonicalizing — so relative,
    absolute, and dangling symlinks match whole-dir behavior after
    promotion to file mode.
  • P1 — doctor allows mutually-exclusive when targets sharing a
    path.
    WhenClause::are_compatible makes duplicate-target detection
    when-aware, matching SPEC's documented laptop/server multi-target
    layout. Genuine duplicates (compatible or absent when) are still
    reported.
  • P2 — init/migrate overwrite guards detect dangling symlinks. The
    refuse-existing-path guards now use symlink_metadata instead of
    Path::exists, so a dangling state.toml/stitch.toml/.bak
    symlink is refused rather than silently replaced by a regular file.
  • P2 — migrate validates the split state before writing. A v0.2
    entry the new validator rejects (e.g. files = ["./bashrc"]) now
    fails fast before any file is written or the legacy config is backed
    up, instead of stranding the user with unloadable state.

All changes are covered by unit + integration tests. cargo fmt,
cargo clippy -D warnings, and the full suite (91 unit + 213 CLI) are
clean.

v0.7.0 — agent interface — JSON envelope, plan/apply --plan, error taxonomy

Choose a tag to compare

@github-actions github-actions released this 25 Aug 02:55
997c765

Features

  • Typed error taxonomy (StitchError / FailureClass): 14 stable exit
    codes (0–13) with class names, messages, and resolution hints. Text mode
    prints hint: lines; --json envelopes include class, code, message,
    hint, and details.
  • JSON envelope with a stable schema: 1. Global --json flag on
    status, list, doctor, prune, render, apply, diff, and plan.
    The envelope (including error) goes to stdout; the exit code stays honest;
    stderr is reserved for hook/subprocess output.
  • stitch render <store>/<file>: read-only in-memory template render to
    stdout; --json emits {source, link_name, sha256, content}.
  • stitch plan: capture apply's exact op list as a stitch/plan JSON
    file. Includes staged-render hashes, config hash, and platform fingerprint.
    Text mode emits the raw plan; --json wraps it in the envelope.
  • stitch apply --plan <file>: verbatim plan execution with preflight +
    per-op precondition re-checks + stale-plan detection. Aborts at the first
    failed op. --dry-run runs validation with no mutations. --plan is
    incompatible with --only and --force (usage error, exit 2).

Trust & safety

  • Plan files are untrusted input. apply --plan validates every op
    against the pinned config: path traversal rejection, backup path validation
    (same directory as target), source under repo, target under configured
    target, and .stitch/render/ gitignore guard.
  • No plaintext in plans. stage_render ops pin only the SHA-256 of the
    approved render; rendered content is not stored in the plan.
  • Stale-plan refusal. Config edits, state.toml edits, platform drift, or
    render-hash drift cause apply --plan to exit 12 before any mutation.
  • Honest exit codes for agents. diff/plan exit with the conflict/error
    class; apply aggregates per-entry classes (single → that code, multiple →
    11); doctor exits 13 on error-level findings.

Internals

  • Refactored compute_plan. apply, diff, plan, and the JSON/text
    renderers all consume the same Plan struct, so the two views cannot drift.
  • New dependencies: serde_json, sha2.

v0.6.0 — templates, edit <entry>, import, staged-render trust foundation

Choose a tag to compare

@github-actions github-actions released this 25 Aug 02:55
a0b533c

Features

  • Templates. Files ending in .tmpl are rendered with minijinja and
    symlinked from .stitch/render/<store>/... (inside the repo, so
    points_into_repo / remove / prune keep working). Context:
    {{ hostname }}, {{ os }}, {{ arch }}, {{ distro }}, {{ shell }},
    {{ vars.key }}, {{ env("VAR") }} (hard-fail) / {{ env("VAR", "default") }}.
    Whole-dir stores containing any .tmpl promote to per-file links. State
    records source names; resolve_entry strips the suffix for the link target.
  • Trust foundation for staging. init appends .stitch/render/ to
    .gitignore and creates the staging root at 0700; renders are 0600.
    doctor errors if the gitignore entry is missing. Rendering is in-memory
    only (no tempfile under a default umask).
  • diff content dimension for templated entries: fresh render vs staged.
    Staging is hash-gated (preserves mtime) and reconciled on apply/remove.
    apply also removes stale stitch-owned file-mode links when a source is
    deleted or renamed; foreign symlinks remain untouched.
  • stitch edit <entry> opens a store or target's repo source (the .tmpl
    when present) — never the staged render. Config-based; works pre-apply.
  • stitch import scans for existing repo-pointing symlinks and registers
    them in state.toml (shares scan.rs with prune).

v0.5.0 — --repo flag + STITCH_REPO env var for out-of-repo invocation

Choose a tag to compare

@github-actions github-actions released this 25 Aug 02:55
a7745e1

Features

  • Run stitch from outside the repo. A global --repo <path> flag and
    STITCH_REPO env var let you operate on a repo without cd-ing into it.
    Precedence: --repo > STITCH_REPO > upward cwd walk (unchanged default).
    An override must point at a directory containing .stitch/ — a typo is
    rejected, not silently applied to the wrong directory. init is cwd-anchored
    and ignores both (it creates a new repo in the current directory).

v0.4.1 — trailing-slash fix

Choose a tag to compare

@github-actions github-actions released this 25 Aug 03:02
9a5d846

Fixes

  • stitch add with a trailing slash now works. stitch add ~/.config/alacritty/
    used to fail at the link step with a confusing rollback error. Root cause:
    symlink(2) rejects a linkpath with a trailing slash (the kernel treats it as
    "must resolve to an existing directory", but the path doesn't exist yet when
    we're creating the symlink). expand_home now strips trailing slashes from
    its result, fixing the issue for every caller (add, apply, scan,
    config load). Root (/) is preserved.