Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ source of truth; `docs/goals.md` lists what any change must keep.
- `ci/check.sh [<dir>]`: 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`.
- `software/…/workspaces/<name>/`: 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/<name>/`: 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`).

Expand All @@ -23,9 +25,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`.
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,19 @@ repos: # cloned into repos/<name> 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 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 <org-path under software/> <name>` copies `templates/workspace/` into
`software/<org-path>/workspaces/<name>/` and opens a PR to `main`. Once it merges, CI creates
`ws/<name>`.
`software/<org-path>/workspaces/<name>/`, adds the project's `defaults/` if it has one, and opens a
PR to `main`. Once it merges, CI creates `ws/<name>`.

**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 <workspace>` checks out another workspace at `linked/<workspace>/`.

## CI (`.github/workflows/delphi.yml`)

Expand Down
45 changes: 26 additions & 19 deletions ci/check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,40 @@
# ci/check.sh [<dir>]: validate Delphi (CI runs it on PRs to main; sync.sh on each proposal). Rules:
# a workspace is software/**/workspaces/<name>/ 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
# template workflow matches .github/workflows/delphi.yml; workspace.yml is `harness: <adapter>` plus
# an optional `repos:` map of `<name>: <git-url>`. Lists every problem; exits 1 if any.
# workspace's .delphi/*.sh and .github/workflows/delphi.yml match templates/workspace/, and the
# template workflow matches .github/workflows/delphi.yml; workspace.yml (and a project's
# defaults/workspace.yml) is `harness: <adapter>` plus an optional `repos:` map of `<name>: <git-url>`.
# 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 .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 ||
bad "$tpl/.github/workflows/delphi.yml: differs from .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 ` <name>: <git-url>`): " $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}
Expand All @@ -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 ` <name>: <git-url>`): " $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"
22 changes: 19 additions & 3 deletions docs/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<name>`** has **that folder as its repo root**. Two directions keep them in sync, both subtree
merges (`git merge -Xsubtree=<folder>`), 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.

```
┌───────────────────────────────┐
Expand Down Expand Up @@ -39,8 +40,9 @@ in normal three-way merges. Nothing is shared or generated between workspaces.
## 2. Repository (main)

```
software/<org>/…/<project>/ README.md AUTHORING.md defaults/ (optional, main only)
software/<org>/…/workspaces/<name>/ workspace.yml CLAUDE.md .claude/… docs/…
.delphi/setup.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
Expand All @@ -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:
<adapter>` and an optional `repos:` map of `<name>: <git-url>`. 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/*.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] [<name>]`; no direction = both)

For every workspace on `origin/main` (or just `<name>`):
Expand Down Expand Up @@ -91,9 +98,18 @@ Reads `repos:` from `workspace.yml`, clones each into `repos/<name>` 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 <repo> <branch> [<base>]` creates or reuses `repos/worktrees/<repo>/<branch>`:
checks out an existing branch, else starts one from `<base>` (default `origin/HEAD`). Same shell rules.
The template's `new-worktree` skill makes worktrees the default way to start a branch.

`.delphi/link.sh <workspace>|main` checks out `origin/ws/<workspace>` (or `origin/main`) as a
detached worktree at `linked/<name>/`, 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 <org-path> <name>`)

Validates the name (format, not on main, no leftover `ws/<name>`), copies `templates/workspace/`
and then `software/<org-path>/defaults/` if present (its `CLAUDE.md` appended to the template's)
into `software/<org-path>/workspaces/<name>/` (filling `{{name}}`/`{{folder}}` in `CLAUDE.md`) in a
temporary worktree, runs `ci/check.sh`, pushes branch `new-workspace/<name>`, and opens a PR. Sync
creates `ws/<name>` once it merges.
Expand Down
31 changes: 31 additions & 0 deletions software/application-software/argos/AUTHORING.md
Original file line number Diff line number Diff line change
@@ -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/<name>` (or `bash .delphi/link.sh <name>` from another workspace, then
`git -C linked/<name> switch -c <branch>`).
2. Edit at normal harness paths: `CLAUDE.md`, `.claude/skills/<skill>/SKILL.md`, `docs/`.
3. Push the branch and open a PR into `ws/<name>`. 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 <name>`. 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.
18 changes: 18 additions & 0 deletions software/application-software/argos/README.md
Original file line number Diff line number Diff line change
@@ -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/<name>/` | One workspace per purpose; branch `ws/<name>` 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.
Original file line number Diff line number Diff line change
@@ -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 <ref>` resolves and `git diff <ref>...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.
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
name: new-worktree
description: Create or reuse a worktree for a branch of a repo in repos/ (repos/worktrees/<repo>/<branch>). 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/<repo>/`, unless the user explicitly says not to use worktrees. `repos/<repo>/` stays a clean checkout of the default branch.

Run from the workspace root:

```
bash .delphi/new-worktree.sh <repo> <branch> [<base>]
```

An existing branch (local or on origin, e.g. a PR's head) is checked out; anything else is created from `<base>` (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/<repo> worktree remove ../worktrees/<repo>/<branch>`.

Argos: always pass `origin/develop` as `<base>`. 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.
Original file line number Diff line number Diff line change
@@ -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 <branch> --title "#{ticket} title" --body-file /tmp/<branch>-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/<branch>/`. Write it to `/tmp/<branch>-pr-body.md`.
Original file line number Diff line number Diff line change
@@ -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/<branch>/`. Write it to `/tmp/<branch>-pr-body.md`.
Loading
Loading