Skip to content

Tags: max-sixty/worktrunk

Tags

v0.79.0

Toggle v0.79.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
changelog: correct the 0.79.0 entries before the tag (#4205)

Verification passes against the v0.78.0 binary found five inaccuracies
in the 0.79.0 changelog. The tag publishes this text as the GitHub
release body, so each is corrected here rather than after the fact.

- **The `--output` entry described behavior 0.78.0 already had.**
Replacing an existing destination is not new; what #4204 changed is that
`--output` pointed at the config being migrated is no longer refused.
The entry now claims only that, and moves out of the shared bullet with
#4153.
- **`wt step prune` shares the integration-check defect**, not just `wt
remove` — both reach it through the same check, so both are named.
- **Any earlier command could poison the `has-added-changes` cache**,
not only the removal itself, so the condition reads "a Worktrunk command
run from a subdirectory" rather than "the command".
- **Only `submodule.<name>.ignore = all` triggers it.** `untracked` and
`dirty` leave a submodule pointer bump visible, so the value is named.
- **The headline "no longer changes what Worktrunk reads" overstated
completeness** — `git status` has no plumbing equivalent. Scoped to the
prompts and the integration check, which is what this release fixes.

Two further changes: "entirely" is restored to the `diff.relative`
condition, where a repro confirmed it load-bearing (a branch also
touching a file inside the subdirectory is retained), and `wt config
create`'s dangling-symlink fix from #4204 gets the entry it was missing.

Entry order within Fixed follows reader interest; no code changes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

v0.78.0

Toggle v0.78.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Release v0.78.0 (#4138)

Version bump to 0.78.0 plus the changelog section for this release, a comment-preservation fix the release review turned up, a PTY test-filter fix, and merges of `main` (commits that landed during the review, including #4124, #4122, #4141, #4140 and #4145).

**Bump level**: minor. `cargo semver-checks` reports 2 breaking library changes — a new `leftover_branch` field on the `GitError::WorktreeCreationFailed` struct variant, and `DEPRECATED_TEMPLATE_VARS` removed. Pre-1.0, so a breaking change takes a minor bump.

## Comment-preservation fix

A config save — declining the commit-generation offer, say — dropped the trailing comment on every value it rewrote. That was already true in v0.77.0 for a line the save itself changed (`skip-commit-generation-prompt = false  # note` → `true`, reproduced on the v0.77.0 binary). #4080 widened it to lines the command never touched: retired template variables now migrate on every load, so the next unrelated save rewrote any line naming one. #4080's own PR said this "needs a call before merge" and merged without one.

- `replace_keeping_decor` keeps the value's decor (spacing after `=`, trailing comment) when `merge_tables` replaces a scalar. Comments *above* a line sit on the key and were never at risk.
- `main`'s #4120 independently reworked the inline-table arm and moved a key's leading comment onto the rewritten header, but documented that a trailing comment after the closing brace "is still dropped". `replace_inline_with_table` now carries it after the header's `]`, so both of its callers keep it.
- Tests: the existing comment-preservation test goes back to a retired name (the only fixture that exercises the replacement) and adds a comment on the mutated line; a new test snapshots and re-parses an inline `[projects]` entry. Each fails without its half of the fix.

Not fixed, and identical on v0.77.0: a save deletes an explicitly written *default* value (e.g. `skip-commit-generation-prompt = false  # note`) as a stale key, comment included.

## `--help` correction

#4104 moved the OpenCode plugin path to etcetera's `Xdg` strategy, which uses `$XDG_CONFIG_HOME` only when absolute — a deliberate, documented choice — but `wt config plugins opencode --help` still said install mirrors OpenCode's precedence. OpenCode reads a relative value as-is, so the help now says an empty `$OPENCODE_CONFIG_DIR` and an empty or relative `$XDG_CONFIG_HOME` are ignored.

## Data-loss surface review

Independent finders swept the full diff, including the five commits merged from `main`. Nothing holds the tag; candidates were adjudicated against v0.77.0 binaries:

- **Fixed in this release, reproduced on v0.77.0**: #4104 stops `wt config shell install` (with Nushell) deleting a `nushell/vendor/autoload/wt.nu` under the current directory when `$XDG_CONFIG_HOME` is empty or relative; #4120 stops a save or migration from writing an inline section's comment inside `[commit]` brackets, which made the config unparseable, and from dropping unknown keys.
- **`wt step prune --min-age` doesn't apply where no reflog exists** — pre-existing, proven by building the pre-#4077 binary. `git clone --bare` and `git fetch` into `refs/heads/*` write no reflog, so in the bare layout `tips-patterns.md` recommends every branch starts unprotected. Docstring corrected here; behavioral fix is follow-up.
- **`wt config update --output <path>` replaces any named file whole**, no existence check or prompt — pre-existing, follow-up.
- **`wt config shell install fish --yes` replaces a hand-written wrapper at the XDG path** — accepted ("ownership is the path", predates #3602), newly reachable only for users with `$XDG_CONFIG_HOME` set. The FAQ's delete inventory now cross-references it.
- **`--var <retired-name>` binds a name nothing references** after #4080 — inert, no shipped usage; follow-up.
- **#4135 migrates nothing**: an existing oh-my-pi hook stays put, `pi uninstall` points at `omp uninstall`. `PI_CODING_AGENT_DIR='~/x'` is used literally (Pi expands `~`) — creates a stray directory, deletes nothing; follow-up.

## Changelog

37 entries; each new or edited entry was re-verified against v0.77.0 until a pass came back clean (six passes in all). Passes caught, among others: the OpenCode `-x` recipe was wrong since 0.76.0, not 0.75.0; #4080's JSON reaches every hook, not just `pre-start`/`post-start`; a quoted git error string git never prints; a copy-button overlap that existed only inside this release; #4104's relative-`XDG_CONFIG_HOME` OpenCode change described as a fix when OpenCode reads that path as-is; and which commands a broken inline section stopped. **Fixed** leads with the data-loss entries.

## Open PRs checked against v0.77.0

- **#4124** fixed a bug new in this release, caused by #4080, and has landed and been merged in here. The hand-rolled template-variable scanner it extends is slated for removal; see its commit message.
- **#4112** — no data-loss regression vs v0.77.0; two small non-safety regressions from #4111 (an uncapped auto-staging file list, a narrow `Nothing to commit` refusal under `status.showUntrackedFiles = no`). Not merged, by decision.
- **#4122** (merged in) and **#4123** — pre-existing bugs, identical on v0.77.0.

## PTY filter fix

`test (macos)` failed once on a shell's failed-`setpgid` diagnostic that landed straight after the capture's trailing `\x1b[0m`, with no newline between, so #4136's line-anchored filter missed it. The filter now also matches right after an SGR escape and keeps the escape; its test gains that shape and fails on the old pattern.

## Validation

- `cargo run -- hook pre-merge --yes` — 4930 tests passed, 1 skipped; clippy, fmt, doc-sync, snapshot and lockfile checks green
- Nightly on the original cut-from tip (https://github.com/max-sixty/worktrunk/actions/runs/35060617020): green across `full-tests` on linux/macos/windows, `feature-powerset`, all `release-target`s, `nix-flake`, `minimal-versions`, `link-check`

> _This was written by Claude Code on behalf of max-sixty_

🤖 Generated with [Claude Code](https://claude.com/claude-code)

v0.77.0

Toggle v0.77.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Release v0.77.0 (#4044)

Version bump to 0.77.0 plus the changelog section for this release.

**Bump level**: minor. `cargo semver-checks` reports 8 breaking library
changes (removed `DeprecationInfo::has_deprecated_patterns`,
`Repository::recent_commit_subjects`, `Repository::diff_stats_summary`,
`config::detect_deprecations`; arity changes on
`compute_migrated_content` and `Progress::record`). Pre-1.0, so a
breaking change takes a minor bump. `wt list --format=json` defaulting
to schema 2 is a user-facing break on its own.

**Validation**

- `cargo run -- hook pre-merge --yes` — 4831 tests passed, 1 skipped;
clippy, fmt, doc-sync, snapshot and lockfile checks green
- Nightly dispatched on the cut-from tip
(https://github.com/max-sixty/worktrunk/actions/runs/34272636821):
full-tests on linux, macos, windows and git-2.43 all green, alongside
release-target ×3, nix-flake, minimal-versions, crate-build, unused-deps
and link-check

**Changelog**

29 entries. Each was verified against its commit's diff by a separate
pass, which caught two uncovered fixes (#4033/#4034 uninstall
idempotency), one entry in the wrong section (#4018 is a fix, not an
improvement), and three unmentioned machine-visible breaks (`wt list`
JSON `marker`/`state`, `copy-ignored --dry-run` dropping
`files`/`bytes`, the hidden-column footer wording).

One correction to the previous release's notes: the `wt config update
--output` entry was filed under `## 0.76.0` by #4021, which landed after
v0.76.0 was tagged. It moves to 0.77.0, where it actually ships.

**Data-loss surface review**

Four independent finders swept the cumulative diff. The destructive core
is untouched — no changes in `src/commands/process.rs`,
`src/git/remove.rs`, `src/utils.rs`, or `src/commands/remove.rs`. This
release removes a loss path: #3984 deletes the fork-PR `git branch -D`
rollback that force-deleted a pre-existing branch, and its reflog, when
the branch name was already taken.

Three follow-ups, none blocking: `run_plugin_removal` reporting success
over a failed marketplace removal; the documented-not-fixed
`post-commit` window in `wt merge`; and `merge_args_into_command`'s
`args` precondition, now dead code since `drop_unsupported_keys` removes
the key regardless.

> _This was written by Claude Code on behalf of max-sixty_

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_0134XuJoGCzwoZ21XXbWujdY

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

v0.76.0

Toggle v0.76.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Release v0.76.0 (#3983)

Cuts 0.76.0. `cargo semver-checks` fails ten lints, so the bump is
minor.

## Changelog filing fix

Five entries had been appended to the already-tagged `## 0.75.0` section
after v0.75.0 shipped (#3977/#2860, #3949 ×2, #3959, #3945). They
describe changes that ship *here*, and GitHub's 0.75.0 release notes —
built from the tag — never showed them. They are moved into `## 0.76.0`
and rewritten to the length ceiling; `## 0.75.0` is restored
byte-identical to `git show v0.75.0:CHANGELOG.md`.

The mechanism is systemic: a PR appends under whatever heading is
currently top, so every post-release PR lands in the shipped section
until the next release opens a new one.

## `--execute` template guidance

`#3977` switched `--execute` expansion to `ShellEscapeMode::Literal`,
which is correct for an argv model. But the migration that
v0.53.0–v0.75.0's deprecation warning *printed* — `-x sh -- -c '<old
body>'` — splices a template variable into text `sh` re-parses, so a
value with spaces word-splits where the old POSIX-escaped model kept it
intact. Worst case is `rm -rf` against unintended paths.

`--execute`'s help now documents passing the variable as a separate
argument and referencing it positionally. `extending.md` already
modelled that form, so no recipe changed.

## Validation

- `nightly` on the cut-from tip (`baf161bf6`): all 14 jobs green — [run
33538866753](https://github.com/max-sixty/worktrunk/actions/runs/33538866753)
- `wt hook pre-merge --yes`: 4713 passed
- Data-loss surface reviewed across the 45-commit diff by four
independent finders; adjudication in the release thread. No new
destructive path — `#3977` removes one, deleting the EXEC directive file
from all five shell wrappers.

> _This was written by Claude Code on behalf of max-sixty_

v0.75.0

Toggle v0.75.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Document post-cut list diff change (#3927)

## Summary

- document PR #3925 in the 0.75.0 changelog after it merged during the
release PR's CI window
- describe the user-visible untracked-file and move-accounting behavior
without implementation detail

## Validation

- independent changelog verification: accurate, 39 words, correctly
linked and credited, no material omission
- `pre-commit run --files CHANGELOG.md`
- `git diff --check`

> _This was written by Codex on behalf of @max-sixty_

v0.74.0

Toggle v0.74.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Release v0.74.0 (#3820)

Cuts 0.74.0. Minor bump: `cargo semver-checks` reports two breaking
library changes from #3808 (`GitError::WorktreePathNotOurs` gained a
field, `WorkingTree::ensure_belongs_to_repo` was renamed), and patch is
disallowed pre-1.0 with semver breakage.

Alongside the release, one fix the release's data-loss review turned up.

## The fix

`wt step promote` stages a worktree's gitignored files through
`<git-common-dir>/wt/staging/promote` and moves them back after the
branch exchange. When a worktree and the git dir sit on different
filesystems, `fs::rename` fails with EXDEV and `copy_and_remove` copies
then unconditionally deletes the source.

#3744 made the copy tolerate a source that vanishes mid-walk — correct
for `wt step copy-ignored`, which never deletes a source, and wrong
here: an incomplete copy reported `Ok` immediately before the delete, so
a gitignored file a concurrent build removed and rewrote was destroyed
rather than moved. Before #3744 the copy errored and the source
survived, so this was a regression introduced in this release window and
caught before it shipped.

`copy_dir_recursive` now returns how many of the entries the walk
collected to copy were not copied. `copy_and_remove` refuses on a
non-zero count and leaves the source in place; `copy-ignored` names the
count and drops it, with `#[must_use]` so a future third caller decides
rather than inheriting the old bug.

A non-regular file is dropped at classification rather than counted, per
@worktrunk-bot's review: a socket carries no content the destination can
be short of, and refusing over one lands worst where it is least
recoverable — `distribute_staged` runs *after* `exchange_branches`, so a
socket that reached staging by same-filesystem rename would kill the
promote with the branches already swapped and the staged files behind a
`check_leftover_staging` refusal whose remedy deletes them. Both
directions are pinned by tests.

## Release gates

- Local `wt hook pre-merge --yes`: 4649 tests, lints, doctests, rustdoc
under `-Dwarnings`.
- `nightly.yaml` green twice on this branch (full 3-OS matrix,
feature-powerset, release-target, nix-flake, minimal-versions), and
green on the cut-from tip 92dfb68.
- Data-loss surface review over `v0.73.0..HEAD` with four independent
finders (behavioral, blast-radius, shipped-automation, keyword).
Thirteen candidates: one real, fixed here; twelve adjudicated acceptable
and signed off.
- Every changelog entry verified against its diff, with attributions and
links checked.

## Known-red check

`codecov/patch` fails on the three `skipped += 1;` counters in
`src/copy.rs`. Each sits inside a pre-existing `ErrorKind::NotFound` arm
that was already uncovered at the base commit — the concurrent-rewrite
races (`read_dir` vanished, `entry.file_type()` vanished,
`set_permissions` vanished), none with a deterministic trigger.
`codecov/project` passes. Merging over it is approved.

> _This was written by Claude Code on behalf of max-sixty_

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

v0.73.0

Toggle v0.73.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Release v0.73.0 (#3804)

Version bump, Cargo.lock, and the 0.73.0 changelog section.

v0.72.0

Toggle v0.72.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Release v0.72.0 (#3759)

Release v0.72.0 — 55 commits since v0.71.0.

Minor bump: `cargo semver-checks` reports 5 breaking library changes, so
patch is disallowed pre-1.0.

## Headline changes

- **`wt merge` / `wt step push` no longer autostash the target
worktree** (#3703). Both strategies now advance the target through one
`advance_target` — a compare-and-swap `update-ref`, then `read-tree -m
-u` in the target worktree — so `refs/stash` is never entered and staged
changes stay staged.
- **Forge classification returns to brand-in-hostname** (#3673),
reverting the exact-DNS-label rule 0.71.0 shipped.
`github-enterprise.acme.com` and friends resolve again with no config.
- **`[projects."…"]` keys match by `*` pattern and carry forge
settings** (#3701), so one user-config entry covers every repository on
a self-hosted host.
- **A published JSON Schema for `wt list --format=json` schema 2**
(#3747), plus machine-readable approval state and `branch_outcome`
(#3710).

Full detail in `CHANGELOG.md`.

## One fix made during the release cut

The release audit surfaced a gap this release's own `advance_target`
rewrite introduced, fixed here rather than deferred:

**`wt merge` / `wt step push` now refuse a target worktree parked
mid-operation.** The target sync is a two-tree merge, which refuses an
unmerged index but *not* a stopped cherry-pick or rebase whose conflict
has already been staged. A target paused between steps could therefore
have the push range written into it, and the user's `--continue` would
commit the synced tree as the step's result. The old fast-forward path
got this check for free from `receive.denyCurrentBranch=updateInstead`,
which refused any unclean target outright; both strategies now ask
directly, and the refusal names the worktree holding the operation.

`test_push_refuses_target_mid_operation` covers it in both shapes a
stopped operation can take, and both are mutation-verified. With the
gate disabled, the push succeeds and writes `feature.txt` into the
mid-cherry-pick worktree.

The rebase case was added in response to review feedback on this PR, and
pins a second dependency. A rebase detaches HEAD, so `git worktree list
--porcelain` reports the target with no branch and `worktree_for_branch`
finds it only because `finalize_worktree` backfills from
`rebase-merge/head-name`. That makes the rebase arm the one place this
guarantee rests on a helper of ours rather than on git — the
fast-forward path it replaced got the refusal from `find_shared_symref`.
With the backfill disabled, `wt step push` succeeds against a worktree
parked mid-rebase while the cherry-pick case still passes, so the gap
was real.

## Validation

- Local gate green: `cargo run -- hook pre-merge --yes` — 4570 tests,
clippy, fmt, doc sync.
- Cross-platform nightly green on the cut-from tip `3817df079` (run
31133551751): full nextest matrix on linux/macOS/Windows,
feature-powerset, all three release triples, nix-flake,
minimal-versions, unused-deps, crate-build, link-check.
- Changelog verified entry-by-entry against the diffs by an independent
pass; every one of the 55 commits either maps to an entry or is a
documented skip.
- `main` advanced during the CI wait. #3762 ships in this release and
now has a changelog entry; the other two commits that landed (#3758,
#3749) touch only `.github/`.
- Data-loss surface reviewed by four independent finders over the
cumulative diff. One further finding — a pre-0.72 `approvals.toml` key
containing `*` being reinterpreted as a wildcard on upgrade — was
reviewed and accepted as out of scope for this release.

> _This was written by Claude Code on behalf of max-sixty_

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

v0.71.0

Toggle v0.71.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Release v0.71.0 (#3670)

Release 0.71.0. Minor bump, forced by ten breaking library-API changes
from the forge, shell, and removal consolidation — patch is disallowed
pre-1.0 with semver breakage.

## Gates

- `cargo run -- hook pre-merge --yes` — 4,491 tests, 1 skipped, all
lints
- `nightly.yaml` on the cut-from tip (`aba7bad08`) — `full-tests` green
on all three platforms; `gate`, `crate-build`, `minimal-versions`,
`check-unused-dependencies`, `link-check`, `nix-flake`, and the musl
release targets green
- `cargo semver-checks check-release -p worktrunk` — 10 major, 0 minor

## Two entries users need to read

**Forge hosts are now classified by exact DNS label.** A host that
carries the forge name only as part of a label stops being detected:
`gitlab-internal.company.com`, `github-mirror.example.com`, and
`mygithub.com` join the lookalikes the change targets. That costs CI
status in `wt list --full`, `wt switch --prs`, and `repo.provider` in
JSON output. `forge.platform` in `.config/wt.toml` restores it. Only the
single-label SSH alias form (`github-personal`) announces itself; a
self-hosted instance at a hyphenated hostname goes quiet without a
message.

**The retired single-file shell directive is gone.** Wrappers written
before 0.37 stop changing directory *and* stop running `--execute` until
`wt config shell install` and a shell restart. Nushell needs the
reinstall because its wrapper is a static file.

## Data-loss surface review

Five independent finders over the cumulative diff — behavioral,
blast-radius, automation-diff, and keyword, plus a dedicated pass on the
consolidation commit.

The removal chain saw heavy churn and moved consistently in the safe
direction: the repo-wide `git worktree prune` that could unregister an
innocent bystander is gone, the lock guard now precedes the
missing-directory fallback, planning is a pure read, and a fresh
topology read guards the compare-and-swap branch delete. No new
unbounded deletion path.

Residual items, adjudicated as acceptable:

1. A worktree directory that *reappears* between plan and execution is
deleted rather than trashed. `prune_worktree_entry` runs `git worktree
remove`, which deletes a clean worktree's ignored files at exit 0 with
no `--force` (verified in a scratch repo). The normal path renames into
`.git/wt/trash/` first, so git finds nothing there; only the
missing-directory fallback and `step prune`'s stale-detached arm are
exposed. Requires a remount inside the window, and #3658 documents the
tradeoff.
2. The picker's `BranchOnly` arm destructures with `..` and discards
`prune_entry`, so `alt-x` on a gone-directory row leaves a stale
registration behind. A regression from #3658, but it leaves *more*
state, not less.
3. `wt config shell install` derives its preview and its deletion from
two separate scans, and `wt switch`'s first-run install offer still
removes legacy files unpreviewed. Both pre-existing; #3648 tightened
`shell install` and did not reach that path.
4. `enable_hermetic_test_env()` is a `pub` process-global in library
code that blinds every git child to user config. No production caller,
carries its own TODO.
5. Two directive-injection regression tests were deleted in the same
release as the directive-protocol rework.

## Follow-up worth filing

Widen the forge diagnostic to any host that would have matched under the
old substring rule but no longer does — today only the single-label SSH
alias form gets a message, so a self-hosted instance at a hyphenated
hostname loses CI status silently. Additive; needs no further breaking
change.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

v0.70.0

Toggle v0.70.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
docs(changelog): cover #3639 and #3641 in 0.70.0 (#3642)

Two commits landed on `main` while #3640's CI ran, so they ship in
0.70.0 but weren't in its changelog. The release's drift check caught
them before the tag.

- **#3641** — a user-facing fix: `wt hook show` printed a section
heading and nothing else for a hook type declared with an empty command
list. Added as its own Fixed entry.
- **#3639** — help-text only, no behavior change, correcting `wt config
alias dry-run`'s description of the `vars.*` preview. Folded into the
existing hook-previews bullet, since it's the same feature's
documentation catching up rather than a separate change.

No version change; `v0.70.0` is tagged after this lands so the tag
covers both.

> _This was written by Claude Code on behalf of Maximilian_