diff --git a/.claude/commands/start-work-cascade.md b/.claude/commands/start-work-cascade.md new file mode 100644 index 0000000..e97521f --- /dev/null +++ b/.claude/commands/start-work-cascade.md @@ -0,0 +1,30 @@ +--- +description: Start a cross-repo worktree cascade for a task +argument-hint: [repo...] +--- + +Create one worktree per repo for this task, so cross-repo changes can be +developed in parallel with matching branch names. + +Run: + +```bash +start-work-cascade [repo...] +``` + +Defaults to `reading-room open-agent agent-skills` under `~/src/personal`. +Each worktree lands at `/.claude/worktrees/` on branch +`` off `origin/main` — the same convention reading-room already +uses. Existing worktrees are skipped. + +After the work is done and merged, clean up with: + +```bash +worktree-cascade-done +``` + +which removes each worktree and deletes the branch (refusing unmerged +branches, mirroring `just worktree-done`). + +If the task touches repos outside the default set, pass them explicitly: +`/start-work-cascade my-task the-met-db wateralert-backend datalink` diff --git a/.claude/settings.local.json.template b/.claude/settings.local.json.template new file mode 100644 index 0000000..f6a764a --- /dev/null +++ b/.claude/settings.local.json.template @@ -0,0 +1,22 @@ +{ + "permissions": { + "allow": [ + "Bash(deno task:*)", + "Bash(deno check:*)", + "Bash(deno fmt:*)", + "Bash(deno lint:*)", + "Bash(deno test:*)", + "Bash(git status)", + "Bash(git diff:*)", + "Bash(git log:*)", + "Bash(git show:*)", + "Bash(git branch:*)", + "Bash(git worktree:*)", + "Bash(gh pr:*)", + "Bash(gh run:*)", + "Bash(gh api repos/tlockney/*)", + "Read(//tmp/**)", + "Read(//Users/tlockney/src/personal/**)" + ] + } +} diff --git a/bin/start-work-cascade b/bin/start-work-cascade new file mode 100755 index 0000000..635b1b6 --- /dev/null +++ b/bin/start-work-cascade @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# start-work-cascade — create one worktree per repo for a task, across +# multiple repos, so a cross-repo change can be developed in parallel +# worktrees with matching branch names. +# +# Usage: +# start-work-cascade [repo...] +# +# Default repos (personal): reading-room open-agent agent-skills +# Override with: START_WORK_CASCADE_REPOS="repo1 repo2" or pass repos as args. +# +# Each worktree lands at /.claude/worktrees/ on branch +# off origin/main — the same convention reading-room already +# uses. Idempotent: existing worktrees are skipped, not recreated. +set -euo pipefail + +TASK="${1:-}" +if [ -z "$TASK" ]; then + echo "usage: start-work-cascade [repo...]" >&2 + exit 1 +fi +shift || true + +if [ "$#" -gt 0 ]; then + REPOS=("$@") +elif [ -n "${START_WORK_CASCADE_REPOS:-}" ]; then + read -r -a REPOS <<< "$START_WORK_CASCADE_REPOS" +else + REPOS=(reading-room open-agent agent-skills) +fi + +SRC_ROOT="${START_WORK_CASCADE_ROOT:-$HOME/src/personal}" + +created=0 +skipped=0 +for repo in "${REPOS[@]}"; do + dir="$SRC_ROOT/$repo" + if [ ! -d "$dir/.git" ] && [ ! -f "$dir/.git" ]; then + echo "skip: $repo (not a repo at $dir)" >&2 + continue + fi + dest="$dir/.claude/worktrees/$TASK" + if [ -e "$dest" ]; then + echo "skip: $repo (worktree exists: $dest)" + skipped=$((skipped + 1)) + continue + fi + ( + cd "$dir" + git fetch -q origin + git worktree add "$dest" -b "$TASK" origin/main + ) + echo "created: $dest" + created=$((created + 1)) +done + +echo +echo "cascade ready: $created created, $skipped skipped" +echo "branches: $TASK (one per repo)" diff --git a/bin/worktree-cascade-done b/bin/worktree-cascade-done new file mode 100755 index 0000000..1a94f48 --- /dev/null +++ b/bin/worktree-cascade-done @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# worktree-cascade-done — finish a cascade: remove the worktree and branch +# in every repo that has one for the task. Mirrors the dotfiles +# `just worktree-done` semantics: -d (never -D) so unmerged branches are +# refused, and the launcher checkout is left alone. +# +# Usage: +# worktree-cascade-done [repo...] +set -euo pipefail + +TASK="${1:-}" +if [ -z "$TASK" ]; then + echo "usage: worktree-cascade-done [repo...]" >&2 + exit 1 +fi +shift || true + +if [ "$#" -gt 0 ]; then + REPOS=("$@") +elif [ -n "${START_WORK_CASCADE_REPOS:-}" ]; then + read -r -a REPOS <<< "$START_WORK_CASCADE_REPOS" +else + REPOS=(reading-room open-agent agent-skills) +fi + +SRC_ROOT="${START_WORK_CASCADE_ROOT:-$HOME/src/personal}" + +removed=0 +for repo in "${REPOS[@]}"; do + dir="$SRC_ROOT/$repo" + dest="$dir/.claude/worktrees/$TASK" + if [ ! -d "$dest" ]; then + continue + fi + ( + cd "$dir" + git worktree remove "$dest" + git branch -d "$TASK" 2>/dev/null || echo " $repo: branch $TASK not deleted (unmerged?)" + ) + echo "removed: $dest" + removed=$((removed + 1)) +done + +echo "cascade done: $removed worktrees removed"