Repository navigation
Conversation
8ae98a8 to
055bbed
Compare
worktrunk-bot
left a comment
There was a problem hiding this comment.
.git/wt/cache/.epoch is a new file Worktrunk creates, and it isn't in the FAQ's What files does Worktrunk create? inventory. Section 4 enumerates every .git/wt/ path down to .git/wt/cache/picker-preview/*.json and closes with "No files outside the six sections above", and CLAUDE.md's Data Safety section names that inventory as what new code changing this surface gets reviewed against. The row is also where to say the stamp deliberately survives wt config state clear — a leading-dot name has no extension(), so clear_json_files skips it — since the same section tells users that command "removes all repository data". Happy to push the row if you want it.
Three more inline: the failed-removal arm, what the whole-tree wipe costs for two of the kinds, and one changelog line that lost its meaning on the way out of the commit message.
worktrunk-bot
left a comment
There was a problem hiding this comment.
The epoch is the right shape for this — one funnel, one constant, and the residual named where the next bumper reads it. Two gaps, both about where the stamp's behavior did and didn't get wired through; inline.
worktrunk-bot
left a comment
There was a problem hiding this comment.
One new gap, on the funnel itself: cache_dir is now a mutation, and wt config state get's inspection reads go through it — so the command documented as pure inspection performs the wipe and then reports the tree it just emptied. Inline.
worktrunk-bot
left a comment
There was a problem hiding this comment.
The UNVERSIONED_KINDS exemption settles the summary/ci-status overcharge at the root rather than in the changelog wording, and clear_epoch in handle_state_clear_all closes the stronger of the two clears. Two things on the new code, both inline.
I ran the two new tests under mutation. The integration test holds up: drop the .epoch removal so the poisoned entry is trusted, and it fails with ✓ Removed branch feature-unmerged (no added changes on main, ⊂). The unit test's new exemption assertion does not — it stays green with the UNVERSIONED_KINDS check removed from discard_governed_kinds entirely.
The state get thread from the last pass narrows but doesn't close: summaries and CI status now survive an inspection read, while sha_cache::count_all still reports the counts the command left behind and # Reading vs resolving still says the aggregate get never persists.
worktrunk-bot
left a comment
There was a problem hiding this comment.
The per-kind sweep and the clear_epoch drop in handle_state_clear_all close what the last pass raised. Two gaps on the new code, both inline: the clear_epoch calls have nothing pinning them, and the exemption docstring states a rule its own third entry doesn't meet.
ce892d5 to
26e06e4
Compare
worktrunk-bot
left a comment
There was a problem hiding this comment.
The threads from the last four passes all close: the per-kind sweep carries on past a failure, the exemption test seeds off cache_root so the mutation fails, both clear handlers drop the stamp and both are pinned, and the UNVERSIONED_KINDS rule covers pr-number. Two new things, both inline — a changelog gap that will otherwise surface after merge, and one clause of that rule that summary doesn't meet.
On the data-loss gate for the release: discard_governed_kinds is a new fs::remove_dir_all that takes any non-exempt directory it finds, but its root is .git/wt/cache/ and src/cache.rs holds the only join("cache") in the tree — staging/promote and staging/relocate sit one level up at .git/wt/, out of reach. Everything it can delete is regenerable, so the hold doesn't apply.
| - **The picker demo shows the CI and summary columns filling in**: `alt-p` widens the rows, the cells land behind them, then it pages a diff, a PR's comment thread, and the PR itself. The homepage omnibus demo opens on the picker. [Docs](https://worktrunk.dev/switch/#interactive-picker) ([#4178](https://github.com/max-sixty/worktrunk/pull/4178)) | ||
|
|
||
| - **The command pages got a sweep**: help text drops internals, deprecated behavior, and edge cases the command reports itself when they happen — `wt step diff`'s "How it works", the `wt hook --var` paragraph, and the `wt config state ci-status` page among them. Every config sample now renders the file it belongs in as its tab. ([#4157](https://github.com/max-sixty/worktrunk/pull/4157), [#4153](https://github.com/max-sixty/worktrunk/pull/4153), [#4173](https://github.com/max-sixty/worktrunk/pull/4173), [#4177](https://github.com/max-sixty/worktrunk/pull/4177)) | ||
|
|
There was a problem hiding this comment.
#4180 landed on main after the cut-from tip 75baa2cd3, so it is already an ancestor of this release commit and ships in 0.79.0 — but nothing here covers it. This is the release skill's step 12 case: the merge squashes onto whatever main tip exists at merge time, so the drift ships whether or not the changelog names it. Folding it in now costs a line; catching it at step 12 costs a follow-up squash PR and another pass over the coverage list.
Whether a Further-reading removal clears the bar for an entry is your call — the section it would join already covers doc-site sweeps of comparable weight. If it does:
| - **The homepage and README drop "Further reading"**: the section listed five external links; the one about Worktrunk itself — @DevOpsToolbox's video — moves into Next steps, beside the other things to do after installing. ([#4180](https://github.com/max-sixty/worktrunk/pull/4180)) | |
`has-added-changes` and `merge-add-probe` are keyed on the branch and target tips with no TTL and no invalidation rule, so an entry answers for whatever code wrote it until its key recurs — which on a finished branch is never. #4146 moved every diff `wt` parses onto plumbing so display config can no longer change what it reads. The porcelain reads it replaced wrote those two kinds, and under `diff.relative` or `submodule.<name>.ignore` they recorded a branch with changes as having none. Nothing clears the cache on upgrade, so the first `wt remove` after installing the fix still reads the old answer and deletes an unmerged branch without `--force`, reporting `no added changes on main`. Reproduced against the fixed binary with a hand-written pre-upgrade entry. `CACHE_EPOCH` in `src/cache.rs` versions what the cached values mean. `ensure_epoch` runs from `cache_dir`, the one funnel every read and write passes through, so no caller reaches a stale entry ahead of the check; a tree stamped with a different epoch is discarded whole. That makes a future generator change one constant bump rather than a rename per affected kind. The stamp is a non-`.json` file at the cache root, so the per-kind sweeps behind `wt config state cache clear` leave it alone. `TestRepo::assemble` stamps the tree it builds: a test that seeds `.git/wt/cache/` by hand means entries this version produced, and unstamped they would be swept before the test read them. `test_remove_branch_only_unmerged_ignores_cache_from_an_older_epoch` covers the upgrade, and fails without the change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
0.79.0 was prepared and merged in #4181 but never tagged, so it never shipped. Cargo.toml and Cargo.lock already carry the version, so this reuses it and folds the thirteen commits that landed since into the same changelog section. New entries cover the worktree-side rename fixes (#4190, #4191), the per-project unknown-field warning (#4123), and the unterminated code fence in the picker's preview (#4183, combined with #4178's column fix). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Removes `CACHE_EPOCH` and the machinery around it, added in #4181. **Time-sensitive:** 0.79.0 is merged to `main` but not tagged and not on crates.io, so the epoch has never reached a user. Landing this before the tag means it never ships. ## What it was protecting Two corner cases in 0.78.0 made `wt remove` delete an unmerged branch without `-D`, each needing two conditions at once. Reproduced against the v0.78.0 binary: - `diff.relative` set **and** the command run from a subdirectory the branch's changes sat entirely outside. Run from the repository root with the same config, 0.78.0 correctly retains the branch. - `submodule.<name>.ignore` set (git config or `.gitmodules`) **and** a submodule bump as the branch's only change. In both, 0.78.0 reports `✓ Removed branch feature (no added changes on main, ⊂)` and 0.79.0 retains it. #4146 fixed how the answer is computed, but 0.78.0 cached its wrong answer keyed on the branch and target tips with no expiry, so the fix can't reach an entry already on disk. ## Why it goes The epoch fires at most once per repository, on the first command after upgrading, and pays off only for a user who hit one of those corner cases *and* has a live cached key *and* then removes that branch. Against that it carried a stamp file, a tree wipe, a global mutex on every cache read and write, a documented hole when two versions share a repository, and a failure path where an undeletable directory left the reader with exactly the entries the wipe existed to discard. So nothing replaces it: a repository that hit one of the corner cases under 0.78.0 keeps that cached answer after upgrading, exactly as it would have kept it on 0.78.0. `wt config state cache clear` drops it for anyone who asks. The changelog entry describes both corner cases and stays descriptive — it names no remediation. ## No new negative behavior 0.78.0 and 0.79.0 read any entry already on disk identically — across `v0.78.0..HEAD` the key functions, the kind set, the consumers of both integration probes, and `check_integration`'s priority order are all unchanged, and no cached struct changed shape. They diverge only on a cache miss, where 0.79.0 is right and 0.78.0 was wrong. So 0.79.0 without the epoch is a strict improvement on 0.78.0, and at worst a tie. ## Also - **Release skill:** two rules. A correctness or data-loss entry states the conditions the bug needed, joined with "and", established by reproducing against the previous release rather than read off the diff — without that, an entry like this one reads as though it fired for everyone. And entries describe: imperative guidance only for a material issue that affects a sizable share of users, which is rare. - **`src/summary.rs`:** keeps the caveat that the summary cache hashes the diff while the prompt sits downstream of that hash, which previously lived only in the docstring this removes. - **`CLAUDE.md`:** records that not versioning the cache is the decision, and that a generator change says in its PR what the old answers get wrong. > _This was written by Claude Code on behalf of max-sixty_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Cuts 0.79.0 from
75baa2cd3. Minor bump:cargo semver-checksreports five breaking library lints, so patch is disallowed pre-1.0.Two commits — the changelog and version bump, plus one fix the release's data-loss review turned up.
The cache fix
has-added-changesandmerge-add-probeare keyed on the branch and target tips with no TTL and no invalidation, so an entry answers for whatever code wrote it until its key recurs — which on a finished branch is never.#4146 moved every diff
wtparses onto plumbing so display config can no longer change what it reads. The porcelain reads it replaced wrote those two kinds, and underdiff.relativeorsubmodule.<name>.ignorethey could record a branch's changes as absent. Nothing clears the cache on upgrade, so a poisoned entry outlives the fix:wt removereads the old answer and deletes the branch without-D.The exposure is narrow — it needs one of those two settings, and for
diff.relativealso a run from a subdirectory the branch's changes sit outside. The commits stay in the object store; it's the ref that goes. But nothing about the upgrade clears it, so for an affected repository the fix in #4146 does nothing until the cache is discarded. Reproduced against the fixed binary with a hand-written pre-upgrade entry:CACHE_EPOCHinsrc/cache.rsversions what the cached values mean.ensure_epochruns fromcache_dir, the one funnel every read and write passes through, and discards a tree stamped with a different epoch. The lock spans the removal, not just the bookkeeping, becausewt listfans the SHA-keyed reads over a thread pool. The sweep attempts every kind and returns the first error, so one undeletable directory doesn't strand the rest inread_dirorder. Bothwt config state clearandwt config state cache cleardrop the stamp with the entries it describes, so a cleared tree re-stamps on the next command rather than vouching for whatever lands there next.UNVERSIONED_KINDSexemptssummary,ci-statusandpr-number, so an upgrade doesn't charge a model call per branch and a forge round-trip for a diff fix.ci-statusandpr-numberhold the forge's own answer, so no change to howwtcomputes things can make them wrong.summaryis exempt on weaker grounds, stated as such in the docstring: its key hashes the diff, but the prompt template andllm::prepare_diff's filtering sit downstream of that hash, so rewording the prompt does leave a stale summary — accepted because the value is display-only, and closing it means hashing the rendered prompt rather than bumping the epoch.Known residual, deliberately deferred: the stamp records the last worktrunk to check a tree, not what wrote each entry, so a 0.78.0 binary run afterwards writes porcelain answers this one trusts. Closing that means putting the epoch in each entry's path, which invalidates by construction and costs the same in a later release. It is named in the
CACHE_EPOCHcomment.TestRepo::assemblestamps the tree it builds, since a test that seeds.git/wt/cache/by hand means entries this version produced.test_remove_branch_only_unmerged_ignores_cache_from_an_older_epochcovers the upgrade, a table-driven unit test covers all three stamp states and the exemption, and both clear handlers are pinned against dropping theirclear_epochcall. Each assertion was checked under mutation rather than left to pass by construction.Checks
cargo run -- hook pre-merge --yesgreen (4925 tests). This PR runs the full cross-platform matrix itself —full-testson macOS, Linux, Windows and git 2.43.0, plus feature-powerset, nix-flake and the release targets — andnightly.yamlre-runs on the merged tip before the tag, since the green run on the cut-from tip predates the cache fix. Changelog verified over three passes against the diffs, the pre-change files, the rendered docs, and a forcedcargo-semver-checksrun.🤖 Generated with Claude Code