diff --git a/.delphi/manifest.yml b/.delphi/manifest.yml index 6718697..8546a04 100644 --- a/.delphi/manifest.yml +++ b/.delphi/manifest.yml @@ -6,7 +6,7 @@ # MINOR something new that existing clones need nothing for # MAJOR clones need cleanup (MINOR while 0.x): add .delphi/migrations/-.sh, which # setup.sh runs once per workspace in each clone (rules in Delphi's CLAUDE.md) -version: 1.1.0 +version: 1.2.0 files: # repo-relative paths; a trailing / means everything under it - .delphi/manifest.yml - .delphi/.gitattributes diff --git a/.delphi/new-worktree.sh b/.delphi/new-worktree.sh index 04c3713..6e53461 100755 --- a/.delphi/new-worktree.sh +++ b/.delphi/new-worktree.sh @@ -1,16 +1,17 @@ #!/usr/bin/env bash # .delphi/new-worktree.sh []: create or reuse worktrees// from -# the clone .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. +# the bare store .delphi/setup.sh made at worktrees//.bare (never the clone 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 # shellcheck source-path=SCRIPTDIR source=paths.sh . "$(dirname "$0")/paths.sh" repo=${1:?usage: new-worktree.sh []} branch=${2:?usage: new-worktree.sh []} base=${3:-origin/HEAD} dest="$DELPHI_WORKTREES/$repo/$branch" -g() { git -C "$DELPHI_REPOS/$repo" "$@"; } -[ -d "$DELPHI_REPOS/$repo" ] || - { echo "new-worktree: no repos/$repo (list it under repos: and run .delphi/setup.sh; references are read-only)" >&2 && exit 1; } +store=$DELPHI_WORKTREES/$repo/.bare +g() { git -C "$store" "$@"; } +[ -d "$store" ] || + { echo "new-worktree: no worktrees/$repo/.bare (list it under repos: and run .delphi/setup.sh; references are read-only)" >&2 && exit 1; } g fetch -q origin if [ -d "$dest" ]; then : elif g rev-parse -q --verify "refs/heads/$branch" >/dev/null; then diff --git a/.delphi/paths.sh b/.delphi/paths.sh index b8ef606..f5961cd 100644 --- a/.delphi/paths.sh +++ b/.delphi/paths.sh @@ -7,7 +7,7 @@ # shellcheck shell=bash DELPHI_ROOT=$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")/.." && pwd) DELPHI_REPOS=$DELPHI_ROOT/repos # clones: repos/ -DELPHI_WORKTREES=$DELPHI_ROOT/worktrees # checkouts: worktrees// +DELPHI_WORKTREES=$DELPHI_ROOT/worktrees # checkouts: worktrees//, from the bare store worktrees//.bare DELPHI_REFERENCES=$DELPHI_ROOT/references # read-only dependency sources: references/ DELPHI_WORKSPACES=$DELPHI_ROOT/workspaces # Delphi clones to read: workspaces/, workspaces/main export DELPHI_ROOT DELPHI_REPOS DELPHI_WORKTREES DELPHI_REFERENCES DELPHI_WORKSPACES diff --git a/.delphi/setup.sh b/.delphi/setup.sh index dc0ff21..7abe7ea 100755 --- a/.delphi/setup.sh +++ b/.delphi/setup.sh @@ -1,8 +1,10 @@ #!/usr/bin/env bash # .delphi/setup.sh: run in a checkout of ws/ (the workspace root; .delphi/ comes from Delphi's # main, which sync keeps on every ws/* branch, so edit it only there; on main this does nothing). For each repo listed under -# `repos:` in workspace.yml, clones it at repos/ (unless present), on its default branch, and -# adds nothing else (.delphi/new-worktree.sh makes worktrees from it). For each repo under +# `repos:` in workspace.yml, clones it at repos/ (unless present). Separately, fetches it into a +# bare store at worktrees//.bare (unless present) and checks out its default branch as the first +# worktree, worktrees// (re-created if missing); .delphi/new-worktree.sh makes the +# others from that store, so worktrees never touch the clone. For each repo under # `references-git:` (read-only dependency sources, `: []`), checks out a shallow, # detached copy of (default: origin's default branch) at references/, refreshed on every # run, with pushes disabled. Each entry under `workspaces:` (`- `, a workspace's dotted name @@ -102,6 +104,23 @@ while read -r kind name url ref; do if [ -e "repos/$name" ]; then echo "repos/$name: already present" else git clone -q "$url" "repos/$name" &2 fi + store=worktrees/$name/.bare + if [ ! -e "$store" ]; then + git init -q --bare "$store" + git -C "$store" remote add origin "$url" + git -C "$store" fetch -q origin /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" >&2 + else + git -C "$store" worktree add -q --track -b "$def" "$PWD/$dest" "origin/$def" + fi done < are full clones on ws/ (or main) tracking origin, pushes allowed; setup.sh switches each back to its branch if switched away and pulls it (migration) 1.0.0 44693a895995 1.0: workspaces: clones live at workspaces/ (was linked/; DELPHI_WORKSPACES, was DELPHI_LINKED); setup.sh also runs each one's own setup.sh, and a workspace inside another's workspaces/ skips its own workspaces: (migration) 1.1.0 a3446f7f763c setup.sh clones each repos: entry at repos/ (a normal clone on its default branch, was a bare store) and adds no worktree; new-worktree.sh makes worktrees from that clone; older bare stores are left as they are +1.2.0 d139aaf9f4a5 worktrees come from their own bare store, worktrees//.bare, separate from the plain clone at repos/; setup.sh makes the store and the default-branch worktree again; an older bare store at repos/ is left as it is diff --git a/README.md b/README.md index ad4f6d4..4b4ba4f 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ git fetch origin && git merge origin/ws/s.app.argos.argos-dev # update your br After your PR merges into `ws/`, CI proposes it: a PR `ws/ → main` from `propose/`, labelled `by: ai` (change the label if you wrote it; CI keeps it). Merge that with a merge commit or squash, never rebase. Never push to `ws/*` or -`main` directly (protect them). Code changes go in the clone at `repos/` (one thing at a time) or a worktree from it, `worktrees//` (`.delphi/new-worktree.sh`), through that repo's own PRs. +`main` directly (protect them). Code changes go in a worktree, `worktrees//` (`.delphi/new-worktree.sh`, from its own store at `worktrees//.bare`, separate from the clone at `repos/`), 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 @@ -64,7 +64,7 @@ workspace. Fix it in a PR into `ws/`: on a branch cut from `ws/`, ru ```yaml name: s.app.argos.argos-dev # its dotted name harness: claude-code -repos: # cloned at repos// by .delphi/setup.sh; worktrees from it in worktrees// +repos: # cloned at repos//; separately, worktrees (default branch first) at worktrees/// argos: https://github.com/Northeastern-Electric-Racing/Argos.git references-git: # read-only dependency sources, shallow-cloned into references// socketioxide: https://github.com/Totodore/socketioxide.git v0.18.7 # optional tag or branch diff --git a/docs/design.md b/docs/design.md index ad6c24f..e321a2c 100644 --- a/docs/design.md +++ b/docs/design.md @@ -197,8 +197,11 @@ Nobody pushes to `ws/*` or `main` directly (branch protection; CI's token is the ## 5. `.delphi/setup.sh` (on every branch; does nothing on main, which has no `workspace.yml`) First runs the workspace's pending migrations (section 5b). Then reads `repos:` from `workspace.yml` and, for each not yet present, clones it at -`repos//` on its default branch, for people working on one thing at a time, and adds nothing -else (an older clone's bare store there is left as it is). Adds the folders `park.sh` parks +`repos//`, a plain clone of its own. Separately, it fetches the repo into a bare store at +`worktrees//.bare/` and checks out its default branch as the first worktree, +`worktrees///` (re-created if missing), so it's there to see and branch from. +Worktrees come only from that store, so they never touch the clone (a branch can be checked out once +per repository, and the two are separate repositories). Adds the folders `park.sh` parks and `.DS_Store` to the clone's `.git/info/exclude` once. For each repo under `references-git:` (read-only dependency sources), it fetches `` (default: origin's `HEAD`) with `--depth 1` into `references//`, checks it out detached, disables pushes, and does this again on every run, so the copy tracks its pin; @@ -236,7 +239,7 @@ out; nothing is repaired or rebuilt. Branches of the same workspace move nothing overwrites: a folder in the way is reported and the other copy stays parked. `.delphi/new-worktree.sh []` creates or reuses `worktrees//` -from the clone at `repos/`: checks out an existing branch, else starts one from `` (default `origin/HEAD`). Same shell rules. +from the store at `worktrees//.bare`: checks out an existing branch, else starts one from `` (default `origin/HEAD`). Same shell rules. The inherited `new-worktree` skill makes worktrees the default way to start a branch. `.delphi/paths.sh` is the layout in one place: sourced, it sets and exports `DELPHI_ROOT` (from the diff --git a/docs/goals.md b/docs/goals.md index c8a8377..1bce948 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` clones the code repos it lists and, separately, checks out their default branch as a worktree. - **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 diff --git a/software/application-software/argos/workspace-defaults/workspace.yml b/software/application-software/argos/workspace-defaults/workspace.yml index 95ff13d..106a38d 100644 --- a/software/application-software/argos/workspace-defaults/workspace.yml +++ b/software/application-software/argos/workspace-defaults/workspace.yml @@ -6,7 +6,7 @@ park: # `- `: root folders that park with this works - .playwright-mcp - .playwright-cli # .delphi/setup.sh checks out everything below (git-ignored); re-run it after any change. -repos: # `: `: code you change; a clone at repos/ (worktrees from it: worktrees//) +repos: # `: `: code you change; a clone at repos/, and separately worktrees// (default branch first) argos: https://github.com/Northeastern-Electric-Racing/Argos.git odyssey-definitions: https://github.com/Northeastern-Electric-Racing/Odyssey-Definitions.git # CAN definitions for verify-telemetry workspaces: # `- ` (dotted) or `- main`: Delphi workspaces to read, not edit from here; a clone on its branch at workspaces/ diff --git a/software/application-software/argos/workspaces/argos-dev/.claude/skills/dependabot-cleanup/scripts/ci.sh b/software/application-software/argos/workspaces/argos-dev/.claude/skills/dependabot-cleanup/scripts/ci.sh index 311c7c7..cd18c4e 100755 --- a/software/application-software/argos/workspaces/argos-dev/.claude/skills/dependabot-cleanup/scripts/ci.sh +++ b/software/application-software/argos/workspaces/argos-dev/.claude/skills/dependabot-cleanup/scripts/ci.sh @@ -19,7 +19,7 @@ while IFS=$'\t' read -r name link; do awk '/##\[group\]Run / { step = $0; n = 0; next } /##\[error\]/ { print step; for (i = 0; i < n; i++) print m[i]; print; exit } tolower($0) ~ /error|fail|panick/ && $0 !~ /^npm error *$/ && n < 10 { m[n++] = $0 }' | cut -c1-200 done <<<"$jobs" -g() { git -C "$DELPHI_REPOS/argos" "$@"; } +g() { git -C "$DELPHI_WORKTREES/argos/.bare" "$@"; } g fetch -q origin develop "$branch" echo "--- develop is $(g rev-list --count "$(g merge-base origin/develop "origin/$branch")..origin/develop") commits past this PR's base" gh run list -R "$R" -b develop -L 20 --json workflowName,conclusion \ diff --git a/software/application-software/argos/workspaces/argos-dev/.claude/skills/dependabot-cleanup/scripts/cleanup.sh b/software/application-software/argos/workspaces/argos-dev/.claude/skills/dependabot-cleanup/scripts/cleanup.sh index b3f2af2..50f6ea7 100755 --- a/software/application-software/argos/workspaces/argos-dev/.claude/skills/dependabot-cleanup/scripts/cleanup.sh +++ b/software/application-software/argos/workspaces/argos-dev/.claude/skills/dependabot-cleanup/scripts/cleanup.sh @@ -6,7 +6,7 @@ set -euo pipefail # shellcheck source=lib.sh . "$(dirname "$0")/lib.sh" [ $# -gt 0 ] || { echo "usage: cleanup.sh ..." >&2 && exit 2; } -g() { git -C "$DELPHI_REPOS/argos" "$@"; } +g() { git -C "$DELPHI_WORKTREES/argos/.bare" "$@"; } for pr in "$@"; do read -r state branch author < <(gh pr view "$pr" -R "$R" --json state,headRefName,author -q '"\(.state) \(.headRefName) \(.author.login)"') case $author in *dependabot*) ;; *) echo "#$pr skipped: author is $author" && continue ;; esac diff --git a/software/application-software/argos/workspaces/argos-dev/CLAUDE.md b/software/application-software/argos/workspaces/argos-dev/CLAUDE.md index e3a6240..cf36082 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 `s.app.argos.argos-dev` (branch `ws/s.app.argos.argos-dev`); code repos are cloned at `repos//` (`.delphi/setup.sh`), for people working on one thing at a time; leave those clones alone and make new branches as worktrees under `worktrees//` (`new-worktree` skill) unless the user says not to. Delphi workspaces (or `main`) under `workspaces:` in `workspace.yml` (`- `, a workspace's dotted name like `s.app.argos.argos-dev`, or `- main`) are clones at `workspaces//`, each on its branch, for reading their context (don't edit them from here; setup switches each back to its branch and pulls it); change what's checked out with the `setup-workspace` skill. To work in another workspace, switch this clone to it (`switch-workspace` skill). Park rough notes with the `journal` skill. Work here unless the task clearly belongs to another project. +You're in Delphi workspace `s.app.argos.argos-dev` (branch `ws/s.app.argos.argos-dev`); code repos are cloned at `repos//`, and separately checked out as worktrees under `worktrees//` (`.delphi/setup.sh` makes the default branch's). Work in worktrees: make each new branch one under `worktrees//` (`new-worktree` skill) unless the user says not to. Delphi workspaces (or `main`) under `workspaces:` in `workspace.yml` (`- `, a workspace's dotted name like `s.app.argos.argos-dev`, or `- main`) are clones at `workspaces//`, each on its branch, for reading their context (don't edit them from here; setup switches each back to its branch and pulls it); change what's checked out with the `setup-workspace` skill. To work in another workspace, switch this clone to it (`switch-workspace` skill). Park rough notes with the `journal` skill. Work here unless the task clearly belongs to another project. Always work from the workspace root (this clone's top, where `workspace.yml` is); the layout exists so one session can reach every worktree from there. Name worktrees by path: `git -C worktrees/argos/ …` runs git there without moving, and a one-off subshell like `(cd worktrees/argos//angular-client && npx ng lint)` runs anything else. If you `cd` into a worktree to look closely at something, `cd` back to the root as soon as you're done. Skill scripts take only names (repo, branch) and find the rest from `$DELPHI_ROOT`, which a session hook sets in every shell (`$DELPHI_WORKTREES` and the other `.delphi/paths.sh` folders too). diff --git a/software/application-software/argos/workspaces/argos-dev/workspace.yml b/software/application-software/argos/workspaces/argos-dev/workspace.yml index 8fb92bd..21302b9 100644 --- a/software/application-software/argos/workspaces/argos-dev/workspace.yml +++ b/software/application-software/argos/workspaces/argos-dev/workspace.yml @@ -8,7 +8,7 @@ park: # `- `: root folders that park with this works - .playwright-mcp - .playwright-cli # .delphi/setup.sh checks out everything below (git-ignored); re-run it after any change. -repos: # `: `: code you change; a clone at repos/ (worktrees from it: worktrees//) +repos: # `: `: code you change; a clone at repos/, and separately worktrees// (default branch first) argos: https://github.com/Northeastern-Electric-Racing/Argos.git odyssey-definitions: https://github.com/Northeastern-Electric-Racing/Odyssey-Definitions.git references-git: # `: []`: dependency sources to read, never edit; shallow copy at references/ diff --git a/software/application-software/argos/workspaces/experimental/CLAUDE.md b/software/application-software/argos/workspaces/experimental/CLAUDE.md index 6fd4d1c..5b7e349 100644 --- a/software/application-software/argos/workspaces/experimental/CLAUDE.md +++ b/software/application-software/argos/workspaces/experimental/CLAUDE.md @@ -1,6 +1,6 @@ # Delphi workspace -You're in Delphi workspace `s.app.argos.experimental` (branch `ws/s.app.argos.experimental`); code repos are cloned at `repos//` (`.delphi/setup.sh`), for people working on one thing at a time; leave those clones alone and make new branches as worktrees under `worktrees//` (`new-worktree` skill) unless the user says not to. Delphi workspaces (or `main`) under `workspaces:` in `workspace.yml` (`- `, a workspace's dotted name like `s.app.argos.argos-dev`, or `- main`) are clones at `workspaces//`, each on its branch, for reading their context (don't edit them from here; setup switches each back to its branch and pulls it); change what's checked out with the `setup-workspace` skill. To work in another workspace, switch this clone to it (`switch-workspace` skill). Park rough notes with the `journal` skill. Work here unless the task clearly belongs to another project. +You're in Delphi workspace `s.app.argos.experimental` (branch `ws/s.app.argos.experimental`); code repos are cloned at `repos//`, and separately checked out as worktrees under `worktrees//` (`.delphi/setup.sh` makes the default branch's). Work in worktrees: make each new branch one under `worktrees//` (`new-worktree` skill) unless the user says not to. Delphi workspaces (or `main`) under `workspaces:` in `workspace.yml` (`- `, a workspace's dotted name like `s.app.argos.argos-dev`, or `- main`) are clones at `workspaces//`, each on its branch, for reading their context (don't edit them from here; setup switches each back to its branch and pulls it); change what's checked out with the `setup-workspace` skill. To work in another workspace, switch this clone to it (`switch-workspace` skill). Park rough notes with the `journal` skill. Work here unless the task clearly belongs to another project. # NER Software conventions diff --git a/software/application-software/argos/workspaces/experimental/workspace.yml b/software/application-software/argos/workspaces/experimental/workspace.yml index 7ce25e9..c8aff80 100644 --- a/software/application-software/argos/workspaces/experimental/workspace.yml +++ b/software/application-software/argos/workspaces/experimental/workspace.yml @@ -8,7 +8,7 @@ park: # `- `: root folders that park with this works - .playwright-mcp - .playwright-cli # .delphi/setup.sh checks out everything below (git-ignored); re-run it after any change. -repos: # `: `: code you change; a clone at repos/ (worktrees from it: worktrees//) +repos: # `: `: code you change; a clone at repos/, and separately worktrees// (default branch first) argos: https://github.com/Northeastern-Electric-Racing/Argos.git odyssey-definitions: https://github.com/Northeastern-Electric-Racing/Odyssey-Definitions.git workspaces: # `- ` (dotted) or `- main`: Delphi workspaces to read, not edit from here; a clone on its branch at workspaces/ diff --git a/software/application-software/nero/workspaces/nero-experimental/CLAUDE.md b/software/application-software/nero/workspaces/nero-experimental/CLAUDE.md index 256164e..b0cc2be 100644 --- a/software/application-software/nero/workspaces/nero-experimental/CLAUDE.md +++ b/software/application-software/nero/workspaces/nero-experimental/CLAUDE.md @@ -1,6 +1,6 @@ # Delphi workspace -You're in Delphi workspace `s.app.nero.nero-experimental` (branch `ws/s.app.nero.nero-experimental`); code repos are cloned at `repos//` (`.delphi/setup.sh`), for people working on one thing at a time; leave those clones alone and make new branches as worktrees under `worktrees//` (`new-worktree` skill) unless the user says not to. Delphi workspaces (or `main`) under `workspaces:` in `workspace.yml` (`- `, a workspace's dotted name like `s.app.argos.argos-dev`, or `- main`) are clones at `workspaces//`, each on its branch, for reading their context (don't edit them from here; setup switches each back to its branch and pulls it); change what's checked out with the `setup-workspace` skill. To work in another workspace, switch this clone to it (`switch-workspace` skill). Park rough notes with the `journal` skill. Work here unless the task clearly belongs to another project. +You're in Delphi workspace `s.app.nero.nero-experimental` (branch `ws/s.app.nero.nero-experimental`); code repos are cloned at `repos//`, and separately checked out as worktrees under `worktrees//` (`.delphi/setup.sh` makes the default branch's). Work in worktrees: make each new branch one under `worktrees//` (`new-worktree` skill) unless the user says not to. Delphi workspaces (or `main`) under `workspaces:` in `workspace.yml` (`- `, a workspace's dotted name like `s.app.argos.argos-dev`, or `- main`) are clones at `workspaces//`, each on its branch, for reading their context (don't edit them from here; setup switches each back to its branch and pulls it); change what's checked out with the `setup-workspace` skill. To work in another workspace, switch this clone to it (`switch-workspace` skill). Park rough notes with the `journal` skill. Work here unless the task clearly belongs to another project. # NER Software conventions diff --git a/software/application-software/nero/workspaces/nero-experimental/workspace.yml b/software/application-software/nero/workspaces/nero-experimental/workspace.yml index 216698d..77dc23c 100644 --- a/software/application-software/nero/workspaces/nero-experimental/workspace.yml +++ b/software/application-software/nero/workspaces/nero-experimental/workspace.yml @@ -6,7 +6,7 @@ park: # `- `: root folders that park with this works - pictures - .playwright-mcp # .delphi/setup.sh checks out everything below (git-ignored); re-run it after any change. -repos: # `: `: code you change; a clone at repos/ (worktrees from it: worktrees//) +repos: # `: `: code you change; a clone at repos/, and separately worktrees// (default branch first) nero: https://github.com/Northeastern-Electric-Racing/Nero-2.0.git workspaces: # `- ` (dotted) or `- main`: Delphi workspaces to read, not edit from here; a clone on its branch at workspaces/ # - main diff --git a/software/application-software/workspace-inherit/.claude/skills/address-pr-comments/scripts/fetch-feedback.sh b/software/application-software/workspace-inherit/.claude/skills/address-pr-comments/scripts/fetch-feedback.sh index 1debe03..ce1efd9 100755 --- a/software/application-software/workspace-inherit/.claude/skills/address-pr-comments/scripts/fetch-feedback.sh +++ b/software/application-software/workspace-inherit/.claude/skills/address-pr-comments/scripts/fetch-feedback.sh @@ -13,7 +13,7 @@ usage="usage: fetch-feedback.sh [--all] |" repo=${1:?$usage} ref=${2:?$usage} # shellcheck source=/dev/null . "${DELPHI_ROOT:?unset: start the session at the workspace root, on Delphi 0.2.0+ (pull its ws/ branch), or source /.delphi/paths.sh}/.delphi/paths.sh" -gh_init fetch-feedback "$DELPHI_REPOS/$repo" +gh_init fetch-feedback "$DELPHI_WORKTREES/$repo/.bare" case "$ref" in *[!0-9]*) get "repos/$SLUG/pulls?state=all&head=${SLUG%%/*}:$ref" | jq '.[0] // error("no PR")' ;; *) get "repos/$SLUG/pulls/$ref" ;; esac >"$TMP/pr" diff --git a/software/application-software/workspaces/hub/workspace.yml b/software/application-software/workspaces/hub/workspace.yml index 9c7e0ca..cfbd5d6 100644 --- a/software/application-software/workspaces/hub/workspace.yml +++ b/software/application-software/workspaces/hub/workspace.yml @@ -6,7 +6,7 @@ plugins: # `- [.]`: plugin sets to enable; CI lists opt-out: # `- `: files from the workspace-inherit/ folders above that CI leaves alone here, so the workspace keeps its own copy (or none) # - .claude/skills/journal # .delphi/setup.sh checks out everything below (git-ignored); re-run it after any change. -repos: # `: `: code you change; a clone at repos/ (worktrees from it: worktrees//) +repos: # `: `: code you change; a clone at repos/, and separately worktrees// (default branch first) # name: https://github.com/Northeastern-Electric-Racing/.git references-git: # `: []`: dependency sources to read, never edit; shallow copy at references/ # name: https://github.com//.git v1.2.3 # no tag or branch: its default branch diff --git a/software/firmware/calypso/workspaces/calypso-dev/CLAUDE.md b/software/firmware/calypso/workspaces/calypso-dev/CLAUDE.md index 8aa680b..a34b57d 100644 --- a/software/firmware/calypso/workspaces/calypso-dev/CLAUDE.md +++ b/software/firmware/calypso/workspaces/calypso-dev/CLAUDE.md @@ -1,6 +1,6 @@ # Delphi workspace -You're in Delphi workspace `s.firmware.calypso.calypso-dev` (branch `ws/s.firmware.calypso.calypso-dev`); code repos are cloned at `repos//` (`.delphi/setup.sh`), for people working on one thing at a time; leave those clones alone and make new branches as worktrees under `worktrees//` (`new-worktree` skill) unless the user says not to. Read-only dependency sources from `references-git:` are checked out under `references//` at the version the workspace pins: read them to learn how a dependency behaves (prefer them over web docs), and never edit, branch, or push them. Delphi workspaces (or `main`) under `workspaces:` in `workspace.yml` (`- `, a workspace's dotted name like `s.app.argos.argos-dev`, or `- main`) are clones at `workspaces//`, each on its branch, for reading their context (don't edit them from here; setup switches each back to its branch and pulls it); change what's checked out with the `setup-workspace` skill. To work in another workspace, switch this clone to it (`switch-workspace` skill). Park rough notes with the `journal` skill. Work here unless the task clearly belongs to another project. +You're in Delphi workspace `s.firmware.calypso.calypso-dev` (branch `ws/s.firmware.calypso.calypso-dev`); code repos are cloned at `repos//`, and separately checked out as worktrees under `worktrees//` (`.delphi/setup.sh` makes the default branch's). Work in worktrees: make each new branch one under `worktrees//` (`new-worktree` skill) unless the user says not to. Read-only dependency sources from `references-git:` are checked out under `references//` at the version the workspace pins: read them to learn how a dependency behaves (prefer them over web docs), and never edit, branch, or push them. Delphi workspaces (or `main`) under `workspaces:` in `workspace.yml` (`- `, a workspace's dotted name like `s.app.argos.argos-dev`, or `- main`) are clones at `workspaces//`, each on its branch, for reading their context (don't edit them from here; setup switches each back to its branch and pulls it); change what's checked out with the `setup-workspace` skill. To work in another workspace, switch this clone to it (`switch-workspace` skill). Park rough notes with the `journal` skill. Work here unless the task clearly belongs to another project. # Calypso diff --git a/software/firmware/calypso/workspaces/calypso-dev/workspace.yml b/software/firmware/calypso/workspaces/calypso-dev/workspace.yml index 46b614c..ffa5a9f 100644 --- a/software/firmware/calypso/workspaces/calypso-dev/workspace.yml +++ b/software/firmware/calypso/workspaces/calypso-dev/workspace.yml @@ -5,7 +5,7 @@ plugins: # `- [.]`: plugin sets to enable; CI lists opt-out: # `- `: files from the workspace-inherit/ folders above that CI leaves alone here, so the workspace keeps its own copy (or none) # - .claude/skills/journal # .delphi/setup.sh checks out everything below (git-ignored); re-run it after any change. -repos: # `: `: code you change; a clone at repos/ (worktrees from it: worktrees//) +repos: # `: `: code you change; a clone at repos/, and separately worktrees// (default branch first) calypso: https://github.com/Northeastern-Electric-Racing/Calypso.git workspaces: # `- ` (dotted) or `- main`: Delphi workspaces to read, not edit from here; a clone on its branch at workspaces/ - main diff --git a/software/workspace-inherit/.claude/skills/new-worktree/SKILL.md b/software/workspace-inherit/.claude/skills/new-worktree/SKILL.md index 9ee9e83..0520aa7 100644 --- a/software/workspace-inherit/.claude/skills/new-worktree/SKILL.md +++ b/software/workspace-inherit/.claude/skills/new-worktree/SKILL.md @@ -10,5 +10,6 @@ bash .delphi/new-worktree.sh [] ``` - An existing branch (local or on origin, e.g. a PR head) is checked out; anything else is created from `` (default: the repo's default branch). Safe to re-run. -- Stay at the workspace root and reach into the worktree by the printed path. Never switch branches in an existing worktree or work in the clone `repos/` (it's for people working on one thing at a time). -- After the branch merges: `git -C repos/ worktree remove ../../worktrees//`. +- Stay at the workspace root and reach into the worktree by the printed path. Never switch branches in an existing worktree or work in its store, `worktrees//.bare`. +- Keep `worktrees///` (made by setup) a clean reference: fetch and fast-forward only. +- After the branch merges: `git -C worktrees//.bare worktree remove ../`. diff --git a/templates/workspace/CLAUDE.md b/templates/workspace/CLAUDE.md index 7a5c872..0a9ef97 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 are cloned at `repos//` (`.delphi/setup.sh`), for people working on one thing at a time; leave those clones alone and make new branches as worktrees under `worktrees//` (`new-worktree` skill) unless the user says not to. Read-only dependency sources from `references-git:` are checked out under `references//` at the version the workspace pins: read them to learn how a dependency behaves (prefer them over web docs), and never edit, branch, or push them. Delphi workspaces (or `main`) under `workspaces:` in `workspace.yml` (`- `, a workspace's dotted name like `s.app.argos.argos-dev`, or `- main`) are clones at `workspaces//`, each on its branch, for reading their context (don't edit them from here; setup switches each back to its branch and pulls it); change what's checked out with the `setup-workspace` skill. To work in another workspace, switch this clone to it (`switch-workspace` skill). Park rough notes with the `journal` skill. Work here unless the task clearly belongs to another project. +You're in Delphi workspace `{{name}}` (branch `ws/{{name}}`); code repos are cloned at `repos//`, and separately checked out as worktrees under `worktrees//` (`.delphi/setup.sh` makes the default branch's). Work in worktrees: make each new branch one under `worktrees//` (`new-worktree` skill) unless the user says not to. Read-only dependency sources from `references-git:` are checked out under `references//` at the version the workspace pins: read them to learn how a dependency behaves (prefer them over web docs), and never edit, branch, or push them. Delphi workspaces (or `main`) under `workspaces:` in `workspace.yml` (`- `, a workspace's dotted name like `s.app.argos.argos-dev`, or `- main`) are clones at `workspaces//`, each on its branch, for reading their context (don't edit them from here; setup switches each back to its branch and pulls it); change what's checked out with the `setup-workspace` skill. To work in another workspace, switch this clone to it (`switch-workspace` skill). Park rough notes with the `journal` skill. Work here unless the task clearly belongs to another project. diff --git a/templates/workspace/workspace.yml b/templates/workspace/workspace.yml index 9902780..12f156d 100644 --- a/templates/workspace/workspace.yml +++ b/templates/workspace/workspace.yml @@ -7,7 +7,7 @@ opt-out: # `- `: files from the workspace-inherit/ folder park: # `- `: root folders that park with this workspace on a switch (.delphi/park.sh), besides the ones it always parks # - pictures # .delphi/setup.sh checks out everything below (git-ignored); re-run it after any change. -repos: # `: `: code you change; a clone at repos/ (worktrees from it: worktrees//) +repos: # `: `: code you change; a clone at repos/, and separately worktrees// (default branch first) # name: https://github.com/Northeastern-Electric-Racing/.git references-git: # `: []`: dependency sources to read, never edit; shallow copy at references/ # name: https://github.com//.git v1.2.3 # no tag or branch: its default branch diff --git a/tests/e2e.sh b/tests/e2e.sh index 17940a4..14f3bb5 100644 --- a/tests/e2e.sh +++ b/tests/e2e.sh @@ -331,7 +331,7 @@ expect_bad unknown-workspace "lists nope, which isn't a workspace on main" sh -c expect_bad template-delphi "templates/workspace/.delphi: scripts live only" mkdir templates/workspace/.delphi 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 at repos/ on the default branch, adds no worktree, and is idempotent +# --- 8. setup.sh clones workspace.yml repos at repos/; separately, a bare store and the default branch under worktrees/; 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 @@ -344,21 +344,27 @@ printf 'name: s.app.argos.argos-dev\nharness: claude-code\nplugins:\n - x.y\npa for r in lib1 lib2; do [ "$(git -C "$t/ws/repos/$r" rev-parse HEAD)" = "$(git -C "$t/$r" rev-parse HEAD)" ] || fail "repos/$r not cloned" [ "$(git -C "$t/ws/repos/$r" branch --show-current)" = "$def" ] || fail "repos/$r not on $def" + [ "$(git -C "$t/ws/worktrees/$r/$def" branch --show-current)" = "$def" ] || fail "$r default worktree not on $def" + [ "$(git -C "$t/ws/worktrees/$r/.bare" rev-parse --is-bare-repository)" = true ] || fail "worktrees/$r/.bare not a bare store" + [ "$(git -C "$t/ws/repos/$r" worktree list | wc -l)" = 1 ] || fail "repos/$r has worktrees" done [ "$(git -C "$t/ws/repos/lib1" rev-parse --is-bare-repository)" = false ] || fail "repos/lib1 is a bare store" -[ ! -e "$t/ws/worktrees" ] || fail "setup.sh made worktrees/" grep -q "repos/lib1: already present" "$t/out" || fail "re-run did not skip" for d in /repos/ /worktrees/ /.journal/ /.playwright-mcp/ /pictures/ .DS_Store; do [ "$(grep -cx "$d" "$t/ws/.git/info/exclude")" = 1 ] || fail "$d not excluded exactly once"; done mkdir -p "$t/ws/.playwright-mcp" "$t/ws/pictures/b" "$t/ws/docs" && touch "$t/ws/.playwright-mcp/p.yml" "$t/ws/pictures/b/s.png" "$t/ws/.DS_Store" "$t/ws/docs/.DS_Store" [ "$(git -C "$t/ws" status --porcelain)" = " M workspace.yml" ] || fail "repos/, worktrees/ or a tool's files show in status: $(git -C "$t/ws" status --porcelain)" -rm -rf "$t/ws/repos/lib2" && /bin/bash "$t/ws/.delphi/setup.sh" >/dev/null 2>&1 && - [ -d "$t/ws/repos/lib2/.git" ] || fail "re-run did not restore a missing clone" -ok "setup.sh clones repos: at repos/ on the default branch, adds no worktree, excludes repos/ and worktrees/ (and tools' files) once, and is idempotent" -w=$t/legacy && git clone -q -b ws/$AN "$t/origin.git" "$w" && cp "$t/ws/workspace.yml" "$w/" && git init -q --bare "$w/repos/lib1" -/bin/bash "$w/.delphi/setup.sh" >"$t/out" 2>&1 || fail "setup.sh failed with an older bare store" -{ grep -q "repos/lib1: already present" "$t/out" && [ "$(git -C "$w/repos/lib1" rev-parse --is-bare-repository)" = true ] && - [ -d "$w/repos/lib2/.git" ]; } || fail "older bare store not left as it is" -ok "setup.sh leaves an older clone's bare store in repos/ as it is" +rm -rf "$t/ws/worktrees/lib2/$def" && git -C "$t/ws/worktrees/lib2/.bare" 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 clones repos: at repos/ and, separately, makes a bare store and the default-branch worktree in worktrees//; excludes both (and tools' files) once; idempotent" +w=$t/legacy && git clone -q -b ws/$AN "$t/origin.git" "$w" && cp "$t/ws/workspace.yml" "$w/" && + git clone -q "$t/lib1.git" "$w/repos/lib1" && echo wip >"$w/repos/lib1/wip" && + git init -q --bare "$w/repos/lib2" && git -C "$w/repos/lib2" remote add origin "$t/lib2.git" && git -C "$w/repos/lib2" fetch -q origin && + git -C "$w/repos/lib2" worktree add -q -b "$def" "$w/worktrees/lib2/$def" "origin/$def" && echo wip >"$w/worktrees/lib2/$def/wip" +/bin/bash "$w/.delphi/setup.sh" >"$t/out" 2>&1 || fail "setup.sh failed on older clones: $(cat "$t/out")" +{ [ "$(git -C "$w/repos/lib2" rev-parse --is-bare-repository)" = true ] && [ "$(git -C "$w/worktrees/lib2/$def" status --porcelain)" = "?? wip" ] && + [ -d "$w/worktrees/lib2/.bare" ] && [ "$(git -C "$w/repos/lib1" branch --show-current)" = "$def" ] && [ -f "$w/repos/lib1/wip" ] && + [ -d "$w/worktrees/lib1/$def" ]; } || fail "an older bare store or plain clone in repos/ was not left as it is: $(cat "$t/out")" +ok "setup.sh leaves an older bare store or plain clone in repos/ (and its worktrees) as it is, and adds the store in worktrees/" git clone -q -c core.autocrlf=true -b ws/$AN "$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>"$t/err" || fail "setup.sh fails with core.autocrlf=true" @@ -394,7 +400,8 @@ local_ws() { # : a local branch of git -C "$w" checkout -q -b "$1" "origin/$2" && printf 'name: %s\nharness: claude-code\npark:\n - pictures\nrepos:\n %s: %s\nreferences-git:\n %s-ref: file://%s\nworkspaces:\n - main\n' \ "$3" "$4" "$t/$4.git" "$4" "$t/$4.git" >"$w/workspace.yml" && git -C "$w" commit -qam "$1" && /bin/bash "$w/.delphi/setup.sh" >/dev/null 2>&1 } -live() { [ "$(git -C "$w/repos/$1" branch --show-current 2>/dev/null)" = "$def" ] && git -C "$w/references/$1-ref" rev-parse -q HEAD >/dev/null && +live() { [ "$(git -C "$w/worktrees/$1/$def" branch --show-current 2>/dev/null)" = "$def" ] && + ! git -C "$w/worktrees/$1/.bare" worktree list --porcelain | grep -q prunable && git -C "$w/references/$1-ref" rev-parse -q HEAD >/dev/null && [ -f "$w/workspaces/main/ci/sync.sh" ]; } local_ws a1 "ws/$AN" "$AN" lib1 || fail "park: setup on argos-dev failed" ours() { [ ! -L "$w/.git/hooks/post-checkout" ] && grep -qx '\[ ! -f .delphi/park.sh \] || exec bash .delphi/park.sh "$@"' "$w/.git/hooks/post-checkout"; } @@ -422,7 +429,7 @@ git -C "$w" checkout -q -b a4 a2 && printf 'name: %s\nharness: claude-code\npark { [ -d "$w/.git" ] && grep -q "won't park '.git'" "$t/err" && grep -q "won't park '../up'" "$t/err" && git -C "$w" checkout -q a2 && live lib1; } || fail "park: a bad park: entry was not skipped: $(cat "$t/err")" ok "park.sh skips a park: entry that isn't a folder name at the root (.git, ../x)" -git -C "$w/repos/lib1" commit -q --allow-empty -m "after restore" || fail "park: restored clone unusable" +git -C "$w/worktrees/lib1/$def" commit -q --allow-empty -m "after restore" || fail "park: restored worktree unusable" { git -C "$w" checkout -q b1 && live lib2 && git -C "$w" checkout -q a1 && live lib1; } || fail "park: second round trip" git -C "$w" checkout -q main && mkdir "$w/references" && git -C "$w" checkout -q a1 2>"$t/err" { grep -q "references/ is in the way" "$t/err" && [ -d "$w/repos/lib1" ] && [ -d "$w/.delphi/parked/$AN/references" ]; } || @@ -519,8 +526,10 @@ nw code feat >/dev/null && [ "$(git -C "$w/worktrees/code/feat" rev-parse HEAD)" 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/worktrees/code/feat" ] || fail "rerun did not reuse" -[ "$(git -C "$w/repos/code" branch --show-current)" = "$def" ] || fail "repos/code left its branch" -ok "new-worktree.sh makes new branches from the default or , checks out existing ones, reuses on rerun" +[ "$(git -C "$w/worktrees/code/$def" branch --show-current)" = "$def" ] || fail "default worktree left its branch" +{ [ "$(git -C "$w/repos/code" branch --show-current)" = "$def" ] && [ "$(git -C "$w/repos/code" worktree list | wc -l)" = 1 ] && + ! git -C "$w/repos/code" rev-parse -q --verify refs/heads/1-new >/dev/null; } || fail "new-worktree touched the clone at repos/code" +ok "new-worktree.sh makes new branches from the default or , checks out existing ones, reuses on rerun, never touching repos/" # skill scripts read the workspace from $DELPHI_ROOT (the session hook evals paths.sh's export line) and every folder from paths.sh (. "$w/.delphi/paths.sh" && [ "$DELPHI_ROOT" = "$w" ] && [ "$DELPHI_REPOS" = "$w/repos" ] && [ "$DELPHI_WORKTREES" = "$w/worktrees" ] && [ "$DELPHI_REFERENCES" = "$w/references" ] && [ "$DELPHI_WORKSPACES" = "$w/workspaces" ]) || @@ -585,7 +594,7 @@ printf 'name: s.electrical.bms-dev\nharness: claude-code\nrepos:\n code: %s\nwo printf 'name: %s\nharness: claude-code\nrepos:\n lib1: %s\nworkspaces:\n - main\n - %s\n' "$AN" "$t/lib1.git" "$BN" >workspace.yml && git commit -qam "sandbox repos" && git push -q origin HEAD:ws/$AN) bash "$w/.delphi/setup.sh" >/dev/null 2>"$t/err" || fail "setup.sh failed with links: $(cat "$t/err")" -{ [ -d "$w/workspaces/$AN/repos/lib1/.git" ] && [ ! -e "$w/workspaces/$AN/workspaces" ]; } || +{ [ -d "$w/workspaces/$AN/worktrees/lib1" ] && [ ! -e "$w/workspaces/$AN/workspaces" ]; } || fail "setup.sh did not run a link's own setup, or that setup cloned its own workspaces: $(cat "$t/err")" [ "$(git -C "$w/workspaces/$AN" rev-parse HEAD)" = "$(o rev-parse "ws/$AN")" ] || fail "copy not at ws/$AN" { [ "$(git -C "$w/workspaces/$AN" branch --show-current)" = "ws/$AN" ] && [ "$(git -C "$w/workspaces/main" branch --show-current)" = main ] && @@ -663,7 +672,7 @@ sw $BN >"$t/out" 2>&1 || fail "switch to bms-dev failed: $(cat "$t/out")" commit_on ws/$AN docs/CONTEXT.md "while away" sw "ws/$AN" >"$t/out" 2>&1 || fail "switch back (by branch) failed: $(cat "$t/out")" { [ "$(git -C "$w" branch --show-current)" = ws/$AN ] && grep -qx "while away" "$w/docs/CONTEXT.md" && - [ -d "$w/repos/lib1/.git" ] && [ -f "$w/workspaces/main/ci/sync.sh" ]; } || fail "switch: argos-dev not restored and fast-forwarded" + [ -d "$w/worktrees/lib1/$def" ] && [ -f "$w/workspaces/main/ci/sync.sh" ]; } || fail "switch: argos-dev not restored and fast-forwarded" { sw main >/dev/null 2>&1 && [ "$(git -C "$w" branch --show-current)" = main ] && [ ! -e "$w/repos" ] && [ -f "$w/ci/sync.sh" ]; } || fail "switch to main" git -C "$w" switch -q ws/$AN