Skip to content
Draft
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
27 changes: 0 additions & 27 deletions .claude/skills/delphi-new-layout/SKILL.md

This file was deleted.

52 changes: 52 additions & 0 deletions .claude/skills/delphi-new-workspace/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
name: delphi-new-workspace
description: Build a new Delphi workspace folder with the user. Interview them about the work, pick shared sources to link from scope recommendations, draft workspace.yml, and open the PR with `delphi create --from`. Use when someone wants a new workspace or harness setup in Delphi.
---

# Create a Delphi workspace

Drive the CLI. Don't write to `context/` yourself.

1. **Interview.** Ask what the work is (team, repo, typical tasks) and pick the closest scope
under `context/` (for example `software/application-software/argos`).
2. **Gather candidates.** Read `recommend:` in the scope's `scope.yml` and in each ancestor's,
closest scope first, and sort each into the key matching its directory. Browse the scopes'
`blocks/`, `docs/`, and `harness/` directories. Run `delphi list` to avoid name clashes and
read existing workspaces' `workspace.yml` for ideas.
3. **Draft** a `workspace.yml` in a temp file (format: `docs/design.md` §2). Paths are relative to
`context/`.
- `name`: the workspace name (`[a-z0-9-]+`, unique repo-wide). `harness: claude-code`.
- `instructions:` (optional): parts `CLAUDE.md` is generated from, usually
the scope's `harness/instructions/workspace.md` first (e.g. `software/application-software/argos/harness/instructions/workspace.md`). The generated file must not be hand-edited; users
change a part instead.
- Linked keys: shared sources kept identical in every workspace that links them. Entry =
`<source> [-> <dest>]`; a directory links every file under it. Each source must sit in its
key's directory in some scope:
- `skills:` a skill directory `…/harness/skills/<n>` → default `.claude/skills/<n>`.
- `blocks:` under `…/blocks/` → default `context/<source>`; usually give `->`, e.g.
`…/blocks/pr-body.md -> .claude/skills/open-pr/pr-body.md`.
- `docs:` under `…/docs/` → default `docs/<path below docs/>`.
- `settings:` one file under `…/harness/settings/` → default `.claude/settings.json`.
Dests must be unique and must not sit inside another directory link's dest (a file link into
one of the workspace's own folders is fine).
- `mcp:` (optional): MCP fragments under `…/harness/mcp/` (each one `"name": {…}` member, no
trailing comma); `.mcp.json` is generated from them and must not be hand-edited.
- `repos:` as `name: git-url` (cloned into `repos/` in a checkout).
**Composing a skill from blocks** (instead of linking a block file next to it): a skill
directory, shared (`…/harness/skills/<n>/`) or the workspace's own (`.claude/skills/<n>/`), may
hold a `skill.yml` with `name`, `description` and `body:` (block paths under a scope's
`blocks/`, relative to `context/`). `delphi sync` generates its `SKILL.md`: frontmatter, then
the blocks joined by a blank line. Put the skill's own text in a block too. The generated file
must not be hand-edited; users edit a block or `skill.yml`. A workspace's own composed skill is
added in a checkout after the PR merges, like any own file (list the blocks in `body:`, not in
`blocks:`).
Only link what should stay shared. Anything team-specific becomes the workspace's own file:
after the PR merges, add it in a checkout and propose it. Tell the user which sources other
workspaces already link: edits to those reach other teams.
4. **Show the draft** and get explicit approval.
5. **Create it:**
`delphi create <scope> <name> --from <draft> --model <your model> --effort <effort> --yes`
It writes the folder, runs `delphi sync` (materializing linked files, `CLAUDE.md`,
`.mcp.json`, composed `SKILL.md`s) and `delphi check`, and prints the branch (`delphi/create/<name>`). On a check
failure, fix the draft and retry.
6. Once the PR merges: `delphi checkout <name>`, then `delphi open <name>`.
59 changes: 36 additions & 23 deletions .claude/skills/delphi-propose/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,43 @@
---
name: delphi-propose
description: Propose a Delphi workspace's changes back to the monorepo. Dry-run the routing, help the user resolve each unresolved item inside the workspace, then run `delphi workspace propose`. Use when someone wants to send workspace edits upstream or asks why an edit is unresolved.
description: Propose a Delphi workspace checkout's changes back to the monorepo. Review `delphi diff`, point out shared impact, dry-run the sync, help resolve sync conflicts, then run `delphi propose`. Use when someone wants to send workspace edits upstream or asks about a sync conflict.
---

# Propose workspace changes

Run these inside the workspace. Drive the CLI. Don't edit the Delphi repo directly.
Run these inside the checkout (the workspace folder). Drive the CLI. Don't edit the Delphi repo
directly.

1. Make sure the work is committed (`git status` is clean).
2. Run `delphi workspace propose --dry-run`. It prints the plan (where each edit routes) and the
**Unresolved** items, each with its path, reason, and diff.
3. For each unresolved item, suggest a fix *in the workspace* and apply it only after the user approves:
- **Edit touches generated/separator lines or spans two blocks:** move the text wholly inside
one block's lines, or split it into two edits. In `CLAUDE.md`, a section set off by blank
lines between fragments routes as a new fragment (`fragment` in the plan).
- **New file outside a recognised place:** move it under `context/<scope>/blocks/` (an
existing scope), `docs/` for a doc, or `.claude/skills/<name>/` for a skill. Otherwise leave it out of the PR
by deleting it or keeping it uncommitted.
- **Deleted file that is still compiled:** drop its entry from `.delphi/manifest.yml` instead.
To replace a block, swap the manifest entry, delete the old file, and add the new one under
`context/<scope>/blocks/`.
- **Patch did not apply (same block edited in two places):** keep the edit in one place only.
- **Binary file:** Delphi doesn't route it. Remove it from the workspace commit.
Commit the fixes, then run `--dry-run` again until only acceptable items remain. Unresolved
items never block the PR. They are listed in its body.
4. Run `delphi workspace propose --model <your model> --effort <effort> --yes`.
It refreshes first. On exit code 2 (merge conflicts), help resolve them, commit, and run it again.
5. Report the branch and PR URL. Proposing again later replaces the same PR with the
workspace's full pending diff.
1. Make sure the work is committed (`git status` is clean). Only committed changes are proposed,
and propose refuses to run with uncommitted changes.
2. Run `delphi diff`. Each changed file gets one line:
- `own`: the workspace's own file; the change lands only in this folder.
- `linked … <- <source>`: a shared file. Propose writes the edit to the source and to every
other workspace that links it. `(shared: a, b)` names those workspaces: tell the user their
edit reaches those teams, and ask them to confirm.
- `generated`: `CLAUDE.md` built from `instructions:`, `.mcp.json` built from `mcp:`, or a
skill's `SKILL.md` built from the blocks in its `skill.yml`. Hand edits are rejected: revert it
and edit the part, fragment, or block (or `skill.yml`) in Delphi instead.
- `sync`: a file outside the folder written by an earlier propose (a source or another
workspace's copy).
`delphi diff --upstream` shows what changed on `main` since the last refresh.
3. Run `delphi propose --dry-run`. It syncs in a throwaway worktree and prints which sources and
other workspaces' copies would be written, then the PR body. Nothing is pushed.
4. **Sync conflicts** (exit 1, `conflict: …`) mean one shared file has different new versions:
- `different edits in A, B`: the same shared file was changed differently in two places (two
copies in this folder, or this folder and a change already on `main`). Make them identical,
or keep the edit in only one of them, commit, and re-run.
- `… is newly linked to <source> but differs from it`: a new `blocks`/`docs`/`skills`/`settings`
entry points at a file that already exists here with other content. Delete the file to take
the source (or make it identical), commit, re-run.
- `generated from instructions:` / `generated from mcp:` / `generated from skill.yml (don't
hand-edit it; edit a block)`: see `generated` above. Restore the file (`git checkout
origin/main -- <file>`), make the change in the block or `skill.yml`, commit, re-run.
- `…/skill.yml: body: missing context/<block>`: a `body:` entry names a block that doesn't
exist; fix the path.
Suggest a fix and apply it only after the user approves.
5. Run `delphi propose --model <your model> --effort <effort> --yes`. It merges `main` first: on
exit code 2 (merge conflicts), help resolve them with git, commit, and run it again. Then it
syncs, commits `delphi: sync shared files`, runs `delphi check`, pushes the branch, and opens
or updates the PR.
6. Report the branch and PR URL. Proposing again later updates the same PR.
40 changes: 40 additions & 0 deletions .github/workflows/delphi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Delphi CI (docs/design.md §5): PRs must pass check and be in sync; main is re-synced after merges.
# The sync commit is pushed with GITHUB_TOKEN, which does not trigger workflows, so it cannot loop.
name: delphi
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
concurrency:
group: delphi-${{ github.ref }}
jobs:
check:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- run: cargo build --release --locked && echo "$PWD/target/release" >> "$GITHUB_PATH"
- run: delphi check && delphi sync --check
sync-main:
if: github.event_name == 'push'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- run: cargo build --release --locked && echo "$PWD/target/release" >> "$GITHUB_PATH"
- run: |
delphi sync --base HEAD
git add -A context
git diff --cached --quiet && exit 0
git -c user.name='github-actions[bot]' -c user.email='41898282+github-actions[bot]@users.noreply.github.com' \
commit -q -m 'delphi: sync shared files' -m "Delphi-Harness: github-actions
Delphi-Model: none
Delphi-Effort: none"
# if main moved on meanwhile, the run for the newer push syncs it
git push origin HEAD:main || { git fetch -q origin main; [ "$(git rev-parse FETCH_HEAD)" != "$GITHUB_SHA" ]; }
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/target
59 changes: 29 additions & 30 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,38 @@
# Delphi — developing the CLI

Delphi stores NER's AI-harness context under `context/` and ships a small bash CLI that compiles
layouts into workspaces, refreshes them, and proposes edits back as PRs. The design spec
(`context/docs/delphi-design.md`) is the source of truth.
Delphi stores NER's AI-harness context under `context/`. Every workspace is a materialized folder
(`context/<scope>/workspaces/<name>/`); shared files are linked to one source and kept identical by
`delphi sync`. A small Rust CLI checks out one workspace folder sparsely, refreshes it, and proposes
edits back as PRs. The design spec (`docs/design.md`) is the source of truth; `docs/goals.md` lists
what any change must keep.

## Layout

- `bin/delphi`: dispatcher. It resolves its own path and sources `lib/core.sh`, then the group's module.
- `lib/core.sh`: messages, `defer` cleanup, prompts, config, `safe_path`, Delphi git access, moves, `parse_args`.
- `lib/parse.sh`: YAML-subset parser (POSIX awk).
- `lib/compile.sh`: layout → files (instructions, blocks, docs, skills, MCP, settings) + `.delphi/lock.tsv`.
- `lib/route.sh` + `lib/route.awk`: pending diff + lock → plan → edits in a PR worktree.
- `lib/pr.sh`: the only write path to Delphi (temp worktree, commit with trailers, push, `gh`).
- `lib/provenance.sh`: harness/model/effort resolution.
- `lib/workspace.sh`, `lib/layout.sh`, `lib/block.sh`, `lib/check.sh`, `lib/setup.sh`: the command groups.
- `lib/harness/<name>.sh`: harness adapters (names + `harness_provenance` + `harness_launch`).
- `dev/sandbox.sh`: throwaway end-to-end playground. It is the only test harness (no automated tests, no CI).
- `.claude/skills/`: LLM workflows that drive the CLI (`delphi-new-layout`, `delphi-propose`).
- `src/main.rs`: dispatcher. Resolves the Delphi repo, then runs the command.
- `src/core.rs`: messages (`die!`, `warn!`, `info!`), deferred cleanup, prompts, config, `safe_path`, running git, fetch/commit/worktree helpers, `parse_args`, path helpers.
- `src/parse.rs`: strict YAML-subset parser.
- `src/workspace.rs`: `workspace.yml` (linked keys `blocks`/`docs`/`skills`/`settings` as `<source> [-> <dest>]` with per-key default dests; generated keys `instructions`/`mcp`) and listing workspaces at a revision.
- `src/sync.rs`: `delphi sync`: generate skills' `SKILL.md` from `skill.yml`, reconcile linked files across workspaces against base revisions, regenerate instruction and MCP files.
- `src/check.rs`: `delphi check`: repo validation.
- `src/checkout.rs`: local checkouts and `checkout`, `open`, `refresh`, `diff`, `propose`, `status`.
- `src/manage.rs`: `create`, `list`, `mv`.
- `src/pr.rs`: the only write path to Delphi (temp worktree, commit with trailers, push, `gh`).
- `src/provenance.rs`: harness/model/effort resolution.
- `src/harness.rs`: harness adapters (file names + provenance + launch).
- `src/setup.rs`: `delphi setup`.
- `tests/`: end-to-end tests. `tests/common` builds a throwaway sandbox (temp dir, bare origin, sample
context, stub `gh` on PATH); `tests/cli.rs` drives the binary against it.
- `.claude/skills/`: LLM workflows that drive the CLI (`delphi-new-workspace`, `delphi-propose`).

## Rules

- Must run under macOS `/bin/bash` 3.2. Test with `/bin/bash`, never zsh or a newer bash.
- Tools: POSIX awk (no gawk extensions), git, gh. jq is optional.
- `cargo build`, `cargo test`, and `cargo clippy --all-targets -- -D warnings` must be clean; run `cargo fmt`.
- Keep it small: fewest lines that implement the spec, terse functions, a short header per file.
- Bash 3.2 pitfalls:
- Functions that `defer` cleanup (`make_tmp`, `delphi_worktree_at`, `pr_begin`) must not run
inside `$(...)`. They return results in `REPLY`.
- errexit is suspended in conditional contexts (`if f`, `f || x`), so critical commands need an explicit `|| die`.
- A dying function inside `$(...)` needs `|| exit 1` at the call site.
- Empty arrays error under `set -u`. Use newline-separated strings.
- A failing command substitution inside a heredoc does not propagate. Assign it to a variable first.
- Use `sed` rather than `grep -v`, which exits 1 on empty input and breaks under pipefail.
- `"$var…"` (a variable followed by a non-ASCII byte) is parsed as a longer name. Write `${var}…`.
An unbound-variable error under the EXIT trap exits with status **0**.
- Every path from config, flags, lock, or `moves.tsv` goes through `safe_path` before use.
- Never test against real GitHub repos. Use the sandbox:
`eval "$(/bin/bash dev/sandbox.sh /tmp/sb1)"`, then `d <command>`.
The sandbox stubs `gh` (log in `$SB/gh.log`), so `--yes` pushes only to its bare origin.
Prefer deleting to adapting.
- Runtime tools: git and gh only. `gh` is used only to open or update PRs.
- Every path from config, flags, `workspace.yml`, or checkout bookkeeping goes through `safe_path` (or
`path_ok` for paths only used inside git objects) before use.
- Never test against real GitHub repos. Add a test using the sandbox in `tests/common`; its `gh`
stub logs to `gh.log`, and `--yes` pushes only to the sandbox's bare origin.
- Try the CLI by hand the same way: point `DELPHI_ROOT` at a sandbox clone, never at this checkout's
real origin.
16 changes: 16 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "delphi"
version = "0.1.0"
edition = "2021"
description = "Delphi CLI: check out workspace folders, keep linked files in sync, propose edits back as PRs"
publish = false

[[bin]]
name = "delphi"
path = "src/main.rs"

[dependencies]
anyhow = "1"
Loading
Loading