From dd0f3959a3e117c454d44a0795a26f5f744db39a Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 14:11:01 +0000 Subject: [PATCH 1/3] Shrink workspace CLAUDE.md Delphi section to one sentence Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LJALeuxPrcwtF3eQmsaZBf --- .../argos/workspaces/argos-dev/CLAUDE.md | 14 +------------- templates/workspace/CLAUDE.md | 14 +------------- 2 files changed, 2 insertions(+), 26 deletions(-) diff --git a/software/application-software/argos/workspaces/argos-dev/CLAUDE.md b/software/application-software/argos/workspaces/argos-dev/CLAUDE.md index 7444991..6e5bd86 100644 --- a/software/application-software/argos/workspaces/argos-dev/CLAUDE.md +++ b/software/application-software/argos/workspaces/argos-dev/CLAUDE.md @@ -1,18 +1,6 @@ # Delphi workspace -This repo root is branch `ws/argos-dev` of Delphi (or a branch cut from it): the folder `software/application-software/argos/workspaces/argos-dev/` on Delphi's `main`, as its own root. It holds two kinds of git repo, and every change belongs to exactly one: - -| Path | What it is | Changes go | -|---|---|---| -| `CLAUDE.md`, `.claude/`, `docs/`, other files here | The workspace | A branch cut from `ws/argos-dev`, then a PR into `ws/argos-dev` | -| `repos//` | A clone of a code repo listed in `workspace.yml` (git-ignored here) | That repo's git, branches, and PRs, per its conventions | - -- First time in a clone: run `.delphi/setup.sh` to clone the repos into `repos/`. -- Run a repo's git, `gh`, build, and test commands inside it (`cd repos/`), never from the root: here, `git` is the workspace branch. -- Code changes never go in the workspace. Context changes (instructions, skills, docs) never go in a code repo. -- Never push to `ws/argos-dev` or Delphi's `main` directly. After your PR merges into `ws/argos-dev`, CI proposes it to `main` as a PR; CI also refreshes `ws/argos-dev` with `main`'s changes. -- Don't edit `.delphi/setup.sh` or `.github/workflows/delphi.yml`; Delphi manages them. -- Update your branch with `git fetch origin && git merge origin/ws/argos-dev`. +You're in Delphi workspace `argos-dev` (branch `ws/argos-dev`); code repos live in `repos/`. Work here unless the user's task clearly belongs to a different project. # NER Software conventions diff --git a/templates/workspace/CLAUDE.md b/templates/workspace/CLAUDE.md index ad7a097..d5adf2a 100644 --- a/templates/workspace/CLAUDE.md +++ b/templates/workspace/CLAUDE.md @@ -1,15 +1,3 @@ # Delphi workspace -This repo root is branch `ws/{{name}}` of Delphi (or a branch cut from it): the folder `{{folder}}/` on Delphi's `main`, as its own root. It holds two kinds of git repo, and every change belongs to exactly one: - -| Path | What it is | Changes go | -|---|---|---| -| `CLAUDE.md`, `.claude/`, `docs/`, other files here | The workspace | A branch cut from `ws/{{name}}`, then a PR into `ws/{{name}}` | -| `repos//` | A clone of a code repo listed in `workspace.yml` (git-ignored here) | That repo's git, branches, and PRs, per its conventions | - -- First time in a clone: run `.delphi/setup.sh` to clone the repos into `repos/`. -- Run a repo's git, `gh`, build, and test commands inside it (`cd repos/`), never from the root: here, `git` is the workspace branch. -- Code changes never go in the workspace. Context changes (instructions, skills, docs) never go in a code repo. -- Never push to `ws/{{name}}` or Delphi's `main` directly. After your PR merges into `ws/{{name}}`, CI proposes it to `main` as a PR; CI also refreshes `ws/{{name}}` with `main`'s changes. -- Don't edit `.delphi/setup.sh` or `.github/workflows/delphi.yml`; Delphi manages them. -- Update your branch with `git fetch origin && git merge origin/ws/{{name}}`. +You're in Delphi workspace `{{name}}` (branch `ws/{{name}}`); code repos live in `repos/`. Work here unless the user's task clearly belongs to a different project. From 00a787ad54eaf197ae1b67595dfd1439bd530253 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 14:14:26 +0000 Subject: [PATCH 2/3] Worktrees as the default way to branch: managed .delphi/new-worktree.sh + template skill Generalizes argos-dev's new-worktree script ( []) into a managed file checked by ci/check.sh, adds a default new-worktree skill to the template, and states in each workspace's Delphi section that new branches are worktrees unless the user says otherwise. argos-dev passes origin/develop as its base. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LJALeuxPrcwtF3eQmsaZBf --- CLAUDE.md | 7 ++++--- README.md | 4 ++-- ci/check.sh | 4 ++-- docs/design.md | 8 ++++++-- .../.claude/skills/implement/SKILL.md | 2 +- .../.claude/skills/new-worktree/SKILL.md | 12 ++++++++---- .../new-worktree/scripts/new-worktree.sh | 17 ----------------- .../argos-dev/.delphi/new-worktree.sh | 17 +++++++++++++++++ .../argos/workspaces/argos-dev/CLAUDE.md | 5 ++--- .../.claude/skills/new-worktree/SKILL.md | 16 ++++++++++++++++ templates/workspace/.delphi/new-worktree.sh | 17 +++++++++++++++++ templates/workspace/CLAUDE.md | 2 +- tests/e2e.sh | 19 +++++++++++++++++-- 13 files changed, 93 insertions(+), 37 deletions(-) delete mode 100644 software/application-software/argos/workspaces/argos-dev/.claude/skills/new-worktree/scripts/new-worktree.sh create mode 100755 software/application-software/argos/workspaces/argos-dev/.delphi/new-worktree.sh create mode 100644 templates/workspace/.claude/skills/new-worktree/SKILL.md create mode 100755 templates/workspace/.delphi/new-worktree.sh diff --git a/CLAUDE.md b/CLAUDE.md index 2646573..4b72ce0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,7 +13,8 @@ source of truth; `docs/goals.md` lists what any change must keep. - `ci/check.sh []`: validates the repo. Runs on PRs to main and on each proposal. - `tools/new-workspace.sh`: new workspace folder from `templates/workspace/` as a PR. - `templates/workspace/`: `workspace.yml`, `CLAUDE.md` (`{{name}}`, `{{folder}}`), and the managed - files every workspace carries unchanged: `.delphi/setup.sh`, `.github/workflows/delphi.yml`. + files every workspace carries unchanged: `.delphi/setup.sh`, `.delphi/new-worktree.sh`, + `.github/workflows/delphi.yml`; and a default `new-worktree` skill. - `software/…/workspaces//`: the workspaces. Org structure is plain directories. - `.github/workflows/delphi.yml` (identical to the template's copy), `.github/CODEOWNERS`. - `tests/e2e.sh`: end-to-end test in a throwaway sandbox (bare origin, stub `gh` logging to `gh.log`). @@ -23,9 +24,9 @@ source of truth; `docs/goals.md` lists what any change must keep. - Keep it small: fewest lines that implement the design; a short header comment per script. Prefer deleting to adapting. Runtime tools: bash, git, and `gh` (only to open or update PRs). - Every script: `#!/usr/bin/env bash`, `set -euo pipefail`, `shellcheck`-clean. -- `.delphi/setup.sh` runs on people's machines: bash 3.2 (macOS) and Git Bash safe. No bash-4 +- `.delphi/*.sh` run on people's machines: bash 3.2 (macOS) and Git Bash safe. No bash-4 features (associative arrays, `mapfile`, `${x,,}`, `|&`), POSIX awk only. -- Changing a managed file (`setup.sh`, the workflow): update the template, `.github/`, and every +- Changing a managed file (`.delphi/*.sh`, the workflow): update the template, `.github/`, and every workspace copy in the same PR, or `ci/check.sh` fails. - `bash tests/e2e.sh` and `ci/check.sh` must pass. Add a test there for every behavior change. - Never test against real GitHub repos or this checkout's origin; use the sandbox in `tests/e2e.sh`. diff --git a/README.md b/README.md index a87ae5a..ee4efdc 100644 --- a/README.md +++ b/README.md @@ -62,8 +62,8 @@ repos: # cloned into repos/ by .delphi/setup.sh ``` The workspace's name is its folder name (unique repo-wide). Every other file in the folder is the -workspace's own, at its normal harness path, except two Delphi manages: `.delphi/setup.sh` and -`.github/workflows/delphi.yml` (copies of the template's). +workspace's own, at its normal harness path, except three Delphi manages: `.delphi/setup.sh`, +`.delphi/new-worktree.sh`, and `.github/workflows/delphi.yml` (copies of the template's). ## New workspace diff --git a/ci/check.sh b/ci/check.sh index 3f2c498..51aaaba 100755 --- a/ci/check.sh +++ b/ci/check.sh @@ -2,7 +2,7 @@ # ci/check.sh []: validate Delphi (CI runs it on PRs to main; sync.sh on each proposal). Rules: # a workspace is software/**/workspaces// with a workspace.yml; names are lowercase letters, # digits, and '-', unique repo-wide; workspaces never nest; no symlinks under software/; each -# workspace's .delphi/setup.sh and .github/workflows/delphi.yml match templates/workspace/, and the +# workspace's .delphi/*.sh and .github/workflows/delphi.yml match templates/workspace/, and the # template workflow matches .github/workflows/delphi.yml; workspace.yml is `harness: ` plus # an optional `repos:` map of `: `. Lists every problem; exits 1 if any. set -euo pipefail @@ -10,7 +10,7 @@ cd "${1:-.}" problems=0 bad() { echo "check: $*" >&2 && problems=$((problems + 1)); } tpl=templates/workspace -managed=".delphi/setup.sh .github/workflows/delphi.yml" +managed=".delphi/setup.sh .delphi/new-worktree.sh .github/workflows/delphi.yml" [ -d software ] || bad "software/ is missing" cmp -s .github/workflows/delphi.yml $tpl/.github/workflows/delphi.yml || diff --git a/docs/design.md b/docs/design.md index 617b962..7a810c0 100644 --- a/docs/design.md +++ b/docs/design.md @@ -40,7 +40,7 @@ in normal three-way merges. Nothing is shared or generated between workspaces. ``` software//…/workspaces// workspace.yml CLAUDE.md .claude/… docs/… - .delphi/setup.sh .github/workflows/delphi.yml .gitattributes + .delphi/setup.sh .delphi/new-worktree.sh .github/workflows/delphi.yml .gitattributes ci/sync.sh ci/check.sh CI scripts tools/new-workspace.sh new workspace as a PR templates/workspace/ what new-workspace copies @@ -51,7 +51,7 @@ tests/e2e.sh sandbox test of all of the above Org folders under `software/` are plain directories. `workspace.yml` is tiny YAML: `harness: ` and an optional `repos:` map of `: `. The name is the folder name: lowercase letters, digits, `-`; unique repo-wide. Workspaces never nest; no symlinks under -`software/`. Each workspace's `.delphi/setup.sh` and `.github/workflows/delphi.yml` equal the +`software/`. Each workspace's `.delphi/setup.sh`, `.delphi/new-worktree.sh`, and `.github/workflows/delphi.yml` equal the template's, and the template's workflow equals main's. `ci/check.sh` enforces all of this and lists every problem. `.gitattributes` (`eol=lf`) keeps `setup.sh` runnable in Git Bash clones. `.github/CODEOWNERS` assigns reviewers per org folder. @@ -91,6 +91,10 @@ Reads `repos:` from `workspace.yml`, clones each into `repos/` unless pres `/repos/` to the clone's `.git/info/exclude` once. Nothing else. 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. +The template's `new-worktree` skill makes worktrees the default way to start a branch. + ## 6. New workspaces (`tools/new-workspace.sh `) Validates the name (format, not on main, no leftover `ws/`), copies `templates/workspace/` 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 fdaaf35..d456f78 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//` (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 `repos/worktrees/argos//` (never in `repos/argos/`). 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 61fa71b..49d2459 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,14 +1,18 @@ --- name: new-worktree -description: Create or reuse the worktree for a branch in this Delphi workspace (repos/worktrees/), checking out an existing branch or starting a new one from origin/develop. Use before starting a ticket, reviewing or fixing a PR branch, or whenever work needs its own checkout. +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. --- +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. + Run from the workspace root: ``` -bash .claude/skills/new-worktree/scripts/new-worktree.sh +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 `origin/develop`. New ticket branches follow `{issue-number}-{kebab-case-title}`. It's safe to re-run. +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//`. -It prints the worktree path: `cd` there and do all work in it. Run `npm ci` in its `angular-client/` before building, testing, or running the client. +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/.claude/skills/new-worktree/scripts/new-worktree.sh b/software/application-software/argos/workspaces/argos-dev/.claude/skills/new-worktree/scripts/new-worktree.sh deleted file mode 100644 index ed6531f..0000000 --- a/software/application-software/argos/workspaces/argos-dev/.claude/skills/new-worktree/scripts/new-worktree.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash -# new-worktree.sh — create or reuse repos/worktrees/ for repos/argos. Prints the path. -# An existing branch (local or on origin) is checked out; a new one starts from origin/develop. -set -euo pipefail - -root=$(cd "$(dirname "$0")/../../../.." && pwd) -branch=${1:?usage: new-worktree.sh } -repo="$root/repos/argos" dest="$root/repos/worktrees/$branch" - -git -C "$repo" fetch -q origin -if [ -d "$dest" ]; then : -elif git -C "$repo" rev-parse -q --verify "$branch" > /dev/null || git -C "$repo" rev-parse -q --verify "origin/$branch" > /dev/null; then - git -C "$repo" worktree add -q "$dest" "$branch" -else - git -C "$repo" worktree add -q --no-track -b "$branch" "$dest" origin/develop -fi -echo "$dest" 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 new file mode 100755 index 0000000..b4f856b --- /dev/null +++ b/software/application-software/argos/workspaces/argos-dev/.delphi/new-worktree.sh @@ -0,0 +1,17 @@ +#!/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. +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" +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 + g worktree add -q "$dest" "$branch" +else + g worktree add -q --no-track -b "$branch" "$dest" "$base" +fi +echo "$dest" diff --git a/software/application-software/argos/workspaces/argos-dev/CLAUDE.md b/software/application-software/argos/workspaces/argos-dev/CLAUDE.md index 6e5bd86..e3fabc6 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/`. Work here unless the user's task clearly belongs to a different project. +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. # NER Software conventions @@ -24,9 +24,8 @@ The Argos repo is checked out at `repos/argos/`. Paths below are relative to a c ## 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//`, and every workflow (implement, test, run, commit, PR) runs there. Create or reuse one with the `new-worktree` skill; it handles new and existing branches. +- 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`. - A new worktree has no `node_modules`: run `npm ci` in its `angular-client/` before testing or running the client. -- After the PR merges, remove it with `git -C repos/argos worktree remove ../worktrees/`. ## Local Development diff --git a/templates/workspace/.claude/skills/new-worktree/SKILL.md b/templates/workspace/.claude/skills/new-worktree/SKILL.md new file mode 100644 index 0000000..3bbd319 --- /dev/null +++ b/templates/workspace/.claude/skills/new-worktree/SKILL.md @@ -0,0 +1,16 @@ +--- +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. +--- + +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. + +Run from the workspace root: + +``` +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//`. diff --git a/templates/workspace/.delphi/new-worktree.sh b/templates/workspace/.delphi/new-worktree.sh new file mode 100755 index 0000000..b4f856b --- /dev/null +++ b/templates/workspace/.delphi/new-worktree.sh @@ -0,0 +1,17 @@ +#!/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. +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" +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 + g worktree add -q "$dest" "$branch" +else + g worktree add -q --no-track -b "$branch" "$dest" "$base" +fi +echo "$dest" diff --git a/templates/workspace/CLAUDE.md b/templates/workspace/CLAUDE.md index d5adf2a..7caacfe 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/`. Work here unless the user's task clearly belongs to a different project. +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. diff --git a/tests/e2e.sh b/tests/e2e.sh index 33962a7..aa821b3 100755 --- a/tests/e2e.sh +++ b/tests/e2e.sh @@ -214,9 +214,24 @@ grep -q "ws/argos-dev: check failed" "$t/err" || fail "check failure not reporte [ "$(o rev-parse propose/argos-dev)" = "$prop_before" ] || fail "failing proposal was pushed" ok "a proposal failing check.sh is reported and not pushed; exit 1" -# --- 11. shellcheck, if installed +# --- 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" +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)" ] || + 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)" ] || + 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" +ok "new-worktree.sh makes new branches from the default or , checks out existing ones, reuses on rerun" + +# --- 12. shellcheck, if installed if command -v shellcheck >/dev/null; then - shellcheck "$src"/ci/*.sh "$src"/tools/*.sh "$src"/tests/*.sh "$src"/templates/workspace/.delphi/setup.sh || + shellcheck "$src"/ci/*.sh "$src"/tools/*.sh "$src"/tests/*.sh "$src"/templates/workspace/.delphi/*.sh || fail "shellcheck" ok "shellcheck clean" fi From 071f710470eb81575e05054bd15bc9213821e9e0 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 14:26:53 +0000 Subject: [PATCH 3/3] Project folders: link-workspace, shared context, authoring guide, defaults - .delphi/link.sh (managed) + link-workspace skill: check out another workspace or Delphi's main as a detached worktree under linked/ for reference. - Project folder (the one holding workspaces/) may carry README.md (shared context), AUTHORING.md (how to work on its workspaces), and defaults/ (files new workspaces start with). new-workspace.sh overlays defaults/, appending its CLAUDE.md; check.sh validates defaults/workspace.yml as a manifest. - Argos gets all three. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LJALeuxPrcwtF3eQmsaZBf --- CLAUDE.md | 7 ++- README.md | 13 ++-- ci/check.sh | 43 +++++++------ docs/design.md | 18 +++++- .../application-software/argos/AUTHORING.md | 31 ++++++++++ software/application-software/argos/README.md | 18 ++++++ .../.claude/skills/code-review/SKILL.md | 12 ++++ .../defaults/.claude/skills/commit/SKILL.md | 6 ++ .../.claude/skills/new-worktree/SKILL.md | 18 ++++++ .../defaults/.claude/skills/open-pr/SKILL.md | 7 +++ .../.claude/skills/update-pr/SKILL.md | 7 +++ .../argos/defaults/CLAUDE.md | 60 +++++++++++++++++++ .../argos/defaults/workspace.yml | 3 + .../.claude/skills/link-workspace/SKILL.md | 16 +++++ .../workspaces/argos-dev/.delphi/link.sh | 22 +++++++ .../.claude/skills/link-workspace/SKILL.md | 16 +++++ templates/workspace/.delphi/link.sh | 22 +++++++ tests/e2e.sh | 30 +++++++++- tools/new-workspace.sh | 13 +++- 19 files changed, 330 insertions(+), 32 deletions(-) create mode 100644 software/application-software/argos/AUTHORING.md create mode 100644 software/application-software/argos/README.md create mode 100644 software/application-software/argos/defaults/.claude/skills/code-review/SKILL.md create mode 100644 software/application-software/argos/defaults/.claude/skills/commit/SKILL.md create mode 100644 software/application-software/argos/defaults/.claude/skills/new-worktree/SKILL.md create mode 100644 software/application-software/argos/defaults/.claude/skills/open-pr/SKILL.md create mode 100644 software/application-software/argos/defaults/.claude/skills/update-pr/SKILL.md create mode 100644 software/application-software/argos/defaults/CLAUDE.md create mode 100644 software/application-software/argos/defaults/workspace.yml create mode 100644 software/application-software/argos/workspaces/argos-dev/.claude/skills/link-workspace/SKILL.md create mode 100755 software/application-software/argos/workspaces/argos-dev/.delphi/link.sh create mode 100644 templates/workspace/.claude/skills/link-workspace/SKILL.md create mode 100755 templates/workspace/.delphi/link.sh diff --git a/CLAUDE.md b/CLAUDE.md index 4b72ce0..1f7bdde 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,9 +13,10 @@ source of truth; `docs/goals.md` lists what any change must keep. - `ci/check.sh []`: validates the repo. Runs on PRs to main and on each proposal. - `tools/new-workspace.sh`: new workspace folder from `templates/workspace/` as a PR. - `templates/workspace/`: `workspace.yml`, `CLAUDE.md` (`{{name}}`, `{{folder}}`), and the managed - files every workspace carries unchanged: `.delphi/setup.sh`, `.delphi/new-worktree.sh`, - `.github/workflows/delphi.yml`; and a default `new-worktree` skill. -- `software/…/workspaces//`: the workspaces. Org structure is plain directories. + files every workspace carries unchanged: `.delphi/{setup,new-worktree,link}.sh`, + `.github/workflows/delphi.yml`; and default `new-worktree` and `link-workspace` skills. +- `software/…/workspaces//`: the workspaces. Org structure is plain directories. A project + folder (the one holding `workspaces/`) may add `README.md`, `AUTHORING.md`, and `defaults/`. - `.github/workflows/delphi.yml` (identical to the template's copy), `.github/CODEOWNERS`. - `tests/e2e.sh`: end-to-end test in a throwaway sandbox (bare origin, stub `gh` logging to `gh.log`). diff --git a/README.md b/README.md index ee4efdc..b00d4d8 100644 --- a/README.md +++ b/README.md @@ -62,14 +62,19 @@ repos: # cloned into repos/ by .delphi/setup.sh ``` The workspace's name is its folder name (unique repo-wide). Every other file in the folder is the -workspace's own, at its normal harness path, except three Delphi manages: `.delphi/setup.sh`, -`.delphi/new-worktree.sh`, and `.github/workflows/delphi.yml` (copies of the template's). +workspace's own, at its normal harness path, except the ones Delphi manages: `.delphi/*.sh` (`setup.sh`, +`new-worktree.sh`, `link.sh`) and `.github/workflows/delphi.yml` (copies of the template's). ## New workspace `tools/new-workspace.sh ` copies `templates/workspace/` into -`software//workspaces//` and opens a PR to `main`. Once it merges, CI creates -`ws/`. +`software//workspaces//`, adds the project's `defaults/` if it has one, and opens a +PR to `main`. Once it merges, CI creates `ws/`. + +**Project folders.** The folder above `workspaces/` (e.g. `software/application-software/argos/`) +can hold shared context (`README.md`), a guide to its workspaces (`AUTHORING.md`), and `defaults/`. +From any workspace, `.delphi/link.sh main` checks out `main` at `linked/main/` to read them, and +`.delphi/link.sh ` checks out another workspace at `linked//`. ## CI (`.github/workflows/delphi.yml`) diff --git a/ci/check.sh b/ci/check.sh index 51aaaba..0d1f66e 100755 --- a/ci/check.sh +++ b/ci/check.sh @@ -3,14 +3,15 @@ # a workspace is software/**/workspaces// with a workspace.yml; names are lowercase letters, # digits, and '-', unique repo-wide; workspaces never nest; no symlinks under software/; each # workspace's .delphi/*.sh and .github/workflows/delphi.yml match templates/workspace/, and the -# template workflow matches .github/workflows/delphi.yml; workspace.yml is `harness: ` plus -# an optional `repos:` map of `: `. Lists every problem; exits 1 if any. +# template workflow matches .github/workflows/delphi.yml; workspace.yml (and a project's +# defaults/workspace.yml) is `harness: ` plus an optional `repos:` map of `: `. +# Lists every problem; exits 1 if any. set -euo pipefail cd "${1:-.}" problems=0 bad() { echo "check: $*" >&2 && problems=$((problems + 1)); } tpl=templates/workspace -managed=".delphi/setup.sh .delphi/new-worktree.sh .github/workflows/delphi.yml" +managed=".delphi/setup.sh .delphi/new-worktree.sh .delphi/link.sh .github/workflows/delphi.yml" [ -d software ] || bad "software/ is missing" cmp -s .github/workflows/delphi.yml $tpl/.github/workflows/delphi.yml || @@ -18,6 +19,23 @@ cmp -s .github/workflows/delphi.yml $tpl/.github/workflows/delphi.yml || while IFS= read -r f; do bad "$f: symlinks are not allowed under software/"; done \ < <(find software -type l 2>/dev/null) +manifest() { + while IFS= read -r msg; do bad "$1: $msg"; done < <(awk ' + /^[[:space:]]*#/ || /^[[:space:]]*$/ { next } + /^harness:/ { v = $0; sub(/^harness:[[:space:]]*/, "", v); sub(/[[:space:]]*#.*$/, "", v) + if (v == "") print "harness is empty"; if (h++) print "duplicate harness"; r = 0; next } + /^repos:[[:space:]]*(#.*)?$/ { r = 1; next } + r && /^[[:space:]]/ { + if ($0 !~ /^[[:space:]]+[A-Za-z0-9._-]+:[[:space:]]+[^[:space:]"#]+[[:space:]]*(#.*)?$/) { + print "bad repos entry (want ` : `): " $0; next } + n = $1; sub(/:$/, "", n) + if (n == "." || n == "..") print "bad repo name: " n + if (seen[n]++) print "duplicate repo: " n + next } + { print "unexpected line: " $0 } + END { if (!h) print "harness is missing" }' "$1") +} + folders="" while IFS= read -r f; do folder=${f%/workspace.yml} @@ -34,21 +52,10 @@ while IFS= read -r f; do done folders="$folders $folder" for m in $managed; do cmp -s "$tpl/$m" "$folder/$m" || bad "$folder/$m: differs from $tpl/$m"; done - while IFS= read -r msg; do bad "$f: $msg"; done < <(awk ' - /^[[:space:]]*#/ || /^[[:space:]]*$/ { next } - /^harness:/ { v = $0; sub(/^harness:[[:space:]]*/, "", v); sub(/[[:space:]]*#.*$/, "", v) - if (v == "") print "harness is empty"; if (h++) print "duplicate harness"; r = 0; next } - /^repos:[[:space:]]*(#.*)?$/ { r = 1; next } - r && /^[[:space:]]/ { - if ($0 !~ /^[[:space:]]+[A-Za-z0-9._-]+:[[:space:]]+[^[:space:]"#]+[[:space:]]*(#.*)?$/) { - print "bad repos entry (want ` : `): " $0; next } - n = $1; sub(/:$/, "", n) - if (n == "." || n == "..") print "bad repo name: " n - if (seen[n]++) print "duplicate repo: " n - next } - { print "unexpected line: " $0 } - END { if (!h) print "harness is missing" }' "$f") -done < <(find software -name workspace.yml -type f 2>/dev/null | sort) + manifest "$f" +done < <(find software -name workspace.yml -type f \( ! -path '*/defaults/workspace.yml' -o -path '*/workspaces/defaults/*' \) 2>/dev/null | sort) +while IFS= read -r f; do manifest "$f"; done \ + < <(find software -path '*/defaults/workspace.yml' ! -path '*/workspaces/defaults/*' -type f 2>/dev/null) [ "$problems" = 0 ] || { echo "check: $problems problem(s)" >&2 && exit 1; } echo "check: ok" diff --git a/docs/design.md b/docs/design.md index 7a810c0..c8512c9 100644 --- a/docs/design.md +++ b/docs/design.md @@ -8,7 +8,8 @@ Goals: `docs/goals.md`. No CLI: plain git plus a few shell scripts run by GitHub normal harness paths (`CLAUDE.md`, `.claude/…`, `docs/…`). For each workspace, branch **`ws/`** has **that folder as its repo root**. Two directions keep them in sync, both subtree merges (`git merge -Xsubtree=`), so the histories stay joined and edits on either side meet -in normal three-way merges. Nothing is shared or generated between workspaces. +in normal three-way merges. Nothing is shared or generated between workspaces: a project's +`defaults/` is copied once at creation, and `link.sh` only checks out other branches to read. ``` ┌───────────────────────────────┐ @@ -39,8 +40,9 @@ in normal three-way merges. Nothing is shared or generated between workspaces. ## 2. Repository (main) ``` +software//…// README.md AUTHORING.md defaults/ (optional, main only) software//…/workspaces// workspace.yml CLAUDE.md .claude/… docs/… - .delphi/setup.sh .delphi/new-worktree.sh .github/workflows/delphi.yml .gitattributes + .delphi/{setup,new-worktree,link}.sh .github/workflows/delphi.yml .gitattributes ci/sync.sh ci/check.sh CI scripts tools/new-workspace.sh new workspace as a PR templates/workspace/ what new-workspace copies @@ -51,11 +53,16 @@ tests/e2e.sh sandbox test of all of the above Org folders under `software/` are plain directories. `workspace.yml` is tiny YAML: `harness: ` and an optional `repos:` map of `: `. The name is the folder name: lowercase letters, digits, `-`; unique repo-wide. Workspaces never nest; no symlinks under -`software/`. Each workspace's `.delphi/setup.sh`, `.delphi/new-worktree.sh`, and `.github/workflows/delphi.yml` equal the +`software/`. Each workspace's `.delphi/*.sh` and `.github/workflows/delphi.yml` equal the template's, and the template's workflow equals main's. `ci/check.sh` enforces all of this and lists every problem. `.gitattributes` (`eol=lf`) keeps `setup.sh` runnable in Git Bash clones. `.github/CODEOWNERS` assigns reviewers per org folder. +The folder holding a `workspaces/` directory is a project folder. It may hold shared context +(`README.md`), a guide to its workspaces (`AUTHORING.md`), and `defaults/`: files a new workspace +there starts with. These live only on `main`; workspaces reach them with `link.sh main`. A +`defaults/workspace.yml` is checked as a manifest but is not a workspace. + ## 3. Sync (`ci/sync.sh [refresh|propose] []`; no direction = both) For every workspace on `origin/main` (or just ``): @@ -95,9 +102,14 @@ and Git Bash: no bash-4 features, POSIX awk only. 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 +detached worktree at `linked//`, updates it on re-runs unless it's on a branch, and adds +`/linked/` to `.git/info/exclude`. The template's `link-workspace` skill explains it. + ## 6. New workspaces (`tools/new-workspace.sh `) Validates the name (format, not on main, no leftover `ws/`), copies `templates/workspace/` +and then `software//defaults/` if present (its `CLAUDE.md` appended to the template's) into `software//workspaces//` (filling `{{name}}`/`{{folder}}` in `CLAUDE.md`) in a temporary worktree, runs `ci/check.sh`, pushes branch `new-workspace/`, and opens a PR. Sync creates `ws/` once it merges. diff --git a/software/application-software/argos/AUTHORING.md b/software/application-software/argos/AUTHORING.md new file mode 100644 index 0000000..f7ebb65 --- /dev/null +++ b/software/application-software/argos/AUTHORING.md @@ -0,0 +1,31 @@ +# Working on Argos workspaces + +A workspace is context (instructions, skills, docs), never code. Delphi's `docs/design.md` has the +full model; this is the Argos-specific short version. + +## Change an existing workspace + +1. Check out `ws/` (or `bash .delphi/link.sh ` from another workspace, then + `git -C linked/ switch -c `). +2. Edit at normal harness paths: `CLAUDE.md`, `.claude/skills//SKILL.md`, `docs/`. +3. Push the branch and open a PR into `ws/`. After it merges, CI proposes it to `main`. + +Don't edit `.delphi/*.sh` or `.github/workflows/delphi.yml`: Delphi manages them. + +## Create a new Argos workspace + +From a Delphi checkout: `tools/new-workspace.sh application-software/argos `. It copies +Delphi's template, then `defaults/` (its `CLAUDE.md` is appended after the Delphi section), and opens +a PR to `main`. Trim what the new workspace doesn't need before merging. + +## Change the defaults or this folder + +Edit on a branch of `main` and open a PR to `main` (from a workspace: `linked/main/`). Defaults only +affect workspaces created afterwards; to change existing ones, PR each workspace too. + +## Writing good workspace context + +- Keep the Delphi section of `CLAUDE.md` as generated; put project rules below it. +- One skill per repeatable workflow, with a `description` that says when to use it. +- Durable facts (glossary, ADRs) go in `docs/`; avoid file paths and code in specs and tickets. +- Make new branches as worktrees (`new-worktree` skill) unless told otherwise. diff --git a/software/application-software/argos/README.md b/software/application-software/argos/README.md new file mode 100644 index 0000000..826e3ad --- /dev/null +++ b/software/application-software/argos/README.md @@ -0,0 +1,18 @@ +# Argos (project) + +Shared context for every Argos workspace. Workspaces read it with `bash .delphi/link.sh main` +(the `link-workspace` skill), at `linked/main/software/application-software/argos/`. + +Argos is NER's real-time telemetry platform: an Angular 19 frontend (`angular-client/`) and a Rust +backend (`scylla-server/`), with schema tooling in `charybdis/` and MQTT broker config in +`siren-base/`. Code: https://github.com/Northeastern-Electric-Racing/Argos (base branch `develop`). + +| Path | What it is | +|---|---| +| `workspaces//` | One workspace per purpose; branch `ws/` is its root | +| `workspaces/argos-dev/` | Day-to-day Argos development: tickets, specs, PRs | +| `defaults/` | What a new Argos workspace starts with (see `AUTHORING.md`) | +| `AUTHORING.md` | How to create and change Argos workspaces | + +Shared facts that every Argos workspace needs belong here or in `defaults/`. Facts that one +workspace needs belong in that workspace. diff --git a/software/application-software/argos/defaults/.claude/skills/code-review/SKILL.md b/software/application-software/argos/defaults/.claude/skills/code-review/SKILL.md new file mode 100644 index 0000000..bc9952f --- /dev/null +++ b/software/application-software/argos/defaults/.claude/skills/code-review/SKILL.md @@ -0,0 +1,12 @@ +--- +name: code-review +description: Review the changes since a fixed point (commit, branch, tag, or merge-base) on two axes, Standards and Spec, in parallel sub-agents. Use when the user wants to review a branch, a PR, work-in-progress changes, or asks to "review since X". +--- + +1. **Fixed point.** Use the one the user gives, or ask. Confirm `git rev-parse ` resolves and `git diff ...HEAD` is non-empty. +2. **Spec.** Use a path the user passes, else the issue referenced in the commits, else `docs/spec/`, else ask. If there is none, skip the Spec axis. +3. **Standards.** Read `CLAUDE.md`, plus `angular-client/CLAUDE.md` and `scylla-server/CLAUDE.md` for the parts the diff touches. +4. **Two parallel `general-purpose` sub-agents**, each given the diff command and commit list, and each asked to stay under 400 words: + - **Standards** (also give it the standards files): cite each violated rule by file. Also flag Fowler code smells (duplication, feature envy, speculative generality, unclear names, …) as judgement calls. Documented rules override smells. Skip anything prettier, eslint or clippy enforces. + - **Spec** (also give it the spec): missing or partial requirements, scope creep, and wrong implementations, quoting the spec line for each. +5. **Report** under `## Standards` and `## Spec`, unmerged, and end with the finding count and worst issue per axis. diff --git a/software/application-software/argos/defaults/.claude/skills/commit/SKILL.md b/software/application-software/argos/defaults/.claude/skills/commit/SKILL.md new file mode 100644 index 0000000..8fa13d0 --- /dev/null +++ b/software/application-software/argos/defaults/.claude/skills/commit/SKILL.md @@ -0,0 +1,6 @@ +--- +name: commit +description: Stage and commit using this repo's commit message convention +--- + +Stage the relevant changes (not unrelated or generated files) and commit using the commit message format in CLAUDE.md. Keep the description imperative and 2–8 words. 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 new file mode 100644 index 0000000..49d2459 --- /dev/null +++ b/software/application-software/argos/defaults/.claude/skills/new-worktree/SKILL.md @@ -0,0 +1,18 @@ +--- +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. +--- + +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. + +Run from the workspace root: + +``` +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//`. + +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/skills/open-pr/SKILL.md b/software/application-software/argos/defaults/.claude/skills/open-pr/SKILL.md new file mode 100644 index 0000000..738d40d --- /dev/null +++ b/software/application-software/argos/defaults/.claude/skills/open-pr/SKILL.md @@ -0,0 +1,7 @@ +--- +name: open-pr +description: Run pre-PR checks, push the branch, and open a draft pull request +--- +Stop if the working tree is dirty. Check the commits since `develop` follow the commit format, lint the frontend if it changed, and check that `origin/develop` merges without conflicts. Then push and run `gh pr create --draft --base develop --head --title "#{ticket} title" --body-file /tmp/-pr-body.md --assignee @me`, and report the URL. + +**PR body:** fill `.github/pull_request_template.md` from the diff. Changes gets 1–3 dense sentences on what landed and the key design choice, with no filler. Remove sections that don't apply, check off the Checklist, and end with `Closes #{ticket}`. For UI changes put `_screenshot pending_` and remind the user to drag-drop screenshots from `pictures//`. Write it to `/tmp/-pr-body.md`. diff --git a/software/application-software/argos/defaults/.claude/skills/update-pr/SKILL.md b/software/application-software/argos/defaults/.claude/skills/update-pr/SKILL.md new file mode 100644 index 0000000..392ab6f --- /dev/null +++ b/software/application-software/argos/defaults/.claude/skills/update-pr/SKILL.md @@ -0,0 +1,7 @@ +--- +name: update-pr +description: Update the current branch's PR description to reflect the latest changes +--- +Rewrite the current branch's PR body (`gh pr view`) to match `git diff develop...HEAD`. Keep human-written text that's still accurate, `user-attachments` screenshots, and `Closes`/`Fixes` refs. Apply it with `gh pr edit --body-file`. + +**PR body:** fill `.github/pull_request_template.md` from the diff. Changes gets 1–3 dense sentences on what landed and the key design choice, with no filler. Remove sections that don't apply, check off the Checklist, and end with `Closes #{ticket}`. For UI changes put `_screenshot pending_` and remind the user to drag-drop screenshots from `pictures//`. Write it to `/tmp/-pr-body.md`. diff --git a/software/application-software/argos/defaults/CLAUDE.md b/software/application-software/argos/defaults/CLAUDE.md new file mode 100644 index 0000000..8d987d8 --- /dev/null +++ b/software/application-software/argos/defaults/CLAUDE.md @@ -0,0 +1,60 @@ +# NER Software conventions + +## Branch & Commit Conventions + +- Branch from `develop` (not `main`) unless told otherwise. Branch name format: `{issue-number}-{kebab-case-title}` (e.g. `533-csv-upload-download-rules`). +- Commit message format: `#{ticket-number} - {concise description}` (e.g. `#533 - add CSV upload endpoint`). + +## Safety Rules + +- Never modify `.env` or secret files without explicit confirmation. +- Never delete files without explicit confirmation. +- Explain reasoning before making architectural changes. + +# Argos + +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`). + +## 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`. +- A new worktree has no `node_modules`: run `npm ci` in its `angular-client/` before testing or running the client. + +## Local Development + +- The backend stack (Postgres, MQTT, Scylla server, Calypso simulator) runs in Docker via the compose files in `compose/`, driven by `argos.sh`. +- Pick the compose profile by what changed: + - Frontend-only changes: `./argos.sh client-dev up` runs everything in Docker, including scylla-server. + - Changes to `scylla-server/`: `./argos.sh scylla-dev up` (everything except scylla-server) plus `cd scylla-server && cargo run` in a separate terminal, so you are not testing a stale binary. +- Frontend client: prefer the `run-local` skill (starts it on the next free port and checks the backend). Direct: `cd angular-client && npm run start` (default port 4200); first compile takes ~10-60s. +- The shell workflow (`argos.sh`, the `run-local` skill, and helpers like `lsof`/`pkill`) assumes a Unix shell. On Windows, run everything from WSL or Git Bash, not `cmd`/PowerShell. + +## Testing + +- Frontend: `cd angular-client && ng test` (Karma/Jasmine). +- Backend: `cd scylla-server && cargo test`. +- Lint and format (frontend): `npx prettier --check "src/**/*.{ts,html,scss}" && npx ng lint`. +- Build (backend): `cargo build`. + +## PR Convention + +- The `/commit` skill applies the commit message format. +- Open PRs against `develop` as drafts. The `/open-pr` skill runs the pre-PR checks (lint, conflict check), pushes, and opens the draft; `/update-pr` refreshes the description. +- Keep PR descriptions tight: at most three backtick usages in the body, and never commit screenshots (drag-drop them into the PR via the GitHub web UI). + +## Screenshots + +Save all Playwright screenshots under `pictures//` at the repo root, using kebab-case descriptive filenames. The `pictures/` folder is git-ignored, so screenshots are never committed; drag-drop them into the PR via the GitHub web UI instead. + +## Code Conventions + +Frontend and backend conventions live alongside their code and auto-load when editing there: +- Angular / TypeScript: see `angular-client/CLAUDE.md`. +- Rust / Axum: see `scylla-server/CLAUDE.md`. + +## Issue tracker + +Issues live in GitHub Issues on `Northeastern-Electric-Racing/Argos` via the `gh` CLI. See `docs/agents/issue-tracker.md` for title, label, and assignment conventions, and `docs/agents/triage-labels.md` for labels. Specs and tickets avoid file paths and code snippets; they go stale. diff --git a/software/application-software/argos/defaults/workspace.yml b/software/application-software/argos/defaults/workspace.yml new file mode 100644 index 0000000..c882011 --- /dev/null +++ b/software/application-software/argos/defaults/workspace.yml @@ -0,0 +1,3 @@ +harness: claude-code +repos: + argos: https://github.com/Northeastern-Electric-Racing/Argos.git diff --git a/software/application-software/argos/workspaces/argos-dev/.claude/skills/link-workspace/SKILL.md b/software/application-software/argos/workspaces/argos-dev/.claude/skills/link-workspace/SKILL.md new file mode 100644 index 0000000..78eeffb --- /dev/null +++ b/software/application-software/argos/workspaces/argos-dev/.claude/skills/link-workspace/SKILL.md @@ -0,0 +1,16 @@ +--- +name: link-workspace +description: Check out another Delphi workspace, or Delphi's main (project-level context and guides), next to this one under linked/ so you can read or reuse its instructions, skills, and docs. Use when the user refers to another workspace or project, or asks how workspaces are organized. +--- + +Run from the workspace root: + +``` +bash .delphi/link.sh # ws/ at linked// +bash .delphi/link.sh main # Delphi's main at linked/main/ +``` + +- List workspaces: `git branch -r --list 'origin/ws/*'`. +- This workspace's project folder (shared context, `AUTHORING.md`, `defaults/`) is the folder above `workspaces//` in `linked/main/`: `git -C linked/main ls-files '*/workspaces//workspace.yml'`. +- Linked checkouts are for reference; re-running updates them. To change one, `git -C linked/ switch -c `, commit, push, and open a PR into `ws/` (or `main` for `linked/main`). +- Remove with `git worktree remove linked/`. diff --git a/software/application-software/argos/workspaces/argos-dev/.delphi/link.sh b/software/application-software/argos/workspaces/argos-dev/.delphi/link.sh new file mode 100755 index 0000000..1cef9f7 --- /dev/null +++ b/software/application-software/argos/workspaces/argos-dev/.delphi/link.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# .delphi/link.sh |main: check out another workspace (ws/) or Delphi's main +# as a detached worktree at linked// of this clone, for reference; print its path. Re-runs +# update it to origin's latest unless you switched it to a branch. Adds /linked/ to +# .git/info/exclude. Runs on bash 3.2 and Git Bash. +set -euo pipefail +cd "$(dirname "$0")/.." +name=${1:?usage: link.sh |main} +case "$name" in main) ref=main ;; -* | *[!a-z0-9-]*) echo "link: bad workspace name '$name'" >&2 && exit 1 ;; *) ref=ws/$name ;; esac +dest=$PWD/linked/$name +git fetch -q origin "+refs/heads/$ref:refs/remotes/origin/$ref" +if [ ! -d "$dest" ]; then + git worktree add -q --detach "$dest" "origin/$ref" +elif git -C "$dest" symbolic-ref -q HEAD >/dev/null; then + echo "link: linked/$name is on a branch; not updated" >&2 +else + git -C "$dest" checkout -q --detach "origin/$ref" +fi +exclude=$(git rev-parse --git-path info/exclude) +mkdir -p "$(dirname "$exclude")" +grep -qxF /linked/ "$exclude" 2>/dev/null || echo /linked/ >>"$exclude" +echo "$dest" diff --git a/templates/workspace/.claude/skills/link-workspace/SKILL.md b/templates/workspace/.claude/skills/link-workspace/SKILL.md new file mode 100644 index 0000000..78eeffb --- /dev/null +++ b/templates/workspace/.claude/skills/link-workspace/SKILL.md @@ -0,0 +1,16 @@ +--- +name: link-workspace +description: Check out another Delphi workspace, or Delphi's main (project-level context and guides), next to this one under linked/ so you can read or reuse its instructions, skills, and docs. Use when the user refers to another workspace or project, or asks how workspaces are organized. +--- + +Run from the workspace root: + +``` +bash .delphi/link.sh # ws/ at linked// +bash .delphi/link.sh main # Delphi's main at linked/main/ +``` + +- List workspaces: `git branch -r --list 'origin/ws/*'`. +- This workspace's project folder (shared context, `AUTHORING.md`, `defaults/`) is the folder above `workspaces//` in `linked/main/`: `git -C linked/main ls-files '*/workspaces//workspace.yml'`. +- Linked checkouts are for reference; re-running updates them. To change one, `git -C linked/ switch -c `, commit, push, and open a PR into `ws/` (or `main` for `linked/main`). +- Remove with `git worktree remove linked/`. diff --git a/templates/workspace/.delphi/link.sh b/templates/workspace/.delphi/link.sh new file mode 100755 index 0000000..1cef9f7 --- /dev/null +++ b/templates/workspace/.delphi/link.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# .delphi/link.sh |main: check out another workspace (ws/) or Delphi's main +# as a detached worktree at linked// of this clone, for reference; print its path. Re-runs +# update it to origin's latest unless you switched it to a branch. Adds /linked/ to +# .git/info/exclude. Runs on bash 3.2 and Git Bash. +set -euo pipefail +cd "$(dirname "$0")/.." +name=${1:?usage: link.sh |main} +case "$name" in main) ref=main ;; -* | *[!a-z0-9-]*) echo "link: bad workspace name '$name'" >&2 && exit 1 ;; *) ref=ws/$name ;; esac +dest=$PWD/linked/$name +git fetch -q origin "+refs/heads/$ref:refs/remotes/origin/$ref" +if [ ! -d "$dest" ]; then + git worktree add -q --detach "$dest" "origin/$ref" +elif git -C "$dest" symbolic-ref -q HEAD >/dev/null; then + echo "link: linked/$name is on a branch; not updated" >&2 +else + git -C "$dest" checkout -q --detach "origin/$ref" +fi +exclude=$(git rev-parse --git-path info/exclude) +mkdir -p "$(dirname "$exclude")" +grep -qxF /linked/ "$exclude" 2>/dev/null || echo /linked/ >>"$exclude" +echo "$dest" diff --git a/tests/e2e.sh b/tests/e2e.sh index aa821b3..43b66c2 100755 --- a/tests/e2e.sh +++ b/tests/e2e.sh @@ -32,6 +32,11 @@ cp -R "$src/ci" "$src/tools" "$src/templates" "$src/.github" "$src/README.md" "$ mkdir -p "$t/seed/$A" "$t/seed/$B" cp -R "$src/$A/." "$t/seed/$A/" cp -R "$src/templates/workspace/." "$t/seed/$B/" +FD=software/electrical/firmware/defaults # project defaults for new workspaces under electrical/firmware +mkdir -p "$t/seed/$FD/.claude/skills/fw" +printf 'harness: claude-code\nrepos:\n fw: https://example.com/fw.git\n' >"$t/seed/$FD/workspace.yml" +printf '# Firmware for {{name}}\n' >"$t/seed/$FD/CLAUDE.md" +echo "fw skill" >"$t/seed/$FD/.claude/skills/fw/SKILL.md" git -C "$t/seed" add -A && git -C "$t/seed" commit -qm init git clone -q --bare "$t/seed" "$t/origin.git" git clone -q "$t/origin.git" "$t/ci" @@ -154,6 +159,8 @@ expect_bad bad-name "name must be" sh -c "mkdir -p software/x/workspaces && cp - expect_bad symlink "symlinks are not allowed" ln -s ../CLAUDE.md "$A/docs/link.md" expect_bad setup-drift "setup.sh: differs" sh -c "echo x >>$A/.delphi/setup.sh" expect_bad workflow-drift "argos-dev/.github/workflows/delphi.yml: differs" sh -c "echo x >>$A/.github/workflows/delphi.yml" +expect_bad defaults-manifest "defaults/workspace.yml: unexpected line" sh -c "printf 'x: y\n' >>${A%/workspaces/*}/defaults/workspace.yml" +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 @@ -184,12 +191,18 @@ F=software/electrical/firmware/workspaces/fw-dev o cat-file -e "new-workspace/fw-dev:$F/workspace.yml" || fail "no workspace.yml on new-workspace/fw-dev" o show "new-workspace/fw-dev:$F/CLAUDE.md" | grep -q "branch \`ws/fw-dev\`" || fail "CLAUDE.md not filled in" ! o show "new-workspace/fw-dev:$F/CLAUDE.md" | grep -q "{{" || fail "placeholder left" +o show "new-workspace/fw-dev:$F/CLAUDE.md" | grep -qx "# Firmware for fw-dev" || fail "defaults CLAUDE.md not appended" +[ "$(o rev-parse "new-workspace/fw-dev:$F/workspace.yml")" = "$(o rev-parse "main:$FD/workspace.yml")" ] || + fail "defaults workspace.yml not used" +o cat-file -e "new-workspace/fw-dev:$F/.claude/skills/fw/SKILL.md" || fail "defaults skill not copied" +o cat-file -e "new-workspace/fw-dev:$F/.claude/skills/link-workspace/SKILL.md" || fail "template skill lost" grep -q "gh pr create --base main --head new-workspace/fw-dev" "$t/gh.log" || fail "no PR for new workspace" (cd "$t/dev" && ! tools/new-workspace.sh x argos-dev 2>/dev/null) || fail "duplicate name accepted" (cd "$t/dev" && ! tools/new-workspace.sh x Bad 2>/dev/null) || fail "bad name accepted" (cd "$t/dev" && ! tools/new-workspace.sh ../x ok 2>/dev/null) || fail "bad org path accepted" [ -z "$(git -C "$t/dev" status --porcelain)" ] || fail "new-workspace touched the checkout" ok "new-workspace.sh opens a PR with the templated folder; rejects duplicates and bad input" +ok "new-workspace.sh adds the project's defaults/ (CLAUDE.md appended, placeholders filled)" ok "new-workspace.sh works when a local new-workspace/ branch is left over" # --- 10. one direction at a time: `sync refresh` only updates ws/, `sync propose` only updates propose/ @@ -229,7 +242,22 @@ nw code 2-dev origin/develop >/dev/null && [ "$(git -C "$w/repos/worktrees/code/ [ "$(git -C "$w/repos/code" branch --show-current)" = main ] || fail "repos/code left its branch" ok "new-worktree.sh makes new branches from the default or , checks out existing ones, reuses on rerun" -# --- 12. shellcheck, if installed +# --- 12. link.sh: other workspaces and main as detached worktrees under linked/, updated on re-run +lk() { bash "$w/.delphi/link.sh" "$@"; } +[ "$(lk argos-dev)" = "$w/linked/argos-dev" ] || fail "link.sh path" +[ "$(git -C "$w/linked/argos-dev" rev-parse HEAD)" = "$(o rev-parse ws/argos-dev)" ] || fail "link not at ws/argos-dev" +{ lk main >/dev/null && [ -f "$w/linked/main/ci/sync.sh" ]; } || fail "link main" +commit_on ws/argos-dev docs/CONTEXT.md "linked update" +{ lk argos-dev >/dev/null && grep -qx "linked update" "$w/linked/argos-dev/docs/CONTEXT.md"; } || fail "re-run did not update" +git -C "$w/linked/argos-dev" switch -q -c mine && commit_on ws/argos-dev docs/CONTEXT.md "after switch" +{ lk argos-dev 2>"$t/err" >/dev/null && grep -q "on a branch; not updated" "$t/err"; } || fail "branch not reported" +[ "$(git -C "$w/linked/argos-dev" branch --show-current)" = mine ] || fail "link moved a branch" +! lk ../x 2>/dev/null || fail "bad link name accepted" +[ "$(grep -cx /linked/ "$w/.git/info/exclude")" = 1 ] || fail "/linked/ not excluded exactly once" +! git -C "$w" status --porcelain | grep -q linked || fail "linked/ shows in status" +ok "link.sh checks out workspaces and main under linked/, updates detached ones, leaves branches" + +# --- 13. shellcheck, if installed if command -v shellcheck >/dev/null; then shellcheck "$src"/ci/*.sh "$src"/tools/*.sh "$src"/tests/*.sh "$src"/templates/workspace/.delphi/*.sh || fail "shellcheck" diff --git a/tools/new-workspace.sh b/tools/new-workspace.sh index e5013f6..cdf3760 100755 --- a/tools/new-workspace.sh +++ b/tools/new-workspace.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash # tools/new-workspace.sh : propose a new workspace as a PR to main. -# In a temporary worktree of origin/main, copies templates/workspace/ to +# In a temporary worktree of origin/main, copies templates/workspace/ and then, if present, the +# project's software//defaults/ (its CLAUDE.md appended to the template's) to # software//workspaces//, checks it, pushes branch new-workspace/, and opens # the PR with gh. Your checkout is untouched. After merge, ci/sync.sh creates ws/. set -euo pipefail @@ -24,11 +25,17 @@ branch=new-workspace/$name wt=$(mktemp -d) git worktree add --quiet --detach "$wt" origin/main trap 'git worktree remove --force "$wt"' EXIT +defaults=$wt/software/$org/defaults mkdir -p "$wt/$folder" cp -R "$wt/templates/workspace/." "$wt/$folder/" -sed -e "s|{{name}}|$name|g" -e "s|{{folder}}|$folder|g" "$wt/templates/workspace/CLAUDE.md" >"$wt/$folder/CLAUDE.md" +if [ -d "$defaults" ]; then + cp -R "$defaults/." "$wt/$folder/" + cp "$wt/templates/workspace/CLAUDE.md" "$wt/$folder/CLAUDE.md" + [ ! -f "$defaults/CLAUDE.md" ] || { echo && cat "$defaults/CLAUDE.md"; } >>"$wt/$folder/CLAUDE.md" +fi +sed -i.bak -e "s|{{name}}|$name|g" -e "s|{{folder}}|$folder|g" "$wt/$folder/CLAUDE.md" && rm "$wt/$folder/CLAUDE.md.bak" "$wt/ci/check.sh" "$wt" git -C "$wt" add -A "$folder" git -C "$wt" commit --quiet -m "New workspace $name at $folder" git -C "$wt" push --quiet origin "HEAD:refs/heads/$branch" -gh pr create --base main --head "$branch" --title "New workspace: $name" --body "Adds \`$folder/\` from templates/workspace. Fill in \`workspace.yml\` repos and \`CLAUDE.md\` before merging; after merge, CI creates branch \`ws/$name\`." +gh pr create --base main --head "$branch" --title "New workspace: $name" --body "Adds \`$folder/\` from templates/workspace (plus \`software/$org/defaults\` if present). Fill in \`workspace.yml\` repos and \`CLAUDE.md\` before merging; after merge, CI creates branch \`ws/$name\`."