Add cargo xtask create-worktree - #185
Merged
Merged
Conversation
Initial design plan for new xtask subcommand that automates worktree setup (git worktree add + .beads/redirect + CLAUDE.local.md context section). Three modes: positional bd-id, --issue N, --upgrade. Plan went through two design-review passes via sub-agent before commit. Pass 1 surfaced 1 blocking + ~12 required issues — main themes: idempotency holes (CRLF, missing END marker, multi-BEGIN), clap-derive struct shape mismatch, hand-rolled date math, missing Manual bootstrap fallback, slug stop-words. Pass 2 caught the `time` feature flags (`macros` + `formatting` not in default set), line-ending sniff edge cases, and stale verification fixtures. Implementation deferred to /writing-plans phase.
Phased TDD breakdown of the design at claude-notes/plans/2026-05-07-create-worktree-xtask.md: scaffolding, pure-function red-green tasks, subprocess wrappers, run() orchestration, end-to-end smoke recipe, and docs/skills updates.
- Idempotency is file-level only. update_claude_local_md can be re-run safely; cargo xtask create-worktree itself errors on existing dir, by design. Phase E test rewritten to assert that behavior. - Add validate_slug helper for --slug overrides: ASCII alnum + dash/ underscore, length cap, no leading/trailing dash, no '.'/'..'. plan_*() invoke it; auto-derived slugs are safe by construction. - run() rolls back (git worktree remove --force + branch -D) on any failure between git_worktree_add and update_claude_local_md, so retries aren't blocked by half-initialized state. - git_worktree_add switches to .output() and surfaces stderr in the anyhow context. - build_section sends external titles through marker_safe() so a title containing the BEGIN/END marker substring cannot terminate the section. - Drop Task C1's premature smoke step (run() is still a stub there); Phase E covers the integration. Phase E preamble notes Git Bash on Windows for the cat/grep/printf commands.
- run()'s rollback now checks exit status and captures stderr for both `git worktree remove --force` and `git branch -D`. On rollback failure it logs explicit manual cleanup commands rather than silently leaving a half-cleaned state. - Add Q2_CREATE_WORKTREE_INJECT_FAIL test hook (one env-var check at the top of the post-worktree-add closure) so Phase E can exercise the rollback end-to-end without modifying production logic. - Phase E Step 5a: the pre-created collision dir must match the computed target path (`.worktrees/bd-spsv-collision-test`, not `.worktrees/ collision-test`). The old smoke would silently succeed at creating a real worktree. - Phase E adds Step 5d: inject failure, assert dir and branch both cleaned up.
detect_line_ending sniffs the first 1 KiB of the file. If a CRLF pair straddled byte 1023/1024, the sniff saw the bare \r at the end of the window and no \n, falling back to LF and silently writing mixed line endings into a CRLF file. Peek one byte past the window when the sniff ends with \r. Add a regression test. Drop the manual-bootstrap catch-22 in worktrees.md (the CLAUDE.local.md stub was sourced from `cargo xtask create-worktree --help`, which is unavailable in the exact scenario the manual bootstrap exists for). Point to the template's source location instead.
The previous wording claimed the --issue template "intentionally has no Beads line" and recommended re-running `cargo xtask create-worktree <bd-id>` to refresh it. Both were wrong: - The --issue template includes a placeholder Beads line pointing users at `br search <N>`; it is not absent, just unresolved. - Re-running with `<bd-id>` creates a separate beads worktree at `.worktrees/<bd-id>-<slug>`, not an update of the existing `.worktrees/issue-<N>`. The xtask is not command-level idempotent by design. Replace with the correct guidance: hand-edit the placeholder line after step 6 creates the bd-XXXX. Explicit DO-NOT for the rerun.
The three-line "Modes (exactly one required)" block in the CreateWorktree doc comment was collapsing to a single paragraph in `cargo xtask create-worktree --help`. Adding `#[command(verbatim_doc_comment)]` keeps the literal line breaks so each mode lands on its own line. Short `-h` still shows only the first sentence (clap default).
`cargo xtask create-worktree` was building the new worktree path as
`PathBuf::from(".worktrees").join(<leaf>)`, which `git worktree add`
resolves against CWD. Running the command from inside an existing
worktree therefore nested the new one as `<wt>/.worktrees/<leaf>`
instead of placing it next to its siblings at `<repo-root>/.worktrees/`.
Resolve the main repository root via `git rev-parse --path-format=absolute
--git-common-dir` once at the top of `run()`, then anchor each planner's
`Plan.dir` to it. Result: the command produces the same layout regardless
of CWD (main, subdir, or another worktree).
Add a small util module with `with_native_separators` so paths emitted by
git on Windows (which uses `/`) display consistently with `PathBuf::join`
output (which uses `\`). Apply it inside `repo_root()` so the entire chain
downstream sees one separator.
Tests: +3 (repo_root smoke + 2 util tests), 58 pass total.
Three changes, all targeting "what should the user / Claude do next": CLI output: by default print only the worktree summary + "cd <dir>, open Claude there (CLAUDE.local.md has the checklist)" pointer. Add a `-v/--verbose` flag that re-enables the manual command list inline. The old default was a numbered 4-step list that conflated `cd && npm install` on one line and described the plan-file step too vaguely. CLAUDE.local.md: each managed section now ends with `## Initial setup`, tailored per mode. - Beads: 3-step checklist (verify --skip-hub-build, npm install if hub-client is in scope, br update --status in_progress) with inline notes explaining that `--skip-hub-build` keeps the step Rust-only and that `npm install` is intentionally separate from `cargo xtask dev-setup` today (tracked in bd-7giz). - Issue: shorter checklist + a note about no beads issue being linked yet and what to edit when triage creates one. - Upgrade: pointer to the `upgrade-cargo-deps` skill, which drives the rest, with a manual-fallback line. Tests: 58/58, existing assertions are content-presence checks so the expanded sections do not regress them.
….local.md (bd-spsv)
Reverts the per-mode `## Initial setup` section that previous commit
added to `CLAUDE.local.md`. That file is loaded by Claude Code on every
session in the worktree; embedding "do this" steps risks Claude re-running
them or treating them as ground truth long after the prep is done.
Instead, print the checklist once on stdout at creation time, grouped by
frequency so the user can see at a glance which lines apply:
# Once per machine (skip if already done)
cargo xtask dev-setup # installs cargo-nextest, wasm-bindgen-cli
# Per worktree
cargo xtask verify --skip-hub-build # confirm HEAD is green (Rust only)
npm install # only if hub-client work is in scope
# (separate from dev-setup today — bd-7giz)
# Per beads issue (this worktree)
br update bd-spsv --status in_progress # claim it
For issue and upgrade modes the per-issue group is replaced with a
single-line note pointing at the relevant skill / next action.
Drops the `-v/--verbose` flag introduced earlier in this branch (the
checklist is short enough that conditional output stopped being worth
the surface area).
… stdout (bd-spsv) Two trims to the post-create output: CLAUDE.local.md: the **Plan:** placeholder (and the **Beads:** placeholder in issue mode) was an HTML comment, invisible in rendered markdown and easy to miss in raw source. Replace with italic prose that explains the expected replacement inline — `_none yet — replace this with claude-notes/plans/YYYY-MM-DD-<name>.md once you create the plan file._` This is readable in both source and rendered views, so anyone (human or Claude) opening the file sees what to fill in without a separate prompt. Stdout: now that the placeholder self-documents, drop the trailing "When you create a plan file... edit the **Plan:** line in CLAUDE.local.md" reminder. Also drop the parenthetical "# (separate from dev-setup today — bd-7giz)" line — that context belongs on the beads issue itself, not on every worktree creation. A corresponding comment is being added to bd-7giz so when it lands the `npm install` line here can be folded into `cargo xtask dev-setup`. Issue mode's "Edit the **Beads:** line in CLAUDE.local.md" reminder is likewise dropped — the new in-place placeholder covers it.
…and stdout (bd-spsv) Add a **Skill:** line to each managed section: `/investigate-beads` for beads worktrees, `/triage` for issue worktrees, `/upgrade-cargo-deps` for upgrade worktrees. CLAUDE.local.md is the durable surface a fresh session lands on weeks later; the stdout is transient. Tweak the stdout tails too: use the slash-prefix form, use "continues" instead of vague "drives". Teach the three skills to skip the create-worktree step when already inside the matching worktree (re-running would fail noisily on `git worktree add`). Each skill explains how to recognise its own worktree from CLAUDE.local.md.
Append the Phase E transcript (help, three modes, four failure cases, anchor-to-repo-root verification, cleanup) to the implementation plan and tick the Phase E checklist boxes. Matches the End-to-end verification section in the PR body.
….md (bd-spsv) rules/worktrees.md § CLAUDE.local.md: extend the inventory of fields the managed section carries to include `**GitHub issue:**`, `**Skill:**`, and note that placeholders are self-documenting. skills/triage: tighten the description of the issue-mode `**Beads:**` placeholder to match the current italic-prose shape.
`create-worktree` is the most frequent xtask developers run (every new worktree), so it earns a top-level cargo alias the same way `dev-setup` does. Saves typing `xtask` and matches what the existing alias precedent sets up. Both forms work side-by-side: `cargo xtask create-worktree ...` still resolves through the generic `xtask` alias, while `cargo create-worktree ...` shortcuts past it.
This was referenced Jul 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Setting up a new worktree in this repo currently means running
git worktree add, hand-creating.beads/redirectwith a fragile relative path, and writing aCLAUDE.local.mdcontext stub by copy-paste from.claude/rules/worktrees.md. The relative-path arithmetic is easy to get wrong, and the three skills that drive most worktree creation (/investigate-beads,/triage,/upgrade-cargo-deps) all carried near-identical bash recipes for it.cargo xtask create-worktreedoes the whole sequence in one shot for the three worktree types this project actually creates:A
cargo create-worktreecargo alias is also wired up (same precedent ascargo dev-setup), so the shorter form works everywhere too:Each mode populates
CLAUDE.local.mdwith a managed BEGIN/END section so a fresh Claude session lands with the right context — beads ID, GitHub link, plan placeholder, and the slash-command to continue the work. The xtask anchors worktrees at the main repo root regardless of cwd, validates--sluggrammar to keep path-traversal mistakes out of.worktrees/, and rolls back cleanly if anything aftergit worktree addfails.The three skills now call the xtask instead of duplicating the bash recipe, and
.claude/rules/worktrees.mdwas rewritten to point at the xtask first with a manual bootstrap as fallback for fresh clones.End-to-end verification
All three modes, four failure cases, and the anchor-to-repo-root invariant verified on Windows (Git Bash + PowerShell) per the Phase E smoke test in
claude-notes/plans/2026-05-11-implement-create-worktree-xtask.md. 58 unit tests pass (cargo nextest run -p xtask).Smoke test transcript
--helpBeads mode + anchor-to-root verification
Invoked from
crates/xtask/to confirm the worktree lands at the main-repo root regardless of cwd:Issue mode
Upgrade mode
Failure cases
.worktrees/bd-spsv-collision-test/; xtask errored beforegit worktree addran, no branch leaked.--sluggrammar —foo/bar(path separator) and..(traversal) both rejected byvalidate_slugwith no FS or git side effects.worktree directory already exists: <path>(by design — file-level idempotency only).Q2_CREATE_WORKTREE_INJECT_FAIL=after_worktree_addinjects a synthetic failure betweengit worktree addand the post-add steps:The injected failure fires after
git worktree addsucceeds, rollback cleans up both the directory and the branch, and the original error propagates as the final exit error. Post-run verification confirmed no leftover dir or branch.