diff --git a/.claude/skills/delphi-new-layout/SKILL.md b/.claude/skills/delphi-new-layout/SKILL.md deleted file mode 100644 index 538975b..0000000 --- a/.claude/skills/delphi-new-layout/SKILL.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -name: delphi-new-layout -description: Build a new Delphi layout with the user. Interview them about the work, pick blocks from scope recommendations, draft manifest.yml, and open the layout PR with `delphi layout new --from`. Use when someone wants a new layout or workspace setup in Delphi. ---- - -# Create a Delphi layout - -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. Browse the scopes' `blocks/`, `docs/`, and `harness/` directories. Run - `delphi layout list` to reuse ideas from existing layouts and avoid name clashes. -3. **Draft** a manifest in a temp file. Use the format in spec §4.3. All paths are relative to - `context/`, and each entry goes under the key that matches its location: - `harness/instructions/*` → `instructions`, `harness/skills/*` → `skills`, - `harness/mcp/*.json` → `mcp`, `harness/settings/*` → `settings` (at most one), - `blocks/*` → `blocks`, `docs/*` → `docs` (a trailing `/*` glob is allowed for both). `name` must equal the layout name - (`[a-z0-9-]+`). `harness: claude-code`. Add `repos:` as `name: git-url`. -4. **Show the draft** and get explicit approval. -5. **Create it:** - `delphi layout new --from --model --effort --yes` - The command runs `check` and prints the branch (`delphi/layout/`). On a check failure, - fix the draft and retry. -6. **Optionally try it** before the PR merges: `delphi workspace new --ref delphi/layout/`. - Once the PR merges, run `delphi workspace refresh --ref main`. diff --git a/.claude/skills/delphi-new-workspace/SKILL.md b/.claude/skills/delphi-new-workspace/SKILL.md new file mode 100644 index 0000000..5db2e18 --- /dev/null +++ b/.claude/skills/delphi-new-workspace/SKILL.md @@ -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 = + ` [-> ]`; 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/` → default `.claude/skills/`. + - `blocks:` under `…/blocks/` → default `context/`; usually give `->`, e.g. + `…/blocks/pr-body.md -> .claude/skills/open-pr/pr-body.md`. + - `docs:` under `…/docs/` → default `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//`) or the workspace's own (`.claude/skills//`), 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 --from --model --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/`). On a check + failure, fix the draft and retry. +6. Once the PR merges: `delphi checkout `, then `delphi open `. diff --git a/.claude/skills/delphi-propose/SKILL.md b/.claude/skills/delphi-propose/SKILL.md index 40879d0..90030a5 100644 --- a/.claude/skills/delphi-propose/SKILL.md +++ b/.claude/skills/delphi-propose/SKILL.md @@ -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//blocks/` (an - existing scope), `docs/` for a doc, or `.claude/skills//` 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//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 --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 … <- `: 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 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 -- `), make the change in the block or `skill.yml`, commit, re-run. + - `…/skill.yml: body: missing context/`: 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 --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. diff --git a/.github/workflows/delphi.yml b/.github/workflows/delphi.yml new file mode 100644 index 0000000..c7eff9e --- /dev/null +++ b/.github/workflows/delphi.yml @@ -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" ]; } diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/CLAUDE.md b/CLAUDE.md index 314804f..296ed28 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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//workspaces//`); 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/.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 ` [-> ]` 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 `. - 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. diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..2d49b21 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "delphi" +version = "0.1.0" +dependencies = [ + "anyhow", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f03e190 --- /dev/null +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md index 31d1692..bbcdb44 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,93 @@ # Delphi -NER's AI-harness context (knowledge, instructions, skills, MCP, settings), stored compressed -by org chart under `context/`, plus a small bash CLI: +NER's AI-harness context (instructions, blocks, docs, skills, MCP, settings), stored once by org chart under +`context/`, plus a small Rust CLI. -- **compile** a *layout* into a local, git-initialized *workspace* where Claude Code runs, -- **refresh** the workspace when `main` moves, -- **propose** workspace edits back as one PR, routed to the blocks they came from. +Every **workspace** is a folder in Delphi, `context//workspaces//`, fully +materialized on `main`: its `CLAUDE.md`, `.claude/skills/…`, and `docs/…` are real files. Some +files are **linked** to a shared source elsewhere in `context/` (a skill several teams use); +`delphi sync` keeps the source and every linked copy identical. `CLAUDE.md` and `.mcp.json` can be +generated from shared parts, and a skill's `SKILL.md` can be composed from shared blocks +(`skill.yml`). The rest are the workspace's own. -Design: `context/docs/delphi-design.md`. +- **checkout** a workspace: a sparse clone holding just that folder, on your own branch, with + its code repos cloned into `repos/`, +- **refresh** it by merging `main` (plain git), +- **propose** your changes as one PR: shared edits are synced to the source and every other + workspace that links them first. + +Design: `docs/design.md`. Goals: `docs/goals.md`. ## Quick start ```sh -bin/delphi setup # once: puts `delphi` on PATH (~/.local/bin, or pass a dir) +cargo install --path . # once: puts `delphi` on PATH +delphi setup # once, inside this checkout: remember where Delphi lives + +delphi list # workspaces on main +delphi checkout argos-dev # sparse clone into ../Delphi-workspaces/argos-dev +delphi open argos-dev # start Claude Code in the workspace folder +delphi diff # what you changed (own / linked, and who shares it) +delphi propose # refresh, sync, check, push, open/update the PR +delphi refresh # merge main into your branch +``` + +Code changes go in `repos/` with that repo's own PRs. Context changes are committed in the +checkout and sent with `propose`. -delphi ws new argos-dev # create the workspace -delphi ws open argos-dev # start Claude Code in it -delphi ws propose # send context edits back as a PR (run inside the workspace) -delphi ws refresh # pull in Delphi updates +## Workspaces + +`context//workspaces//workspace.yml`: + +```yaml +name: argos-dev +harness: claude-code +instructions: # optional: CLAUDE.md is generated from these parts + - harness/instructions/workspace.md + - software/harness/instructions/base.md +skills: # linked: [-> ], kept in sync + - software/harness/skills/commit # default dest: .claude/skills/commit +blocks: # default dest: context/ + - software/application-software/argos/blocks/glossary.md # default dest: context/ +docs: # default dest: docs/ + - software/application-software/argos/docs/CONTEXT.md +mcp: # optional: .mcp.json is generated from these fragments + - software/harness/mcp/github.json +settings: software/harness/settings/default.json # linked to .claude/settings.json +repos: # cloned into repos/ in a checkout, git-ignored + argos: https://github.com/Northeastern-Electric-Racing/Argos.git ``` -Code changes go in `repos/argos` with its own PRs. Context changes (CLAUDE.md, skills, docs) are -committed in the workspace and sent back with `propose`. +A skill directory (shared or the workspace's own) may hold a `skill.yml`; `delphi sync` then +generates its `SKILL.md` from blocks (don't hand-edit it; edit a block): + +```yaml +name: open-pr +description: Run pre-PR checks and open a draft pull request +body: # blocks under a scope's blocks/, joined by a blank line + - software/application-software/argos/blocks/open-pr.md + - software/application-software/argos/blocks/pr-body.md +``` ## Commands ``` -delphi layout new [--from ] create a layout (branch + PR) -delphi layout list list layouts on origin/main -delphi workspace new [--as ] [--ref ] -delphi workspace open|refresh|propose|status (alias: ws) -delphi block mv move a block (branch + PR) -delphi check validate the repo -delphi setup [dir] put `delphi` on PATH +delphi create --from new workspace folder (PR) +delphi list workspaces on origin/main +delphi checkout [--as ] sparse clone of the folder on ws// +delphi open|refresh|diff|propose [] work in a checkout (diff --upstream, propose --dry-run) +delphi status every local checkout: dirty, ahead, behind +delphi mv move a source or workspace (PR) +delphi sync [--check] [--base ] reconcile linked files in this Delphi checkout +delphi check validate the repo +delphi setup [dir] remember this Delphi checkout ``` Commands that write to Delphi accept `--model`, `--effort` (provenance) and `--yes`. Without -`--yes`, a non-interactive run fails fast instead of prompting. +`--yes`, a non-interactive run fails fast instead of prompting. CI +(`.github/workflows/delphi.yml`) runs `check` and `sync --check` on PRs and `sync` on `main`. ## Developing Delphi -Test CLI changes in the sandbox, never against GitHub (see `CLAUDE.md`): -`eval "$(/bin/bash dev/sandbox.sh /tmp/delphi-sb)"`, then `d `. +`cargo test` runs the end-to-end tests in throwaway sandboxes (stub `gh`, local bare origin); +never test against GitHub. See `CLAUDE.md`. diff --git a/bin/delphi b/bin/delphi deleted file mode 100755 index 2738a08..0000000 --- a/bin/delphi +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env bash -# Delphi CLI entry point. Resolves its own location, then dispatches on the command group. -set -euo pipefail - -src=${BASH_SOURCE[0]} -while [ -L "$src" ]; do - dir=$(cd -P "$(dirname "$src")" && pwd) - src=$(readlink "$src") - case $src in /*) ;; *) src="$dir/$src" ;; esac -done -DELPHI_ROOT=$(cd -P "$(dirname "$src")/.." && pwd) -export DELPHI_ROOT - -. "$DELPHI_ROOT/lib/core.sh" - -usage() { - cat <<'EOF' -usage: delphi [args] - - layout new [--from ] create a layout (branch + PR) - layout list list layouts on origin/main - - workspace new [--as ] [--ref ] - workspace open [] [--model m] [--effort e] [--shell] - workspace refresh [] [--ref ] - workspace propose [] [--dry-run] - workspace status [--offline] (alias: ws) - - block mv move/rename a block - - check validate the repo - setup [dir] put `delphi` on PATH (default ~/.local/bin) - -Commands that write to Delphi accept --model, --effort, --yes. -EOF -} - -group=${1:-} -[ $# -gt 0 ] && shift -case $group in - layout) . "$DELPHI_ROOT/lib/layout.sh"; layout_main "$@" ;; - workspace|ws) . "$DELPHI_ROOT/lib/workspace.sh"; workspace_main "$@" ;; - block) . "$DELPHI_ROOT/lib/block.sh"; block_main "$@" ;; - check) . "$DELPHI_ROOT/lib/check.sh"; check_main "$@" ;; - setup) . "$DELPHI_ROOT/lib/setup.sh"; setup_main "$@" ;; - ""|-h|--help|help) usage ;; - *) die "unknown command '$group' (see: delphi help)" ;; -esac diff --git a/context/docs/delphi-design.md b/context/docs/delphi-design.md deleted file mode 100644 index 067876d..0000000 --- a/context/docs/delphi-design.md +++ /dev/null @@ -1,473 +0,0 @@ -# Delphi — Context Repository Design - -**Date:** 2026-09-23 -**Status:** Draft for review -**Owner:** Application Software (NER) - -## 1. Purpose - -Delphi is a monorepo that stores Northeastern Electric Racing's AI-harness context — knowledge, instructions, skills, MCP definitions, settings — in a **compressed** form organized by the org chart, plus a small bash CLI that: - -1. **Compiles** a chosen combination of that context (a *layout*) into an **uncompressed**, local, git-initialized *workspace* where a harness (Claude Code first) runs for real development. -2. **Refreshes** that workspace when Delphi's `main` moves. -3. **Proposes** changes made in the workspace back to the monorepo as a PR, routing each edit to the block it came from and recording the model, harness, and effort level used. - -High-level commands stay simple; complexity lives in `lib/`. - -## 2. Terminology - -| Term | Meaning | -|---|---| -| **Scope** | A directory under `context/` representing an org unit (club → area → subteam → team). Identified by its path. | -| **Block** | A tracked unit of context, identified by its path relative to `context/`. | -| **Basic block** | Text/markdown under a scope's `blocks/`. | -| **Doc** | Project documentation (glossary, ADRs) under a scope's `docs/`, compiled into the workspace's `docs/`. | -| **Harness block** | Under a scope's `harness/`: instruction fragments, skills, MCP fragments, settings. | -| **Layout** | A `manifest.yml` under a scope's `layouts//` selecting blocks, a harness, and code repos. Compressed side. | -| **Workspace** | A local git-initialized directory, outside the repo, compiled from a layout. Uncompressed side. Many workspaces may come from one layout. | -| **Compile** | The deterministic function `(delphi commit, layout) → files + lock`. | -| **Lock** | `.delphi/lock.tsv`: segment map from compiled output line ranges to their sources. | -| **Segment** | A contiguous line range of one output file that came from one source. | -| **Pending diff** | `git diff generated-merged HEAD` over routable paths: everything the workspace has changed relative to its latest compile. | - -## 3. Repository structure - -``` -Delphi/ -├── CLAUDE.md # for developing Delphi itself -├── .claude/skills/ -│ ├── delphi-new-layout/ # LLM workflow: build a layout from recommendations -│ └── delphi-propose/ # LLM workflow: resolve unresolved items, then propose -├── delphi.conf # key=value repo config -├── moves.tsv # block rename/move log -├── bin/delphi # dispatcher -├── lib/ -│ ├── core.sh # config, logging, prompts, safe_path, layout lookup, move resolution -│ ├── parse.sh # awk YAML-subset parser -│ ├── compile.sh # layout → files + lock -│ ├── route.sh # pending diff + lock → routing plan -│ ├── pr.sh # temp worktree, commit w/ trailers, push + gh PR -│ ├── provenance.sh # resolve harness/model/effort -│ ├── layout.sh # `delphi layout …` -│ ├── workspace.sh # `delphi workspace …` -│ ├── block.sh # `delphi block …` -│ ├── check.sh # `delphi check` -│ └── harness/ -│ └── claude-code.sh # harness adapter -└── context/ # root scope = NER club-wide - ├── scope.yml - ├── blocks/ docs/ harness/ layouts/ - └── software/ # child scope - ├── scope.yml - ├── blocks/ docs/ harness/ layouts/ - ├── finishline/ - ├── application-software/ - │ ├── argos/ - │ └── nero/ - └── firmware/ - └── … -``` - -### 3.1 Scope shape - -Every scope directory contains a required `scope.yml` and any of these **reserved** subdirectories: - -``` -/ -├── scope.yml -├── blocks/ # basic blocks: *.md, *.txt (subdirs allowed) -├── docs/ # project docs: CONTEXT.md, adr/*.md (subdirs allowed) -├── harness/ -│ ├── instructions/*.md # fragments stacked into the harness instruction file -│ ├── skills//SKILL.md (+ any files) # native skill -│ ├── skills/.skill # built skill spec -│ ├── mcp/.json # MCP server fragment -│ └── settings/.json # harness settings file -└── layouts//manifest.yml -``` - -Any other subdirectory is a child scope. Scope names follow the Fall 2026 roster structure (`Software → FinishLine | Software Product | Application Software → {Argos, NERO} | Firmware → {…}`); scopes are created as content arrives, not up front. - -**No file anywhere under `context/` may be named after a harness instruction file** (`CLAUDE.md`, `AGENTS.md`, …) — enforced by `check`. - -### 3.2 Workspace location - -Workspaces compile **outside** the repo so Delphi's own `CLAUDE.md` is never loaded as an ancestor file: - -``` -/ # default ../Delphi-workspaces (relative to repo root) -├── argos-dev/ -└── argos-dev--bug-612/ -``` - -## 4. File formats - -### 4.1 YAML subset (parsed by `lib/parse.sh`) - -All `.yml` and `.skill` files use this subset: - -- Full-line `#` comments and trailing ` #` comments. -- Top-level `key: value` (scalar; unquoted or `"double-quoted"`). -- Top-level `key:` followed by ` - item` lines (list; two-space indent). -- Top-level `key:` followed by ` subkey: value` lines (one-level map; two-space indent). -- **Not supported** (parse error with `file:line`): tabs, anchors/aliases, flow style (`[a, b]`, `{}`), multi-line strings, deeper nesting. - -Parser output (tab-separated, one record per line): - -| Construct | Output | -|---|---| -| scalar | `keyvalue` | -| list item | `keyvalue` (repeated) | -| map entry | `keysubkeyvalue` | - -Consumers know each key's type from the schemas below. Parser uses POSIX awk only (verified against BSD awk and mawk). - -### 4.2 `scope.yml` - -```yaml -name: Argos -recommend: # paths relative to context/; offered, never auto-included - - software/application-software/argos/blocks/overview.md - - software/application-software/argos/harness/skills/run-tests -``` - -### 4.3 `manifest.yml` (layout) - -```yaml -name: argos-dev # must equal the layout directory name; unique repo-wide -harness: claude-code # required; must match lib/harness/.sh -instructions: # stacked, in order, into $HARNESS_INSTRUCTIONS - - software/harness/instructions/base.md - - software/application-software/argos/harness/instructions/argos.md -blocks: # basic blocks; trailing * glob allowed (non-recursive, sorted) - - software/application-software/argos/blocks/* -docs: # project docs; same entry rules as blocks - - software/application-software/argos/docs/CONTEXT.md - - software/application-software/argos/docs/adr/* -skills: # native skill dir or .skill spec - - software/application-software/argos/harness/skills/run-tests -mcp: - - software/harness/mcp/github.json -settings: software/harness/settings/default.json # optional; at most one in v1 -repos: # name: git URL - argos: git@github.com:Northeastern-Electric-Racing/Argos.git -``` - -All paths are relative to `context/`. Only `name` and `harness` are required. Unknown keys are errors. - -### 4.4 `.skill` (built skill spec) - -```yaml -name: run-tests -description: Run and interpret Argos test suites. -body: # stacked into SKILL.md body - - software/application-software/argos/blocks/testing.md -references: # copied to references/ - - software/application-software/argos/blocks/ci.md -``` - -Lets one basic block serve as plain context in one layout and as a skill in another without duplication. - -### 4.5 MCP fragment - -Each `harness/mcp/.json` contains exactly one member of the `mcpServers` object, no trailing comma: - -```json -"github": { - "command": "gh-mcp", - "args": [] -} -``` - -### 4.6 `delphi.conf` - -``` -workspace_root=../Delphi-workspaces -stale_days=14 -``` - -Parsed line-by-line as `key=value` (never `source`d). Relative paths resolve against the repo root. `DELPHI_WORKSPACE_ROOT` env var overrides `workspace_root`. - -### 4.7 `moves.tsv` - -``` -# oldnewdate -software/blocks/git.md software/blocks/git-conventions.md 2026-10-02 -software/application-software/argos/blocks/old-dir software/application-software/argos/blocks/ops 2026-10-05 -``` - -Append-only. Rows are applied **in order, each once** (exact match, or directory-prefix match for directory entries), and only the rows that are new to the consumer: `block mv` applies the row it appends; a workspace applies the rows added since its `compile_commit`. Paths may therefore be reused, and a block can be moved back. - -### 4.8 `lock.tsv` - -``` -# output start end source sha -CLAUDE.md 1 3 @gen:delphi - -CLAUDE.md 4 4 @glue - -CLAUDE.md 5 44 software/harness/instructions/base.md a1b2c3… -CLAUDE.md 45 45 @glue - -CLAUDE.md 46 92 software/application-software/argos/harness/instructions/argos.md d4e5f6… -.claude/skills/run-tests/SKILL.md 1 4 @gen:software/application-software/argos/harness/skills/run-tests.skill - -.claude/skills/run-tests/SKILL.md 5 60 software/application-software/argos/blocks/testing.md f7a8b9… -.delphi/manifest.yml 1 20 software/application-software/argos/layouts/argos-dev/manifest.yml 0c1d2e… -``` - -`sha` is the git blob id of the source at the compiled commit. Every line of every compiled file except `lock.tsv` itself belongs to exactly one segment. Source kinds: a path under `context/`, `@glue` (separator lines), `@gen:` (generated lines). - -### 4.9 Workspace state - -| Location | In workspace git? | Contents | -|---|---|---| -| `.delphi/manifest.yml` | tracked (compiled) | Editable copy of the layout manifest; edits propose back to the layout. | -| `.delphi/lock.tsv` | tracked (compiled) | Segment map of the compile. Always read from `generated-merged`, never the working copy. | -| `.git/delphi/meta` | not tracked (inside `.git/`) | `key=value`: `layout`, `layout_path`, `ref` (Delphi branch the workspace tracks, default `main`), `created`, `last_proposed`, `last_proposed_hash`, `last_pushed`, `pending_since`, `compile_commit` (the Delphi commit `generated-merged`'s content corresponds to — authoritative; may be newer than that commit's trailer when later compiles were identical). | -| `.git/info/exclude` | not tracked | `repos/`, `worktrees/`, `$HARNESS_IGNORE`. | -| `.git/hooks/commit-msg` | not tracked | Provenance trailer hook (§9). | - -Keeping state inside `.git/` means Delphi bookkeeping never dirties the working tree and never shows up in the pending diff. - -**Refs in the workspace repo:** - -| Ref | Meaning | -|---|---| -| `generated` (branch) | History of compiles only. Each commit's message is `delphi: compile @` with trailer `Delphi-Compile: `. | -| `generated-merged` (tag) | The latest compile that has been **merged into `working`**. Its current Delphi commit is `meta.compile_commit`. | -| `working` | The user's branch, where all development happens. | - -`generated` and `generated-merged` differ only while a refresh is pending (compile committed, merge not yet completed). - -The workspace repo is created with `git init` and shares no history with Delphi; none of these refs are Delphi branches. `generated` holds only Delphi's compiled output (never user work); `working` starts from the first compile and merges each later one. The only links back to Delphi are the `Delphi-Compile` trailer text and `meta.compile_commit`. - -## 5. Compiling (`lib/compile.sh`) - -`compile `: deterministic. Reads sources from a temporary detached worktree of Delphi at ``, writes output files and `/.delphi/lock.tsv`. Each source file's content is emitted with a trailing newline added if missing, so segments never share a line. - -| Manifest key | Output | Segments | -|---|---|---| -| `instructions` | `$HARNESS_INSTRUCTIONS`: delphi header, then fragments in order, one blank line between each. Emitted even when the list is empty (header only). | `@gen:delphi` header, `@glue` blank lines, block per fragment | -| `blocks` | `context/` — mirrors the block's path exactly (e.g. `software/application-software/argos/blocks/ops/x.md` → `context/software/application-software/argos/blocks/ops/x.md`) | one segment per file | -| `docs` | `docs/` (e.g. `software/application-software/argos/docs/adr/0001-x.md` → `docs/adr/0001-x.md`) | one segment per file | -| `skills` (native) | `$HARNESS_SKILLS_DIR//…`, each file (subdirectories included, e.g. `scripts/`) copied 1:1, executable bit kept | one segment per file | -| `skills` (`.skill`) | `$HARNESS_SKILLS_DIR//SKILL.md` = generated frontmatter (`name`, `description`) + body blocks with `@glue` between; references copied to `references/` | `@gen:` frontmatter, block per body/reference | -| `mcp` | `$HARNESS_MCP_FILE`: `{"mcpServers": {` + fragments separated by a `,` line + `}}`. Omitted when the list is empty. | `@gen:delphi` wrapper, block per fragment, `@glue` commas | -| `settings` | `$HARNESS_SETTINGS_FILE`, copied 1:1 | one segment | -| (always) | `.delphi/manifest.yml` copy of the layout manifest | one segment | - -**Delphi header** (top of the instruction file, placed first so appends at the end of the file land in a real block): a short note that this workspace was compiled by Delphi from layout ``, that edits are expected, and that finished work should be committed so `delphi workspace propose` can send it upstream. - -Compile errors (missing path, empty glob, parse error, duplicate output path, unsafe path) abort with no partial output. - -## 6. Commands - -`bin/delphi [args]`. `ws` is an alias for `workspace`. Commands that take `[]` default to the workspace containing the current directory. - -**Common flags** for commands that write to Delphi (`layout new`, `block mv`, `workspace propose`): `--model `, `--effort ` (provenance, §9) and `--yes` (skip the push confirmation; also `DELPHI_YES=1`). Without `--yes`, a non-interactive run (no TTY — e.g. an agent's shell tool) fails fast with a message to pass `--yes` rather than hanging. - -### 6.1 `delphi layout new [--from ]` - -1. Validate: scope has `scope.yml`; `` matches `[a-z0-9-]+` and is unique repo-wide. -2. Without `--from` (basic script): collect `recommend` entries from the scope and each ancestor (closest first, deduped); ask y/n per item; ask harness (choices = `lib/harness/*.sh`); prompt for repos (`name url` lines until blank). Items are placed into manifest keys by path: `*/harness/instructions/*` → `instructions`, `*/harness/skills/*` → `skills`, `*/harness/mcp/*` → `mcp`, `*/harness/settings/*` → `settings` (more than one → re-ask), `*/blocks/*` → `blocks`, `*/docs/*` → `docs`. -3. With `--from`: use the given manifest verbatim (used by the `delphi-new-layout` skill). -4. Via `pr.sh` (§7): write `context//layouts//manifest.yml`, run `check`, commit, PR on branch `delphi/layout/`. Prints the branch name. - -### 6.2 `delphi layout list` - -Prints `namescopeharness` for every layout on `origin/main`. - -### 6.3 `delphi workspace new [--as ] [--ref ]` - -1. `git fetch`. Resolve the layout by name **in the tree of `origin/`** (default `main`), following `moves.tsv` at that commit. Workspace name defaults to the layout name; error if `/` exists. -2. Compile at `origin/` into a temp dir. -3. `git init` the workspace; write `.git/info/exclude`, `.git/delphi/meta` (with `ref`, `pending_since` empty), and the `commit-msg` hook. -4. Copy the compile in, commit on `generated`, tag `generated-merged`, create and check out `working` from it; set `meta.compile_commit`. The working tree is clean. -5. Clone each repo into `repos/`; create empty `worktrees/`. Clone failures warn and are summarized at the end; the workspace is still usable. - -`--ref` lets you try a layout before its PR merges (`workspace new argos-dev --ref delphi/layout/argos-dev`). Such a workspace refreshes from that branch; `propose` refuses until the layout exists on `main` (§6.6), and `refresh --ref main` switches it over once merged. - -### 6.4 `delphi workspace open [] [--model m] [--effort e] [--shell]` - -1. Resolve workspace (argument, current directory, or numbered picker over `status` output). -2. Warn if the compile is behind its ref (suggest `refresh`) or if any workspace is `unproposed` for more than `stale_days`. -3. Export `DELPHI_HARNESS`, `DELPHI_MODEL`, `DELPHI_EFFORT` and `exec harness_launch` in the workspace directory; with `--shell`, `exec $SHELL` there instead. - -### 6.5 `delphi workspace refresh [] [--ref ]` - -Idempotent; re-running always picks up where it left off. No `--continue`. - -1. Require a clean working tree and no merge in progress. `--ref` updates `meta.ref` first. -2. `git fetch` Delphi; resolve the layout path through `moves.tsv` at `origin/`; update `meta.layout_path`. -3. **Compile step** — skipped if `generated` is ahead of `generated-merged` (a pending refresh): compile at `origin/` commit `C` into a temporary worktree of the workspace repo checked out on `generated`, replacing its tracked contents. - - Content changed → commit with trailer `Delphi-Compile: C`. - - Content identical → set `meta.compile_commit = C` (no commits; keeps the recorded commit current, e.g. after a squash-merged layout PR or unrelated `main` activity), report "up to date", exit 0. - If `origin/` no longer exists, fail with "branch `` is gone — run `refresh --ref main`". -4. **Merge step** — skipped if `generated` is already an ancestor of `HEAD`: `git merge --no-edit generated`. On conflict, exit **2**: "resolve conflicts, commit, then re-run `delphi workspace refresh`." -5. **Finalize** (only once `generated` is an ancestor of `HEAD`): move tag `generated-merged` to `generated`; set `meta.compile_commit` from that commit's `Delphi-Compile` trailer. Rewrite any moved paths in `.delphi/manifest.yml`; if changed, commit `delphi: apply moves`. If the pending diff is now empty, set `meta.pending_since = HEAD`. Exit 0. - -All commits Delphi itself makes in a workspace (compiles, merges, `apply moves`) use `--no-verify` so the provenance hook never tags them. - -**Exit codes:** 0 = up to date or finalized; 2 = merge conflict awaiting resolution; 1 = error. - -If a refresh is pending when `--ref` changes, the pending compile is merged and finalized first, then the compile step runs once more against the new ref. Compile and merge commits only appear in workspace history when the compiled content actually changed. - -### 6.6 `delphi workspace propose [] [--dry-run]` - -**One live PR per workspace**, on branch `delphi/propose//` (`` from `gh api user --jq .login`; workspace names are local, so the user part prevents teammates' branches colliding). Each propose rebuilds that branch from scratch with the **entire** current pending diff and force-updates it, so the PR always equals "everything this workspace still differs from `main` by." Proposing twice never duplicates; a merged PR's changes drop out after the next refresh; a closed/rejected change keeps reappearing until it is reverted in the workspace (the documented escape hatch). - -1. `--dry-run`: skip refresh; route against the current `generated-merged` (warn if behind); print the plan and unresolved items; change nothing; stop. -2. Require a clean working tree and `meta.ref = main` (else: "layout not on main yet — merge its PR, then `refresh --ref main`"). Run `refresh`; continue only if it exits 0. -3. Compute the pending diff and route it (§8) into a plan. -4. If the plan has no routed changes and no unresolved items: print "nothing to propose", exit 0. -5. Via `pr.sh`, building from `meta.compile_commit` (so patches apply exactly), in order: - 1. **Layout manifest** — if `.delphi/manifest.yml` changed, replace the layout manifest with it (moved paths resolved). - 2. **Block edits** — apply routed hunks. - 3. **New blocks** — add files; append each to the layout manifest's `blocks:`/`docs:`/`skills:` unless an existing entry or glob already covers it. New instruction fragments (§8) are inserted into `instructions:` at their position. - 4. Run `check`; abort (no push) on failure, printing the violations. - Each step with changes is one commit with provenance trailers. -6. Push (force, with an explicit lease — §7); `gh pr create` if no open PR exists for the branch, otherwise `gh pr edit` to replace the body. Refuse if an open PR on the branch was authored by someone other than the current `gh` user. PR body: routed-change summary, provenance table (§9), **Unresolved** section (each item as a fenced diff with its workspace path and reason). -7. Set `meta.last_proposed` = today and `meta.last_proposed_hash` = pending diff hash. - -**Pending diff hash:** `git hash-object` of the `-U0` pending diff with `@@` hunk headers and `index` lines stripped, so upstream changes that only shift line numbers don't flip a workspace back to `unproposed`. - -### 6.7 `delphi workspace status [--offline]` - -For each directory in `workspace_root` containing `.git/delphi/meta`, print: workspace, layout, ref, dirty flag, **state**, age, behind flag. - -| State | Condition | -|---|---| -| `clean` | pending diff empty | -| `proposed` | pending diff hash = `last_proposed_hash` | -| `unproposed` | otherwise | - -Age = days since `last_proposed` (or `created`) for `unproposed`. Behind = `origin/` has commits since `meta.compile_commit` touching any lock source, the layout manifest, or a directory covered by a manifest glob. `--offline` skips `git fetch`. Status cannot see whether a PR was closed; a rejected workspace stays `proposed` until its change is reverted or re-proposed. - -### 6.8 `delphi block mv ` - -Only paths under a scope's `blocks/`, `docs/`, or `harness/` (layouts cannot be moved in v1). Via `pr.sh`: validate `old` exists and `new` does not, both inside `context/`; `git mv`; append to `moves.tsv`; rewrite exact and directory-prefix references in every `manifest.yml`, `scope.yml`, and `.skill`; run `check`; commit; PR on branch `delphi/mv/-`. Existing workspaces pick up the move on their next `refresh`/`propose`. - -### 6.9 `delphi check` - -Exits non-zero listing every violation: - -- Every scope directory has `scope.yml`; every `.yml`/`.skill` parses. -- Manifests: required keys present, no unknown keys, `name` equals directory, harness adapter exists, at most one `settings`, layout names unique. -- Every referenced path exists (each glob matches ≥1 file) and resolves inside `context/`. -- Entries are under the key matching their location (`skills` entries are a dir with `SKILL.md` or a `.skill` file, etc.). -- No file under `context/` is named any adapter's `$HARNESS_INSTRUCTIONS`. -- `moves.tsv` rows well-formed. -- MCP fragments form valid JSON when wrapped — only if `jq` is installed; otherwise skipped with a note. - -### 6.10 `delphi setup [dir]` - -Writes a two-line `delphi` wrapper (`exec /bin/delphi "$@"`) into `dir` (default `~/.local/bin`) and warns if `dir` is not on `PATH`. A wrapper rather than a symlink, because Git Bash copies on `ln -s` by default. - -## 7. Writing to Delphi (`lib/pr.sh`) - -All commands that modify the monorepo use one path: - -1. `git fetch --prune`; create a temporary worktree of Delphi on the command's branch, starting from a given commit (default `origin/main`). The user's own checkout is never touched. -2. Caller applies changes inside it. -3. Commit with provenance trailers (§9). -4. Confirm `Push and open/update PR? [y/N]` unless `--yes`; then push and `gh pr create` / `gh pr edit` with the caller's body plus the provenance table. Propose branches are force-pushed with an explicit lease: `--force-with-lease=:` where `` is `meta.last_pushed`, the commit this workspace last pushed (empty when the branch is absent on the remote, e.g. auto-deleted after a merge). If the remote branch exists but differs from `last_pushed`, someone else pushed to it: propose stops ("review the PR, then re-run"), records the remote sha, and the next run overwrites it. -5. Remove the temp worktree via `trap` on success or failure. - -## 8. Change routing (`lib/route.sh`) - -Input: pending diff (`git diff --no-renames generated-merged HEAD`), excluding `repos/`, `worktrees/`, `.delphi/lock.tsv`. Lock read from `generated-merged`. Output: plan rows `kindworkspace-pathtarget[reason]`. Checked in this order per file: - -| # | Change | Result | -|---|---|---| -| 1 | `.delphi/manifest.yml` modified | layout manifest replace (whole file) | -| 2 | Binary | unresolved | -| 3 | Deleted | no-op if its source is no longer referenced by the workspace's `.delphi/manifest.yml` (dropped via the manifest); otherwise unresolved — blocks are dropped by editing `.delphi/manifest.yml` | -| 4 | Modified, in lock | per-hunk routing (below) | -| 5 | Added at `context/

` where `

` is `/blocks/…` and `` is an existing scope at `meta.compile_commit` | new block at `

` | -| 5a | Added at `docs//` | new doc beside the compiled docs already in `docs//` (their source directory), else at `/docs//` | -| 6 | Added under `$HARNESS_SKILLS_DIR//` where `` is a native skill in the lock | new file in that skill's source directory | -| 7 | Added under `$HARNESS_SKILLS_DIR//` where `` is not in the lock | new native skill at `/harness/skills//` | -| 8 | Anything else | unresolved | - -**Hunk routing** (from `git diff -U0`; base-side range `a,n`; patches applied with `git apply --unidiff-zero`): - -| Hunk | Result | -|---|---| -| Insertion (`n = 0`) in `$HARNESS_INSTRUCTIONS` at a segment boundary (`a = 0` or `a = seg.end`) | **new fragment**, except the leading/trailing lines touching a neighbouring block with no blank line between, which extend that block (rows below). The rest, trimmed of blank lines, goes to `/harness/instructions/.md` (slug from its first line; `-2`, `-3`, … when taken) and is listed in the layout's `instructions:` after the preceding fragment (first when none) | -| All base lines `a..a+n-1` inside one block segment | patch that block (`block_line = base_line − seg.start + 1`) | -| Insertion (`n = 0`) after line `a`, where `seg.start ≤ a < seg.end` of a block segment | patch that block | -| Insertion after the last line of a block segment (`a = seg.end`), when the next line is end-of-file or a non-block segment | append to that block | -| Insertion at file start (`a = 0`), when the first segment is a block | prepend to that block | -| Insertion right after a `@glue`/`@gen` segment (`a = seg.end`), when the next segment is a block | prepend to that next block | -| Inside `@gen:.skill`, touching only `name:`/`description:` lines | rewrite those keys in the `.skill` spec | -| Anything else: in `@glue`/other `@gen`, spanning segments, or `git apply` fails (e.g. the same block edited in two outputs) | unresolved | - -**Replacing a block** needs no special case: swap the entry in `.delphi/manifest.yml` (old path → new path), delete the old compiled file, and add the new one under `context//blocks/`. The deletion is a no-op (rule 3), the manifest edit routes by rule 1, and the new file by rule 5. - -**Placement is strict by design:** a file not in a recognised location stays unresolved; Delphi never guesses where it belongs. - -All routed hunks for one (output file, block) pair are combined into a single patch so line offsets stay consistent. - -Unresolved items never block the PR; they are listed in it. They are resolved by changing the **workspace** so the change becomes routable (move text into a block's region, move a new file under `context//blocks/`, edit `.delphi/manifest.yml`, or revert it), then proposing again. - -## 9. Provenance - -Every commit Delphi writes to the monorepo carries trailers; every PR body repeats them as a table. - -``` -Delphi-Harness: claude-code 2.4.1 -Delphi-Model: claude-opus-5-5 -Delphi-Effort: high -Delphi-Layout: software/application-software/argos/layouts/argos-dev -Delphi-Workspace: argos-dev--bug-612 # propose only -Delphi-Base: 0cdd375 # propose only: meta.compile_commit -``` - -**Resolution order** (`lib/provenance.sh`), per field: CLI flag → `DELPHI_*` env (set by `workspace open`) → adapter's `harness_provenance` fallback. If still missing: prompt when interactive (accepting `none` for runs without an LLM); error when non-interactive. Never written as "unknown". - -**Workspace commit hook:** `.git/hooks/commit-msg` appends `Delphi-Harness/Model/Effort` trailers from `DELPHI_*` env vars when set and not already present. - -**Propose aggregation:** the PR table lists the proposing session's values plus each distinct trailer combination from non-merge workspace commits in `pending_since..HEAD` (all of `working` if `pending_since` is empty). - -**Known gap:** if the model changes mid-session (e.g. `/model`), the env vars from `workspace open` go stale; flags on `propose` override. - -## 10. Harness adapter contract - -`lib/harness/.sh` is sourced and must define: - -| Name | Kind | claude-code value | -|---|---|---| -| `HARNESS_INSTRUCTIONS` | var | `CLAUDE.md` | -| `HARNESS_SKILLS_DIR` | var | `.claude/skills` | -| `HARNESS_MCP_FILE` | var | `.mcp.json` | -| `HARNESS_SETTINGS_FILE` | var | `.claude/settings.json` | -| `HARNESS_IGNORE` | var | `.claude/settings.local.json` | -| `harness_provenance` | fn | prints 3 lines: `claude-code `, model, effort (from `CLAUDE_CODE_EFFORT_LEVEL` when set) | -| `harness_launch ` | fn | `exec claude` with `--model` when given and `CLAUDE_CODE_EFFORT_LEVEL` set | - -The generic compiler does all file work; adapters only supply names and two functions. New harnesses = new adapter file. - -## 11. LLM workflows (Delphi repo skills) - -- **`delphi-new-layout`** (primary way to create layouts): interviews the user about the work, reads `scope.yml` recommendations up the scope chain and browses available blocks, drafts a manifest, shows it for approval, then runs `delphi layout new --from --model … --effort … --yes`. Optionally follows with `delphi workspace new --ref delphi/layout/` to try it. -- **`delphi-propose`**: runs `delphi workspace propose --dry-run`, walks the user through each unresolved item with a suggested fix in the workspace (per §8's resolution list), applies and commits approved fixes, then runs `delphi workspace propose --model … --effort … --yes`. - -Both are optional conveniences; every operation is available through the scripts alone. - -## 12. Cross-cutting rules - -- **Locating Delphi:** `bin/delphi` resolves its own real path (a `readlink` loop, no `readlink -f`) and uses its parent as the repo root, both in the Delphi checkout and inside workspaces. Install = put `Delphi/bin` on `PATH` or symlink `bin/delphi`. -- **Bash 3.2 compatible** (macOS default): no associative arrays, `mapfile`, `${x,,}`, etc. `set -euo pipefail` in every entry point. -- **Dependencies:** `git`, `awk` (POSIX features only), `sed`, `gh` (only for opening PRs). `jq` optional (check only). Blob ids and diff hashes via `git hash-object`. -- **Path safety:** every path from config, flags, lock, or `moves.tsv` goes through `safe_path ` (rejects absolute paths, `..` escapes, and symlinks resolving outside ``) before any read, write, or delete. Deletes happen only via `git` or inside `mktemp -d` dirs. (Prior prototypes all failed this.) -- **Errors:** actionable messages naming the file/line or command to run next; temp worktrees cleaned via `trap`; multi-step operations are idempotent on re-run, never half-applied silently. - -## 13. Out of scope for v1 - -- Automated tests and CI (deferred by decision until the working version has been used). -- Seed content (Argos scope and layout come next, separately). -- Harness adapters other than claude-code. -- Merging multiple settings files; recursive globs; automatic routing of deletions; moving/renaming layouts. -- Managing `worktrees/` beyond creating the directory (use `git worktree` directly). -- Mechanical, Electrical, and Business scopes. - -## 14. Resolved decisions - -- Default `workspace_root` is `../Delphi-workspaces` (sibling of the Delphi clone); configurable via `delphi.conf` or `DELPHI_WORKSPACE_ROOT`. diff --git a/context/harness/instructions/workspace.md b/context/harness/instructions/workspace.md deleted file mode 100644 index d2d5306..0000000 --- a/context/harness/instructions/workspace.md +++ /dev/null @@ -1,13 +0,0 @@ -# Delphi workspace - -You are in a Delphi workspace: a folder that pairs team context with the code repos it's about. It has two kinds of git repo, and every change belongs to exactly one: - -| Path | What it is | Changes go | -|---|---|---| -| `CLAUDE.md`, `.claude/`, `context/`, `docs/` | Team context and docs pulled from Delphi | Workspace git (`working` branch); `delphi workspace propose` opens the Delphi PR | -| `repos//` | A normal clone of a code repo, with its own remote | That repo's git, branches, and PRs, per its conventions | -| `worktrees/` | Empty; for extra checkouts of a repo (`git -C repos/ worktree add ../../worktrees/ `) | Same as the repo it came from | - -- Run a repo's git, `gh`, build, and test commands from inside that repo (`cd repos/`), never from the workspace root: at the root, `git` is the workspace repo. -- Code changes never go in the workspace git (`repos/` and `worktrees/` are git-ignored there). Context changes (instructions, skills, blocks) never go in a code repo. -- `.delphi/` is Delphi bookkeeping. Don't edit it by hand. diff --git a/context/software/application-software/argos/harness/instructions/argos.md b/context/software/application-software/argos/harness/instructions/argos.md index 5207194..cfa00a8 100644 --- a/context/software/application-software/argos/harness/instructions/argos.md +++ b/context/software/application-software/argos/harness/instructions/argos.md @@ -7,9 +7,9 @@ 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 `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//`, 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. - 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/`. +- After the PR merges, remove it with `git -C repos/argos worktree remove ../worktrees/`. ## Local Development diff --git a/context/software/harness/instructions/base.md b/context/software/application-software/argos/harness/instructions/base.md similarity index 100% rename from context/software/harness/instructions/base.md rename to context/software/application-software/argos/harness/instructions/base.md diff --git a/context/software/application-software/argos/harness/instructions/workspace.md b/context/software/application-software/argos/harness/instructions/workspace.md new file mode 100644 index 0000000..ccb62e0 --- /dev/null +++ b/context/software/application-software/argos/harness/instructions/workspace.md @@ -0,0 +1,15 @@ +# Delphi workspace + +You are in a Delphi workspace: this folder, checked out from Delphi on your own branch. It pairs team context with the code repos it's about, and holds two kinds of git repo. Every change belongs to exactly one: + +| Path | What it is | Changes go | +|---|---|---| +| `CLAUDE.md`, `.claude/`, `docs/`, other files here | The workspace, a folder in Delphi (a sparse checkout that holds only this folder) | Commit on the checkout's branch; `delphi propose` opens the Delphi PR | +| `repos//` | A separate clone of a code repo, with its own remote (git-ignored by the workspace) | That repo's git, branches, and PRs, per its conventions | + +- Run a repo's git, `gh`, build, and test commands from inside that repo (`cd repos/`), never from this folder: here, `git` is the Delphi checkout. +- Code changes never go in the workspace's git. Context changes (instructions, skills, docs) never go in a code repo. +- Some files are **linked** to a shared source in Delphi (see `links:` in `workspace.yml`). Edit them here like any file: when you propose, `delphi sync` writes your edit to the source and to every other workspace that links it. `delphi diff` tags those files `linked` and lists the workspaces they are `shared` with. If someone else changed the same shared file differently, sync reports a conflict instead of guessing. +- Files that aren't linked are this workspace's own. To add one, just add it. +- If `workspace.yml` lists `instructions:`, `CLAUDE.md` is generated from those parts: don't edit it by hand; edit the part instead (link it into this folder under `links:` and edit the copy, or change it in Delphi). +- `delphi diff` shows what you changed versus `main`; `delphi diff --upstream` shows what changed on `main` since your last `delphi refresh`. `delphi refresh` merges `main` into your branch (conflicts are resolved with plain git). `delphi propose` sends your committed changes as one PR. diff --git a/context/software/application-software/argos/layouts/argos-dev/manifest.yml b/context/software/application-software/argos/layouts/argos-dev/manifest.yml deleted file mode 100644 index e4857c5..0000000 --- a/context/software/application-software/argos/layouts/argos-dev/manifest.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: argos-dev -harness: claude-code -instructions: - - harness/instructions/workspace.md - - software/harness/instructions/base.md - - software/application-software/argos/harness/instructions/argos.md -docs: - - software/application-software/argos/docs/CONTEXT.md -skills: - - software/application-software/argos/harness/skills/grill-with-docs - - software/application-software/argos/harness/skills/to-spec - - software/application-software/argos/harness/skills/to-tickets - - software/application-software/argos/harness/skills/implement - - software/application-software/argos/harness/skills/code-review - - software/application-software/argos/harness/skills/commit - - software/application-software/argos/harness/skills/open-pr.skill - - software/application-software/argos/harness/skills/update-pr.skill - - software/application-software/argos/harness/skills/run-local - - software/application-software/argos/harness/skills/new-worktree -repos: - argos: https://github.com/Northeastern-Electric-Racing/Argos.git diff --git a/context/software/application-software/argos/scope.yml b/context/software/application-software/argos/scope.yml index df18e9a..ec68d6c 100644 --- a/context/software/application-software/argos/scope.yml +++ b/context/software/application-software/argos/scope.yml @@ -1,9 +1,8 @@ name: Argos recommend: - - software/application-software/argos/harness/skills/grill-with-docs - - software/application-software/argos/harness/skills/to-spec - - software/application-software/argos/harness/skills/to-tickets - - software/application-software/argos/harness/skills/implement + - software/application-software/argos/harness/instructions/workspace.md + - software/application-software/argos/harness/instructions/base.md + - software/application-software/argos/harness/instructions/argos.md - software/application-software/argos/harness/skills/commit - - software/application-software/argos/harness/skills/open-pr.skill - - software/application-software/argos/harness/skills/run-local + - software/application-software/argos/harness/skills/to-tickets + - software/application-software/argos/blocks/pr-body.md diff --git a/context/software/application-software/argos/harness/skills/code-review/SKILL.md b/context/software/application-software/argos/workspaces/argos-dev/.claude/skills/code-review/SKILL.md similarity index 100% rename from context/software/application-software/argos/harness/skills/code-review/SKILL.md rename to context/software/application-software/argos/workspaces/argos-dev/.claude/skills/code-review/SKILL.md diff --git a/context/software/application-software/argos/workspaces/argos-dev/.claude/skills/commit/SKILL.md b/context/software/application-software/argos/workspaces/argos-dev/.claude/skills/commit/SKILL.md new file mode 100644 index 0000000..8fa13d0 --- /dev/null +++ b/context/software/application-software/argos/workspaces/argos-dev/.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/context/software/application-software/argos/harness/skills/grill-with-docs/SKILL.md b/context/software/application-software/argos/workspaces/argos-dev/.claude/skills/grill-with-docs/SKILL.md similarity index 100% rename from context/software/application-software/argos/harness/skills/grill-with-docs/SKILL.md rename to context/software/application-software/argos/workspaces/argos-dev/.claude/skills/grill-with-docs/SKILL.md diff --git a/context/software/application-software/argos/harness/skills/implement/SKILL.md b/context/software/application-software/argos/workspaces/argos-dev/.claude/skills/implement/SKILL.md similarity index 87% rename from context/software/application-software/argos/harness/skills/implement/SKILL.md rename to context/software/application-software/argos/workspaces/argos-dev/.claude/skills/implement/SKILL.md index de73d34..fdaaf35 100644 --- a/context/software/application-software/argos/harness/skills/implement/SKILL.md +++ b/context/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 `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//` (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/context/software/application-software/argos/harness/skills/new-worktree/SKILL.md b/context/software/application-software/argos/workspaces/argos-dev/.claude/skills/new-worktree/SKILL.md similarity index 71% rename from context/software/application-software/argos/harness/skills/new-worktree/SKILL.md rename to context/software/application-software/argos/workspaces/argos-dev/.claude/skills/new-worktree/SKILL.md index 9b3ddef..61fa71b 100644 --- a/context/software/application-software/argos/harness/skills/new-worktree/SKILL.md +++ b/context/software/application-software/argos/workspaces/argos-dev/.claude/skills/new-worktree/SKILL.md @@ -1,6 +1,6 @@ --- name: new-worktree -description: Create or reuse the worktree for a branch in this Delphi workspace (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 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. --- Run from the workspace root: diff --git a/context/software/application-software/argos/harness/skills/new-worktree/scripts/new-worktree.sh b/context/software/application-software/argos/workspaces/argos-dev/.claude/skills/new-worktree/scripts/new-worktree.sh similarity index 77% rename from context/software/application-software/argos/harness/skills/new-worktree/scripts/new-worktree.sh rename to context/software/application-software/argos/workspaces/argos-dev/.claude/skills/new-worktree/scripts/new-worktree.sh index 7d6cfcf..ed6531f 100644 --- a/context/software/application-software/argos/harness/skills/new-worktree/scripts/new-worktree.sh +++ b/context/software/application-software/argos/workspaces/argos-dev/.claude/skills/new-worktree/scripts/new-worktree.sh @@ -1,11 +1,11 @@ #!/usr/bin/env bash -# new-worktree.sh — create or reuse worktrees/ for repos/argos. Prints the path. +# 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/worktrees/$branch" +repo="$root/repos/argos" dest="$root/repos/worktrees/$branch" git -C "$repo" fetch -q origin if [ -d "$dest" ]; then : diff --git a/context/software/application-software/argos/workspaces/argos-dev/.claude/skills/open-pr/SKILL.md b/context/software/application-software/argos/workspaces/argos-dev/.claude/skills/open-pr/SKILL.md new file mode 100644 index 0000000..738d40d --- /dev/null +++ b/context/software/application-software/argos/workspaces/argos-dev/.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/context/software/application-software/argos/harness/skills/open-pr.skill b/context/software/application-software/argos/workspaces/argos-dev/.claude/skills/open-pr/skill.yml similarity index 100% rename from context/software/application-software/argos/harness/skills/open-pr.skill rename to context/software/application-software/argos/workspaces/argos-dev/.claude/skills/open-pr/skill.yml diff --git a/context/software/application-software/argos/harness/skills/run-local/SKILL.md b/context/software/application-software/argos/workspaces/argos-dev/.claude/skills/run-local/SKILL.md similarity index 100% rename from context/software/application-software/argos/harness/skills/run-local/SKILL.md rename to context/software/application-software/argos/workspaces/argos-dev/.claude/skills/run-local/SKILL.md diff --git a/context/software/application-software/argos/harness/skills/to-spec/SKILL.md b/context/software/application-software/argos/workspaces/argos-dev/.claude/skills/to-spec/SKILL.md similarity index 100% rename from context/software/application-software/argos/harness/skills/to-spec/SKILL.md rename to context/software/application-software/argos/workspaces/argos-dev/.claude/skills/to-spec/SKILL.md diff --git a/context/software/application-software/argos/workspaces/argos-dev/.claude/skills/to-tickets/SKILL.md b/context/software/application-software/argos/workspaces/argos-dev/.claude/skills/to-tickets/SKILL.md new file mode 100644 index 0000000..13e83a4 --- /dev/null +++ b/context/software/application-software/argos/workspaces/argos-dev/.claude/skills/to-tickets/SKILL.md @@ -0,0 +1,29 @@ +--- +name: to-tickets +description: Break a plan, spec, or the current conversation into tracer-bullet tickets with blocking edges, published to the issue tracker. Use when the user wants to turn a plan into issues or break down work. +disable-model-invocation: true +--- + +1. **Context.** Work from the conversation. If given a spec or issue, read its body and comments. Explore the code if needed, looking for prefactors that make the change easy. +2. **Slice** into tracer bullets. Each ticket: + - is a narrow but complete path through every layer (schema, API, UI, tests), demoable on its own, and fits one fresh context; + - is AFK (no human needed) where possible, HITL otherwise; + - lists the tickets that block it. Prefactors go first. + + A wide mechanical refactor (rename a column, retype a shared symbol) can't land as a vertical slice. Sequence it expand → migrate in batches → contract, one ticket per step. +3. **Quiz.** Show a numbered list of title, AFK/HITL, blocked by, and what it delivers. Ask about granularity, blocking edges, merges or splits, and AFK/HITL. Iterate until approved. +4. **Create** the issues in dependency order with `ready-for-agent`. Link blockers, and set `Parent` to the spec issue only if one exists. Don't modify the parent. + + +## Parent +The spec issue (omit if none). + +## What to build +The end-to-end behavior, from the user's perspective. + +## Acceptance criteria +- [ ] … + +## Blocked by +Blocking tickets, or "None — can start immediately". + diff --git a/context/software/application-software/argos/workspaces/argos-dev/.claude/skills/update-pr/SKILL.md b/context/software/application-software/argos/workspaces/argos-dev/.claude/skills/update-pr/SKILL.md new file mode 100644 index 0000000..392ab6f --- /dev/null +++ b/context/software/application-software/argos/workspaces/argos-dev/.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/context/software/application-software/argos/harness/skills/update-pr.skill b/context/software/application-software/argos/workspaces/argos-dev/.claude/skills/update-pr/skill.yml similarity index 100% rename from context/software/application-software/argos/harness/skills/update-pr.skill rename to context/software/application-software/argos/workspaces/argos-dev/.claude/skills/update-pr/skill.yml diff --git a/context/software/application-software/argos/workspaces/argos-dev/CLAUDE.md b/context/software/application-software/argos/workspaces/argos-dev/CLAUDE.md new file mode 100644 index 0000000..c9decc9 --- /dev/null +++ b/context/software/application-software/argos/workspaces/argos-dev/CLAUDE.md @@ -0,0 +1,85 @@ +# Delphi workspace + +You are in a Delphi workspace: this folder, checked out from Delphi on your own branch. It pairs team context with the code repos it's about, and holds two kinds of git repo. Every change belongs to exactly one: + +| Path | What it is | Changes go | +|---|---|---| +| `CLAUDE.md`, `.claude/`, `docs/`, other files here | The workspace, a folder in Delphi (a sparse checkout that holds only this folder) | Commit on the checkout's branch; `delphi propose` opens the Delphi PR | +| `repos//` | A separate clone of a code repo, with its own remote (git-ignored by the workspace) | That repo's git, branches, and PRs, per its conventions | + +- Run a repo's git, `gh`, build, and test commands from inside that repo (`cd repos/`), never from this folder: here, `git` is the Delphi checkout. +- Code changes never go in the workspace's git. Context changes (instructions, skills, docs) never go in a code repo. +- Some files are **linked** to a shared source in Delphi (see `links:` in `workspace.yml`). Edit them here like any file: when you propose, `delphi sync` writes your edit to the source and to every other workspace that links it. `delphi diff` tags those files `linked` and lists the workspaces they are `shared` with. If someone else changed the same shared file differently, sync reports a conflict instead of guessing. +- Files that aren't linked are this workspace's own. To add one, just add it. +- If `workspace.yml` lists `instructions:`, `CLAUDE.md` is generated from those parts: don't edit it by hand; edit the part instead (link it into this folder under `links:` and edit the copy, or change it in Delphi). +- `delphi diff` shows what you changed versus `main`; `delphi diff --upstream` shows what changed on `main` since your last `delphi refresh`. `delphi refresh` merges `main` into your branch (conflicts are resolved with plain git). `delphi propose` sends your committed changes as one PR. + +# 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//`, 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. +- 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 + +- 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`. + +## Workflow + +Idea to ship: `/grill-with-docs` → `/to-spec` → `/to-tickets` → `/implement` (test-first, then `/code-review` and `/commit`) → `/open-pr`. A trivial one-liner goes straight to `/implement`. + +## 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. + +## Domain docs + +The glossary and ADRs are workspace docs, not files in `repos/argos/`: `docs/CONTEXT.md` and `docs/adr/` at the workspace root. Edit them there and they're proposed back to Delphi. Read `docs/CONTEXT.md` and the relevant ADRs before exploring, use the glossary's terms, and flag any conflict with an ADR. ADR filenames follow `repos/argos/docs/agents/domain.md` (`--.md`). diff --git a/context/software/application-software/argos/docs/CONTEXT.md b/context/software/application-software/argos/workspaces/argos-dev/docs/CONTEXT.md similarity index 100% rename from context/software/application-software/argos/docs/CONTEXT.md rename to context/software/application-software/argos/workspaces/argos-dev/docs/CONTEXT.md diff --git a/context/software/application-software/argos/workspaces/argos-dev/workspace.yml b/context/software/application-software/argos/workspaces/argos-dev/workspace.yml new file mode 100644 index 0000000..3fdb9f1 --- /dev/null +++ b/context/software/application-software/argos/workspaces/argos-dev/workspace.yml @@ -0,0 +1,11 @@ +name: argos-dev +harness: claude-code +instructions: # CLAUDE.md is generated from these parts (edit a part, not CLAUDE.md) + - software/application-software/argos/harness/instructions/workspace.md + - software/application-software/argos/harness/instructions/base.md + - software/application-software/argos/harness/instructions/argos.md +skills: # shared with other workspaces; delphi sync keeps them equal + - software/application-software/argos/harness/skills/commit + - software/application-software/argos/harness/skills/to-tickets +repos: + argos: https://github.com/Northeastern-Electric-Racing/Argos.git diff --git a/delphi.conf b/delphi.conf index 8131a5c..e324447 100644 --- a/delphi.conf +++ b/delphi.conf @@ -1,3 +1,3 @@ # Delphi repo config (key=value). Relative paths resolve against the repo root. +# workspace_root: where `delphi checkout` puts checkouts. workspace_root=../Delphi-workspaces -stale_days=14 diff --git a/dev/sandbox.sh b/dev/sandbox.sh deleted file mode 100755 index b970a7f..0000000 --- a/dev/sandbox.sh +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env bash -# dev/sandbox.sh — throwaway playground for trying Delphi end to end without GitHub. -# -# eval "$(dev/sandbox.sh)" # prints exports; then use: d … -# -# Creates, in a fresh temp dir ($SB): -# origin.git bare "remote" for Delphi (stands in for GitHub) -# bin/gh stub gh: logs to gh.log, remembers PRs in gh.prs (so --yes works end to end) -# Delphi/ clone with this checkout's bin/ lib/ delphi.conf moves.tsv + sample content -# argos.git bare code repo referenced by the sample layout -# Delphi-workspaces/ where workspaces land (the default ../Delphi-workspaces) -# `d` runs the sandbox CLI under /bin/bash (macOS bash 3.2) to catch bash-4-isms. -set -euo pipefail - -here=$(cd -P "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) -SB=${1:-$(mktemp -d "${TMPDIR:-/tmp}/delphi-sb.XXXXXX")} -mkdir -p "$SB" -SB=$(cd -P "$SB" && pwd) - -git init -q --bare -b main "$SB/origin.git" -git clone -q "$SB/origin.git" "$SB/Delphi" 2>/dev/null -cp -R "$here/bin" "$here/lib" "$SB/Delphi/" -cp "$here/delphi.conf" "$here/moves.tsv" "$SB/Delphi/" - -C="$SB/Delphi/context" -A="$C/software/application-software/argos" -mkdir -p "$C/software/harness/instructions" "$C/software/harness/mcp" \ - "$A/blocks" "$A/harness/instructions" "$A/harness/skills/run-tests" "$A/layouts/argos-dev" - -printf 'name: NER\n' > "$C/scope.yml" -printf 'name: Software\nrecommend:\n - software/harness/instructions/base.md\n' > "$C/software/scope.yml" -printf 'name: Application Software\n' > "$C/software/application-software/scope.yml" -cat > "$A/scope.yml" <<'EOF' -name: Argos -recommend: - - software/application-software/argos/blocks/overview.md - - software/application-software/argos/harness/skills/run-tests -EOF -printf '# Software conventions\n\n- Use conventional commits.\n- Open PRs against develop.\n' \ - > "$C/software/harness/instructions/base.md" -printf '"github": {\n "command": "gh-mcp",\n "args": []\n}\n' > "$C/software/harness/mcp/github.json" -printf '# Argos\n\nArgos is the telemetry dashboard.\nIt has an Angular client and a Rust server.\n' \ - > "$A/harness/instructions/argos.md" -printf '# Argos overview\n\nLine one.\nLine two.\nLine three.\n' > "$A/blocks/overview.md" -printf '# Testing\n\nRun `npm test` in angular-client.\nRun `cargo test` in the server.\n' > "$A/blocks/testing.md" -printf -- '---\nname: run-tests\ndescription: Run the Argos test suites.\n---\n\nRun both suites and summarize failures.\n' \ - > "$A/harness/skills/run-tests/SKILL.md" -mkdir -p "$A/harness/skills/run-tests/scripts" -printf '#!/bin/sh\necho running tests\n' > "$A/harness/skills/run-tests/scripts/run.sh" -chmod +x "$A/harness/skills/run-tests/scripts/run.sh" -cat > "$A/harness/skills/triage.skill" <<'EOF' -name: triage -description: Triage a failing Argos test. -body: - - software/application-software/argos/blocks/testing.md -EOF - -git init -q -b main "$SB/argos-src" -printf '# Argos code\n' > "$SB/argos-src/README.md" -git -C "$SB/argos-src" add -A && git -C "$SB/argos-src" commit -qm init -git clone -q --bare "$SB/argos-src" "$SB/argos.git" - -cat > "$A/layouts/argos-dev/manifest.yml" </dev/null -git -C "$SB/Delphi" branch -q -u origin/main 2>/dev/null || true - -mkdir -p "$SB/bin"; : > "$SB/gh.prs" -{ printf '#!/bin/sh\nSB=%q\n' "$SB"; cat <<'EOF'; } > "$SB/bin/gh" -# stub gh for the Delphi sandbox: never contacts GitHub -echo "gh $*" >> "$SB/gh.log" -n=$(awk -v b="$4" '$1 == b { print $2 }' "$SB/gh.prs") # args: pr list|create --head … -case "$1 $2" in - "api user") echo sandbox-user ;; - "pr list") [ -z "$n" ] || { [ "$8" = author ] && echo sandbox-user || echo "$n"; } ;; - "pr create") n=$(($(wc -l < "$SB/gh.prs") + 1)); echo "$4 $n" >> "$SB/gh.prs"; echo "https://github.invalid/pr/$n" ;; -esac -exit 0 -EOF -chmod +x "$SB/bin/gh" - -cat < \ No newline at end of file diff --git a/docs/delphi-flow.png b/docs/delphi-flow.png new file mode 100644 index 0000000..51efe38 Binary files /dev/null and b/docs/delphi-flow.png differ diff --git a/docs/design.md b/docs/design.md new file mode 100644 index 0000000..641f3b8 --- /dev/null +++ b/docs/design.md @@ -0,0 +1,100 @@ +# Delphi — Design (v4: compiled on main, sparse checkouts) + +Goals: `docs/goals.md`. Flow diagram: `docs/delphi-flow.png`. + +## 1. Model + +Context is stored **compressed** (blocks, instruction fragments, skill specs, MCP fragments) and +selected by **layouts**, exactly as in v1. The difference is where compiling happens: every +layout's compiled workspace is **committed on `main`** in `layouts//out/`, regenerated by +CI whenever sources change. Working on a workspace is a **sparse checkout of that layout folder** +on your own branch. You edit the compiled files freely, line by line; `propose` routes each edit +back to the block it came from (v1 routing), recompiles every layout, and opens a PR. + +## 2. Repository + +``` +context// + scope.yml + blocks/ docs/ # basic blocks, docs + harness/instructions/*.md skills// skills/.skill mcp/*.json settings/*.json + layouts// + manifest.yml # v1 keys, unchanged + out/ # compiled workspace (committed; don't edit on main) + CLAUDE.md .claude/skills/… .mcp.json context/… docs/… + .delphi/lock.tsv # line ranges → source blocks +``` + +`manifest.yml` keys (v1): `name`, `harness`, `instructions`, `blocks`, `docs`, `skills` (native +skill dir or `.skill` spec), `mcp`, `settings`, `repos`. `.skill` spec: `name`, `description`, +`body` (blocks stacked into `SKILL.md`), `references`. File formats, compile rules, and the lock +format are v1's (`out/` replaces the old workspace root; no `.delphi/manifest.yml` copy — the real +`manifest.yml` sits next to `out/` in your checkout). + +## 3. Compile (`delphi compile [--check]`) + +Deterministic v1 compile of every layout from the current tree into its `out/`. `--check` changes +nothing and exits 1 if any `out/` is stale. CI: on PRs, `delphi check` + `delphi compile --check`; +on pushes to `main`, `delphi compile` and commit if anything changed. + +## 4. Checkouts + +`git clone --filter=blob:none --no-checkout` of Delphi's origin, **non-cone** sparse pattern for +`/context//layouts//` (so Delphi's root files are never checked out), branch +`ws//`. The harness runs in `out/`. `out/repos/` and the adapter's local +settings file are git-ignored via `.git/info/exclude`. Bookkeeping: `.git/delphi/meta`. + +## 5. Routing (propose) + +Pending diff = `merge-base(HEAD, origin/main)..HEAD` inside the layout folder (committed changes +only). Lock = `out/.delphi/lock.tsv` at the merge-base. Rules are v1's §8: + +- `manifest.yml` edits are ordinary edits (they're the real file). +- `out/` edits: per-hunk routing into blocks (inside a block, append, prepend); a blank-line-set-off + section at a fragment boundary of the instruction file → new fragment + `/harness/instructions/.md`; `.skill` `name:`/`description:` edits → spec keys; + new files under `out/context//blocks/`, `out/docs/`, `out///` → new sources + (added to the manifest); deletions whose source was dropped from `manifest.yml` → no-op. +- Anything else (generated/separator lines, spanning segments, binary, unknown files) is + **unresolved**. + +Then, in a temp full worktree: apply the routed edits to sources, recompile **every** layout, and +compare this layout's recompiled `out/` with your edited `out/`. Any difference means an edit +didn't route: propose lists each unresolved item (path, reason, diff) and **stops without pushing** +— otherwise the recompile on `main` would silently drop it. Fix it in the checkout (move the text +into a block, revert it, or edit `manifest.yml`) and re-run. + +**Shared impact:** layouts other than yours whose `out/` changed in the recompile are listed in +`diff`/`--dry-run` output and the PR body (`also updates: nero-dev, …`). + +## 6. Commands + +| Command | Does | +|---|---| +| `delphi create --from ` | new layout + compiled `out/` via PR | +| `delphi list` | layouts on `origin/main` (name, scope, harness) | +| `delphi checkout [--as ]` | sparse clone, branch, clone repos | +| `delphi open [checkout] [--shell]` | warn if behind `main`; launch the harness (or a shell) in `out/` | +| `delphi refresh [checkout]` | `git fetch` + `git merge origin/main` (exit 2 on conflict) | +| `delphi diff [checkout] [--upstream]` | your changes vs `main` with where each routes (plan) and unresolved items; `--upstream`: what changed on `main` since, with author and subject | +| `delphi propose [checkout] [--dry-run]` | refresh, route, recompile, verify (§5), `check`, commit with trailers, push the branch, one PR per checkout | +| `delphi status` | every local checkout: dirty, ahead (unpushed), behind `main` | +| `delphi mv ` | move a source, rewrite every manifest/`scope.yml`/`.skill`, recompile, PR | +| `delphi compile [--check]` | §3 | +| `delphi check` | v1 checks + every `out/` is fresh | +| `delphi setup [dir]` | remember where the Delphi checkout is | + +Writers take `--yes`, `--model`, `--effort`. + +## 7. Unchanged + +v1 file formats, compile, lock, and routing rules; provenance (trailers, commit hook, resolution +order); `--yes`/non-interactive rules; `gh` only for PRs; `safe_path` on every path from config, +flags, manifests, lock; exit codes (0 ok, 1 error, 2 merge conflict); offline tolerance. + +## 8. Removed + +From v1: workspaces outside Delphi, the `generated`/`generated-merged` refs and compile-on-refresh, +`meta.compile_commit`, `moves.tsv` (moves are commits; `mv` rewrites manifests), the +`.delphi/manifest.yml` copy, the proposed-hash state. From v3: `workspace.yml`, links, `sync`, +`skill.yml`, `workspaces/`. diff --git a/docs/goals.md b/docs/goals.md new file mode 100644 index 0000000..5aa8479 --- /dev/null +++ b/docs/goals.md @@ -0,0 +1,55 @@ +# Delphi CLI — Goals + +What the CLI must achieve, not how. Any rewrite or simplification must keep these. +Details live in the spec (`docs/design.md`). + +## Purpose + +Store NER's AI context (instructions, blocks, docs, skills, MCP, settings) once, organized by the +org chart. Let anyone turn a chosen set of it into a working directory, keep that directory current, +and send improvements back — with or without an AI harness. + +## Goals + +**G1. Compressed sources, compiled workspaces on `main`.** Context is stored once as blocks and +selected by layouts. Every layout's compiled workspace (files at normal harness locations) is +committed on `main` and always matches its sources. Every compiled line is traceable to its block. + +**G2. Work locally on just your workspace.** A checkout contains only that layout's folder, on your +own branch. Code repos listed in the layout are cloned into it. + +**G3. Refresh.** Pull the latest `main` into a checkout without losing the user's edits. +Conflicts are shown with git's normal tools. + +**G4. Know what changed.** At any time, show what the user changed versus `main`, what changed on +`main` since, and whether the changes have been proposed yet. + +**G5. Propose.** Send a checkout's changes back as one pull request per checkout. + +**G5a. Edit line by line.** Any line of a compiled file can be edited; each edit lands in the +block it came from, new sections become new fragments, and every layout using that block is +recompiled. Edits Delphi can't place are reported and block the PR, never guessed or dropped. + +**G6. Provenance.** Every change written to Delphi records which harness, model, and effort made +it. + +**G7. Keep Delphi valid.** `check` catches broken manifests, missing paths, and bad structure +before anything is pushed. New workspaces and moves are created through PRs too. + +**G8. Harness-agnostic.** Supporting a new harness (beyond Claude Code) means adding one small +adapter: file names plus how to launch it. + +## Invariants + +- **I1.** Changes reach `main` only through PRs; Delphi never rewrites a user's uncommitted work. +- **I2.** Reject unsafe paths (absolute, `..`, symlinks escaping) before any read or write. +- **I3.** Workspace bookkeeping never shows up as a user change. +- **I4.** Clear errors that say what to run next; never hang waiting for input in scripts. +- **I5.** Works offline where possible; GitHub (`gh`) is only needed to open PRs. +- **I6.** Easy to install (`cargo install`) and to test end to end without real GitHub. + +## Open questions + +- Should "proposed" come from GitHub's PR state instead of a local hash? +- Should a rejected change stop reappearing without reverting it? +- Resolved: strict YAML subset kept; `.skill` specs kept (v4). diff --git a/lib/block.sh b/lib/block.sh deleted file mode 100644 index 7341e0b..0000000 --- a/lib/block.sh +++ /dev/null @@ -1,39 +0,0 @@ -# block.sh — `delphi block mv `: move a block, log it in moves.tsv, rewrite references. -. "$DELPHI_ROOT/lib/parse.sh" -. "$DELPHI_ROOT/lib/provenance.sh" -. "$DELPHI_ROOT/lib/pr.sh" -. "$DELPHI_ROOT/lib/check.sh" - -block_main() { - local verb=${1:-} - [ $# -gt 0 ] && shift - parse_args "--model --effort --yes" "$@"; eval "set -- $ARGS" - [ "$verb" = mv ] && [ $# -eq 2 ] || die "usage: delphi block mv " - block_mv "${1%/}" "${2%/}" -} - -block_mv() { - local old=$1 new=$2 p src dst f row - for p in "$old" "$new"; do - path_ok "$p" || die "unsafe path: '$p'" - case "/$p" in */blocks/?*|*/docs/?*|*/harness/?*) ;; *) die "'$p' is not under a scope's blocks/, docs/, or harness/" ;; esac - done - delphi_fetch - p=$(delphi_commit main) || exit 1 - provenance_resolve "$OPT_MODEL" "$OPT_EFFORT" claude-code - pr_begin "delphi/mv/${old##*/}-$(date +%Y%m%d)" "$p" - src=$(safe_path "$PR_WT/context" "$old") || exit 1 - dst=$(safe_path "$PR_WT/context" "$new") || exit 1 - [ -e "$src" ] || die "no such block on origin/main: $old" - [ -e "$dst" ] && die "already exists on origin/main: $new" - mkdir -p "$(dirname "$dst")" && git -C "$PR_WT" mv "context/$old" "context/$new" || die "git mv failed" - make_tmp; row="$REPLY/row" - printf '%s\t%s\t%s\n' "$old" "$new" "$(date +%Y-%m-%d)" | tee -a "$PR_WT/moves.tsv" > "$row" - while IFS= read -r f; do rewrite_moves "$row" "$f" || true; done < $new" || die "nothing to commit" - pr_finish "delphi: move $old -> $new" "Moves \`$old\` to \`$new\` and rewrites references. Workspaces follow on their next refresh." - printf '%s\n' "$PR_BRANCH" -} diff --git a/lib/check.sh b/lib/check.sh deleted file mode 100644 index 9ba5048..0000000 --- a/lib/check.sh +++ /dev/null @@ -1,148 +0,0 @@ -# check.sh — repo-wide validation. `check_tree ` prints every violation, returns 1 if any. -# Layouts are validated by compiling them; the rules here cover what compile doesn't enforce. -. "$DELPHI_ROOT/lib/parse.sh" -. "$DELPHI_ROOT/lib/compile.sh" - -check_main() { - [ $# -eq 0 ] || die "usage: delphi check" - if check_tree "$DELPHI_ROOT"; then info "check: ok"; else exit 1; fi -} - -_ce() { printf '%s\n' "$*" >> "$_CHECK_ERRS"; } - -# _ck_entry : placement rule for the kind (instructions|skills|mcp| -# settings|body); `body` (skill specs) and `any` (recommendations) must also exist. -_ck_entry() { - local f=$1 key=$2 p=$3 want='*' - path_ok "${p%/\*}" || { _ce "$f: $key: unsafe path '$p'"; return 0; } - case $4 in - instructions) want='*/harness/instructions/*' ;; skills) want='*/harness/skills/*' ;; - mcp) want='*/harness/mcp/*.json' ;; settings) want='*/harness/settings/*' ;; body) want='*/blocks/*' ;; - esac - case "/$p" in $want) ;; *) _ce "$f: $key: '$p' is not under ${want#\*/}" ;; esac - case $4 in body|any) [ -e "$_CK_ROOT/context/$p" ] || _ce "$f: $key: missing '$p'" ;; esac - return 0 -} - -check_tree() { - _CK_ROOT=$1 - local ctx="$1/context" f rel recs k p n names dup name hfile h i=0 - make_tmp; _CHECK_ERRS="$REPLY/errs"; : > "$_CHECK_ERRS" - [ -d "$ctx" ] || { _ce "missing context/ directory"; cat "$_CHECK_ERRS" >&2; return 1; } - - # scopes: every non-reserved directory needs scope.yml - while IFS= read -r d; do - [ -f "$d/scope.yml" ] || _ce "${d#$1/}: scope directory has no scope.yml" - for f in "$d"/*; do - [ -f "$f" ] && [ "${f##*/}" != scope.yml ] && _ce "${f#$1/}: stray file (scopes hold only scope.yml, blocks/, docs/, harness/, layouts/, child scopes)" - done - done < /dev/null 2>> "$_CHECK_ERRS" || true - done </dev/null) || continue - while IFS= read -r p; do [ -n "$p" ] && _ck_entry "${f#$1/}" recommend "$p" any; done </dev/null) || continue - [ -n "$(yaml_get "$recs" name)" ] || _ce "$rel: missing name" - [ -n "$(yaml_get "$recs" description)" ] || _ce "$rel: missing description" - for k in $(yaml_keys "$recs"); do - case $k in name|description|body|references) ;; *) _ce "$rel: unknown key '$k'" ;; esac - done - while IFS= read -r p; do [ -n "$p" ] && _ck_entry "$rel" body "$p" body; done </dev/null) || continue - name=$(yaml_get "$recs" name) - dup=${f%/manifest.yml}; dup=${dup##*/} - [ -n "$name" ] || _ce "$rel: missing name" - [ "$name" = "$dup" ] || _ce "$rel: name '$name' must equal its directory '$dup'" - in_list "$name" "$names" && _ce "$rel: layout name '$name' is not unique" - names="$names -$name" - h=$(yaml_get "$recs" harness) - if [ -z "$h" ]; then _ce "$rel: missing harness" - elif [ ! -f "$DELPHI_ROOT/lib/harness/$h.sh" ]; then _ce "$rel: unknown harness '$h'" - else # compile it: catches missing paths, empty globs, duplicate outputs, bad skill specs - i=$((i + 1)); mkdir "$_CHECK_ERRS.$i" - ( compile "$1" "$(dirname "${rel#context/}")" "$_CHECK_ERRS.$i" ) 2>&1 > /dev/null | sed "s#^delphi: #$rel: #" >> "$_CHECK_ERRS" || true - fi - for k in $(yaml_keys "$recs"); do - case $k in name|harness|instructions|blocks|docs|skills|mcp|settings|repos) ;; *) _ce "$rel: unknown key '$k'" ;; esac - done - n=$(yaml_list "$recs" settings | awk 'NF' | awk 'END { print NR }') - [ "$n" -le 1 ] || _ce "$rel: at most one settings file" - for k in instructions skills mcp settings; do - while IFS= read -r p; do [ -n "$p" ] && _ck_entry "$rel" "$k" "$p" "$k"; done <newdate\n", NR }' "$1/moves.tsv" >> "$_CHECK_ERRS" - - # MCP fragments are valid JSON (only if jq is installed) - if command -v jq > /dev/null 2>&1; then - while IFS= read -r f; do - [ -z "$f" ] && continue - { printf '{'; cat "$f"; printf '}'; } | jq empty > /dev/null 2>&1 || _ce "${f#$1/}: not a valid mcpServers member" - done <&2; return 1; fi - return 0 -} diff --git a/lib/compile.sh b/lib/compile.sh deleted file mode 100644 index 7fabfef..0000000 --- a/lib/compile.sh +++ /dev/null @@ -1,182 +0,0 @@ -# compile.sh — layout -> workspace files + .delphi/lock.tsv (segment map). Deterministic. -# -# compile -# a checked-out Delphi tree (usually a temp worktree at a specific commit) -# layout directory relative to context/ (…/layouts/) -# empty directory to write into -# Requires parse.sh. Sets R_HARNESS to the layout's harness name. - -_r_count() { if [ -f "$1" ]; then wc -l < "$1" | tr -d ' '; else echo 0; fi; } - -# _r_seg -_r_seg() { printf '%s\t%s\t%s\t%s\t%s\n' "$1" "$2" "$3" "$4" "$5" >> "$R_LOCK"; } - -# _r_file : append a source file (relative to context/) to an output file. -_r_file() { - local out="$R_OUT/$1" src start end - src=$(safe_path "$R_SRC/context" "$2") || exit 1 - [ -f "$src" ] || die "compile: missing file context/$2" - [ -s "$src" ] || die "compile: empty file context/$2" - mkdir -p "$(dirname "$out")" - start=$(( $(_r_count "$out") + 1 )) - cat "$src" >> "$out" - [ -n "$(tail -c 1 "$src")" ] && printf '\n' >> "$out" - end=$(_r_count "$out") - _r_seg "$1" "$start" "$end" "$2" "$(git hash-object "$src")" -} - -# _r_copy : 1:1 copy (keeps the executable bit); the output path must not already exist. -_r_copy() { - [ -e "$R_OUT/$1" ] && die "compile: two sources map to the same output '$1'" - _r_file "$1" "$2" - if [ -x "$R_SRC/context/$2" ]; then chmod +x "$R_OUT/$1" || die "compile: cannot chmod $1"; fi -} - -# _r_text