Skip to content

Add cargo xtask create-worktree - #185

Merged
cscheid merged 34 commits into
mainfrom
feature/bd-spsv-create-worktree-xtask
May 12, 2026
Merged

Add cargo xtask create-worktree#185
cscheid merged 34 commits into
mainfrom
feature/bd-spsv-create-worktree-xtask

Conversation

@cderv

@cderv cderv commented May 12, 2026

Copy link
Copy Markdown
Member

Setting up a new worktree in this repo currently means running git worktree add, hand-creating .beads/redirect with a fragile relative path, and writing a CLAUDE.local.md context 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-worktree does the whole sequence in one shot for the three worktree types this project actually creates:

cargo xtask create-worktree <bd-id>     # beads issue
cargo xtask create-worktree --issue N   # GitHub issue triage
cargo xtask create-worktree --upgrade   # cargo dependency upgrade (date-based branch)

A cargo create-worktree cargo alias is also wired up (same precedent as cargo dev-setup), so the shorter form works everywhere too:

cargo create-worktree bd-XXXX
cargo create-worktree --issue 184
cargo create-worktree --upgrade

Each mode populates CLAUDE.local.md with 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 --slug grammar to keep path-traversal mistakes out of .worktrees/, and rolls back cleanly if anything after git worktree add fails.

The three skills now call the xtask instead of duplicating the bash recipe, and .claude/rules/worktrees.md was 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

--help

$ cargo xtask create-worktree --help
Create a new git worktree with beads redirect and CLAUDE.local.md context stub.

Modes (exactly one required):
  <bd-id>      — beads issue (positional)
  --issue N    — GitHub issue triage
  --upgrade    — cargo dependency upgrade (date-based branch)

Usage: xtask.exe create-worktree [OPTIONS] <BEADS_ID|--issue <ISSUE>|--upgrade>

Arguments:
  [BEADS_ID]
          Beads issue ID, e.g. `bd-1d3e`. Reads `br show <id>` for title and external_ref

Options:
      --issue <ISSUE>
          GitHub issue number, e.g. `157`. Reads `gh issue view`

      --upgrade
          Cargo dependency upgrade — uses today's date for branch name

      --slug <SLUG>
          Override auto-derived slug. In beads mode replaces the derived slug;
          in issue/upgrade modes appended as a suffix (for parallel-worktree workflows)

      --base <BASE>
          Base branch

          [default: main]

  -h, --help
          Print help (see a summary with '-h')

Beads mode + anchor-to-root verification

Invoked from crates/xtask/ to confirm the worktree lands at the main-repo root regardless of cwd:

$ cd crates/xtask
$ cargo xtask create-worktree bd-spsv --slug anchor-test
Created worktree: C:\Users\chris\Documents\DEV_R\q2\.worktrees\bd-spsv-anchor-test/
  Branch:  beads/bd-spsv-anchor-test
  Beads:   bd-spsv — Add cargo xtask create-worktree command with CLAUDE.local.md stub

Next:
  cd C:\Users\chris\Documents\DEV_R\q2\.worktrees\bd-spsv-anchor-test

  Open a Claude Code session there — CLAUDE.local.md gives it the
  worktree context (branch, beads/GitHub link, base). Copy whichever of
  the prep commands below 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

  # Per beads issue (this worktree)
  br update bd-spsv --status in_progress      # claim it
  # `/investigate-beads` reloads context if you need it.

Issue mode

$ cargo xtask create-worktree --issue 184 --slug ts-issue
Created worktree: C:\Users\chris\Documents\DEV_R\q2\.worktrees\issue-184-ts-issue/
  Branch:  issue-184-ts-issue
  Issue:   #184 — Indented (4-space) code blocks are parsed as paragraphs, and re-emitted unindented
  URL:     https://github.com/quarto-dev/q2/issues/184

Next:
  cd C:\Users\chris\Documents\DEV_R\q2\.worktrees\issue-184-ts-issue

  # ...same Per worktree setup as above...

  # `/triage` continues the investigation — it files a beads issue
  # when concrete work surfaces.

Upgrade mode

$ cargo xtask create-worktree --upgrade --slug ts-upgrade
Created worktree: C:\Users\chris\Documents\DEV_R\q2\.worktrees\cargo-upgrade-2026-05-12-ts-upgrade/
  Branch:  cargo-upgrade-2026-05-12-ts-upgrade
  Task:    Cargo dependency upgrade — 2026-05-12

Next:
  # ...same Per worktree setup as above...

  # `/upgrade-cargo-deps` continues this worktree's work.

Failure cases

  • Existing directory collision — pre-created .worktrees/bd-spsv-collision-test/; xtask errored before git worktree add ran, no branch leaked.
  • Invalid --slug grammarfoo/bar (path separator) and .. (traversal) both rejected by validate_slug with no FS or git side effects.
  • Re-run on existing worktree — fails with worktree directory already exists: <path> (by design — file-level idempotency only).
  • Rollback pathQ2_CREATE_WORKTREE_INJECT_FAIL=after_worktree_add injects a synthetic failure between git worktree add and the post-add steps:
$ Q2_CREATE_WORKTREE_INJECT_FAIL=after_worktree_add \
    cargo xtask create-worktree bd-spsv --slug rollback-test
error after worktree creation: Q2_CREATE_WORKTREE_INJECT_FAIL=after_worktree_add (test hook)
rolling back worktree C:\Users\chris\Documents\DEV_R\q2\.worktrees\bd-spsv-rollback-test and branch beads/bd-spsv-rollback-test ...
rollback complete.
Error: Q2_CREATE_WORKTREE_INJECT_FAIL=after_worktree_add (test hook)

The injected failure fires after git worktree add succeeds, 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.

cderv added 30 commits May 7, 2026 19:09
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.
cderv added 4 commits May 12, 2026 15:09
…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.
@cscheid
cscheid merged commit e3201e6 into main May 12, 2026
4 checks passed
@cscheid
cscheid deleted the feature/bd-spsv-create-worktree-xtask branch May 12, 2026 18:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants