Skip to content

feat: redesign merge with rebase styles and PR-style hook gates#471

Merged
avihut merged 122 commits intomasterfrom
daft-330/feat/merge
May 6, 2026
Merged

feat: redesign merge with rebase styles and PR-style hook gates#471
avihut merged 122 commits intomasterfrom
daft-330/feat/merge

Conversation

@avihut
Copy link
Copy Markdown
Owner

@avihut avihut commented May 5, 2026

Summary

  • Reshape daft merge to a GitHub-PR-style flow: four merge styles (--merge, --squash, --rebase, --rebase-merge), two cleanup outcomes (--remove-branch, --keep-branch), and --set-default to persist the resolved choices to git config --local.
  • Pre-merge hooks now abort by default (PR-check semantics) with per-hook failMode: warn override; post-merge fires only on successful merges (not on squash-staged-only, not on conflict).
  • Hard cutover on the legacy flag/config surface — --ff/--no-ff/--ff-only, --squash (toggle), -r/-rb/--and-branch, and daft.merge.{ff,squash,postMerge.removeSourceWorktree,postMerge.alsoRemoveSourceBranch} are all gone. Pre-1.0 surface, no compat shims (per feedback_breaking_change_marker.md).
  • Output redesigned for clarity after field testing: persistent intent line at the top (Merging X → Y (style · cleanup [· saving as default])), per-source cleanup heading before each worktree-pre-remove box, on: <target> segment in the hook-box title for worktree-scoped phases, and the Updated repository defaults notice moved to a trailing footnote.
  • Net: 120 commits, 24 new YAML scenarios (65 total under tests/manual/scenarios/merge/), 1695 unit tests passing, clippy clean, man pages regenerated. Spec + plan committed under docs/superpowers/.

Fixes #330.

Test plan

CI / automated:

  • mise run fmt:check — clean
  • mise run clippy — zero warnings
  • mise run test:unit — 1695 passing
  • mise run test:manual -- --ci — all merge scenarios green (308 steps)
  • mise run man:verify — man pages up to date

Manual smoke (each on a fresh sandbox repo):

  • Style matrix: --merge produces a merge commit on FF-able input; --squash --no-edit produces single-parent squash; --rebase yields linear history with no merge commit; --rebase-merge yields linear ancestors plus a final merge commit. Verify with git log --graph --oneline.
  • Cleanup × style: each style with --remove-branch removes worktree + branch; each with --keep-branch preserves both; each with no flag honors daft.merge.cleanup.
  • --set-default round-trip: run with flags, confirm git config --get daft.merge.style and daft.merge.cleanup persisted; subsequent flagless invocation picks them up; defaults footnote prints after the success line.
  • Hook scope rendering: single-source merge shows Merging X → Y … at top, Cleaning up X (worktree, local branch) before the box, hook-box title carries worktree-pre-remove on: X. Multi-source octopus produces a separate box per source with the right on: target each time.
  • Pre-merge hook gates: default abort blocks the merge (no cleanup, no post-merge fires); per-hook failMode: warn override allows the merge to proceed and post-merge to fire.
  • Mid-operation paths: induce a conflict, then daft merge --continue after resolving (note documented limitation: --continue does not run cleanup); daft merge --abort; cross-worktree merge via --into <other>.
  • Remote-sync interaction: --remove-branch with daft.branch_delete.delete_remote=true also removes the source remote-tracking branch.
  • Shell completions: re-eval daft completions <shell> and confirm tab completion offers --merge, --squash, --rebase, --rebase-merge, --remove-branch, --keep-branch, --set-default (and no longer offers the removed legacy flags).

🤖 Generated with Claude Code

avihut and others added 30 commits May 3, 2026 00:01
Cross-worktree merge verb with full git flag parity, layered config,
optional post-merge cleanup, and explicit handling of worktree-specific
edge cases (ephemeral worktree for targets without one, promote-on-conflict,
cross-worktree abort/continue).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the YAML-file config layer (~/.config/daft/config.toml, .daft/daft.yml)
with git config daft.merge.* keys, matching how DaftSettings already works
(daft.autocd, daft.checkout.push, etc.). Key names switch to lowerCamelCase.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
17-slice TDD-driven plan: scaffolding, CWD merge, --into resolution,
safety rails, octopus announcement, full git flag passthrough, finish
commands, conflict handling, FF plumbing, ephemeral worktree + prompt,
promote-on-conflict, -r/-rb cleanup, layered config, list --merging,
completions, docs/man/SKILL, and final CI verification.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After comparing daft merge against worktrunk's wt merge, two features
are worth pulling into scope:

- `-y` / `--yes`: auto-accept interactive prompts (currently just the
  ephemeral-worktree prompt; future-proofs new prompts). Implies
  --adopt-target. Logs the coercion so invocations stay self-describing.
- `merge-pre` / `merge-post` hooks: daft-layer hooks that carry
  cross-worktree / octopus / ephemeral context (info git's native hooks
  can't see). merge-pre aborts on failure; merge-post warns only.

Spec updated with goals, CLI surface, full Hooks section with env-var
contracts. Plan adds Task 10.1.5/10.1.6 for -y and a new Slice 15 for
the hook integration, renumbering downstream slices.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Generated by the pre-commit man:gen hook as a side effect of
registering git-worktree-merge in xtask. Slice 17 regenerates and
authors the full reference page at docs/cli/daft-merge.md; these
stubs land early so the working tree stays clean for subsequent
tasks.
Task 1.2 now lists src/lib.rs as a file to modify and adds a step for
the DAFT_VERBS entry (blocker found during pilot — bare `daft merge`
verb path fails without it).

Slice 17 gets a new Task 17.0 to wire daft_verb_tip() and
related_commands() in xtask so the auto-generated
docs/cli/git-worktree-merge.md picks up the tip box and See Also
section its peers have. Also renumbers Slice 18's tasks 17.1/17.2 to
18.1/18.2 (missed during the earlier hook-adoption renumber).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduce src/core/worktree/merge.rs with the minimal Slice-2 surface:
StartParams (sources), StartOutcome (already_up_to_date, conflicted), and
execute_start() which dispatches `git merge <sources...>` inside a target
worktree via std::process::Command, mirroring the established rebase.rs
pattern. Conflict detection is exit-status-based; already_up_to_date is
stubbed to false and upgraded in later slices. Registers the module in
src/core/worktree/mod.rs alphabetically.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace Task 1.1's skeleton with the Slice-2 wiring: clap Args carrying
the positional SOURCE list and --verbose, a repo guard, and a dispatch
to core::worktree::merge::execute_start using std::env::current_dir()
as the target worktree. Maps StartOutcome to user-facing output: a
success line on a clean merge, "Already up to date." for no-op, and a
bail with the continue hint on conflict.

Regenerated man pages (man/daft-merge.1, man/git-worktree-merge.1) and
the CLI docs stub (docs/cli/git-worktree-merge.md) are included because
the added positional argument changed the generated output; the
pre-commit verify hook requires these to be in sync with source.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add tests/manual/scenarios/merge/basic.yml verifying the Slice-2 happy
path: clone the standard-remote fixture, check out feature/test-feature
to materialise it as a worktree, then from the main worktree run
`git-worktree-merge feature/test-feature` and assert exit 0 plus
"Merge complete." in the output. A final `git log --oneline main`
sanity-checks that main still has a history.

Also register the git-worktree-merge dev symlink in
mise-tasks/setup/rust. Slice 1 added the command to the xtask COMMANDS
list (man pages, completions) but did not add the dev symlink the
scenario runner needs to exec git-worktree-merge; without it the
scenario fails with command-not-found. Deviation from the task's
file-restriction is intentional and minimal.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the incorrect rebase.rs:295 citation and explain the direct
Command usage as a Slice-2 shortcut to be replaced by
GitCommand::merge_in later. Rename StartOutcome.conflicted to failed
to avoid conflating true conflicts with other non-zero exits; the
field now documents the Slice 5+ refinement. Strengthen the basic
merge scenario's post-condition to assert the feature commit is
reachable from main, not just that git log succeeds. Document the
caller invariant on execute_start and delete the redundant empty-
sources bail (clap's num_args = 1.. enforces it) along with the
dead module_exports_are_visible test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces the --into flag on the merge command and a matching target
field on core StartParams. Target resolution and dispatch land in the
next commit; for now the flag is wired end-to-end as None by default.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds ResolvedTarget and resolve_target to the merge core. Delegates path
resolution to GitCommand::resolve_worktree_path for explicit targets and
reads the target branch via `git -C <path> symbolic-ref --short HEAD`.
When no target is supplied, uses get_current_worktree_path and
symbolic_ref_short_head against the process CWD.

Unit tests cover branch_at_path against a fresh git init tempdir; the
happy-path multi-worktree resolution is left to the YAML scenario since
GitCommand's CWD-based helpers are hostile to parallel in-process tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Refactors execute_start to take &GitCommand and project_root, routes the
call through resolve_target, and dispatches `git merge` with the target
worktree as CWD. Command layer now loads DaftSettings for the gitoxide
backend choice and constructs GitCommand, mirroring carry.rs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a YAML scenario that exercises the end-to-end --into path: from
the feature worktree, run `daft merge --into main feature/...`, then
verify main contains the feature commits without ever changing CWD
into the target worktree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…de parity

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds validate_distinct() pre-flight check that bails with an actionable
error when any source branch equals the resolved target branch. Called
from execute_start before the merge invocation so users see a clear
"cannot merge branch 'X' into the same branch" instead of git's noisy
"Already up to date." for what is almost always an --into typo.

Covered by a unit test for both the refuse and allow paths plus a YAML
scenario that runs `git-worktree-merge main --into main` and asserts
exit 1 with "same branch" in stderr.
Adds detect_in_progress() which inspects the target worktree's real git
directory (following the gitdir pointer for linked worktrees) for the
state files git writes when a merge/rebase/cherry-pick/bisect is paused:
MERGE_HEAD, rebase-merge/, rebase-apply/, CHERRY_PICK_HEAD, BISECT_LOG.

execute_start now bails with "target worktree 'X' is mid-rebase; finish
or abort it first" (or equivalent) when such a state is detected, after
the source==target check and before the merge invocation. Stacking two
operations' conflicts on the user is always the wrong move.

Unit tests cover each op variant, the clean case, the rebase-apply
alternate path, and the linked-worktree gitdir-pointer codepath. A YAML
scenario induces an in-progress rebase in main and verifies refusal.
Adds validate_clean_target() which delegates to the existing
GitCommand::has_uncommitted_changes_in helper (src/git/stash.rs) so
dirtiness is judged by the same `git status --porcelain` check the rest
of daft uses — including treating untracked files as dirty.

execute_start now refuses with "target worktree 'X' has uncommitted
changes; commit, stash, or allow via `daft.merge.requireCleanTarget=false`"
after the source==target and in-progress checks. The config toggle
referenced in the hint lands in Slice 13; for now the behavior is
hard-coded to always-refuse and a TODO in the docstring points to the
follow-up slice.

Unit tests cover both the clean and untracked-file-dirty paths using
tempdir-backed real git repos with identity set via env vars (never
global git config). A YAML scenario clones, checks out feature, dirties
main with an untracked file, and verifies the merge is refused with
"uncommitted changes" in stderr.
- validate_distinct now strips refs/heads/ prefix before comparing,
  so `daft merge refs/heads/main --into main` is refused. Doc-comment
  notes comparison remains nominal (origin/main, SHAs not normalized).
- detect_in_progress uses is_dir() for rebase-merge/rebase-apply markers
  and now bails with context when the resolved git_dir is missing.
- Malformed `.git` file pointer (missing `gitdir:` prefix) errors out
  instead of silently falling back to the worktree path.
- validate_clean_target error message no longer references
  daft.merge.requireCleanTarget (the config lands in slice 13).
- New unit tests: later-source match and refs/heads/ prefix cases.
Add a pure `announcement()` helper in `core::worktree::merge` and call it
from `execute_start` before invoking `git merge`. When two or more sources
are supplied, the helper returns "Merging N sources into <target> via
octopus strategy"; `execute_start` emits that line to stderr so it stays
out of stdout (reserved for the final "Merge complete." / "Already up to
date." result) but remains visible even when git's octopus refuses a
conflict.

Covered by new unit tests and two YAML scenarios (`merge:octopus`,
`merge:octopus-conflict`).
Add CLI args for all 14 git merge passthrough flag groups (message/editor,
ff mode, squash, commit, signoff, strategy, strategy options, gpg signing,
signature verification, allow-unrelated-histories, stat). Flags flow from
clap Args into a new EffectiveFlags struct on StartParams, which is
serialized back into argv between the merge keyword and the source list
via render_flags.

Clap conflicts_with/conflicts_with_all attributes enforce mutual exclusion
of paired flags (--ff / --no-ff / --ff-only, etc.) at parse time. -S uses
num_args = 0..=1 + default_missing_value = "" to distinguish bare -S
(default key -> GpgSign::Default) from -S<KEYID> (GpgSign::KeyId).

Unit tests (13 new) cover render_flags for every variant: empty, message
+ file pairing, each FfMode, multiple -X options as separate pairs, each
GpgSign variant, edit/no-edit, allow-unrelated-histories toggle, and a
full combination that locks the declared emission order.

YAML scenarios (8 new) smoke-test that each flag threads through end to
end: ff default, --ff-only refusal on non-FF, --no-ff merge commit,
--squash staging, --signoff trailer, -s ours, -X theirs, and
already-up-to-date no-op. No gpg.yml scenario; GPG requires a configured
key and the serializer is covered by unit tests.

Man pages and CLI docs regenerated to document the new flags.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lp text

Switch `execute_start` from `.status()` to `.output()` so the core layer can
observe git's stdout and set `StartOutcome.already_up_to_date = true` when
git emits "Already up to date.". The captured stdout/stderr are re-emitted
verbatim to our own stdio via `write_all`, preserving what the user sees.

The command layer no longer prints its own "Already up to date." line in
that branch — git's passthrough already said it — avoiding the double-print
contradicted by a trailing "Merge complete.". The `already-up-to-date.yml`
scenario asserts "Merge complete." is absent from the second-merge output.

Also drop the "(also `-n`)" tail from the `--no-stat` help text; clap's
`-n, --no-stat` header already advertises the short alias. Regenerated the
daft-merge and git-worktree-merge man pages and the CLI reference page for
the help-text update.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…es via plumbing

Commit c51789e0 switched `execute_start`'s git invocation from `.status()`
to `.output()` to detect "Already up to date." in stdout. That broke the
interactive merge-commit editor path: for a non-FF merge without `-m`,
git launches `$EDITOR` for the commit message, which requires stdio to
be inherited from the parent TTY. Capturing stdout/stderr makes that
unpredictable.

Replace the stdout scrape with a plumbing pre-flight before argv is
built: `rev-parse` each source and the target branch, then ask
`merge-base --is-ancestor` whether every source is already reachable
from the target. If so, print "Already up to date." directly and return
early without invoking `git merge`. For real merges, revert to
`.status()` so stdio inherits the parent TTY and the editor works
again.

For octopus (multi-source) merges, the predicate is "all sources are
ancestors" — if any source has new commits, we still run git merge.
If `rev-parse` fails for an invalid ref, skip the short-circuit and let
`git merge` surface its usual error.

Run the pre-flight before the octopus announcement so an up-to-date
multi-source invocation doesn't herald a strategy we won't actually
run.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ints

- `--abort`/`--continue`/`--quit` now conflict with every start-only flag,
  so `daft merge --abort -m msg` errors at parse time instead of silently
  ignoring the start-mode flag.
- Finish mode accepts `--into <target>` as a fallback when no positional
  is given; passing both is rejected.
- `execute_finish` routes through `ensure_merge_in_progress` and resolves
  branch names for candidate worktrees so single-candidate retry hints
  show a concrete `daft merge --abort <branch>` command.
- Adds `merge/abort-hint-elsewhere.yml` covering the "merges in progress
  elsewhere" hint path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
avihut and others added 14 commits May 3, 2026 23:42
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Delete ff.yml, ff-only.yml (FF-default and ff-only behaviors removed),
no-ff.yml (default is now always-merge-commit, covered by new scenarios),
and remove-source.yml (worktree-only removal without branch delete is no
longer expressible via daft merge).

Update all -rb → -r, --no-ff → --merge, daft.merge.ff → daft.merge.style.
Add --no-edit to all default-style merge commands to avoid editor
invocation in CI. Update assertions from "Fast-forwarded" to
"Merged ... into ... (commit" wherever the default style now forces a
merge commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MergeHookRunner was using HooksConfig::default() instead of
load_hooks_config(), causing git config keys like
daft.hooks.preMerge.failMode to be silently ignored. This made
the warn-mode override ineffective: a failing pre-merge hook would
always abort even after setting failMode=warn via git config.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Covers all four merge styles (--merge, --squash, --rebase,
--rebase-merge), both cleanup modes (keep, remove-branch), the
--set-default config writer, and pre/post-merge hook behavior under
preMerge.failMode=warn.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- daft-merge.md: new Key Options table (--merge/--squash/--rebase/
  --rebase-merge/-r/--keep-branch/--set-default), rebase/rebase-merge
  examples, --set-default section, updated Configuration table with
  daft.merge.style + daft.merge.cleanup, migration table from v1.9 flags
- hooks.md: document preMerge.failMode=warn git config override, update
  DAFT_MERGE_MODE to include rebase/rebase-merge, fix -r/-rb refs
- SKILL.md: update command synopsis and Cross-worktree Merges section
  with new flag set, default behavior note, and --set-default guidance

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The new default MergeStyle::Merge always creates a merge commit, which
triggers a TTY guard early in start-mode. Scenarios that relied on pre-flight
checks (dirty-target, same-source, conflict, abort setups) hit the TTY guard
before reaching those checks.

Add --no-edit to the merge invocations in 16 scenarios so the TTY guard is
bypassed and the actual behaviors under test are exercised.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
detect_in_progress_state hardcoded path.join(".git") as a directory,
breaking --continue/--abort in every linked worktree (where .git is a
gitdir-pointer file). Now follows the same gitdir resolution that
detect_in_progress uses.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…merge

Spec requires these flags to conflict with rebase styles since rebase
has no auto-commit toggle. Without the clap conflicts, daft silently
accepted invalid combinations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous fix in 1511b99 covered MergeHookRunner but missed two sites:
the cleanup loop's HookExecutor (worktree-pre/post-remove hooks) and
fire_worktree_post_create_hook (worktree-post-create on ephemeral
promotion). Without this, those paths ignore daft.hooks.* git config
(trust level, hook paths). Also removes now-unused HooksConfig import.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Spec requires post-merge to fire only when a commit landed. The
squash_staged_only path stages changes but produces no commit; firing
post-merge there would deliver an empty commit_sha to hook scripts and
violate the "merge happened" trigger contract. Fixes both the regular
target-path path (line 1631) and the ephemeral worktree path (line 2060).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Slice 3 CLI cutover replaced --ff/--no-ff/--ff-only/--squash/
--no-squash/-r/--remove/-b/--and-branch with --merge/--squash/--rebase/
--rebase-merge/-r/--remove-branch/--keep-branch/--set-default but missed
the hardcoded flag lists that bash, zsh, fish, and fig completions
embed as string constants. CLAUDE.md flags this exact case: flag
completions for git-worktree-* commands are auto-generated from clap
Args, but the hardcoded subcommand strings in bash/zsh/fish/fig must be
updated manually when flags change.

Users will need to re-eval daft completions <shell> in active shells
(or restart) to pick up the updated lists.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Field testing surfaced two confusing aspects of `daft merge`'s output:
the user couldn't tell whether daft had understood their flags (the only
signal was a transient spinner that often vanished too fast to read),
and the cleanup hook box left them unsure which worktree it was acting
on — alarming when merging from a sibling worktree, since at first
glance it looked like the current worktree might be removed.

Three changes address this without behavior changes:

1. Persistent intent line at the start: "Merging X → Y (style · cleanup
   [· saving as default])". Lands once and stays in scrollback so the
   rest of the output reads in context.

2. Per-source cleanup section heading right before the worktree-pre-remove
   hook box: "Cleaning up X (worktree, local branch)". Labels the target
   in the user's main output stream before the box appears.

3. Hook-box title gains an `on: <target>` segment for worktree-scoped
   phases (worktree-pre-create / -post-create / -pre-remove / -post-remove);
   project-scoped phases (pre-merge, post-merge, post-clone) keep the bare
   title. The redundant `hook:` label is also dropped — the box border
   itself signals "this is a hook".

The `--set-default` notice ("Updated repository defaults: …") is also
moved from mid-flow (between the merge and cleanup) to a trailing footnote
after the success line, so it reads as a side effect rather than
interleaved with operational steps. Defaults are still persisted before
cleanup, and the notice fires on cleanup-failure paths too — the values
were saved regardless of cleanup outcome.

Pinned scenarios updated:
- merge-pre-post-merge-hooks-render-rich.yml: title contract changed,
  `output_not_contains` guards against the legacy `hook:` label.
- New hook-box-target-segment.yml: load-bearing assertion that
  `worktree-pre-remove  on: feature/test-feature` appears in cleanup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@avihut avihut added this to the Public Launch milestone May 5, 2026
@avihut avihut added the feat New feature label May 5, 2026
@avihut avihut self-assigned this May 5, 2026
avihut and others added 2 commits May 6, 2026 08:17
Two PR-check failures, both regressions from this branch's new merge
command landing without updating hardcoded CI lists:

- Integration tests (yaml suite): all merge scenarios failed with
  "git-worktree-merge: command not found". The integration-tests job in
  test.yml has a hardcoded symlink-creation list that didn't include the
  new command. Same omission in the man-page validation list, the
  CLI-docs validation list, and the homebrew-simulation install/verify
  lists. Adding it to all six keeps them consistent.

- Unit tests: 5 merge.rs tests panicked in CI with "empty ident name"
  because production code (git merge / git rebase) inherits the test
  process env but not the GIT_AUTHOR_*/GIT_COMMITTER_* env vars that
  init_repo only set for the bootstrap commit. Fix by writing
  user.name/user.email to local git config inside init_repo, so any git
  invocation on the test repo (test code or production code under test)
  has an identity. Local-only — no global config touched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`finish_mode_continue_resumes_rebase` failed in CI with "Terminal is
dumb, but EDITOR unset" — `git rebase --continue` after a conflict
resolution invokes `git commit` which opens an editor to confirm the
original message. CI runners are headless with no EDITOR/GIT_EDITOR set.

Set core.editor=true in the test repo's local config so any implicit
editor invocation is a successful no-op that preserves the existing
commit message. Test repo only — global config untouched.

Reproduced locally with `env -i HOME=/tmp/empty PATH=...` to mimic
the CI environment, confirmed fix passes the failing test and all 130
merge module tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@avihut avihut merged commit c6244bf into master May 6, 2026
11 checks passed
@avihut avihut deleted the daft-330/feat/merge branch May 6, 2026 05:34
avihut added a commit that referenced this pull request May 6, 2026
Master shipped the cross-worktree merge feature (#471) before this branch's
IA restructure landed, so the merge docs entered the old IA shape (one
guide/hooks.md, one guide/configuration.md, one guide/worktree-workflow.md,
table-of-contents merge entries in the legacy sidebar). The rebase brought
the new CLI ref pages and the merge config additions through cleanly via
git's rename detection on configuration.md, but the rest needed manual
porting.

Changes:

- New page: Worktrees → Merging across worktrees. Substantively expanded
  from master's 25-line section in guide/worktree-workflow.md into a proper
  how-to: quick examples, merge styles, conflicts, cleanup, ephemeral
  targets, and hook gates. Sidebar position: between "Running commands" and
  "Shortcuts".
- Hooks → Lifecycle: added pre-merge / post-merge rows to the hook-types
  table, env-var tables for both, and a Merge hooks section covering
  semantics, fail modes (including the daft.hooks.preMerge.failMode
  override), and the squash-abort case.
- Hooks → Overview: flipped merge gates from Roadmap to Shipped in the
  boundaries table; updated the "Where to next" line.
- Hooks → Roadmap: removed the merge-gates entry (shipped); added a
  "Recently shipped" pointer to the new merge content.
- Reference → Configuration: reconciled the merge-keys table to the v1.10
  schema (daft.merge.style + daft.merge.cleanup) per the authoritative
  daft-merge.md migration table. Master's preexisting tabular content
  documented the v1.9 keys that were removed in #471.
- Reference → CLI sidebar: added daft-merge under Maintenance and
  worktree-merge under Git Commands → Maintenance.
- Landing: updated Worktrees and Hooks pillar cards to reflect the merge
  feature and shipped merge-gates story (no new card — landing stays
  pillar-shaped).
- _redirects: /guide/hooks now lands on /hooks/lifecycle so anchors like
  #merge-hooks survive the redirect (Cloudflare can't match on fragments).
- CLI ref: fixed two relative ../guide/* links in daft-merge.md to absolute
  paths in the new IA.

Build clean with strict link checking; biome and prettier pass.
avihut added a commit that referenced this pull request May 9, 2026
Master shipped the cross-worktree merge feature (#471) before this branch's
IA restructure landed, so the merge docs entered the old IA shape (one
guide/hooks.md, one guide/configuration.md, one guide/worktree-workflow.md,
table-of-contents merge entries in the legacy sidebar). The rebase brought
the new CLI ref pages and the merge config additions through cleanly via
git's rename detection on configuration.md, but the rest needed manual
porting.

Changes:

- New page: Worktrees → Merging across worktrees. Substantively expanded
  from master's 25-line section in guide/worktree-workflow.md into a proper
  how-to: quick examples, merge styles, conflicts, cleanup, ephemeral
  targets, and hook gates. Sidebar position: between "Running commands" and
  "Shortcuts".
- Hooks → Lifecycle: added pre-merge / post-merge rows to the hook-types
  table, env-var tables for both, and a Merge hooks section covering
  semantics, fail modes (including the daft.hooks.preMerge.failMode
  override), and the squash-abort case.
- Hooks → Overview: flipped merge gates from Roadmap to Shipped in the
  boundaries table; updated the "Where to next" line.
- Hooks → Roadmap: removed the merge-gates entry (shipped); added a
  "Recently shipped" pointer to the new merge content.
- Reference → Configuration: reconciled the merge-keys table to the v1.10
  schema (daft.merge.style + daft.merge.cleanup) per the authoritative
  daft-merge.md migration table. Master's preexisting tabular content
  documented the v1.9 keys that were removed in #471.
- Reference → CLI sidebar: added daft-merge under Maintenance and
  worktree-merge under Git Commands → Maintenance.
- Landing: updated Worktrees and Hooks pillar cards to reflect the merge
  feature and shipped merge-gates story (no new card — landing stays
  pillar-shaped).
- _redirects: /guide/hooks now lands on /hooks/lifecycle so anchors like
  #merge-hooks survive the redirect (Cloudflare can't match on fragments).
- CLI ref: fixed two relative ../guide/* links in daft-merge.md to absolute
  paths in the new IA.

Build clean with strict link checking; biome and prettier pass.
avihut added a commit that referenced this pull request May 9, 2026
Master shipped the cross-worktree merge feature (#471) before this branch's
IA restructure landed, so the merge docs entered the old IA shape (one
guide/hooks.md, one guide/configuration.md, one guide/worktree-workflow.md,
table-of-contents merge entries in the legacy sidebar). The rebase brought
the new CLI ref pages and the merge config additions through cleanly via
git's rename detection on configuration.md, but the rest needed manual
porting.

Changes:

- New page: Worktrees → Merging across worktrees. Substantively expanded
  from master's 25-line section in guide/worktree-workflow.md into a proper
  how-to: quick examples, merge styles, conflicts, cleanup, ephemeral
  targets, and hook gates. Sidebar position: between "Running commands" and
  "Shortcuts".
- Hooks → Lifecycle: added pre-merge / post-merge rows to the hook-types
  table, env-var tables for both, and a Merge hooks section covering
  semantics, fail modes (including the daft.hooks.preMerge.failMode
  override), and the squash-abort case.
- Hooks → Overview: flipped merge gates from Roadmap to Shipped in the
  boundaries table; updated the "Where to next" line.
- Hooks → Roadmap: removed the merge-gates entry (shipped); added a
  "Recently shipped" pointer to the new merge content.
- Reference → Configuration: reconciled the merge-keys table to the v1.10
  schema (daft.merge.style + daft.merge.cleanup) per the authoritative
  daft-merge.md migration table. Master's preexisting tabular content
  documented the v1.9 keys that were removed in #471.
- Reference → CLI sidebar: added daft-merge under Maintenance and
  worktree-merge under Git Commands → Maintenance.
- Landing: updated Worktrees and Hooks pillar cards to reflect the merge
  feature and shipped merge-gates story (no new card — landing stays
  pillar-shaped).
- _redirects: /guide/hooks now lands on /hooks/lifecycle so anchors like
  #merge-hooks survive the redirect (Cloudflare can't match on fragments).
- CLI ref: fixed two relative ../guide/* links in daft-merge.md to absolute
  paths in the new IA.

Build clean with strict link checking; biome and prettier pass.
avihut added a commit that referenced this pull request May 9, 2026
Master shipped the cross-worktree merge feature (#471) before this branch's
IA restructure landed, so the merge docs entered the old IA shape (one
guide/hooks.md, one guide/configuration.md, one guide/worktree-workflow.md,
table-of-contents merge entries in the legacy sidebar). The rebase brought
the new CLI ref pages and the merge config additions through cleanly via
git's rename detection on configuration.md, but the rest needed manual
porting.

Changes:

- New page: Worktrees → Merging across worktrees. Substantively expanded
  from master's 25-line section in guide/worktree-workflow.md into a proper
  how-to: quick examples, merge styles, conflicts, cleanup, ephemeral
  targets, and hook gates. Sidebar position: between "Running commands" and
  "Shortcuts".
- Hooks → Lifecycle: added pre-merge / post-merge rows to the hook-types
  table, env-var tables for both, and a Merge hooks section covering
  semantics, fail modes (including the daft.hooks.preMerge.failMode
  override), and the squash-abort case.
- Hooks → Overview: flipped merge gates from Roadmap to Shipped in the
  boundaries table; updated the "Where to next" line.
- Hooks → Roadmap: removed the merge-gates entry (shipped); added a
  "Recently shipped" pointer to the new merge content.
- Reference → Configuration: reconciled the merge-keys table to the v1.10
  schema (daft.merge.style + daft.merge.cleanup) per the authoritative
  daft-merge.md migration table. Master's preexisting tabular content
  documented the v1.9 keys that were removed in #471.
- Reference → CLI sidebar: added daft-merge under Maintenance and
  worktree-merge under Git Commands → Maintenance.
- Landing: updated Worktrees and Hooks pillar cards to reflect the merge
  feature and shipped merge-gates story (no new card — landing stays
  pillar-shaped).
- _redirects: /guide/hooks now lands on /hooks/lifecycle so anchors like
  #merge-hooks survive the redirect (Cloudflare can't match on fragments).
- CLI ref: fixed two relative ../guide/* links in daft-merge.md to absolute
  paths in the new IA.

Build clean with strict link checking; biome and prettier pass.
@avihut avihut mentioned this pull request May 9, 2026
26 tasks
avihut added a commit that referenced this pull request May 9, 2026
* chore(docs): install Diátaxis skills for project-wide doc IA work

Adds two project-scoped agent skills under .claude/skills/:

- diataxis-organize-docs: audits an existing docs corpus, classifies
  pages into the four Diátaxis quadrants (tutorial/how-to/reference/
  explanation), and proposes a directory restructure. Used to drive
  the upcoming docs IA overhaul.
- writing-documentation-with-diataxis: companion skill for writing
  individual pages in the appropriate Diátaxis style, anchored on the
  "Diataxis Compass" two-question diagnostic.

Skipping Rust fmt/clippy/test gates: this commit only adds static
markdown under .claude/skills/ plus skills-lock.json — no Rust touched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(spec): design for docs IA restructure with cookbook section

Captures the brainstorm output for daft-398, expanded from "add a
cookbook" to a full IA restructure that includes the cookbook.

Decisions:
- Pillar-driven IA (Worktrees / Hooks / Networking) with Diátaxis
  quadrants honored inside each pillar (Overview = Explanation; peer
  How-to and Reference sections)
- Cookbook elevated to top-level (not buried under About like mise),
  taxonomized by tooling / language / scenario, recipes pillar-tagged
- Reference unified, not pillar-fragmented
- "Hooks-as-boundaries" thesis surfaced from parallel merge-feature
  work: daft hooks as a local-parallel-to-GitHub-Actions surface,
  with each code-evolution stage (worktree create / commit / merge /
  worktree teardown) getting its own gate

Out of scope (follow-up issues):
- Full git hooks drop-in (lefthook-style)
- Merge hooks
- Networking pillar content
- Visual rebrand

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(spec): unify follow-up issues with feature tickets

Per "docs and features must enter together," restructure the spec's
out-of-scope section to track docs alongside feature work:

- #330 (Merge Branch) absorbs merge-hooks docs (was tracked at #464)
- #357 (Repo Catalog) absorbs Networking pillar docs (was at #466)
- #468 (new feat — full git-hooks drop-in) absorbs lefthook drop-in
  docs (was at #465)

#464, #465, #466 closed as superseded; their work is happening, just
under the unified feature tickets.

Adds a "Coordination with #330" section covering the rebase strategy:
land this branch first, #330 converts its old-IA docs to new-IA paths
during its rebase prep. Avoids holding this branch open while #330
finishes (stale rebases) and avoids rework.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(plan): implementation plan for docs IA restructure

8-phase plan covering: vitepress config + redirects scaffolding,
Worktrees pillar pages, Hooks pillar (split + thesis Overview),
Reference moves, About pages (Why daft, glossary, FAQ,
troubleshooting, comparison, networking roadmap), Cookbook
structure with 3 anchor recipes (mise, direnv, monorepo) and 10
stubs, per-pillar Recipes filter, and final cleanup.

Each phase is independently committable. Build verification
(`mise run docs:site:build`) gates every task. CLI ref stays at
docs/cli/ on disk; vitepress rewrites surface them at /reference/
cli/*. Cloudflare _redirects keep legacy URLs alive.

Recipe authoring strategy: anchor recipes (mise, direnv, monorepo)
written in full inline; stubs ship with frontmatter + outline so
sidebar + search index are populated immediately, with full prose
following as additional commits on this branch.

Plan paired with the design spec at
docs/superpowers/specs/2026-05-03-docs-ia-restructure-design.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: scaffold pillar directory placeholders

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(vitepress): adopt pillar IA in sidebar + nav + URL rewrites

Replaces the legacy 'Getting Started / Guide / daft Commands / Git
Commands / Project' sidebar with the new pillar IA from #398:

  Getting Started | Worktrees | Hooks | Cookbook | Reference | About

Top nav exposes the three product surfaces (Worktrees / Hooks /
Cookbook) plus the version + GitHub link.

Adds vitepress rewrites so docs/cli/*.md (still autogenerated by
xtask at the same path) surface at /reference/cli/* URLs without
needing to move generated files.

ignoreDeadLinks is temporarily true; will revert in the final
migration cleanup task.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: add Cloudflare redirects for legacy URLs

Maps pre-restructure URLs (/guide/*, /cli/*, /contributing,
/changelog) to their new homes under the pillar IA. 301 permanent
redirects so search engine results flow to the new locations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(worktrees): write pillar Overview, retire guide/worktree-workflow

Establishes the Worktrees pillar's spine page. Content sourced from
the existing guide/worktree-workflow.md, plus a new 'adoption arc'
section that names the three stages (code -> env -> automation) and
links forward into the cookbook and the Hooks pillar.

Closes the worktree-workflow legacy URL via the redirect added in
Task 1.3.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(worktrees): migrate layouts page to pillar

Move guide/layouts.md → worktrees/layouts.md and append a
"Where to next" footer linking to the daft layout CLI reference
and the monorepo cookbook recipe.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(spec,plan): restore historical /guide/X references corrupted by 8100bdf9

Task 2.2's blanket grep+sed `/guide/layouts` → `/worktrees/layouts`
rewrote semantic mentions of the legacy URL inside the IA spec and
the IA plan, where those references are intentionally describing
the pre-migration state:

- Spec section "URL redirects": example legacy URLs got rewritten
- Plan _redirects table: redirect rule became circular
- Plan migration steps: "Delete docs/guide/layouts.md" became
  "Delete docs/worktrees/layouts.md"

Restores both files to their state at b4331076 (the commit before
Task 2.2). Future migration tasks must scope grep+sed to exclude
docs/superpowers/ so spec/plan history is preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(worktrees): move adopting-existing-repos to pillar

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(worktrees): rename running-commands-across-worktrees to running-commands

Shorter path under the pillar. Cross-links updated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(worktrees): move shortcuts to pillar

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: restore _redirects circular-redirect lines from corruption

Tasks 2.3, 2.4, and 2.5 ran `grep -rln '/guide/X' docs/` for cross-
link updates, which matched the LHS of the corresponding redirect
rules in docs/public/_redirects. The blanket sed rewrote both LHS
and RHS (since the RHS was already the new path, only LHS matched),
producing three circular self-redirects like:

  /worktrees/shortcuts  /worktrees/shortcuts  301

Restores _redirects to its canonical state from commit 69235d44.

Future migration tasks must scope grep+sed to exclude:
- docs/superpowers/ (intentional legacy URL history)
- docs/public/ (the whole point of _redirects is mapping old→new)
- docs/node_modules/ (build artifact)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(worktrees): move multi-remote to pillar with cross-links

Move guide/multi-remote.md → worktrees/multi-remote.md and append
a "Where to next" footer linking to the daft multi-remote CLI ref
and the fork workflow cookbook recipe.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(hooks): write pillar Overview with boundaries thesis

Frames daft hooks as a local-parallel-to-GitHub-Actions surface,
with each code-evolution stage (worktree create / commit / merge /
worktree teardown) getting its own gate. Honestly marks which
stages are shipped vs roadmap (#330, #468).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(hooks): extract lifecycle reference from guide/hooks.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(hooks): extract daft.yml schema reference from guide/hooks.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(hooks): write job-orchestration explanation page

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(hooks): write trust-and-security explanation page

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(hooks): write roadmap stub for #330 (merge) and #468 (commit drop-in)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(hooks): retire guide/hooks.md (content migrated to pillar pages)

The legacy guide/hooks.md was a kitchen-sink page mixing
Explanation + Reference + How-to. Its content now lives in:

- hooks/index.md         (Overview, with the boundaries thesis)
- hooks/lifecycle.md     (Reference: hook types, env vars, exit codes)
- hooks/yaml-reference.md (Reference: full daft.yml schema)
- hooks/job-orchestration.md (Explanation: parallelism, deps, conditions)
- hooks/trust-and-security.md (Explanation: trust-on-first-use)
- hooks/roadmap.md       (Stub for #330, #468)

Cross-links updated. Legacy URL /guide/hooks redirects to /hooks/
via the Cloudflare _redirects file (added in Task 1.3).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(reference): move configuration page to reference/

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(reference): move output-formats page to reference/

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(reference): move agent skill page to reference/ with multi-agent name

The skill applies to any agent that supports the agent-skills standard,
not just Claude. Renames `claude-skill.md` to `agent-skill.md` and
updates cross-links accordingly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(reference): write reference hub overview

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(about): move contributing and changelog under about/

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(about): write the why-daft thesis page

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(about): write glossary

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(about): write FAQ

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(about): write troubleshooting

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(about): write comparison page (vs git worktree, lefthook, gitup, Actions)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(about): write networking-roadmap stub linking #357

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(about): write about hub overview

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(cookbook): write cookbook hub with by-tooling/by-language/by-scenario taxonomy

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(cookbook): write mise recipe (anchor)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(cookbook): write direnv recipe (anchor)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(cookbook): write monorepo recipe (anchor)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(cookbook): stub remaining 9 recipes with outlines

Stub recipes get frontmatter + outline + cross-links to anchor
recipes. They render as valid pages so sidebar entries don't 404
and the search index includes the topics. Full recipe content
follows as additional commits on this branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(cookbook): build-time recipe metadata loader

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(cookbook): add per-pillar recipe filter via URL query param

The Worktrees and Hooks pillar sidebars now have a 'Recipes' entry
that deep-links to /cookbook/?pillar=<name>. The cookbook index
reads the query param and filters its recipe list accordingly. The
canonical home of all recipes remains /cookbook/ — the filter is
just a discoverability shortcut.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: fix vestigial RecipeFilter tag + broken background-jobs anchor

Two cleanup items from earlier phases:

1. Phase 7 left a `<RecipeFilter />` tag in cookbook/index.md that
   has no registered component. Removed.
2. Task 3.7's cross-link rewrite landed `/hooks/index.md#background-jobs`
   in daft-hooks-jobs.md, but the anchor lives on yaml-reference.md.
   Fixed to point at the right page.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(getting-started): narrate the worktree adoption arc in Quick Start

Restructures Quick Start as a 3-stage tutorial mirroring the
gradient (code -> env -> automation). Stage 1 keeps the original
worktree walkthrough; Stages 2 and 3 link forward to cookbook
recipes and the Hooks pillar.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: update landing hero copy + features for pillar IA

Aligns the landing page with the new pillar shape (Worktrees,
Hooks, Cookbook) and the parallel-dev thesis. Full marquee-features
revamp remains scoped to #386.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: re-enable strict link checking after IA migration

All pages from docs/guide/ have been migrated to their new pillar
homes. Empty directory removed. ignoreDeadLinks reverted to false
so future PRs catch broken cross-links.

Fixed 21 dead links surfaced by strict checking — all were relative
paths in docs/cli/ files that broke because VitePress resolves them
against the rewritten reference/cli/ virtual path. Converted to
absolute paths, and fixed stale ./hooks.md, ./layouts.md,
./worktree-workflow.md, ./shortcuts.md references in reference/ and
worktrees/ that pointed at pages which no longer exist at those paths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: rename Cookbook pillar to Recipes; drop pillar-sidebar Recipes shortcuts

The "Cookbook" pillar was conceptually fine but caused two UX issues:

- The sidebar's pillar shortcuts ("Recipes" under Worktrees and Hooks) pointed
  at /cookbook/?pillar=<name>, which VitePress's active-link matcher
  (path-only, query ignored) rendered as both Recipes and Cookbook → Overview
  highlighted simultaneously.
- The pillar-shortcut indirection implied two separate destinations when there
  was only one — the cookbook hub with a runtime filter applied.

Fix:

- Rename the pillar surface "Cookbook" → "Recipes" everywhere (top nav,
  sidebar group, page heading, frontmatter title, prose). Move the URL path
  /cookbook/ → /recipes/ so the IA stays coherent: label, URL, and concept all
  match.
- Drop the duplicate "Recipes" entries under the Worktrees and Hooks sidebars.
  Inline cross-links from pillar pages still deep-link with ?pillar= filters,
  but the sidebar no longer presents the same destination twice.

Build remains clean with ignoreDeadLinks: false; biome lint passes.

* docs: integrate cross-worktree merge feature into pillar IA

Master shipped the cross-worktree merge feature (#471) before this branch's
IA restructure landed, so the merge docs entered the old IA shape (one
guide/hooks.md, one guide/configuration.md, one guide/worktree-workflow.md,
table-of-contents merge entries in the legacy sidebar). The rebase brought
the new CLI ref pages and the merge config additions through cleanly via
git's rename detection on configuration.md, but the rest needed manual
porting.

Changes:

- New page: Worktrees → Merging across worktrees. Substantively expanded
  from master's 25-line section in guide/worktree-workflow.md into a proper
  how-to: quick examples, merge styles, conflicts, cleanup, ephemeral
  targets, and hook gates. Sidebar position: between "Running commands" and
  "Shortcuts".
- Hooks → Lifecycle: added pre-merge / post-merge rows to the hook-types
  table, env-var tables for both, and a Merge hooks section covering
  semantics, fail modes (including the daft.hooks.preMerge.failMode
  override), and the squash-abort case.
- Hooks → Overview: flipped merge gates from Roadmap to Shipped in the
  boundaries table; updated the "Where to next" line.
- Hooks → Roadmap: removed the merge-gates entry (shipped); added a
  "Recently shipped" pointer to the new merge content.
- Reference → Configuration: reconciled the merge-keys table to the v1.10
  schema (daft.merge.style + daft.merge.cleanup) per the authoritative
  daft-merge.md migration table. Master's preexisting tabular content
  documented the v1.9 keys that were removed in #471.
- Reference → CLI sidebar: added daft-merge under Maintenance and
  worktree-merge under Git Commands → Maintenance.
- Landing: updated Worktrees and Hooks pillar cards to reflect the merge
  feature and shipped merge-gates story (no new card — landing stays
  pillar-shaped).
- _redirects: /guide/hooks now lands on /hooks/lifecycle so anchors like
  #merge-hooks survive the redirect (Cloudflare can't match on fragments).
- CLI ref: fixed two relative ../guide/* links in daft-merge.md to absolute
  paths in the new IA.

Build clean with strict link checking; biome and prettier pass.

* docs(about): fix changelog include path after Phase 5 file move

When the changelog page moved from docs/changelog.md to docs/about/changelog.md
in Phase 5, the relative include `../CHANGELOG.md` kept resolving to its
original target — but that target is now docs/CHANGELOG.md (which doesn't
exist) instead of the repo-root CHANGELOG.md. The include silently failed
and the page rendered only the boilerplate footer (`daft release-notes`
hint + GitHub Releases link).

Changing the include to `../../CHANGELOG.md` reaches the repo root from the
new location, so the full release history renders again as it did on master.

* docs(recipes): scaffold patterns + walkthroughs structure

Replaces the by-tooling/by-language/by-scenario taxonomy with a
patterns-and-walkthroughs structure. Adds 13 placeholder pages, updates
the sidebar to the new shape, and rewrites the recipe data loader to walk
the new layout while emitting a `kind` discriminator
(pattern/walkthrough/anti-pattern/reference).

Old by-* recipe files are left in place for now so existing cross-links
keep building; they get retired in a later commit after content is ported
into the new patterns and external links are swept.

This commit is the structural scaffold; content lands in subsequent
commits per pattern/walkthrough/reference.

Refs the recipes restructure plan at
~/.claude/plans/zany-tumbling-quasar.md.

* docs(recipes): write toolchain-bootstrap pattern

Pattern recipe for installing dependencies idempotently when a worktree is
created. Covers Node (pnpm/npm/yarn/bun), Python (uv/pip/poetry), Rust
(cargo fetch — distinguished from cargo build, which belongs in
background-warmup), and Go (go mod download). Lockfile-strict commands
table makes the idempotency story explicit. Cross-links to Background
warmup, Declarative envs, Cleanup on remove, Sharing caches, and the
shared-mutable-state anti-pattern.

* docs(recipes): write background-warmup pattern

Pattern recipe for detached prebuild work using background: true. Covers
Rust cargo build (per-worktree, multi-package scoping), Go build cache
priming (shared cache safety), Vite optimizer warmup with root:, Gradle
daemon + deps, and sccache/ccache prime as a cross-worktree win.
Cancellation semantics (SIGTERM on worktree remove) and the codegen
warning (codegen is correctness, not optimization) are explicit.
Cross-links to toolchain-bootstrap (the upstream install), job
orchestration (background/needs/parallel), sharing-caches, and
shared-mutable-state.

* docs(recipes): write env-vars-and-secrets pattern

Pattern recipe for per-worktree env vars and secrets. Absorbs the
existing by-tooling/direnv content (seed-from-template + direnv allow)
and broadens to vault lookups (1Password/Bitwarden/AWS Secrets Manager),
sops + age for committed encrypted secrets, mise [env] declarative
config, per-job env: for scoped values, and a deterministic
branch-name-to-port allocation. Idempotency section flags the
must-guard cp pattern. Anti-patterns link to the secrets-in-hooks
reference.

* docs(recipes): write declarative-envs pattern

Pattern recipe for mise/asdf/nvm/pyenv as the declarative alternative or
complement to imperative hooks. Absorbs the existing by-tooling/mise
content and broadens to asdf, nvm, pyenv, mise [env]/[tasks], and
devbox/nix-direnv. Division-of-labor table makes the
declarative-vs-imperative split explicit. Warning against putting
long-running setup in mise [tasks] (which doesn't fire on worktree
create). Cross-links to env-vars-and-secrets, toolchain-bootstrap, and
CI parity.

* docs(recipes): write services-with-ports pattern

Pattern recipe for booting docker-compose stacks per worktree without
collision. COMPOSE_PROJECT_NAME for naming, deterministic
branch-name-to-port-range hashing for ports, symmetric pre-remove
teardown with -v to leave nothing on disk. Variants for compose
profiles, podman, native processes, adopting an existing stack, and
multi-file compose. Idempotency note distinguishes safe `up -d` from
destructive `down -v`. Anti-patterns flag external volumes
(cross-link), hardcoded ports, and skipping pre-remove.

* docs(recipes): write cleanup-on-remove pattern

Pattern recipe for the symmetric pre-remove half of lifecycle
automation. Compose teardown with -v --remove-orphans, native
processes via PID file, fallback cleanup by port, external registry
deregistration, and per-removal-reason logic via
DAFT_REMOVAL_REASON. Idempotency table flags which cleanups need
|| true. Warning against putting cleanup in worktree-post-remove
(too late — worktree directory is gone). Move-hook gating via
DAFT_IS_MOVE so cleanup doesn't fire during daft rename.

* docs(recipes): write ci-parity pattern

Pattern recipe for running daft.yml in CI with the same hooks fired on
local worktree creation. GitHub Actions, GitLab CI, generic-shell
variants. DAFT_NONINTERACTIVE + git daft-hooks trust --all for trust in
non-interactive contexts. skip:/only: with env CI=true for splitting
local-only and CI-only jobs. CI-specific env vars and secrets via the
provider's secret store. Layer caching with a daft-aware base image and
DAFT_SKIP_TAGS to bypass already-installed work. Warning against
running pre-remove hooks in CI.

Replaces the by-scenario/ci-integration stub.

* docs(recipes): write rust-binary walkthrough

End-to-end walkthrough threading toolchain-bootstrap (cargo fetch) and
background-warmup (cargo build) for a Rust binary project. Steps build
incrementally: minimal fetch → add warmup → scope in workspace →
optional sccache for cross-worktree sharing → verify with daft hooks
log show. Final daft.yml is two jobs, four lines of logic. "What you
got" section frames the before/after value.

* docs(recipes): write node-monorepo-services walkthrough

End-to-end walkthrough threading toolchain-bootstrap (pnpm install with
shared store), env-vars (per-worktree port allocation),
services-with-ports (compose with COMPOSE_PROJECT_NAME and per-worktree
ports for postgres/redis/minio), migrations as a hook step, and
cleanup-on-remove (down -v --remove-orphans). Optional profiles +
only:/skip: for opt-in heavy services. Full final daft.yml at the end.

Replaces the by-scenario/monorepo.md anchor recipe (deleted) — the new
walkthrough subsumes its content and adds services + ports + cleanup.

* docs(recipes): write python-uv-secrets walkthrough

End-to-end walkthrough threading declarative-envs (mise pinning Python
+ ruff + uv versions + committed [env] defaults), toolchain-bootstrap
(uv sync --frozen), and env-vars-and-secrets (sops + age, decrypted
to .env, loaded via direnv dotenv). The interesting bit: layering
declarative tool config and committed env defaults with hook-fetched
secrets. Final daft.yml has 4 jobs with explicit needs:; plus
mise.toml, .sops.yaml, secrets.enc.env all committed.

* docs(recipes): retire by-* taxonomy, sweep cross-links, rewrite hub

Deletes the 11 remaining by-tooling/by-language/by-scenario recipe files
(content was either folded into patterns/walkthroughs in prior commits
or was a stub with little salvageable content). The fork-workflow stub
is dropped entirely — it was always a worktrees-pillar topic, and
/worktrees/multi-remote covers it.

External cross-links rewritten to new homes:
- /recipes/by-scenario/monorepo → /recipes/walkthroughs/node-monorepo-services
  (about/faq.md, worktrees/layouts.md)
- /recipes/by-tooling/{mise,direnv} → /recipes/declarative-envs +
  /recipes/env-vars-and-secrets (worktrees/index.md, getting-started/quick-start.md)
- /recipes/by-scenario/fork-workflow links removed; replaced with
  /recipes/?pillar=worktrees (worktrees/multi-remote.md, worktrees/merging.md)

Cloudflare _redirects gain three targeted rewrites and three catch-alls
so existing /recipes/by-* URLs land on a useful new page.

Hub rewrite (docs/recipes/index.md): reorganized around walkthroughs +
patterns (grouped by lifecycle stage: setup / steady state / teardown)
+ references shelf. Vue script reads the new loader's `kind` field and
groups dynamically. ?pillar= filter still works.

* docs(recipes): write reference pages

Three reference pages supporting the patterns:

- sharing-caches.md: per-tool answers for what's safe to share between
  worktrees (pnpm store, cargo registry, Go caches, ccache/sccache) vs
  not (node_modules, target/, .venv/, build dirs).
- anti-patterns/shared-mutable-state.md: the unsafe-sharing flip side
  with concrete failure modes (silent target/ corruption, mystery test
  failures from shared node_modules, pgdata corruption from shared
  volumes).
- anti-patterns/secrets-in-hooks.md: how secrets accidentally land in
  committed hooks (hardcoded keys, echoed in logs, env: on backgrounded
  jobs visible in ps, baked into images, .env decrypted output that's
  not gitignored) and what to do instead.

* docs(recipes): escape \${{ }} in secrets-in-hooks anti-pattern

VitePress / Vue parses {{ }} as interpolation even inside inline
\`code\` spans. The GitHub Actions \${{ secrets.API_KEY }} mention in
the prose was triggering "Cannot read properties of undefined (reading
'API_KEY')" during SSR. Wrapping in <span v-pre> opts the inline code
out of Vue parsing.

Code-block occurrences (in ci-parity.md) are unaffected — fenced code
blocks aren't Vue-parsed, only inline code.

Builds clean against ignoreDeadLinks: false.

* docs(hooks): cross-link Recipes → CI parity from Hooks pillar

Adds the Hooks-pillar end of the CI-parity cross-reference (per the
locked decision: CI parity lives in Recipes, with a pointer from Hooks).

- hooks/index.md Where-to-next: new bullet linking to /recipes/ci-parity
- hooks/yaml-reference.md: new "Running these in CI" section pointing
  at /recipes/ci-parity for the GitHub Actions / GitLab / generic
  patterns and the local-only skip: { env: { CI: "true" } } tip

* docs(recipes): rewrite toolchain-bootstrap with motivation-driven vignette

Lead with a concrete starting state (Node monorepo with bin/setup.sh,
forgotten-after-git-pull pain) and frame the recipe as a state diff
against that baseline. Move the failMode tuning out of the intro into
a dedicated subsection. Replace the Composes-with / See-also / Anti-
patterns trio with a single Where-to-next of three prioritized links.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(recipes): rewrite background-warmup with motivation-driven vignette

Open with the Rust workspace + 4-minute first-build pain that motivates
reaching for a warmup, then frame the recipe as the second job that gets
added to the post-create hook (needs: the install). Reconcile the
default (--workspace) with the rust-binary walkthrough's choice. Trim
cross-link density and consolidate cancellation / no-critical-work
guidance into Idempotency & safety.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(recipes): rewrite declarative-envs with vignette + own mise [env]

Open with the polyglot version-drift vignette (.nvmrc + .python-version
+ FROM rust:1.78 in three places). Make this page the canonical home
for mise [env] (non-secret defaults) — env-vars-and-secrets shrinks its
section to a one-liner pointer in a follow-up commit. Replace the asdf
"precursor" history with usable plugin-install guidance. Drop the
devbox/nix-direnv one-liner (unfair to summarize in one line; users on
those projects need their own docs).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(recipes): rewrite env-vars-and-secrets with vignette + scope reduction

Open with the Slack-DM'd .env / committed-DATABASE_URL vignette that
motivates the pattern. Cleanly cede mise [env] coverage to declarative-
envs (one-liner pointer instead of a full subsection). Variants now
cluster strictly by source (vault / sops / per-job env). Move per-
worktree derived values (branch-port hash) out of variants into its
own subsection — it's a derived-value pattern, not a source variant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(recipes): rewrite services-with-ports + fold port allocation into minimal

Open with the "5432 already in use" / two-parallel-features vignette
that motivates per-worktree compose stacks. Fold port allocation INTO
the Recipe section so the minimal config is self-contained — no more
forward references to undefined PORT_POSTGRES vars. Variants now
strictly by runtime (compose profiles, podman, native, multi-file,
adopt-existing). Defer the deeper teardown story to cleanup-on-remove
rather than re-deriving it inline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(recipes): rewrite cleanup-on-remove with vignette + dedupe warning

Open with the housekeeping vignette: 23 stale containers, 14 leaked
volumes, 12 GB consumed, a port held by a process from a deleted
branch. Consolidate the post-remove-vs-pre-remove warning to a single
canonical spot inside Idempotency & safety (was stated 3x previously).
Variants now cluster strictly by resource type (compose / PID file /
port / external registry / per-removal-reason). Move "run in parallel"
note out of variants — it's default behavior, not a variant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(recipes): rewrite ci-parity with vignette + skip-warmup decision rule

Open with the daft.yml/test.yml drift vignette (protoc added in one
place, two-day CI breakage). Variants now strictly by vendor (GHA /
GitLab / generic). Move skip-conditions, CI-specific env vars, and
layer caching out of variants into their own subsections. Add explicit
decision rule for warmups: skip when the cache is per-worktree, run
when it primes a shared cache the test step benefits from.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(recipes): rewrite rust-binary walkthrough + reconcile with pattern

Open with the project-shape vignette (three-crate workspace, ~200 deps,
4-minute first build). Steps now defer to patterns rather than
re-derive them — toolchain-bootstrap (cargo fetch) and background-
warmup (cargo build --workspace) are linked, not re-explained.
Reconcile final daft.yml with background-warmup pattern: both show
--workspace + sccache, with the trade-off discussed in one place.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(recipes): rewrite node-monorepo-services walkthrough + Step 2/3 fix

Open with the project-shape vignette + the team's current 5-step
README ritual that motivates daft adoption. Move the DATABASE_URL
warning out of Step 3 — DATABASE_URL is now written into .envrc by
Step 2 alongside the ports, with the deferred-expansion (\$PORT_POSTGRES)
explained inline. Drop the tacked-on Step 6 (compose profiles); link
out to the services-with-ports pattern instead. Final daft.yml is
internally consistent — no warning-as-correction.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(recipes): rewrite python-uv-secrets walkthrough + extract team prereq

Open with the rotated-DB-password / Slack-DM vignette that motivates
sops adoption. Extract the team-onboarding sops/age key generation
into a Prerequisite section before any per-worktree daft step — sops
is one-time team setup, not per-worktree work, and mixing them
confused the previous flow. Per-worktree steps now focus strictly on
hook config: mise install, uv sync --frozen, sops decrypt, direnv
load.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(recipes): drop irrelevant team-size and timing details from vignettes

The recipes don't change at "two months old" vs "a year old," at "three
engineers" vs "seventy," or at "last week" vs "last quarter." Those
details are creative-writing color, not motivation.

Strip them out across all 10 pages: drop repo age, team headcount,
specific weeks/months, and other outside details that don't shape the
setup. Keep what actually motivates the recipe — failure modes,
ritual shape, the recurrence (not the cadence) of pain.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(recipes): catch line-wrapped "two engineers" the prior pass missed

The earlier replace_all matched on the single-line phrase. Prettier had
wrapped this instance across two lines, so it slipped through. Fix the
last remaining "Two engineers on parallel features just work" in the
node-monorepo-services walkthrough.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: internal style guide for writing recipes

Captures the writing principles we worked out during the recipes IA
restructure and narrative rewrite: motivation-first vignettes (with
the three-property bar), single-axis variants, self-contained minimal
recipe, walkthroughs that cite patterns rather than re-derive them,
and the cap on cross-link density.

Lives at docs/WRITING-RECIPES.md and excluded from the VitePress build
via srcExclude (alongside the existing WEBSITE-BOOTSTRAP.md and
HISTORY.md). Confirmed absent from dist/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(recipes): drop transitive dep counts from Rust vignettes

Same principle as the team-size/age cleanup: the recipe doesn't change
at 50 vs 200 vs 500 transitive deps. What's load-bearing is "the build
is slow" with a concrete time cost (4 minutes). The dep count is the
cause, not a recipe constraint.

Now matches the WRITING-RECIPES.md guidance ("dep counts" listed as a
non-belonger in the vignette).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(recipes): fix malformed ::: directive blocks across 9 pages

The closing ::: on a content line doesn't terminate the VitePress
container, so the next ## heading gets swallowed into the warning/tip
box. The user-visible symptoms were Step 4 missing from the TOC in
python-uv-secrets and Step 3 missing in rust-binary; the same bug
existed in 7 more pages where the swallowed content was a paragraph
or table, less obvious but still wrong.

Standardize all 9 directive blocks: title on its own line, content
separate, closing ::: alone on its line. Add a "VitePress directive
format" subsection to WRITING-RECIPES.md so this doesn't recur.

Files fixed: background-warmup, ci-parity, cleanup-on-remove,
declarative-envs, env-vars-and-secrets, services-with-ports,
walkthroughs/node-monorepo-services, walkthroughs/python-uv-secrets,
walkthroughs/rust-binary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(recipes): blank-line ::: directive format that survives prettier

Previous fix put the closing ::: on its own line, but prettier
(proseWrap: always) joined it back to the content line on commit, so
the user-reported heading-swallow bug came back. The fix that survives
both VitePress and prettier is blank-line separation:

    ::: warning Title

    Content paragraph.

    :::

Verified by running prettier --write on the result; no rejoining.
Update WRITING-RECIPES.md with the prettier interaction explained, so
the next person doesn't waste a debugging cycle on this.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: convert WRITING-RECIPES doc into a project skill

Move the recipe-writing style guide from docs/WRITING-RECIPES.md to
.claude/skills/writing-recipes/SKILL.md, following the existing
convention (.claude/skills/writing-documentation-with-diataxis/ has
the same shape: full content with frontmatter, no parallel doc).

The frontmatter description is tuned to auto-activate when an agent
is creating or revising recipe pages. Humans still discover it via
.claude/skills/ — same as the diataxis skill.

Drop the docs/WRITING-RECIPES.md file and its srcExclude entry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(recipes): add adopting-from-direnv pattern

Bridges the most common adoption path: teams already using direnv reach
for daft to cover the slow rituals (install, services, codegen,
cleanup) that direnv was never meant to handle. Three Variants by what
direnv already manages — tool versions via use mise, secrets via
dotenv, and PATH_add bin — each naming what daft skips because direnv
already does it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(recipes): split services-with-ports Variants into starting-state and runtime axes

Promotes the buried 11-line 'Adopting an existing stack' sub-bullet to a
first-class adopt-existing variant alongside green-field, on a new 'by
starting state' axis. Adopt-existing now distinguishes two cases —
compose.yaml already uses env-var ports (drops into green-field Recipe
as-is) vs hardcodes ports (gets container/network isolation but host
ports still collide; worthwhile adoption with a roadmap to graduation).

The runtime variants (compose profiles, podman, native processes,
multi-file) move into their own 'by runtime' Variants section, leaving
each axis internally single-axis as the writing-recipes skill prescribes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(recipes): add migrating-from-setup-sh walkthrough

Threads toolchain-bootstrap, services-with-ports, background-warmup,
and cleanup-on-remove to dismantle the typical bin/setup.sh ritual one
section at a time. Final state: setup.sh deleted, README's getting-
started shrinks to two lines, daft.yml is the source of truth, and the
cleanup-on-remove pre-remove hook handles teardown the imperative
script never had.

Honestly handles the parallel-worktree case: COMPOSE_PROJECT_NAME
gives container/network/volume isolation, but if compose.yaml hardcodes
ports the host-side still collide. Points readers at services-with-
ports adopt-existing for the upgrade path rather than glossing over it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(recipes): fix migrating-from-setup-sh formatting nits

Two prettier-induced regressions in the just-landed walkthrough:

- The 'Patterns we'll thread' list used '+' as prose conjunction
  between two backtick-quoted commands; prettier reflowed it onto an
  indented next line, which markdown then renders as a sub-bullet.
  Replaced with 'and' so prose stays prose.

- The README example used a 4-backtick fence around a markdown block
  that itself contains a 3-backtick bash fence. Prettier added a stray
  trailing 4-backtick that broke the close. Simplified to inline prose
  describing the two-line README content rather than re-rendering it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(recipes): add github-actions walkthrough

Threads ci-parity for a real .github/workflows/ci.yml that consumes a
daft-managed project. Three structural steps (install daft, trust
hooks, run worktree-post-create); the cache/install/build/codegen work
disappears because daft hooks run does it. Covers the warmup-skip-in-
CI decision rule from ci-parity inline rather than re-deriving it.

Final workflow ~25 lines, replacing a 12-step duplicate that drifted
from daft.yml whenever someone added a dep to one and not the other.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(recipes): add editor-integration pattern

Vignette: Python team's gitignored .vscode/settings.json forces every
fresh worktree through a 'Select Interpreter' ritual. Recipe seeds
.vscode/settings.json from worktree-post-create with an idempotent
'write only if absent' guard, so customized settings survive but
fresh worktrees get sane defaults. ${workspaceFolder} keeps the
seeded JSON portable across worktrees.

Variants by editor: VS Code other languages (Rust, Node), IntelliJ
(honest about why .idea/ XML isn't safe to seed from a hook — points
at one-time setup script instead), Helix/LSP-generic (committed
pyproject.toml [tool.pyright] block, no hook needed).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(recipes): add symptom-keyed troubleshooting reference

Vite-shaped reference page: H2 per symptom (verbatim search wording),
1-3 paragraphs per entry naming the root cause and linking to the
canonical pattern. Six symptoms covering background-warmup,
trust-and-security, env-vars-and-secrets, cleanup-on-remove, ci-parity.

Cross-linked with /about/troubleshooting at the top of each page so a
reader landing on the wrong one finds the other. The about page covers
general daft (install, layout); the recipes page covers hook
configuration and recipe-specific symptoms.

Registered as a reference via REFERENCE_FILES (recipe-list.ts) so it
shows in the recipes hub's References section automatically and not
as a pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(about): add Recipe glossary entry

Distinguishes a cookbook recipe (a documented pattern in docs/recipes/)
from a daft.yml job entry, which some ecosystems (notably just) call a
'recipe' in their own config sense. Cheap insurance now that the
research surfaced the naming collision.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: keep writing-recipes skill in sync with recipe structural changes

Two paired changes:

1. CLAUDE.md gains a guideline alongside the existing SKILL.md (agent-
   facing) bullet: structural changes to how recipes are written must
   land in .claude/skills/writing-recipes/SKILL.md. Divergence between
   the skill and the recipes is the silent way the conventions rot.

2. The writing-recipes skill itself gains a 'Show files; don't describe
   them' section. Verbal descriptions of structured file content
   (.envrc, mise.toml, daft.yml) are hard to follow; the reader has to
   reconstruct the file from prose. Show the file as a code block after
   a sentence or two of intro. Trees describe layout; code blocks
   describe content; use both.

The verification checklist gains a matching item.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(recipes): show files instead of describing them in three vignettes

Audit pass following the new writing-recipes skill rule: file contents
go in code blocks, not prose.

- toolchain-bootstrap: bin/setup.sh content was a verbal one-liner;
  now a labeled bash block showing the actual script.
- declarative-envs: three pinning files described in a bullet list;
  now a single code block with file path and content per line, which
  reads cleaner for one-line files than three separate blocks.
- ci-parity: daft.yml's six jobs and test.yml's five steps were
  described verbally as 'six jobs' and 'five steps in slightly different
  order'; now both files are shown abridged so the reader sees the
  parallel structure (and the divergence to come) in one glance.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(recipes): rewrite adopting-from-direnv direnv-pure and add adopting-from-mise

Two paired changes:

1. The original adopting-from-direnv vignette modeled direnv and mise
   as a hybrid (.envrc using 'use mise', mise.toml pinning versions
   activated by direnv). That misrepresents the real choice — they
   are alternatives, not complements. Rewrite the vignette to a
   direnv-pure setup: .envrc with dotenv + PATH_add, tool versions
   managed externally. Drop the use-mise variant; add a layout-style
   variant ('layout python', 'layout ruby') that's an actual
   direnv-only pattern.

2. Add the missing companion adopting-from-mise. Same shape, mise-only
   starting state. mise.toml shown as a code block instead of described
   verbally. Variants by what mise is already managing: [tools], [env],
   [tasks].

Both vignettes now follow the writing-recipes 'show files; don't
describe them' rule that landed in the previous commit.

Where-to-next on each page links to the other (sibling adoption
recipes for the alternative tool).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(recipes): add migration walkthroughs between direnv and mise

Two paired walkthroughs covering both directions, since teams pick
one or the other and may want to switch.

migrating-from-direnv-to-mise (the more common direction): replace
scattered .nvmrc / .python-version + .envrc exports with one
mise.toml. Two options for secrets — keep direnv just for dotenv
(Option A) or use mise's _.file (Option B) — with a decision rule
for picking between them. daft.yml stays untouched.

migrating-from-mise-to-direnv (the rarer reverse direction):
replace mise.toml with .envrc + per-language version managers
(nvm, pyenv). Honest about being uncommon up front; the trade-off
section names the cost (more tools per dev) so the reader can
decide if it's worth it. daft.yml stays untouched here too.

Both walkthroughs link to each other in 'Where to next' and to the
respective Adopting-from-X steady-state recipe.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(recipes): remove direnv↔mise migration walkthroughs

These migrations are between env tools, not to/from daft — outside
daft's responsibility. Mise's and direnv's own docs cover the
direct-tool-migration story better than daft's docs ever could.

Adoption recipes in the to/from-daft direction stay (adopting-from-
direnv, adopting-from-mise, migrating-from-setup-sh) and are about to
be regrouped under a dedicated Adoption section in the next commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(recipes): group adoption recipes under a dedicated section

Adoption-shaped recipes lived in two places — adopting-from-direnv and
adopting-from-mise under Patterns: Setup, migrating-from-setup-sh under
Walkthroughs. They form a natural group: 'introducing daft into an
existing setup.' Promote them to their own Adoption section in the hub
and the sidebar, and exclude them from their original groupings so they
appear once each.

The hub intro is restructured to name the three kinds of recipe pages
(adoption, walkthrough, pattern) so readers see the taxonomy
immediately.

The new Adoption section lands first in both the hub and sidebar — it's
the right entry point for new daft users.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(recipes): add layering-direnv and layering-mise patterns

The missing direction in the adoption family: a team running daft
alone decides to add direnv (or mise) on top. Symmetric inverses of
adopting-from-direnv and adopting-from-mise.

Each pattern shows three landing changes — config file at the root
(.envrc or mise.toml), per-dev shell-rc activation snippet, and
optional daft.yml updates. Variants by what the new tool covers
(just env loading vs env + PATH; just tools vs tools + env vs tools +
env + dotenv).

Both pages cross-link to each other ("the alternative if you'd
rather have X") and to the corresponding adopting-from-X steady-state
recipe.

Registered in the Adoption section in the hub and sidebar.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(recipes): note daft's pair-with-direnv-or-mise recommendation in adoption guides

Add a tip block at the top of each adoption guide making explicit that daft
recommends pairing with direnv or mise rather than substituting for them, and
pointing to the corresponding layering guide for readers coming from a
daft-only starting state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(about): remove networking-roadmap stub page

Delete the networking-roadmap page along with its sidebar entry, the about
hub link, and the two glossary rows ("Networking" / "Repo catalog") that
pointed to it. Add a 301 redirect from the old URL to /about/why-daft, where
the networking pillar is still acknowledged in the thesis.

The page was a hard stub for a feature that has not been built — its content
lives in #357 and will return as docs alongside the implementation, per
"docs and features enter together."

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(about): drop stale #330 reference from comparison

The "vs GitHub Actions PR checks" parenthetical said the comparison would
be fully realized "once #330 and #468 ship." #330 (merge feature) has
shipped — drop it from the gate, leaving only #468 (commit-stage hooks)
as the remaining surface that completes the picture.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: update stale references downstream of pillar IA

The IA restructure retired /guide/* and /cli/* in favor of pillar pages
(/worktrees/, /hooks/) and a unified /reference/. Update the references
that pointed at the old structure:

- README.md: hooks-guide and Agent Skill links → canonical daft.avihu.dev
  URLs at /hooks/ and /reference/agent-skill (the old links also used the
  pre-subdomain-move host avihu.dev/daft/...).
- CLAUDE.md: "Documentation Site" path map updated to reflect pillar
  layout (worktrees/, hooks/, recipes/, reference/, about/).

Man pages already verify clean (mise run man:verify) — they're generated
from clap Args structs and were untouched by this branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(xtask): point CLI-doc generator at /reference/output-formats

The IA restructure moved docs/guide/output-formats.md to
docs/reference/output-formats.md and rewrote the cross-references in the
committed docs/cli/*.md files accordingly. The xtask source that
generates those files was missed, so xtask-test (which regenerates and
diffs) failed on PR #485.

Update the hardcoded link in render_structured_output_section() to the
canonical site path /reference/output-formats so regeneration is a no-op
against the committed tree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(recipes): replace hardcoded ADOPTION_LINKS with kind: adoption frontmatter

Address PR review feedback. The Adoption section in the recipes hub was
gated on a hardcoded ADOPTION_LINKS array in index.md, with no build-time
check that it stayed in sync with the actual files. Adding a new adoption
recipe required edits in three places (file, ADOPTION_LINKS, sidebar) and
silently mis-classified the recipe if any was missed.

Move the classification into per-recipe frontmatter:

- Add "adoption" to the RecipeKind union; let frontmatter kind override
  the path-based default in toRecipe.
- Add `kind: adoption` to adopting-from-{direnv,mise}, layering-{direnv,
  mise}, and walkthroughs/migrating-from-setup-sh.
- Replace `ADOPTION_LINKS.includes(r.link)` with `r.kind === 'adoption'`
  in the hub's three computed properties.
- Have recipes.data.ts re-export Recipe directly so the public Data type
  stays in lock-step with RecipeKind.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Merge Branch

1 participant