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
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,15 @@ workspace. Fix it in a PR into `ws/<name>`: on a branch cut from `ws/<name>`, ru
harness: claude-code
repos: # stored in repos/<name>, checked out in worktrees/<name>/ by .delphi/setup.sh
argos: https://github.com/Northeastern-Electric-Racing/Argos.git
references-git: # read-only dependency sources, shallow-cloned into references/<name>/
socketioxide: https://github.com/Totodore/socketioxide.git v0.18.7 # optional tag or branch
```

`repos:` are the code you change. `references-git:` are open-source dependencies the agent reads for
context: `setup.sh` checks each out shallow and detached at its pin (or the default branch), with
pushes disabled, and refreshes them on every run. They get no store and no worktrees. Names are
unique across both maps. The `-git` suffix leaves room for other kinds of references later.

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 the ones Delphi manages: `.delphi/*.sh` (`setup.sh`,
`new-worktree.sh`, `link.sh`) and `.github/workflows/delphi.yml` (copies of the template's).
Expand Down
10 changes: 7 additions & 3 deletions ci/check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
# 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 (and a project's
# defaults/workspace.yml) is `harness: <adapter>` plus an optional `repos:` map of `<name>: <git-url>`.
# defaults/workspace.yml) is `harness: <adapter>` plus an optional `repos:` map of `<name>: <git-url>`
# and an optional `references-git:` map of `<name>: <git-url> [<ref>]`; names are unique across both.
# Lists every problem; exits 1 if any.
set -euo pipefail
cd "${1:-.}"
Expand All @@ -25,9 +26,12 @@ manifest() {
/^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 }
/^references-git:[[:space:]]*(#.*)?$/ { r = 2; next }
r == 1 && /^[[:space:]]/ && $0 !~ /^[[:space:]]+[A-Za-z0-9._-]+:[[:space:]]+[^[:space:]"#]+[[:space:]]*(#.*)?$/ {
print "bad repos entry (want ` <name>: <git-url>`): " $0; next }
r == 2 && /^[[:space:]]/ && $0 !~ /^[[:space:]]+[A-Za-z0-9._-]+:[[:space:]]+[^[:space:]"#]+([[:space:]]+[A-Za-z0-9@._\/-]+)?[[:space:]]*(#.*)?$/ {
print "bad references-git entry (want ` <name>: <git-url> [<ref>]`): " $0; 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
Expand Down
9 changes: 7 additions & 2 deletions docs/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ 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:
<adapter>`, an optional `repos:` map of `<name>: <git-url>`, and an optional `references-git:` map of
`<name>: <git-url> [<ref>]` (names unique across both maps). The name is the folder name:
lowercase letters, digits, `-`; unique repo-wide. Workspaces never nest; no symlinks under
`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
Expand Down Expand Up @@ -97,7 +98,11 @@ Nobody pushes to `ws/*` or `main` directly (branch protection; CI's token is the
Reads `repos:` from `workspace.yml` and, for each not yet present, fetches it into a bare store at
`repos/<name>`, then checks out its default branch as the first worktree,
`worktrees/<name>/<default-branch>/` (re-created if missing). Adds `/repos/` and `/worktrees/` to
the clone's `.git/info/exclude` once. Nothing else: code is edited only in worktrees. Must run on macOS `/bin/bash` 3.2
the clone's `.git/info/exclude` once. For each repo under `references-git:` (read-only dependency
sources), it fetches `<ref>` (default: origin's `HEAD`) with `--depth 1` into `references/<name>/`,
checks it out detached, disables pushes, and does this again on every run, so the copy tracks its pin;
a failed fetch is reported and the old copy kept. Adds `/references/` to the exclude too; references get
no store and no worktrees (`new-worktree.sh` refuses them). Nothing else: code is edited only in worktrees. Must run on macOS `/bin/bash` 3.2
and Git Bash: no bash-4 features, POSIX awk only.

`.delphi/new-worktree.sh <repo> <branch> [<base>]` creates or reuses `worktrees/<repo>/<branch>`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ root=$(cd "$(dirname "$0")/.." && pwd)
repo=${1:?usage: new-worktree.sh <repo> <branch> [<base>]} branch=${2:?usage: new-worktree.sh <repo> <branch> [<base>]}
base=${3:-origin/HEAD} dest="$root/worktrees/$repo/$branch"
g() { git -C "$root/repos/$repo" "$@"; }
[ -d "$root/repos/$repo" ] ||
{ echo "new-worktree: no repos/$repo (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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,40 @@
#!/usr/bin/env bash
# .delphi/setup.sh: run in a checkout of ws/<name> (the workspace root). For each repo listed under
# `repos:` in workspace.yml, fetches it into a bare store at repos/<name> (unless present) and checks
# out its default branch as the first worktree, worktrees/<name>/<default-branch>. Adds /repos/ and
# /worktrees/ to this clone's .git/info/exclude. Safe to re-run. Keep it bash 3.2 (macOS) and Git
# Bash safe: no bash-4 features, POSIX awk only.
# out its default branch as the first worktree, worktrees/<name>/<default-branch>. For each repo under
# `references-git:` (read-only dependency sources, `<name>: <git-url> [<ref>]`), checks out a shallow,
# detached copy of <ref> (default: origin's default branch) at references/<name>, refreshed on every
# run, with pushes disabled. Adds /repos/, /worktrees/ and /references/ to this clone's
# .git/info/exclude. Safe to re-run. Keep it bash 3.2 (macOS) and Git Bash safe: no bash-4
# features, POSIX awk only.
set -euo pipefail
cd "$(dirname "$0")/.."

list=$(awk '
/^[[:space:]]*#/ || /^[[:space:]]*$/ { next }
/^repos:/ { r = 1; next }
/^[^[:space:]]/ { r = 0 }
r { sub(/^[[:space:]]+/, ""); sub(/[[:space:]]+#.*$/, ""); n = index($0, ":")
if (n) print substr($0, 1, n - 1), substr($0, n + 1) }' workspace.yml)
/^repos:/ { s = "repo"; next }
/^references-git:/ { s = "ref"; next }
/^[^[:space:]]/ { s = "" }
s { sub(/^[[:space:]]+/, ""); sub(/[[:space:]]+#.*$/, ""); n = index($0, ":")
if (n) print s, substr($0, 1, n - 1), substr($0, n + 1) }' workspace.yml)

while read -r name url; do
reference() { # <name> <url> [<ref>]: shallow, detached, push-disabled checkout at references/<name>
local dir=references/$1
[ -d "$dir/.git" ] || { git init -q "$dir" && git -C "$dir" remote add origin "$2"; }
git -C "$dir" remote set-url origin "$2"
git -C "$dir" remote set-url --push origin DISABLED-references-are-read-only
if git -C "$dir" fetch -q --depth 1 origin "${3:-HEAD}" </dev/null &&
git -C "$dir" checkout -q --detach FETCH_HEAD; then
echo "references/$1: at ${3:-default branch} ($(git -C "$dir" rev-parse --short HEAD))"
else
echo "setup: could not update references/$1 to ${3:-the default branch}" >&2
fi
}

while read -r kind name url ref; do
[ -n "$name" ] || continue
case "$name" in . | .. | *[!A-Za-z0-9._-]*) echo "setup: skipping unsafe repo name '$name'" >&2 && continue ;; esac
if [ "$kind" = ref ]; then reference "$name" "$url" ${ref:+"$ref"} && continue; fi
store=repos/$name
if [ -e "$store" ]; then
echo "repos/$name: already present"
Expand All @@ -43,6 +61,6 @@ EOF2

exclude=$(git rev-parse --git-path info/exclude)
mkdir -p "$(dirname "$exclude")"
for d in /repos/ /worktrees/; do
for d in /repos/ /worktrees/ /references/; do
grep -qxF "$d" "$exclude" 2>/dev/null || echo "$d" >>"$exclude"
done
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@ harness: claude-code
repos:
argos: https://github.com/Northeastern-Electric-Racing/Argos.git
odyssey-definitions: https://github.com/Northeastern-Electric-Racing/Odyssey-Definitions.git
references-git: # read-only dependency sources; pins match scylla-server/Cargo.lock and angular-client/package-lock.json on develop
socketioxide: https://github.com/Totodore/socketioxide.git socketioxide-v0.18.7
socket.io: https://github.com/socketio/socket.io.git socket.io-client@4.8.1 # monorepo: client, server, engine.io, parser
zenoh: https://github.com/eclipse-zenoh/zenoh.git 1.10.1
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ root=$(cd "$(dirname "$0")/.." && pwd)
repo=${1:?usage: new-worktree.sh <repo> <branch> [<base>]} branch=${2:?usage: new-worktree.sh <repo> <branch> [<base>]}
base=${3:-origin/HEAD} dest="$root/worktrees/$repo/$branch"
g() { git -C "$root/repos/$repo" "$@"; }
[ -d "$root/repos/$repo" ] ||
{ echo "new-worktree: no repos/$repo (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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,40 @@
#!/usr/bin/env bash
# .delphi/setup.sh: run in a checkout of ws/<name> (the workspace root). For each repo listed under
# `repos:` in workspace.yml, fetches it into a bare store at repos/<name> (unless present) and checks
# out its default branch as the first worktree, worktrees/<name>/<default-branch>. Adds /repos/ and
# /worktrees/ to this clone's .git/info/exclude. Safe to re-run. Keep it bash 3.2 (macOS) and Git
# Bash safe: no bash-4 features, POSIX awk only.
# out its default branch as the first worktree, worktrees/<name>/<default-branch>. For each repo under
# `references-git:` (read-only dependency sources, `<name>: <git-url> [<ref>]`), checks out a shallow,
# detached copy of <ref> (default: origin's default branch) at references/<name>, refreshed on every
# run, with pushes disabled. Adds /repos/, /worktrees/ and /references/ to this clone's
# .git/info/exclude. Safe to re-run. Keep it bash 3.2 (macOS) and Git Bash safe: no bash-4
# features, POSIX awk only.
set -euo pipefail
cd "$(dirname "$0")/.."

list=$(awk '
/^[[:space:]]*#/ || /^[[:space:]]*$/ { next }
/^repos:/ { r = 1; next }
/^[^[:space:]]/ { r = 0 }
r { sub(/^[[:space:]]+/, ""); sub(/[[:space:]]+#.*$/, ""); n = index($0, ":")
if (n) print substr($0, 1, n - 1), substr($0, n + 1) }' workspace.yml)
/^repos:/ { s = "repo"; next }
/^references-git:/ { s = "ref"; next }
/^[^[:space:]]/ { s = "" }
s { sub(/^[[:space:]]+/, ""); sub(/[[:space:]]+#.*$/, ""); n = index($0, ":")
if (n) print s, substr($0, 1, n - 1), substr($0, n + 1) }' workspace.yml)

while read -r name url; do
reference() { # <name> <url> [<ref>]: shallow, detached, push-disabled checkout at references/<name>
local dir=references/$1
[ -d "$dir/.git" ] || { git init -q "$dir" && git -C "$dir" remote add origin "$2"; }
git -C "$dir" remote set-url origin "$2"
git -C "$dir" remote set-url --push origin DISABLED-references-are-read-only
if git -C "$dir" fetch -q --depth 1 origin "${3:-HEAD}" </dev/null &&
git -C "$dir" checkout -q --detach FETCH_HEAD; then
echo "references/$1: at ${3:-default branch} ($(git -C "$dir" rev-parse --short HEAD))"
else
echo "setup: could not update references/$1 to ${3:-the default branch}" >&2
fi
}

while read -r kind name url ref; do
[ -n "$name" ] || continue
case "$name" in . | .. | *[!A-Za-z0-9._-]*) echo "setup: skipping unsafe repo name '$name'" >&2 && continue ;; esac
if [ "$kind" = ref ]; then reference "$name" "$url" ${ref:+"$ref"} && continue; fi
store=repos/$name
if [ -e "$store" ]; then
echo "repos/$name: already present"
Expand All @@ -43,6 +61,6 @@ EOF2

exclude=$(git rev-parse --git-path info/exclude)
mkdir -p "$(dirname "$exclude")"
for d in /repos/ /worktrees/; do
for d in /repos/ /worktrees/ /references/; do
grep -qxF "$d" "$exclude" 2>/dev/null || echo "$d" >>"$exclude"
done
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ root=$(cd "$(dirname "$0")/.." && pwd)
repo=${1:?usage: new-worktree.sh <repo> <branch> [<base>]} branch=${2:?usage: new-worktree.sh <repo> <branch> [<base>]}
base=${3:-origin/HEAD} dest="$root/worktrees/$repo/$branch"
g() { git -C "$root/repos/$repo" "$@"; }
[ -d "$root/repos/$repo" ] ||
{ echo "new-worktree: no repos/$repo (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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,40 @@
#!/usr/bin/env bash
# .delphi/setup.sh: run in a checkout of ws/<name> (the workspace root). For each repo listed under
# `repos:` in workspace.yml, fetches it into a bare store at repos/<name> (unless present) and checks
# out its default branch as the first worktree, worktrees/<name>/<default-branch>. Adds /repos/ and
# /worktrees/ to this clone's .git/info/exclude. Safe to re-run. Keep it bash 3.2 (macOS) and Git
# Bash safe: no bash-4 features, POSIX awk only.
# out its default branch as the first worktree, worktrees/<name>/<default-branch>. For each repo under
# `references-git:` (read-only dependency sources, `<name>: <git-url> [<ref>]`), checks out a shallow,
# detached copy of <ref> (default: origin's default branch) at references/<name>, refreshed on every
# run, with pushes disabled. Adds /repos/, /worktrees/ and /references/ to this clone's
# .git/info/exclude. Safe to re-run. Keep it bash 3.2 (macOS) and Git Bash safe: no bash-4
# features, POSIX awk only.
set -euo pipefail
cd "$(dirname "$0")/.."

list=$(awk '
/^[[:space:]]*#/ || /^[[:space:]]*$/ { next }
/^repos:/ { r = 1; next }
/^[^[:space:]]/ { r = 0 }
r { sub(/^[[:space:]]+/, ""); sub(/[[:space:]]+#.*$/, ""); n = index($0, ":")
if (n) print substr($0, 1, n - 1), substr($0, n + 1) }' workspace.yml)
/^repos:/ { s = "repo"; next }
/^references-git:/ { s = "ref"; next }
/^[^[:space:]]/ { s = "" }
s { sub(/^[[:space:]]+/, ""); sub(/[[:space:]]+#.*$/, ""); n = index($0, ":")
if (n) print s, substr($0, 1, n - 1), substr($0, n + 1) }' workspace.yml)

while read -r name url; do
reference() { # <name> <url> [<ref>]: shallow, detached, push-disabled checkout at references/<name>
local dir=references/$1
[ -d "$dir/.git" ] || { git init -q "$dir" && git -C "$dir" remote add origin "$2"; }
git -C "$dir" remote set-url origin "$2"
git -C "$dir" remote set-url --push origin DISABLED-references-are-read-only
if git -C "$dir" fetch -q --depth 1 origin "${3:-HEAD}" </dev/null &&
git -C "$dir" checkout -q --detach FETCH_HEAD; then
echo "references/$1: at ${3:-default branch} ($(git -C "$dir" rev-parse --short HEAD))"
else
echo "setup: could not update references/$1 to ${3:-the default branch}" >&2
fi
}

while read -r kind name url ref; do
[ -n "$name" ] || continue
case "$name" in . | .. | *[!A-Za-z0-9._-]*) echo "setup: skipping unsafe repo name '$name'" >&2 && continue ;; esac
if [ "$kind" = ref ]; then reference "$name" "$url" ${ref:+"$ref"} && continue; fi
store=repos/$name
if [ -e "$store" ]; then
echo "repos/$name: already present"
Expand All @@ -43,6 +61,6 @@ EOF2

exclude=$(git rev-parse --git-path info/exclude)
mkdir -p "$(dirname "$exclude")"
for d in /repos/ /worktrees/; do
for d in /repos/ /worktrees/ /references/; do
grep -qxF "$d" "$exclude" 2>/dev/null || echo "$d" >>"$exclude"
done
2 changes: 2 additions & 0 deletions templates/workspace/.delphi/new-worktree.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ root=$(cd "$(dirname "$0")/.." && pwd)
repo=${1:?usage: new-worktree.sh <repo> <branch> [<base>]} branch=${2:?usage: new-worktree.sh <repo> <branch> [<base>]}
base=${3:-origin/HEAD} dest="$root/worktrees/$repo/$branch"
g() { git -C "$root/repos/$repo" "$@"; }
[ -d "$root/repos/$repo" ] ||
{ echo "new-worktree: no repos/$repo (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
Expand Down
36 changes: 27 additions & 9 deletions templates/workspace/.delphi/setup.sh
Original file line number Diff line number Diff line change
@@ -1,22 +1,40 @@
#!/usr/bin/env bash
# .delphi/setup.sh: run in a checkout of ws/<name> (the workspace root). For each repo listed under
# `repos:` in workspace.yml, fetches it into a bare store at repos/<name> (unless present) and checks
# out its default branch as the first worktree, worktrees/<name>/<default-branch>. Adds /repos/ and
# /worktrees/ to this clone's .git/info/exclude. Safe to re-run. Keep it bash 3.2 (macOS) and Git
# Bash safe: no bash-4 features, POSIX awk only.
# out its default branch as the first worktree, worktrees/<name>/<default-branch>. For each repo under
# `references-git:` (read-only dependency sources, `<name>: <git-url> [<ref>]`), checks out a shallow,
# detached copy of <ref> (default: origin's default branch) at references/<name>, refreshed on every
# run, with pushes disabled. Adds /repos/, /worktrees/ and /references/ to this clone's
# .git/info/exclude. Safe to re-run. Keep it bash 3.2 (macOS) and Git Bash safe: no bash-4
# features, POSIX awk only.
set -euo pipefail
cd "$(dirname "$0")/.."

list=$(awk '
/^[[:space:]]*#/ || /^[[:space:]]*$/ { next }
/^repos:/ { r = 1; next }
/^[^[:space:]]/ { r = 0 }
r { sub(/^[[:space:]]+/, ""); sub(/[[:space:]]+#.*$/, ""); n = index($0, ":")
if (n) print substr($0, 1, n - 1), substr($0, n + 1) }' workspace.yml)
/^repos:/ { s = "repo"; next }
/^references-git:/ { s = "ref"; next }
/^[^[:space:]]/ { s = "" }
s { sub(/^[[:space:]]+/, ""); sub(/[[:space:]]+#.*$/, ""); n = index($0, ":")
if (n) print s, substr($0, 1, n - 1), substr($0, n + 1) }' workspace.yml)

while read -r name url; do
reference() { # <name> <url> [<ref>]: shallow, detached, push-disabled checkout at references/<name>
local dir=references/$1
[ -d "$dir/.git" ] || { git init -q "$dir" && git -C "$dir" remote add origin "$2"; }
git -C "$dir" remote set-url origin "$2"
git -C "$dir" remote set-url --push origin DISABLED-references-are-read-only
if git -C "$dir" fetch -q --depth 1 origin "${3:-HEAD}" </dev/null &&
git -C "$dir" checkout -q --detach FETCH_HEAD; then
echo "references/$1: at ${3:-default branch} ($(git -C "$dir" rev-parse --short HEAD))"
else
echo "setup: could not update references/$1 to ${3:-the default branch}" >&2
fi
}

while read -r kind name url ref; do
[ -n "$name" ] || continue
case "$name" in . | .. | *[!A-Za-z0-9._-]*) echo "setup: skipping unsafe repo name '$name'" >&2 && continue ;; esac
if [ "$kind" = ref ]; then reference "$name" "$url" ${ref:+"$ref"} && continue; fi
store=repos/$name
if [ -e "$store" ]; then
echo "repos/$name: already present"
Expand All @@ -43,6 +61,6 @@ EOF2

exclude=$(git rev-parse --git-path info/exclude)
mkdir -p "$(dirname "$exclude")"
for d in /repos/ /worktrees/; do
for d in /repos/ /worktrees/ /references/; do
grep -qxF "$d" "$exclude" 2>/dev/null || echo "$d" >>"$exclude"
done
Loading
Loading