diff --git a/README.md b/README.md index b00d4d8..a093332 100644 --- a/README.md +++ b/README.md @@ -40,14 +40,14 @@ histories stay joined and edits on either side meet in normal three-way merges. ```sh git clone -b ws/argos-dev https://github.com/Northeastern-Electric-Racing/Delphi.git argos-dev -cd argos-dev && .delphi/setup.sh # clones workspace.yml's repos into repos/ (git-ignored) +cd argos-dev && .delphi/setup.sh # repos/ stores + default-branch worktrees in worktrees/ (git-ignored) git switch -c my-change # edit, commit, push, open a PR into ws/argos-dev git fetch origin && git merge origin/ws/argos-dev # update your branch any time ``` After your PR merges into `ws/`, CI proposes it: a PR `ws/ → main` from `propose/`. Merge that with a merge commit or squash, never rebase. Never push to `ws/*` or -`main` directly (protect them). Code changes go in `repos/`, through that repo's own PRs. +`main` directly (protect them). Code changes go in a worktree, `worktrees//` (`.delphi/new-worktree.sh`), through that repo's own PRs. **Conflicts.** If `main` and `ws/` changed the same lines, CI lists the files and skips that workspace. Fix it in a PR into `ws/`: on a branch cut from `ws/`, run @@ -57,7 +57,7 @@ workspace. Fix it in a PR into `ws/`: on a branch cut from `ws/`, ru ```yaml harness: claude-code -repos: # cloned into repos/ by .delphi/setup.sh +repos: # stored in repos/, checked out in worktrees// by .delphi/setup.sh argos: https://github.com/Northeastern-Electric-Racing/Argos.git ``` diff --git a/docs/design.md b/docs/design.md index c8512c9..66b2622 100644 --- a/docs/design.md +++ b/docs/design.md @@ -94,12 +94,14 @@ Nobody pushes to `ws/*` or `main` directly (branch protection; CI's token is the ## 5. `.delphi/setup.sh` (in every workspace) -Reads `repos:` from `workspace.yml`, clones each into `repos/` unless present, and adds -`/repos/` to the clone's `.git/info/exclude` once. Nothing else. Must run on macOS `/bin/bash` 3.2 +Reads `repos:` from `workspace.yml` and, for each not yet present, fetches it into a bare store at +`repos/`, then checks out its default branch as the first worktree, +`worktrees///` (re-created if missing). Adds `/repos/` and `/worktrees/` to +the clone's `.git/info/exclude` once. Nothing else: code is edited only in worktrees. Must run on macOS `/bin/bash` 3.2 and Git Bash: no bash-4 features, POSIX awk only. -`.delphi/new-worktree.sh []` creates or reuses `repos/worktrees//`: -checks out an existing branch, else starts one from `` (default `origin/HEAD`). Same shell rules. +`.delphi/new-worktree.sh []` creates or reuses `worktrees//` +from that store: checks out an existing branch, else starts one from `` (default `origin/HEAD`). Same shell rules. The template's `new-worktree` skill makes worktrees the default way to start a branch. `.delphi/link.sh |main` checks out `origin/ws/` (or `origin/main`) as a diff --git a/docs/goals.md b/docs/goals.md index bfce84a..7f9f4da 100644 --- a/docs/goals.md +++ b/docs/goals.md @@ -12,7 +12,7 @@ anyone, person or agent, work on a workspace with plain git and send improvement - **G1. Workspaces live in Delphi.** Each workspace is a folder on `main` with every file at its normal harness location. - **G2. The workspace is the repo root.** Checking out `ws/` gives exactly that workspace at - the root. `.delphi/setup.sh` clones the code repos it lists. + the root. `.delphi/setup.sh` checks out the code repos it lists as worktrees. - **G3. Both directions, automatically.** Refresh: changes on `main` reach `ws/`. Propose: merged workspace changes reach `main` as a PR. - **G4. Conflicts are reported, never guessed.** A conflicting workspace is skipped with its files @@ -25,6 +25,6 @@ anyone, person or agent, work on a workspace with plain git and send improvement - **I1.** Changes reach `main` and `ws/*` only through PRs (CI's refreshes excepted). - **I2.** `ws/` history is joined to `main`; no unrelated histories, no force-pushes to `ws/*`. -- **I3.** Code repos in `repos/` never appear as workspace changes. +- **I3.** Code repos in `repos/` and `worktrees/` never appear as workspace changes. - **I4.** `setup.sh` works on macOS bash 3.2 and Git Bash. - **I5.** Everything is testable end to end without GitHub (`tests/e2e.sh`). diff --git a/software/application-software/argos/defaults/.claude/skills/new-worktree/SKILL.md b/software/application-software/argos/defaults/.claude/skills/new-worktree/SKILL.md index 49d2459..633143f 100644 --- a/software/application-software/argos/defaults/.claude/skills/new-worktree/SKILL.md +++ b/software/application-software/argos/defaults/.claude/skills/new-worktree/SKILL.md @@ -1,9 +1,9 @@ --- name: new-worktree -description: Create or reuse a worktree for a branch of a repo in repos/ (repos/worktrees//). Worktrees are the default way to make or check out any new branch; use this before starting a ticket, reviewing or fixing a PR branch, or any other branch work, unless the user says not to use worktrees. +description: Create or reuse a worktree for a branch of a code repo (worktrees//). Worktrees are the default way to make or check out any new branch; use this before starting a ticket, reviewing or fixing a PR branch, or any other branch work, unless the user says not to use worktrees. --- -Make every new branch as a worktree, never by branching in `repos//`, unless the user explicitly says not to use worktrees. `repos//` stays a clean checkout of the default branch. +`repos/` is a bare store (no files); every checkout of a code repo is a worktree under `worktrees//`. `.delphi/setup.sh` makes the default branch's (`worktrees///`); keep it a clean reference: fetch and fast-forward only. Make every other branch with this script, never by switching branches in an existing worktree, unless the user explicitly says not to use worktrees. Run from the workspace root: @@ -13,6 +13,6 @@ bash .delphi/new-worktree.sh [] An existing branch (local or on origin, e.g. a PR's head) is checked out; anything else is created from `` (default: the repo's default branch). It's safe to re-run. It prints the worktree path: `cd` there and do all work in it. -After the branch merges, remove it with `git -C repos/ worktree remove ../worktrees//`. +After the branch merges, remove it with `git -C repos/ worktree remove ../../worktrees//`. Argos: always pass `origin/develop` as ``. New ticket branches follow `{issue-number}-{kebab-case-title}`. Run `npm ci` in the worktree's `angular-client/` before building, testing, or running the client. diff --git a/software/application-software/argos/defaults/CLAUDE.md b/software/application-software/argos/defaults/CLAUDE.md index 8d987d8..6a3a6f7 100644 --- a/software/application-software/argos/defaults/CLAUDE.md +++ b/software/application-software/argos/defaults/CLAUDE.md @@ -15,12 +15,12 @@ Argos is a real-time telemetry platform for Northeastern Electric Racing (NER). Angular 19 frontend (`angular-client/`) and Rust backend (`scylla-server/`), with schema tooling in `charybdis/` and MQTT broker config in `siren-base/`. -The Argos repo is checked out at `repos/argos/`. Paths below are relative to a checkout of it. The ticket number is the branch's leading number (`533-csv-upload` → `#533`). +The Argos repo's `develop` is checked out at `worktrees/argos/develop/`. Paths below are relative to any Argos worktree. The ticket number is the branch's leading number (`533-csv-upload` → `#533`). ## Worktrees -- `repos/argos/` is a clean reference to `develop`. Never edit, branch, commit, or run dev servers there. Only fetch, fast-forward `develop`, and manage worktrees from it. -- Every ticket gets its own worktree at `repos/worktrees/argos//`, and every workflow (implement, test, run, commit, PR) runs there. Create or reuse one with the `new-worktree` skill, based on `origin/develop`: `bash .delphi/new-worktree.sh argos origin/develop`. +- `worktrees/argos/develop/` is a clean reference to `develop`. Never edit, branch, commit, or run dev servers there; only fetch and fast-forward it. +- Every ticket gets its own worktree at `worktrees/argos//`, and every workflow (implement, test, run, commit, PR) runs there. Create or reuse one with the `new-worktree` skill, based on `origin/develop`: `bash .delphi/new-worktree.sh argos origin/develop`. - A new worktree has no `node_modules`: run `npm ci` in its `angular-client/` before testing or running the client. ## Local Development diff --git a/software/application-software/argos/workspaces/argos-dev/.claude/skills/implement/SKILL.md b/software/application-software/argos/workspaces/argos-dev/.claude/skills/implement/SKILL.md index d456f78..ca033f3 100644 --- a/software/application-software/argos/workspaces/argos-dev/.claude/skills/implement/SKILL.md +++ b/software/application-software/argos/workspaces/argos-dev/.claude/skills/implement/SKILL.md @@ -4,7 +4,7 @@ description: Implement a piece of work based on a spec or set of tickets. disable-model-invocation: true --- -Implement the ticket(s) the user names, one ticket per fresh context, each in its own worktree at `repos/worktrees/argos//` (never in `repos/argos/`). Create the worktree first if it doesn't exist. +Implement the ticket(s) the user names, one ticket per fresh context, each in its own worktree at `worktrees/argos//` (never in `worktrees/argos/develop/`). Create the worktree first if it doesn't exist. 1. Work test-first at the spec's agreed seams, one behavior at a time: write a failing test, then the minimum code to pass it, and refactor only while green. Test behavior through public interfaces, and mock only at system boundaries. 2. Check as you go: diff --git a/software/application-software/argos/workspaces/argos-dev/.claude/skills/new-worktree/SKILL.md b/software/application-software/argos/workspaces/argos-dev/.claude/skills/new-worktree/SKILL.md index 49d2459..633143f 100644 --- a/software/application-software/argos/workspaces/argos-dev/.claude/skills/new-worktree/SKILL.md +++ b/software/application-software/argos/workspaces/argos-dev/.claude/skills/new-worktree/SKILL.md @@ -1,9 +1,9 @@ --- name: new-worktree -description: Create or reuse a worktree for a branch of a repo in repos/ (repos/worktrees//). Worktrees are the default way to make or check out any new branch; use this before starting a ticket, reviewing or fixing a PR branch, or any other branch work, unless the user says not to use worktrees. +description: Create or reuse a worktree for a branch of a code repo (worktrees//). Worktrees are the default way to make or check out any new branch; use this before starting a ticket, reviewing or fixing a PR branch, or any other branch work, unless the user says not to use worktrees. --- -Make every new branch as a worktree, never by branching in `repos//`, unless the user explicitly says not to use worktrees. `repos//` stays a clean checkout of the default branch. +`repos/` is a bare store (no files); every checkout of a code repo is a worktree under `worktrees//`. `.delphi/setup.sh` makes the default branch's (`worktrees///`); keep it a clean reference: fetch and fast-forward only. Make every other branch with this script, never by switching branches in an existing worktree, unless the user explicitly says not to use worktrees. Run from the workspace root: @@ -13,6 +13,6 @@ bash .delphi/new-worktree.sh [] An existing branch (local or on origin, e.g. a PR's head) is checked out; anything else is created from `` (default: the repo's default branch). It's safe to re-run. It prints the worktree path: `cd` there and do all work in it. -After the branch merges, remove it with `git -C repos/ worktree remove ../worktrees//`. +After the branch merges, remove it with `git -C repos/ worktree remove ../../worktrees//`. Argos: always pass `origin/develop` as ``. New ticket branches follow `{issue-number}-{kebab-case-title}`. Run `npm ci` in the worktree's `angular-client/` before building, testing, or running the client. diff --git a/software/application-software/argos/workspaces/argos-dev/.delphi/new-worktree.sh b/software/application-software/argos/workspaces/argos-dev/.delphi/new-worktree.sh index b4f856b..34a2f1d 100755 --- a/software/application-software/argos/workspaces/argos-dev/.delphi/new-worktree.sh +++ b/software/application-software/argos/workspaces/argos-dev/.delphi/new-worktree.sh @@ -1,16 +1,19 @@ #!/usr/bin/env bash -# .delphi/new-worktree.sh []: create or reuse repos/worktrees// -# for repos/ and print its path. An existing branch (local or on origin) is checked out; a new -# one starts from (default: origin's default branch). Runs on bash 3.2 and Git Bash. +# .delphi/new-worktree.sh []: create or reuse worktrees// from +# the store .delphi/setup.sh made at repos/, and print its path. An existing branch +# (local or on origin) is checked out; a new one starts from (default: origin's default +# branch). Runs on bash 3.2 and Git Bash. set -euo pipefail root=$(cd "$(dirname "$0")/.." && pwd) repo=${1:?usage: new-worktree.sh []} branch=${2:?usage: new-worktree.sh []} -base=${3:-origin/HEAD} dest="$root/repos/worktrees/$repo/$branch" +base=${3:-origin/HEAD} dest="$root/worktrees/$repo/$branch" g() { git -C "$root/repos/$repo" "$@"; } g fetch -q origin if [ -d "$dest" ]; then : -elif g rev-parse -q --verify "refs/heads/$branch" >/dev/null || g rev-parse -q --verify "refs/remotes/origin/$branch" >/dev/null; then +elif g rev-parse -q --verify "refs/heads/$branch" >/dev/null; then g worktree add -q "$dest" "$branch" +elif g rev-parse -q --verify "refs/remotes/origin/$branch" >/dev/null; then + g worktree add -q --track -b "$branch" "$dest" "origin/$branch" else g worktree add -q --no-track -b "$branch" "$dest" "$base" fi diff --git a/software/application-software/argos/workspaces/argos-dev/.delphi/setup.sh b/software/application-software/argos/workspaces/argos-dev/.delphi/setup.sh index 22ceb98..9cc1d1e 100755 --- a/software/application-software/argos/workspaces/argos-dev/.delphi/setup.sh +++ b/software/application-software/argos/workspaces/argos-dev/.delphi/setup.sh @@ -1,8 +1,9 @@ #!/usr/bin/env bash -# .delphi/setup.sh: run in a checkout of ws/ (the workspace root). Clones each repo listed -# under `repos:` in workspace.yml into repos/ (skipping ones already there) and adds /repos/ -# to this clone's .git/info/exclude. Safe to re-run. Keep it bash 3.2 (macOS) and Git Bash safe: -# no bash-4 features, POSIX awk only. +# .delphi/setup.sh: run in a checkout of ws/ (the workspace root). For each repo listed under +# `repos:` in workspace.yml, fetches it into a bare store at repos/ (unless present) and checks +# out its default branch as the first worktree, worktrees//. Adds /repos/ and +# /worktrees/ to this clone's .git/info/exclude. Safe to re-run. Keep it bash 3.2 (macOS) and Git +# Bash safe: no bash-4 features, POSIX awk only. set -euo pipefail cd "$(dirname "$0")/.." @@ -16,15 +17,32 @@ list=$(awk ' while read -r name url; do [ -n "$name" ] || continue case "$name" in . | .. | *[!A-Za-z0-9._-]*) echo "setup: skipping unsafe repo name '$name'" >&2 && continue ;; esac - if [ -e "repos/$name" ]; then + store=repos/$name + if [ -e "$store" ]; then echo "repos/$name: already present" else - git clone "$url" "repos/$name" /dev/null &2 && continue; } + def=${def#origin/} + dest=worktrees/$name/$def + if [ -e "$dest" ]; then continue + elif git -C "$store" rev-parse -q --verify "refs/heads/$def" >/dev/null; then + git -C "$store" worktree add -q "$PWD/$dest" "$def" || + echo "setup: could not check out $dest (is $def checked out in repos/$name?)" >&2 + else + git -C "$store" worktree add -q --track -b "$def" "$PWD/$dest" "origin/$def" + fi +done </dev/null || echo /repos/ >>"$exclude" +for d in /repos/ /worktrees/; do + grep -qxF "$d" "$exclude" 2>/dev/null || echo "$d" >>"$exclude" +done diff --git a/software/application-software/argos/workspaces/argos-dev/CLAUDE.md b/software/application-software/argos/workspaces/argos-dev/CLAUDE.md index e3fabc6..cdf2397 100644 --- a/software/application-software/argos/workspaces/argos-dev/CLAUDE.md +++ b/software/application-software/argos/workspaces/argos-dev/CLAUDE.md @@ -1,6 +1,6 @@ # Delphi workspace -You're in Delphi workspace `argos-dev` (branch `ws/argos-dev`); code repos live in `repos/`. Make new branches as worktrees (`new-worktree` skill) unless the user says not to. Work here unless the task clearly belongs to another project. +You're in Delphi workspace `argos-dev` (branch `ws/argos-dev`); code repos are checked out as worktrees under `worktrees//` (`.delphi/setup.sh` makes the default branch's). Make new branches as worktrees (`new-worktree` skill) unless the user says not to. Work here unless the task clearly belongs to another project. # NER Software conventions @@ -19,12 +19,12 @@ You're in Delphi workspace `argos-dev` (branch `ws/argos-dev`); code repos live Argos is a real-time telemetry platform for Northeastern Electric Racing (NER). Angular 19 frontend (`angular-client/`) and Rust backend (`scylla-server/`), with schema tooling in `charybdis/` and MQTT broker config in `siren-base/`. -The Argos repo is checked out at `repos/argos/`. Paths below are relative to a checkout of it. The ticket number is the branch's leading number (`533-csv-upload` → `#533`). +The Argos repo's `develop` is checked out at `worktrees/argos/develop/`. Paths below are relative to any Argos worktree. The ticket number is the branch's leading number (`533-csv-upload` → `#533`). ## Worktrees -- `repos/argos/` is a clean reference to `develop`. Never edit, branch, commit, or run dev servers there. Only fetch, fast-forward `develop`, and manage worktrees from it. -- Every ticket gets its own worktree at `repos/worktrees/argos//`, and every workflow (implement, test, run, commit, PR) runs there. Create or reuse one with the `new-worktree` skill, based on `origin/develop`: `bash .delphi/new-worktree.sh argos origin/develop`. +- `worktrees/argos/develop/` is a clean reference to `develop`. Never edit, branch, commit, or run dev servers there; only fetch and fast-forward it. +- Every ticket gets its own worktree at `worktrees/argos//`, and every workflow (implement, test, run, commit, PR) runs there. Create or reuse one with the `new-worktree` skill, based on `origin/develop`: `bash .delphi/new-worktree.sh argos origin/develop`. - A new worktree has no `node_modules`: run `npm ci` in its `angular-client/` before testing or running the client. ## Local Development @@ -69,4 +69,4 @@ Issues live in GitHub Issues on `Northeastern-Electric-Racing/Argos` via the `gh ## Domain docs -The glossary and ADRs are workspace docs, not files in `repos/argos/`: `docs/CONTEXT.md` and `docs/adr/` at the workspace root. Edit them there and they're proposed back to Delphi. Read `docs/CONTEXT.md` and the relevant ADRs before exploring, use the glossary's terms, and flag any conflict with an ADR. ADR filenames follow `repos/argos/docs/agents/domain.md` (`--.md`). +The glossary and ADRs are workspace docs, not files in the Argos repo: `docs/CONTEXT.md` and `docs/adr/` at the workspace root. Edit them there and they're proposed back to Delphi. Read `docs/CONTEXT.md` and the relevant ADRs before exploring, use the glossary's terms, and flag any conflict with an ADR. ADR filenames follow `docs/agents/domain.md` in any Argos worktree (`--.md`). diff --git a/templates/workspace/.claude/skills/new-worktree/SKILL.md b/templates/workspace/.claude/skills/new-worktree/SKILL.md index 3bbd319..bdf76e8 100644 --- a/templates/workspace/.claude/skills/new-worktree/SKILL.md +++ b/templates/workspace/.claude/skills/new-worktree/SKILL.md @@ -1,9 +1,9 @@ --- name: new-worktree -description: Create or reuse a worktree for a branch of a repo in repos/ (repos/worktrees//). Worktrees are the default way to make or check out any new branch; use this before starting a ticket, reviewing or fixing a PR branch, or any other branch work, unless the user says not to use worktrees. +description: Create or reuse a worktree for a branch of a code repo (worktrees//). Worktrees are the default way to make or check out any new branch; use this before starting a ticket, reviewing or fixing a PR branch, or any other branch work, unless the user says not to use worktrees. --- -Make every new branch as a worktree, never by branching in `repos//`, unless the user explicitly says not to use worktrees. `repos//` stays a clean checkout of the default branch. +`repos/` is a bare store (no files); every checkout of a code repo is a worktree under `worktrees//`. `.delphi/setup.sh` makes the default branch's (`worktrees///`); keep it a clean reference: fetch and fast-forward only. Make every other branch with this script, never by switching branches in an existing worktree, unless the user explicitly says not to use worktrees. Run from the workspace root: @@ -13,4 +13,4 @@ bash .delphi/new-worktree.sh [] An existing branch (local or on origin, e.g. a PR's head) is checked out; anything else is created from `` (default: the repo's default branch). It's safe to re-run. It prints the worktree path: `cd` there and do all work in it. -After the branch merges, remove it with `git -C repos/ worktree remove ../worktrees//`. +After the branch merges, remove it with `git -C repos/ worktree remove ../../worktrees//`. diff --git a/templates/workspace/.delphi/new-worktree.sh b/templates/workspace/.delphi/new-worktree.sh index b4f856b..34a2f1d 100755 --- a/templates/workspace/.delphi/new-worktree.sh +++ b/templates/workspace/.delphi/new-worktree.sh @@ -1,16 +1,19 @@ #!/usr/bin/env bash -# .delphi/new-worktree.sh []: create or reuse repos/worktrees// -# for repos/ and print its path. An existing branch (local or on origin) is checked out; a new -# one starts from (default: origin's default branch). Runs on bash 3.2 and Git Bash. +# .delphi/new-worktree.sh []: create or reuse worktrees// from +# the store .delphi/setup.sh made at repos/, and print its path. An existing branch +# (local or on origin) is checked out; a new one starts from (default: origin's default +# branch). Runs on bash 3.2 and Git Bash. set -euo pipefail root=$(cd "$(dirname "$0")/.." && pwd) repo=${1:?usage: new-worktree.sh []} branch=${2:?usage: new-worktree.sh []} -base=${3:-origin/HEAD} dest="$root/repos/worktrees/$repo/$branch" +base=${3:-origin/HEAD} dest="$root/worktrees/$repo/$branch" g() { git -C "$root/repos/$repo" "$@"; } g fetch -q origin if [ -d "$dest" ]; then : -elif g rev-parse -q --verify "refs/heads/$branch" >/dev/null || g rev-parse -q --verify "refs/remotes/origin/$branch" >/dev/null; then +elif g rev-parse -q --verify "refs/heads/$branch" >/dev/null; then g worktree add -q "$dest" "$branch" +elif g rev-parse -q --verify "refs/remotes/origin/$branch" >/dev/null; then + g worktree add -q --track -b "$branch" "$dest" "origin/$branch" else g worktree add -q --no-track -b "$branch" "$dest" "$base" fi diff --git a/templates/workspace/.delphi/setup.sh b/templates/workspace/.delphi/setup.sh index 22ceb98..9cc1d1e 100755 --- a/templates/workspace/.delphi/setup.sh +++ b/templates/workspace/.delphi/setup.sh @@ -1,8 +1,9 @@ #!/usr/bin/env bash -# .delphi/setup.sh: run in a checkout of ws/ (the workspace root). Clones each repo listed -# under `repos:` in workspace.yml into repos/ (skipping ones already there) and adds /repos/ -# to this clone's .git/info/exclude. Safe to re-run. Keep it bash 3.2 (macOS) and Git Bash safe: -# no bash-4 features, POSIX awk only. +# .delphi/setup.sh: run in a checkout of ws/ (the workspace root). For each repo listed under +# `repos:` in workspace.yml, fetches it into a bare store at repos/ (unless present) and checks +# out its default branch as the first worktree, worktrees//. Adds /repos/ and +# /worktrees/ to this clone's .git/info/exclude. Safe to re-run. Keep it bash 3.2 (macOS) and Git +# Bash safe: no bash-4 features, POSIX awk only. set -euo pipefail cd "$(dirname "$0")/.." @@ -16,15 +17,32 @@ list=$(awk ' while read -r name url; do [ -n "$name" ] || continue case "$name" in . | .. | *[!A-Za-z0-9._-]*) echo "setup: skipping unsafe repo name '$name'" >&2 && continue ;; esac - if [ -e "repos/$name" ]; then + store=repos/$name + if [ -e "$store" ]; then echo "repos/$name: already present" else - git clone "$url" "repos/$name" /dev/null &2 && continue; } + def=${def#origin/} + dest=worktrees/$name/$def + if [ -e "$dest" ]; then continue + elif git -C "$store" rev-parse -q --verify "refs/heads/$def" >/dev/null; then + git -C "$store" worktree add -q "$PWD/$dest" "$def" || + echo "setup: could not check out $dest (is $def checked out in repos/$name?)" >&2 + else + git -C "$store" worktree add -q --track -b "$def" "$PWD/$dest" "origin/$def" + fi +done </dev/null || echo /repos/ >>"$exclude" +for d in /repos/ /worktrees/; do + grep -qxF "$d" "$exclude" 2>/dev/null || echo "$d" >>"$exclude" +done diff --git a/templates/workspace/CLAUDE.md b/templates/workspace/CLAUDE.md index 7caacfe..87dc67a 100644 --- a/templates/workspace/CLAUDE.md +++ b/templates/workspace/CLAUDE.md @@ -1,3 +1,3 @@ # Delphi workspace -You're in Delphi workspace `{{name}}` (branch `ws/{{name}}`); code repos live in `repos/`. Make new branches as worktrees (`new-worktree` skill) unless the user says not to. Work here unless the task clearly belongs to another project. +You're in Delphi workspace `{{name}}` (branch `ws/{{name}}`); code repos are checked out as worktrees under `worktrees//` (`.delphi/setup.sh` makes the default branch's). Make new branches as worktrees (`new-worktree` skill) unless the user says not to. Work here unless the task clearly belongs to another project. diff --git a/tests/e2e.sh b/tests/e2e.sh index 43b66c2..2c490e4 100755 --- a/tests/e2e.sh +++ b/tests/e2e.sh @@ -163,20 +163,31 @@ expect_bad defaults-manifest "defaults/workspace.yml: unexpected line" sh -c "pr expect_bad defaults-named-workspace "defaults/.delphi/link.sh: differs" sh -c "mkdir -p software/x/workspaces && cp -R $A software/x/workspaces/defaults && echo x >>software/x/workspaces/defaults/.delphi/link.sh" expect_bad template-workflow-drift "differs from .github/workflows/delphi.yml" sh -c "echo x >>.github/workflows/delphi.yml" -# --- 8. setup.sh clones workspace.yml repos into repos/ and is idempotent +# --- 8. setup.sh fetches workspace.yml repos into repos/, checks out the default branch under worktrees/, and is idempotent for r in lib1 lib2; do git init -q "$t/$r" && git -C "$t/$r" commit -q --allow-empty -m "$r" && git clone -q --bare "$t/$r" "$t/$r.git" done +def=$(git -C "$t/lib1" branch --show-current) git clone -q -b ws/argos-dev "$t/origin.git" "$t/ws" printf 'harness: claude-code\nrepos:\n # a comment\n lib1: %s # trailing\n lib2: %s\n' \ "$t/lib1.git" "$t/lib2.git" >"$t/ws/workspace.yml" /bin/bash "$t/ws/.delphi/setup.sh" >/dev/null 2>&1 || fail "setup.sh failed" /bin/bash "$t/ws/.delphi/setup.sh" >"$t/out" 2>&1 || fail "setup.sh re-run failed" -{ [ -d "$t/ws/repos/lib1/.git" ] && [ -d "$t/ws/repos/lib2/.git" ]; } || fail "repos not cloned" +for r in lib1 lib2; do + [ "$(git -C "$t/ws/worktrees/$r/$def" rev-parse HEAD)" = "$(git -C "$t/$r" rev-parse HEAD)" ] || fail "$r default worktree missing" + [ "$(git -C "$t/ws/worktrees/$r/$def" branch --show-current)" = "$def" ] || fail "$r default worktree not on $def" +done +[ "$(git -C "$t/ws/repos/lib1" rev-parse --is-bare-repository)" = true ] || fail "repos/lib1 not a bare store" grep -q "repos/lib1: already present" "$t/out" || fail "re-run did not skip" -[ "$(grep -cx /repos/ "$t/ws/.git/info/exclude")" = 1 ] || fail "exclude not exactly once" -[ "$(git -C "$t/ws" status --porcelain)" = " M workspace.yml" ] || fail "repos/ shows in status" -ok "setup.sh clones repos into repos/, excludes them once, and is idempotent" +for d in /repos/ /worktrees/; do [ "$(grep -cx "$d" "$t/ws/.git/info/exclude")" = 1 ] || fail "$d not excluded exactly once"; done +[ "$(git -C "$t/ws" status --porcelain)" = " M workspace.yml" ] || fail "repos/ or worktrees/ shows in status" +rm -rf "$t/ws/worktrees/lib2" && git -C "$t/ws/repos/lib2" worktree prune && /bin/bash "$t/ws/.delphi/setup.sh" >/dev/null 2>&1 && + [ -d "$t/ws/worktrees/lib2/$def" ] || fail "re-run did not restore a missing default worktree" +ok "setup.sh makes a bare store in repos/ plus a default-branch worktree in worktrees/, excludes both once, and is idempotent" +w=$t/legacy && git clone -q -b ws/argos-dev "$t/origin.git" "$w" && cp "$t/ws/workspace.yml" "$w/" && git clone -q "$t/lib1.git" "$w/repos/lib1" +/bin/bash "$w/.delphi/setup.sh" >/dev/null 2>"$t/err" || fail "setup.sh failed on a pre-existing clone" +{ grep -q "could not check out worktrees/lib1/$def" "$t/err" && [ -d "$w/worktrees/lib2/$def" ]; } || fail "pre-existing clone not reported" +ok "setup.sh warns and carries on when repos/ is an older plain clone" git clone -q -c core.autocrlf=true -b ws/argos-dev "$t/origin.git" "$t/ws-crlf" cp "$t/ws/workspace.yml" "$t/ws-crlf/" /bin/bash "$t/ws-crlf/.delphi/setup.sh" >/dev/null 2>&1 || fail "setup.sh fails with core.autocrlf=true" @@ -230,16 +241,20 @@ ok "a proposal failing check.sh is reported and not pushed; exit 1" # --- 11. new-worktree.sh: new branch from the default branch or ; existing branch checked out; reruns reuse git init -q "$t/code" && git -C "$t/code" commit -q --allow-empty -m one && git -C "$t/code" branch feat && git -C "$t/code" branch develop && git -C "$t/code" commit -q --allow-empty -m two -w=$t/nw && git clone -q -b ws/bms-dev "$t/origin.git" "$w" && git clone -q "$t/code" "$w/repos/code" +def=$(git -C "$t/code" branch --show-current) +w=$t/nw && git clone -q -b ws/bms-dev "$t/origin.git" "$w" +printf 'harness: claude-code\nrepos:\n code: %s\n' "$t/code" >"$w/workspace.yml" +bash "$w/.delphi/setup.sh" >/dev/null 2>&1 || fail "setup.sh failed for new-worktree test" nw() { bash "$w/.delphi/new-worktree.sh" "$@"; } -[ "$(nw code 1-new)" = "$w/repos/worktrees/code/1-new" ] || fail "new-worktree path" -[ "$(git -C "$w/repos/worktrees/code/1-new" rev-parse HEAD)" = "$(git -C "$t/code" rev-parse main)" ] || fail "new branch not from default" -nw code feat >/dev/null && [ "$(git -C "$w/repos/worktrees/code/feat" rev-parse HEAD)" = "$(git -C "$t/code" rev-parse feat)" ] || +[ "$(nw code 1-new)" = "$w/worktrees/code/1-new" ] || fail "new-worktree path" +[ "$(git -C "$w/worktrees/code/1-new" rev-parse HEAD)" = "$(git -C "$t/code" rev-parse "$def")" ] || fail "new branch not from default" +nw code feat >/dev/null && [ "$(git -C "$w/worktrees/code/feat" rev-parse HEAD)" = "$(git -C "$t/code" rev-parse feat)" ] || fail "existing branch not checked out" -nw code 2-dev origin/develop >/dev/null && [ "$(git -C "$w/repos/worktrees/code/2-dev" rev-parse HEAD)" = "$(git -C "$t/code" rev-parse develop)" ] || +[ "$(git -C "$w/worktrees/code/feat" rev-parse --abbrev-ref '@{u}')" = origin/feat ] || fail "existing branch not tracking origin" +nw code 2-dev origin/develop >/dev/null && [ "$(git -C "$w/worktrees/code/2-dev" rev-parse HEAD)" = "$(git -C "$t/code" rev-parse develop)" ] || fail " ignored" -[ "$(nw code feat)" = "$w/repos/worktrees/code/feat" ] || fail "rerun did not reuse" -[ "$(git -C "$w/repos/code" branch --show-current)" = main ] || fail "repos/code left its branch" +[ "$(nw code feat)" = "$w/worktrees/code/feat" ] || fail "rerun did not reuse" +[ "$(git -C "$w/worktrees/code/$def" branch --show-current)" = "$def" ] || fail "default worktree left its branch" ok "new-worktree.sh makes new branches from the default or , checks out existing ones, reuses on rerun" # --- 12. link.sh: other workspaces and main as detached worktrees under linked/, updated on re-run