Repository navigation
Conversation
worktrunk-bot
left a comment
There was a problem hiding this comment.
Reviewing as a draft — flagging anything that looks worth a quick fix. Mark ready for a full review.
The structure is clean and the test coverage across forges (including the Azure non-canonical and unknown-fallback cases) is thorough. One design subtlety worth deciding on before this finalizes:
The top-level repo (from the remote URL, GitRemoteUrl::repo_info) consults the configured [forge].platform override, but ci.repo (from the PR/MR URL, repo_info_from_ref_url) derives provider purely from URL shape + host substring — it never sees the config override. For a forge on a non-canonical host these two can disagree for the same logical provider: e.g. a GitHub Enterprise host like git.company.com with platform = "github" reports repo.provider = "github" but ci.repo.provider = "unknown" (the pull marker only matches when the host contains("github")). Same story for Azure on a generic host.
Worth deciding whether that's intended (ci.repo reflects only what the URL encodes) and, if so, documenting it in the repo object section — a consumer filtering on .ci.repo.provider would otherwise be surprised that it can diverge from .repo.provider.
96e5f62 to
f3613b5
Compare
worktrunk-bot
left a comment
There was a problem hiding this comment.
The structured repo / ci.repo shapes are clean, and threading the configured [forge].platform hint into ci.repo resolves the repo.provider vs ci.repo.provider divergence from the draft round — the opaque-host test makes that explicit. The override-wins precedence in repo_info() also matches the existing choose_pr_provider_forge_platform_override_wins convention, so that's consistent.
Two things to address before merge, both likely to surface as codecov/patch misses (a hard gate on this repo):
repo_web_url()is now dead — its only two callers (json_output.rs,statusline.rs) both moved torepo_info()in this PR, and nothing else references it. Its body changed in the diff but is never exercised. Deletion candidate per the dead-code policy.- The
v3fallback branch inazure_repo_info_partshas no test coverage.
Details inline.
f3613b5 to
d11bb4e
Compare
d11bb4e to
a4b6c3f
Compare
worktrunk-bot
left a comment
There was a problem hiding this comment.
The structured repo / ci.repo shapes read well, and the per-forge ref-URL tests are thorough. Two things stand out on this revision, plus one inline cleanup.
Windows test regression from the fixture change. affected tests (windows, advisory) failed on integration_tests::bare_repository::test_bare_repo_equivalent_to_normal_repo (bare_repository.rs:580) with left: 4, right: 7 — the normal-repo wt list shows three extra rows, i.e. the feature-a/b/c worktrees the test removes up front are still listed. cargo-affected selected this test specifically because the PR edits src/testing/mod.rs, and the ci run on the base commit (ba6a3c2b5) was green, so this is new to the PR rather than a pre-existing flake. The suspect is the fixture switch from relative gitdir links (_git→.git rewrite) to absolute dunce::canonicalize-based links — git worktree remove against repo.root_path().parent().join(...) no longer matches the registered worktree path on Windows. The required test (windows) job runs the full suite and is still pending; it will likely hit the same failure, which would block merge. I can't run Windows here to pin the exact mismatch — could you verify this leg locally on Windows (or in CI) before this lands? If the relative-link approach was itself causing the "Git-version-dependent prunable" results, it may be worth splitting the fixture change into its own PR so it can be validated across platforms independently of the metadata feature.
ci.repo Azure parsing has dead and untested branches. In azure_repo_segments (src/git/remote_ref/mod.rs), its only caller passes a host parsed from a PR/MR web URL (parse_ref_url_parts rejects non-http(s) schemes), so the ssh.dev.azure.com / v3 branch can never fire — Codecov flags those lines as uncovered, and per the dead-code policy in CLAUDE.md it's a deletion candidate. Inline suggestion below. (This is the analogue of the prior round's azure_repo_info_parts fix in url.rs, where the v3 branch is reachable because ssh remotes are valid there — the asymmetry is correct, the ssh branch just doesn't belong in the ref-URL path.)
The adjacent .visualstudio.com branch is the opposite case: reachable (Azure PR URLs on *.visualstudio.com hosts) but exercised by no test, so it's also flagged by Codecov. Worth a https://myorg.visualstudio.com/myproject/_git/myrepo/pullrequest/9 case in repo_info_from_ref_url_per_forge. The remaining patch misses are the trivial provider-mapping arms (From<GitRepoProvider> for JsonRepoProvider, From<CiPlatform> for GitRepoProvider) that no test threads a GitLab/Gitea/Azure value through.
Happy to push a commit removing the ssh branch and adding the visualstudio + provider-mapping coverage if useful — the Windows fixture question is the one I can't resolve from here.
a4b6c3f to
2ccf160
Compare
2ccf160 to
8bcb05b
Compare
worktrunk-bot
left a comment
There was a problem hiding this comment.
The feature code reads well — the repo / ci.repo split, the [forge].platform override threading, and the per-forge ref-URL coverage all look solid, and repo_url stays byte-identical to the old repo_web_url() path (both route Azure through fork_remote_url with the same host mapping, so no regression there).
One thing that will fail CI, in the regenerated help snapshots: both help_list_long.snap and help_list_narrow_80.snap flipped their info.env block from GIT_EDITOR: "" to GIT_PAGER: "". That line isn't set by the harness — isolate_subprocess_env (src/testing/mod.rs, the std::env::vars() scrub) env_removes every GIT_* key present in the parent environment, and insta-cmd records each removed key as : "". So the env block captures whichever GIT_* vars happened to exist on the host at regeneration time. CI's environment has GIT_EDITOR (every other help snapshot on main shows GIT_EDITOR: "", none show GIT_PAGER), so when the help_list_* tests run in test (linux|macos|windows) they'll record GIT_EDITOR: "" and mismatch these committed snapshots.
These two were almost certainly regenerated in an environment that had GIT_PAGER set but not GIT_EDITOR. The robust fix is to regenerate with the same GIT_* env CI has (e.g. unset GIT_PAGER / ensure GIT_EDITOR is set, then cargo insta test); the inline suggestions below are the direct one-line patch to match what CI will produce.
worktrunk-bot
left a comment
There was a problem hiding this comment.
The merge of main is clean. Re-flagging one thing from the prior review that's still live: the two help_list_* snapshots were resolved but the fix wasn't applied — both still record GIT_PAGER: "" where every other help snapshot in the tree (39 of them) records GIT_EDITOR: "". That line is an accidental capture of whichever GIT_* var existed on the regeneration host (isolate_subprocess_env records each scrubbed GIT_* key as : ""), so CI — which has GIT_EDITOR set — will record GIT_EDITOR: "" and the still-pending test (linux|macos|windows) jobs will mismatch these two. Inline one-line reverts below; the robust alternative is to regenerate under CI's GIT_* env.
Dismissing my earlier approval: the two help_list snapshots still record GIT_PAGER:"" (every other help snapshot records GIT_EDITOR:""), so the required test (linux|macos|windows) jobs will fail. Re-flagged with inline reverts. Will re-approve once the snapshots match CI's env.
| } | ||
| } | ||
|
|
||
| impl From<CiPlatform> for GitRepoProvider { |
There was a problem hiding this comment.
@worktrunk-bot should these be one struct? I worry a bit when I see this mapping...
There was a problem hiding this comment.
There are actually three enums in this chain, each pinned to a different boundary, and only one of the two mappings is avoidable:
CiPlatform(4 variants, noUnknown) — config parsing (forge.platform, via strumDisplay/EnumString) and CI detection.Repository::ci_platform()returnsOption<CiPlatform>, whereNoneis the "not a forge we fetch CI for" state.GitRepoProvider(those 4 +Unknown) — repo metadata, whereUnknownis a legitimate value: a parseable URL whose host and config don't identify a supported forge.JsonRepoProvider(same 5 +#[serde(rename)]+JsonSchema) — the wire format.
The mapping you flagged, From<CiPlatform> for GitRepoProvider, exists purely because GitRepoProvider is CiPlatform plus Unknown. Collapsing the two into one enum isn't free in either direction:
CiPlatformcan't absorbUnknownwithout it leaking everywhere CI is fetched —ci_platform()/configured_ci_platform()already useOptionto mean "no supported forge", and there's no CI to fetch forUnknown, so it becomes a dead arm in every CI path.JsonRepoProvideris the establishedJson*serde-boundary pattern. Folding it in would put serde/schemars derives on asrc/git/core type and weld the JSON representation to the internal enum's shape.
If the worry is specifically the two variant lists drifting out of sync, the genuine reduction is to model "+Unknown" in the type rather than as a parallel list:
enum GitRepoProvider {
Known(CiPlatform),
Unknown,
}That deletes the 4-arm From<CiPlatform> match (p.into() becomes Known(p)) and makes "this is CiPlatform plus Unknown" impossible to get out of sync. The cost is at the ~10 match sites in remote_ref/mod.rs, which become GitRepoProvider::Known(CiPlatform::GitHub) etc., and the From<GitRepoProvider> for JsonRepoProvider boundary still has to exist. So it trades one mechanical mapping for slightly noisier matches — worth it if the can't-drift guarantee is the thing you want, a wash otherwise.
There was a problem hiding this comment.
I don't think we should worry about coupling the serialization to the types. Do we have separate types anywhere else in the repo?
There was a problem hiding this comment.
Good question — for a 1:1 enum like this, no, and the convention actually runs the other way: derive serde on the domain enum and serialize it directly, no parallel wire enum, no From.
The closest precedent is a sibling field in this very struct. JsonCi.source (json_output.rs:230) embeds the domain enum CiSource directly — assigned verbatim (source: pr.source, L543). CiSource lives in the CI layer (ci_status/mod.rs:296), carries its own #[serde(rename = "pr", alias = "pull-request")], and derives Serialize, Deserialize, JsonSchema right on the domain type. CiStatus does the same with rename_all = "kebab-case". So there's no JsonCiSource mirror — JsonRepoProvider + From<GitRepoProvider> is the only enum split of its kind in the tree.
Coupling serde onto a src/git/ type also already has precedent: IntegrationReason (git/mod.rs:103) derives serde::Serialize with kebab-case.
So I'd agree with collapsing it: add Serialize, JsonSchema to GitRepoProvider, move the per-variant renames onto it, drop JsonRepoProvider, its From impl (L306), and the round-trip test. That deletes a match arm you'd otherwise have to keep in sync each time a forge is added.
Two small notes if you go that way
- Keep explicit per-variant renames, not
rename_all.rename_all = "kebab-case"would emitgit-hub/git-lab, so the variants need#[serde(rename = "github")]etc. (same asCiSource's explicitrename).azure-devops/unknownare unaffected. JsonSchemawould be the first schemars derive insrc/git/(serde::Serializeis already there viaIntegrationReason;JsonSchemaisn't). It's the same crate, so no new dependency — just the git module starting to carry a wire-schema derive. If keeping schemars out ofsrc/git/is a goal, that's the one argument for leaving the mirror injson_output.rs; otherwise theCiSourceshape is the more consistent pick.
The one thing the separate-Json* convention genuinely buys you is at the struct level — JsonItem/JsonCi/JsonRepo reshape and rename domain data with no 1:1 counterpart, so they earn their keep. A 1:1 enum mirror doesn't.
There was a problem hiding this comment.
Implemented this!
| | `repo_url` | string | Web URL of the repository the PR/MR targets; absent for branch workflow URLs | | ||
| | `repo` | object | Structured metadata for the repository the PR/MR targets; never includes `remote` | |
There was a problem hiding this comment.
what's the purpose of having both these fields?
There was a problem hiding this comment.
repo_url is already released JSON API — it shipped in v0.56.0 via #2941, so consumers can be parsing it today. repo is the structured form this PR adds, and repo.url carries the byte-identical string: repo_url is now literally repo.map(|r| r.url.clone()) (json_output.rs:464, and the ci.repo_url analogue at L545).
So both exist for backward compatibility: an existing repo_url consumer keeps working, while new consumers read host / owner / name / provider / project off repo instead of re-parsing the URL string. Same split for ci.repo_url vs ci.repo.
The alternative is to drop repo_url in favor of repo.url — that's the cleaner shape, but it's a breaking change to a field released two versions ago, so it's a deprecation call rather than a free cleanup.
There was a problem hiding this comment.
Hey @max-sixty I added this just as a QOL improvement on top of repo_url in 0.56.0 so that worktrunk providers don't need to parse up the repo URL but instead can grab the repo identity props directly. I know it's not the prettiest thing to have these similar props in the JSON API.
Quick context is I'm working on an ai agent orchestration app on top of worktrunk (love worktrunk btw!) in TS and this seemed like a win/win for worktrunk and wosm
@worktrunk-bot could you open a separate PR that makes running tests much more robust to the environment? folks who are contributing shouldn't have to worry about matching their local environment at all |
|
Opened #3037 for this. Root cause: insta-cmd records The fix is a dynamic |
|
@worktrunk-bot can you do a pass over this and find places we can simplify / cut the code mass? it seems @jeremy0dell has done a good job, but maybe the LLM has bloated the code a bit, based on the triple Struct case What other opportunities do we have for simplifying and canonicalizing? I'm keen to keep the code sleek. you can apply them directly if they're relatively persuasive, unless @jeremy0dell would rather apply them himself (@jeremy0dell you're obv free to revert anything) |
JsonRepo mirrored GitRepoInfo field-for-field, with only an extra `remote`, plus a manual `from_git_repo_info` mapping. Move `remote` onto GitRepoInfo and serialize it directly, so a single canonical struct backs both the `repo` and `ci.repo` JSON objects. Repository::repo_info() now populates GitRepoInfo::remote and returns just the struct. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Did a simplification pass, @max-sixty. The "triple struct" was Applied ( Other opportunities I looked atLeft alone deliberately — the bigger remaining duplication, but risky. Provider detection and Azure org/project/repo extraction now live in two places: Tiny, safe if you want it. In let name = repo_segments.last()?.to_string();
let owner = repo_segments[1..repo_segments.len() - 1].join("/");appears twice (Azure-unrecognized branch and the final branch) — could hoist to one Intentional, not bloat. The top-level |
|
@worktrunk-bot please resolve conflicts |
…rovider-semantics # Conflicts: # docs/content/list.md # skills/worktrunk/reference/list.md # src/cli/mod.rs # src/commands/list/json_output.rs # tests/snapshots/integration__integration_tests__help__help_list_long.snap # tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap
|
Resolved the conflicts with The conflicts were all in the
|
#3037) Opened at @max-sixty's request in #3021 (comment) — make running/regenerating tests robust to the contributor's local environment so nobody has to match CI's `GIT_*` setup. ## The problem `insta_cmd` records every env var a test command sets **or removes** — Rust's `Command::env_remove` registers in `get_envs()`, and insta-cmd serializes a removal as `KEY: ""`. `isolate_subprocess_env` scrubs whichever `GIT_*` / `WORKTRUNK_*` keys exist in the **parent** environment, so the recorded `env:` block depends on the host: - CI has `GIT_EDITOR` set → `GIT_EDITOR: ""` - A contributor's box might have `GIT_PAGER` → `GIT_PAGER: ""`, or neither, or both This is what bit #3021: `help_list_long.snap` / `help_list_narrow_80.snap` were regenerated on a box with `GIT_PAGER` and picked up `GIT_PAGER: ""` instead of CI's `GIT_EDITOR: ""`. Note this is **cosmetic, not a CI failure**: insta's `matches_fully` compares only the snapshot body (`self.contents == other.contents`), never the `info:`/`env:` metadata. But the spurious machine-to-machine diff is exactly what makes contributors think they have to match CI's environment. ## The fix A dynamic `.env` redaction in `add_standard_env_redactions` (`strip_host_scrubbed_env_markers`) drops every empty-valued `GIT_*` / `WORKTRUNK_*` entry from the recorded block. It runs as its own pass *after* the per-key `.env.*` redactions, so affirmatively-set vars (config paths → `[TEST_CONFIG]`, coverage → `[LLVM_PROFILE_FILE]`) already hold non-empty placeholders and are kept. The explicitly-named unconditional scrubs (`NO_COLOR`, `SHELL`, `PSModulePath`) are host-independent and left intact. Result: regenerating on any machine produces the same `env:` block, regardless of the contributor's `GIT_*` environment. ## Why no snapshots are regenerated The `info:` block is never compared, so a metadata-only difference never triggers a rewrite — even `INSTA_UPDATE=always` leaves an unchanged-body snapshot alone. Existing committed snapshots keep their (now-redundant) markers until their body next changes, at which point the rewrite drops them cleanly. This avoids a disruptive ~1000-file cosmetic diff that would conflict with every in-flight snapshot PR; the robustness applies to all future regenerations from this point on. If you'd prefer the tree uniform now, a follow-up `cargo insta test --accept` bulk regen is a clean separate commit — happy to push it. ## Verification - New unit test `strip_host_scrubbed_env_markers_normalizes_env_block` covers drop/keep/passthrough. - Confirmed end-to-end: deleting `help_list_long.snap` and forcing a fresh write drops `GIT_EDITOR: ""` while keeping `NO_COLOR`/`SHELL`/`PSModulePath` and the `"0"`-valued `WORKTRUNK_TEST_*` vars. - `help` (57), `snapshot_formatting_guard` (6), and `ci_status` (68) snapshot suites pass; `cargo fmt --check` and `cargo clippy --tests --all-features` clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
| | `is_previous` | boolean | Previous worktree from wt switch | | ||
| | `ci` | object | CI status (see below); absent when no CI | | ||
| | `repo_url` | string | Repository web URL derived from the primary remote; absent when the remote URL cannot be parsed | | ||
| | `repo` | object | Structured repository metadata (see below); includes `remote` | |
There was a problem hiding this comment.
I don't understand this, vs. the "never includes remote" phrase below? Can you explain?
Ideally we would canonicalize onto repo, deprecate the repo_url variable in favor of repo.url.
There was a problem hiding this comment.
remote here isn't repo data — it's a single string naming the local git remote the metadata was derived from (e.g. "origin"). See GitRepoInfo: the field is Option<String>, "Set only for the top-level repo of wt list; absent for PR/MR-URL-derived metadata such as ci.repo."
The two phrases describe where that name can exist:
- Top-level
repois built inRepository::repo_info()fromself.primary_remote()→info.remote = Some(remote). It came from a local remote, so it can report which one → "includesremote". ci.repois derived from the PR/MR URL (for fork PRs, the upstream target), not from any local remote, so there's no remote name to attach → "never includesremote".
So the asymmetry is exactly that one field. I can make the docs clearer — the column descriptions read like remote is a sub-object. Something like "names the local git remote it was derived from (e.g. origin)" on the repo row, dropping the bare "includes remote" / "never includes remote" phrasing, would be less ambiguous.
On canonicalizing onto repo and deprecating repo_url/ci.repo_url in favor of repo.url/ci.repo.url: agreed that's the cleaner end state. It's orthogonal to this PR (which keeps them as compat fields), so I'd suggest a follow-up rather than folding it in here — and since these are documented JSON fields, dropping them is a soft break for consumers, so a deprecation-noted transition (keep emitting, document as deprecated, remove later) reads better than a hard cut. Happy to open that follow-up PR if you'd like it.
There was a problem hiding this comment.
Agree that canonicalizing onto repo would improve the API but yeah, I wouldn't want anything to break
There was a problem hiding this comment.
@max-sixty Just checking, do you want me to close this? I can make a PR canonicalizing onto repo or just leave the API alone for now and parse everything on my end
|
sorry for the delay and thank you again @jeremy0dell ! |
Release v0.60.0 (minor bump from 0.59.0). Minor because semver-checks flagged a breaking library-API change and five new features landed since v0.59.0. The data-loss surface review (4 independent finders) cleared the one change that touches it (relocating dirty linked worktrees, #3104). ### Improved - Package installs now complete branch/worktree names via dynamic registration (#3105, thanks @bendrucker) - `wt step relocate` moves dirty linked worktrees (#3104, thanks @lunaynx for reporting) - `--dry-run` previews print to stdout (#3085) - Picker gutter distinguishes local (`/`) and remote (`|`) branches (#3115) - `wt list --format=json` gains structured `repo`/`ci.repo` metadata (#3021, thanks @jeremy0dell) - `wt step eval --format=json` (#3106) - `--format=claude-code` rejected on `wt list` / `wt config state get` (#3116) ### Fixed - `cargo install worktrunk` builds from crates.io again (#3124, fixes #3123, thanks @kerrickstaley for reporting) - Tab-completion works in repos with no commits (#3097, closes #3094) - `wt config state get ci-status --format=json` honors `[forge].platform` (#3120) ### Documentation - `wt list --help` example tables fit the terminal (#3125) - `/wt-switch-create` cross-repo handling reworked (#3118) - Help-text and web-doc refinements (#3096, #3108, #3110, #3112, #3126) Full notes in `CHANGELOG.md`. > _This was written by Claude Code on behalf of max_
Reconcile the v0.60.0 release with the custom-columns feature. Both sides changed `JsonItem::from_list_item`: main replaced the `repo_url` arg with a `repo: Option<&GitRepoInfo>` + `ci_provider_override` pair (#3021, #3120), while this branch added a trailing `custom_columns` arg. The merged signature carries all three (main's two, then `custom_columns`), and every call site (`to_json_items`, the statusline path, and the JSON unit tests) passes the combined argument list. `ItemKind::Branch` also gained a `BranchScope` payload on main (gutter sigils, #3115); the `new_branch_with_scope` constructor now sets both `custom_values` and `kind: ItemKind::Branch(scope)`. `to_json_items` keeps this branch's snapshot-based `all_vars` read alongside main's `repo_info`/forge-platform lookups. The `wt list --help` snapshot was regenerated from the merged `cli/mod.rs`, combining main's width-chopped example tables with the custom-columns column row and config section. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reconcile the v0.60.0 release with the custom-columns feature. Both sides changed `JsonItem::from_list_item`: main replaced the `repo_url` arg with a `repo: Option<&GitRepoInfo>` + `ci_provider_override` pair (#3021, #3120), while this branch added a trailing `custom_columns` arg. The merged signature carries all three (main's two, then `custom_columns`), and every call site (`to_json_items`, the statusline path, and the JSON unit tests) passes the combined argument list. `ItemKind::Branch` also gained a `BranchScope` payload on main (gutter sigils, #3115); the `new_branch_with_scope` constructor now sets both `custom_values` and `kind: ItemKind::Branch(scope)`. `to_json_items` keeps this branch's snapshot-based `all_vars` read alongside main's `repo_info`/forge-platform lookups. The `wt list --help` snapshot was regenerated from the merged `cli/mod.rs`, combining main's width-chopped example tables with the custom-columns column row and config section. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
worktrunk 0.60.0 Created-by: HarmonybrewBot Commit-by: HarmonybrewBot Merged-by: HarmonybrewBot Description: Created by `brew bump` --- Created with `brew bump-formula-pr`.<details> <summary>release notes</summary> <pre>## Release Notes ### Improved - **Package installs complete branch and worktree names**: A plain `brew install worktrunk` (or other package install) now tab-completes branch and worktree names, not just subcommands and flags. `wt config shell completions <shell>` emits a dynamic registration that calls the binary at completion time (the maintained `clap_complete::env` path), matching what `gh`, `rustup`, and `kubectl` ship. ([#3105](max-sixty/worktrunk#3105), thanks @bendrucker) - **`wt step relocate` moves dirty linked worktrees**: Relocating a linked worktree with uncommitted or untracked changes no longer skips it. `git worktree move` carries those files along, so the dirty-skip was a worktrunk policy rather than a git limitation. The main worktree still skips when dirty (without `--commit`), since its relocation falls back to `git checkout`, which refuses to switch over a dirty tree. ([#3104](max-sixty/worktrunk#3104), thanks @lunaynx for reporting) - **`--dry-run` previews print to stdout**: `wt hook <type>`, `wt step relocate`, `wt step prune`, `wt step copy-ignored`, and `wt config shell install`/`uninstall` now send their `--dry-run` preview to stdout (the command's answer) while narration stays on stderr. This matches `wt list`, `git clean -n`, and `terraform plan`, and keeps previews pageable and pipeable. (Breaking: scripts reading these previews from stderr should now read stdout) ([#3085](max-sixty/worktrunk#3085)) - **Picker gutter distinguishes local and remote branches**: With `--branches` and `--remotes`, the `wt switch` picker marks each row's kind in the gutter: `/` for a local branch without a worktree and `|` for a remote branch, alongside the existing `@`/`^`/`+` worktree glyphs. ([#3115](max-sixty/worktrunk#3115)) - **`wt list --format=json` gains structured `repo` metadata**: JSON output adds `repo` (the local checkout's primary remote) and `ci.repo` (the repository `ci.url` targets, which differs for fork PRs) objects carrying `host`/`owner`/`name`/`provider`; the existing `repo_url` and `ci.repo_url` strings remain. The provider honors the configured `[forge].platform` on hosts that can't be auto-detected. ([#3021](max-sixty/worktrunk#3021), thanks @jeremy0dell) - **`wt step eval --format=json`**: `wt step eval` gains a structured `{name, template, result}` JSON lane on stdout, the machine-readable analog of its `-v` view. Text mode is unchanged. ([#3106](max-sixty/worktrunk#3106)) - **`--format=claude-code` rejected where it never applied**: `wt list` and `wt config state get` accepted `--format=claude-code` silently and treated it as `table`; the value only ever meant anything on `wt list statusline`. Both now fail fast with `invalid value 'claude-code'`. (Breaking: `wt list --format=claude-code` and `wt config state get --format=claude-code` now error) ([#3116](max-sixty/worktrunk#3116)) ### Fixed - **`cargo install worktrunk` builds from crates.io**: Installing from crates.io failed to compile with `environment variable VERGEN_GIT_DESCRIBE not defined at compile time`, because the package archive has no `.git` for the build script to read. The version lookup now falls back to the cargo package version when git-describe is unavailable. ([#3124](max-sixty/worktrunk#3124), fixes [#3123](max-sixty/worktrunk#3123), thanks @kerrickstaley for reporting) - **Tab-completion works in repos with no commits**: `wt switch <TAB>` and `wt remove <TAB>` returned nothing on a fresh `git init` repo, because the unborn default branch has no entry under `refs/heads/`. Completion now also draws on worktree branches, so the unborn `main` (and any `wt switch --create` branch on an empty repo) completes. ([#3097](max-sixty/worktrunk#3097), closes [#3094](max-sixty/worktrunk#3094)) - **`wt config state get ci-status --format=json` honors `[forge].platform`**: On a self-hosted host the parser can't recognize (Gitea, Azure DevOps, GitHub Enterprise on a generic domain), this path reported `provider: "unknown"` while `wt list --format=json` reported the configured provider. All structured-output paths now route the `[forge].platform` override through one accessor, so they stay consistent. ([#3120](max-sixty/worktrunk#3120)) ### Documentation - **`wt list --help` example tables fit the terminal**: The captured `wt list` example tables in `--help` chop to terminal width with a dimmed ellipsis (matching real `wt list`) instead of word-wrapping and shearing their columns; hand-authored command examples still wrap. ([#3125](max-sixty/worktrunk#3125)) - **`/wt-switch-create` cross-repo handling reworked**: The Claude Code plugin command's skill reworks how it creates and enters a worktree in another repo, built around what the harness actually does (`EnterWorktree` re-roots within the current repo; a `cd` reaches another repo when it's in `additionalDirectories`). The procedure dropped from five steps to three. ([#3118](max-sixty/worktrunk#3118)) - **Help-text and docs refinements**: `--format` help renders its values inline (`[possible values: table, json]`) instead of an expanded block; `wt switch` and `wt step push` help got smaller clarifications; and web-doc terminal blocks no longer wrap command-only lines. ([#3096](max-sixty/worktrunk#3096), [#3108](max-sixty/worktrunk#3108), [#3110](max-sixty/worktrunk#3110), [#3112](max-sixty/worktrunk#3112), [#3126](max-sixty/worktrunk#3126)) ## Install worktrunk 0.60.0 ### Install prebuilt binaries via shell script ```sh curl --proto '=https' --tlsv1.2 -LsSf https://github.com/max-sixty/worktrunk/releases/download/v0.60.0/worktrunk-installer.sh | sh && wt config shell install ``` ### Install prebuilt binaries via powershell script ```sh powershell -ExecutionPolicy Bypass -c "irm https://github.com/max-sixty/worktrunk/releases/download/v0.60.0/worktrunk-installer.ps1 | iex"; git-wt config shell install ``` ### Install prebuilt binaries via Homebrew ```sh brew install worktrunk && wt config shell install ``` ## Download worktrunk 0.60.0 | File | Platform | Checksum | |--------|----------|----------| | [worktrunk-aarch64-apple-darwin.tar.xz](https://github.com/max-sixty/worktrunk/releases/download/v0.60.0/worktrunk-aarch64-apple-darwin.tar.xz) | Apple Silicon macOS | [checksum](https://github.com/max-sixty/worktrunk/releases/download/v0.60.0/worktrunk-aarch64-apple-darwin.tar.xz.sha256) | | [worktrunk-x86_64-apple-darwin.tar.xz](https://github.com/max-sixty/worktrunk/releases/download/v0.60.0/worktrunk-x86_64-apple-darwin.tar.xz) | Intel macOS | [checksum](https://github.com/max-sixty/worktrunk/releases/download/v0.60.0/worktrunk-x86_64-apple-darwin.tar.xz.sha256) | | [worktrunk-x86_64-pc-windows-msvc.zip](https://github.com/max-sixty/worktrunk/releases/download/v0.60.0/worktrunk-x86_64-pc-windows-msvc.zip) | x64 Windows | [checksum](https://github.com/max-sixty/worktrunk/releases/download/v0.60.0/worktrunk-x86_64-pc-windows-msvc.zip.sha256) | | [worktrunk-aarch64-unknown-linux-musl.tar.xz](https://github.com/max-sixty/worktrunk/releases/download/v0.60.0/worktrunk-aarch64-unknown-linux-musl.tar.xz) | ARM64 MUSL Linux | [checksum](https://github.com/max-sixty/worktrunk/releases/download/v0.60.0/worktrunk-aarch64-unknown-linux-musl.tar.xz.sha256) | | [worktrunk-x86_64-unknown-linux-musl.tar.xz](https://github.com/max-sixty/worktrunk/releases/download/v0.60.0/worktrunk-x86_64-unknown-linux-musl.tar.xz) | x64 MUSL Linux | [checksum](https://github.com/max-sixty/worktrunk/releases/download/v0.60.0/worktrunk-x86_64-unknown-linux-musl.tar.xz.sha256) | ### Install via Cargo ```sh cargo install worktrunk && wt config shell install ``` ### Install via Winget (Windows) ```sh winget install max-sixty.worktrunk && git-wt config shell install ``` ### Install via AUR (Arch Linux) ```sh paru worktrunk-bin && wt config shell install ``` </pre> <p>View the full release notes at <a href="https://github.com/max-sixty/worktrunk/releases/tag/v0.60.0">https://github.com/max-sixty/worktrunk/releases/tag/v0.60.0</a>.</p> </details> <hr> See merge request: Harmonybrew/homebrew-core!11933
Extends the JSON repository metadata added around
repo_urlwith structuredrepoobjects forwt list,wt list statusline, and PR CI output.repo_urlandci.repo_urlstay as compatibility fields and continue to carry the same URL strings.repodescribes the local checkout's primary remote.ci.repodescribes the repository targeted byci.url, which can differ for fork PRs.The provider field now uses the same
[forge].platformparser as CI detection. That keeps configured values consistent with existing behavior:azuredevopsis accepted, while values such as" github "are ignored and fall back to host detection.ci.reponow receives the same configured provider hint that CI detection used to find the PR or MR. That keepsrepo.providerandci.repo.provideraligned on opaque hosts such asgit.company.comwithplatform = "github".Azure DevOps keeps the shape guard: only Azure-shaped URLs with an organization, project, and repository report
provider: "azure-devops"and setproject. If config says Azure DevOps but the URL is a generic non-Azure shape likehttps://git.example.com/owner/repo, Worktrunk keeps the oldrepo_urlvalue but emitsprovider: "unknown"with generichost,owner, andnamemetadata.Notes
GitRemoteUrl::repo_info()owns the remote URL metadata shape and reusesCiPlatformfor configured provider overrides.repo_info_from_ref_url_with_provider()letsci.repouse the same configured provider hint while keepingrepo_url_from_ref_url()unchanged.https://git.example.com/org/project/_git/repostill report Azure DevOps withproject.git worktree addoutput and avoiding Git-version-dependent prunable worktree results.Tests
cargo test --lib repo_info --features shell-integration-testscargo test --bins test_json_repo_provider_from_git_repo_provider --features shell-integration-testscargo test --test integration test_list_full_json_ci_repo_uses_configured_provider_for_opaque_hostcargo test --test integration test_list_full_json_ci_repo_from_pr_urlcargo test --test integration test_list_json_configured_azure_generic_remote_is_unknowncargo test --test integration test_docs_are_in_synccargo test --test integration --features shell-integration-tests test_helpcargo test --test integration --features shell-integration-tests integration_tests::bare_repository::test_bare_repo_equivalent_to_normal_repocargo test --test integration --features shell-integration-tests integration_tests::ci_status::test_list_full_filters_by_repo_ownercargo test --test integration --features shell-integration-tests integration_tests::switch::test_switch_with_relative_worktree_pathscargo llvm-cov --features shell-integration-tests --cobertura --output-path /tmp/worktrunk-cobertura.xmlcargo run -- hook pre-merge --yesCI is green on head
8bcb05b1f, including Linux, macOS, Windows, Codecov patch, and Codecov project.