Tags: max-sixty/worktrunk
Tags
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>
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)
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>
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_
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_
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>
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>
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)
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_
PreviousNext