diff --git a/.claude/.claude-plugin/README.md b/.claude/.claude-plugin/README.md new file mode 100644 index 0000000..e4bcd78 --- /dev/null +++ b/.claude/.claude-plugin/README.md @@ -0,0 +1,31 @@ +# `orchestrator` plugin + +This repository IS the plugin. The plugin root is `.claude/` (where this manifest lives), and the +repo dogfoods its own harness: the same `.claude/agents`, `.claude/commands`, and `.claude/scripts` +that ship to downstream installs are what runs the live self-hosted PR loop here. + +## Dual command invocation +- **In-repo (dogfooding):** commands run as project-level slash commands, e.g. `/pr-loop`, + `/pr-loop-self`, `/harden`, `/setup-orchestrator`, `/test-pr`. +- **Installed as a plugin:** Claude Code auto-namespaces commands under the plugin `name` + (`orchestrator`), so the same commands become `/orchestrator:pr-loop`, + `/orchestrator:pr-loop-self`, etc. No file renames are needed for this — the namespace comes + from `name` in `plugin.json`, not from filenames. + +## The `${CLAUDE_PLUGIN_ROOT:-.claude}` fallback +Agent/command prompts invoke scripts as: + + bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/.sh + +When the plugin is installed, Claude Code sets `CLAUDE_PLUGIN_ROOT` to the install path and the +shipped `scripts/` resolve there. In-repo, the variable is unset, so the fallback expands to +`.claude`, giving the exact same `.claude/scripts/.sh` path the harness has always used — +the live self-hosting loop is unaffected. + +## What's plugin-distributable (phase 1 scope) +Auto-discovered from the plugin root: `commands/`, `agents/`, `hooks/hooks.json`, `scripts/`. + +Not distributed by this plugin (repo-scaffolded, project-specific): +- `.claude/workflows/*.js` — deterministic fan-out workflows, not plugin-portable. +- `.claude/self/*` — this repo's OWN self-hosting adapter (gates, checks), not for downstream + projects; downstream adopters get the placeholder `.claude/gates.json` instead. diff --git a/.claude/.claude-plugin/plugin.json b/.claude/.claude-plugin/plugin.json new file mode 100644 index 0000000..2b29db1 --- /dev/null +++ b/.claude/.claude-plugin/plugin.json @@ -0,0 +1,6 @@ +{ + "name": "orchestrator", + "version": "0.1.0", + "description": "Multi-agent orchestration harness: fan out sub-tasks to isolated worktree implementers, gate them, and route results through reviewers.", + "author": { "name": "Roberto Cano" } +} diff --git a/.claude/agents/implementer.md b/.claude/agents/implementer.md index 7b0749b..63e5c97 100644 --- a/.claude/agents/implementer.md +++ b/.claude/agents/implementer.md @@ -16,7 +16,7 @@ Never call bare `gh`. EVERY `gh` invocation (PR create/update, comments, `gh api - `CLAUDE.md` — conventions, style, definition of done. ## Workflow -1. **Bootstrap your worktree.** Your worktree is a fresh checkout that lacks toolchain state living outside the tree (`node_modules`, Foundry libs from `forge install`, shared caches). Run `bash .claude/scripts/worktree.sh setup` first if present — it runs the adapter's `worktree.setup` hook so that *every* gate is runnable here, not just in the main checkout. Empty/unconfigured = it skips harmlessly. If setup fails, fix it before proceeding — a half-bootstrapped worktree makes gates lie. +1. **Bootstrap your worktree.** Your worktree is a fresh checkout that lacks toolchain state living outside the tree (`node_modules`, Foundry libs from `forge install`, shared caches). Run `bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/worktree.sh setup` first if present — it runs the adapter's `worktree.setup` hook so that *every* gate is runnable here, not just in the main checkout. Empty/unconfigured = it skips harmlessly. If setup fails, fix it before proceeding — a half-bootstrapped worktree makes gates lie. 2. **Explore, don't guess.** Delegate codebase discovery to the `Explore` subagent to map the files you'll touch. Stay read-only until you understand the area. 3. **Respect your boundary.** You were assigned a module/path. NEVER edit files outside it. If the task truly requires touching another module, stop and report back to the orchestrator — do not reach across the boundary. 4. **Implement in small commits.** Match surrounding code style. Write/extend tests alongside the change. @@ -26,7 +26,7 @@ Never call bare `gh`. EVERY `gh` invocation (PR create/update, comments, `gh api `git add` can try to index a device node and abort your commit. Add the files you actually changed, by name. Ignore any `crw-` device-node entries `git status` shows — they are sandbox masks, not your work. 5. **Self-gate before declaring done.** Run, in order, the commands from `.claude/gates.json`: `build` → `lint` → `typecheck` → `test_affected` → `coverage`. Use `.claude/scripts/gate.sh ` if present. Fix anything that fails. Do not report done with a red gate. -6. **Open a PR** (or leave the branch ready, per `CLAUDE.md` merge policy). Then run `bash .claude/scripts/worktree.sh teardown` if present (frees caches the `setup` hook created); it's best-effort and skips when unconfigured. +6. **Open a PR** (or leave the branch ready, per `CLAUDE.md` merge policy). Then run `bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/worktree.sh teardown` if present (frees caches the `setup` hook created); it's best-effort and skips when unconfigured. 7. **Report back** in this format: ``` - Sub-task: diff --git a/.claude/agents/test-runner.md b/.claude/agents/test-runner.md index fc1cab9..9061948 100644 --- a/.claude/agents/test-runner.md +++ b/.claude/agents/test-runner.md @@ -11,7 +11,7 @@ You run the project's gates and report results. You do not fix code — you repo If any gate or check needs `gh` (e.g. fetching CI status via `gh api`/`gh run`), call it through `.claude/scripts/bot-gh.sh`, never bare `gh`, so it runs as the bot. ## What to run -If you're gating a **fresh isolated worktree** (not the main checkout), first run `bash .claude/scripts/worktree.sh setup` if present — it bootstraps toolchain state that lives outside the tree (`node_modules`, `forge install`, caches) so gates don't fail for lack of setup rather than real defects. Empty/unconfigured = skips. +If you're gating a **fresh isolated worktree** (not the main checkout), first run `bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/worktree.sh setup` if present — it bootstraps toolchain state that lives outside the tree (`node_modules`, `forge install`, caches) so gates don't fail for lack of setup rather than real defects. Empty/unconfigured = skips. Read `.claude/gates.json` and run the requested gates (or all configured ones) using `.claude/scripts/gate.sh <name>` when available, else the raw command from the file. Typical order: `install` (if needed) → `build` → `lint` → `typecheck` → `test` (or `test_affected`) → `coverage` → `e2e` → `security`. diff --git a/.claude/commands/pr-loop-self.md b/.claude/commands/pr-loop-self.md index 216b61d..e247f5f 100644 --- a/.claude/commands/pr-loop-self.md +++ b/.claude/commands/pr-loop-self.md @@ -7,7 +7,7 @@ You are (re)arming this project's self-hosted PR loop — the loop that works TH (cron jobs die when Claude Code exits and may not persist across restarts even when durable), so it is lost at the start of each new session. This command restores the whole loop in one step. Do BOTH parts. -The repo is derived from the git remote (`bash .claude/scripts/bot-gh.sh repo view --json nameWithOwner -q .nameWithOwner`); the bot login defaults to `$BOT_LOGIN`. This command is self-hosting only: it always points every gate and every agent at `GATES_FILE=.claude/self/gates.json` (see `.claude/self/README.md`), never the placeholder root `.claude/gates.json`. +The repo is derived from the git remote (`bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/bot-gh.sh repo view --json nameWithOwner -q .nameWithOwner`); the bot login defaults to `$BOT_LOGIN`. This command is self-hosting only: it always points every gate and every agent at `GATES_FILE=.claude/self/gates.json` (see `.claude/self/README.md`), never the placeholder root `.claude/gates.json`. ## 1. (Re)arm the cron — idempotent - Call `CronList`. If a job already exists whose prompt mentions "self-hosted PR loop", leave it (do not duplicate) and report its id + schedule. (This job's armed prompt never contains the base loop's exact marker substring "autonomous PR loop" — it says "self-hosted PR loop" instead — so `/pr-loop`'s idempotency check, which matches on "autonomous PR loop", will never match this job. Conversely, the base loop's armed prompt never contains "self-hosted PR loop", so this command's idempotency check will never match the base loop's job. The two loops therefore never cross-match in either direction.) @@ -15,14 +15,14 @@ The repo is derived from the git remote (`bash .claude/scripts/bot-gh.sh repo vi Prompt to use (the tick logic, with adaptive STEP 0): -> Run one tick of the self-hosted PR loop. Resolve the repo with `bash .claude/scripts/bot-gh.sh repo view --json nameWithOwner -q .nameWithOwner`. Export `GATES_FILE=.claude/self/gates.json` for every gate/orchestration step, and instruct every spawned agent (orchestrator, implementers, reviewers) to read `.claude/self/gates.json` — NOT the placeholder root `.claude/gates.json` — as its adapter (module map, gates, review lenses). Follow docs/USAGE.md and .claude/agents/* for mechanics; reviewer lenses + consensus per `.claude/self/gates.json` (`correctness`, `tests`; consensus `all`). Every `gate.sh` invocation MUST be run as `GATES_FILE=.claude/self/gates.json bash .claude/scripts/gate.sh <name>`. ALL `gh` interaction (yours and every agent's) MUST run as the bot via `.claude/scripts/bot-gh.sh` — never bare `gh`; only `git` commits/pushes stay as the owner. +> Run one tick of the self-hosted PR loop. Resolve the repo with `bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/bot-gh.sh repo view --json nameWithOwner -q .nameWithOwner`. Export `GATES_FILE=.claude/self/gates.json` for every gate/orchestration step, and instruct every spawned agent (orchestrator, implementers, reviewers) to read `.claude/self/gates.json` — NOT the placeholder root `.claude/gates.json` — as its adapter (module map, gates, review lenses). Follow docs/USAGE.md and .claude/agents/* for mechanics; reviewer lenses + consensus per `.claude/self/gates.json` (`correctness`, `tests`; consensus `all`). Every `gate.sh` invocation MUST be run as `GATES_FILE=.claude/self/gates.json bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/gate.sh <name>`. ALL `gh` interaction (yours and every agent's) MUST run as the bot via `.claude/scripts/bot-gh.sh` — never bare `gh`; only `git` commits/pushes stay as the owner. > > STEP 0 — adaptive cadence: count open PRs (base = `.claude/self/gates.json` merge.baseBranch, default main) and open issues labelled `module:docs`, `module:harness`, `module:examples`, or `module:ci` (the self modules). Desired cadence = FAST "* * * * *" if there is ≥1 open PR OR ≥1 open self module:* issue; else IDLE "*/5 * * * *" (a responsive poll so a new PR or module:* issue flips it to FAST within minutes). If this job's current schedule != desired, CronDelete this job and CronCreate a durable replacement with this SAME prompt at the desired schedule. > > Then, in order: -> 1. POLL: run `bash .claude/scripts/notify-poll.sh`; summarize new issues / PR comments / reviews and the open-PR status section. -> 2. MERGE: run `bash .claude/scripts/merge-ready.sh`; report each PR merged or why skipped. (It only merges PRs the owner APPROVED that are CI-green & mergeable; never approves.) -> 3. ADDRESS FEEDBACK: run `bash .claude/scripts/pr-feedback.sh`; for each PR it lists (bot-authored, with unaddressed CHANGES_REQUESTED), run orchestrator→worktree implementer→reviewer-lenses (self adapter: `GATES_FILE=.claude/self/gates.json`, lenses `correctness`/`tests`, consensus `all`) on the SAME branch, push to update the PR in place, and post the `<!-- claude-addressed -->` marker comment via bot-gh.sh. Do NOT merge here. +> 1. POLL: run `bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/notify-poll.sh`; summarize new issues / PR comments / reviews and the open-PR status section. +> 2. MERGE: run `bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/merge-ready.sh`; report each PR merged or why skipped. (It only merges PRs the owner APPROVED that are CI-green & mergeable; never approves.) +> 3. ADDRESS FEEDBACK: run `bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/pr-feedback.sh`; for each PR it lists (bot-authored, with unaddressed CHANGES_REQUESTED), run orchestrator→worktree implementer→reviewer-lenses (self adapter: `GATES_FILE=.claude/self/gates.json`, lenses `correctness`/`tests`, consensus `all`) on the SAME branch, push to update the PR in place, and post the `<!-- claude-addressed -->` marker comment via bot-gh.sh. Do NOT merge here. > 4. ADVANCE: ONLY when there are ZERO open PRs — pick the lowest-numbered open self `module:*` issue (`module:docs`, `module:harness`, `module:examples`, `module:ci`) with no feat/issue-<n>-* branch; drive it through the orchestrator using `.claude/self/gates.json` as the adapter (scope → worktree implementer → `GATES_FILE=.claude/self/gates.json gate.sh` gates → reviewer lenses `correctness`/`tests` consensus `all` → bot PR). One issue in flight at a time. > 5. If nothing actionable, reply exactly one line: "No actionable activity." diff --git a/.claude/commands/pr-loop.md b/.claude/commands/pr-loop.md index 3332055..61c0dd5 100644 --- a/.claude/commands/pr-loop.md +++ b/.claude/commands/pr-loop.md @@ -4,7 +4,7 @@ description: Arm (or re-arm) the autonomous PR-loop cron and run one tick now You are (re)arming this project's autonomous PR loop. The loop is session-scoped (cron jobs die when Claude Code exits and may not persist across restarts even when durable), so it is lost at the start of each new session. This command restores the whole loop in one step. Do BOTH parts. -The repo is derived from the git remote (`bash .claude/scripts/bot-gh.sh repo view --json nameWithOwner -q .nameWithOwner`); the bot login defaults to `$BOT_LOGIN`. Nothing here is project-specific — it reads `.claude/gates.json`, `.claude/scripts/*`, and `docs/USAGE.md`. +The repo is derived from the git remote (`bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/bot-gh.sh repo view --json nameWithOwner -q .nameWithOwner`); the bot login defaults to `$BOT_LOGIN`. Nothing here is project-specific — it reads `.claude/gates.json`, `.claude/scripts/*`, and `docs/USAGE.md`. ## 1. (Re)arm the cron — idempotent - Call `CronList`. If a job already exists whose prompt mentions "autonomous PR loop", leave it (do not duplicate) and report its id + schedule. @@ -12,14 +12,14 @@ The repo is derived from the git remote (`bash .claude/scripts/bot-gh.sh repo vi Prompt to use (the tick logic, with adaptive STEP 0): -> Run one tick of the autonomous PR loop. Resolve the repo with `bash .claude/scripts/bot-gh.sh repo view --json nameWithOwner -q .nameWithOwner`. Follow docs/USAGE.md and .claude/agents/*; reviewer lenses + consensus per .claude/gates.json. ALL `gh` interaction (yours and every agent's) MUST run as the bot via `.claude/scripts/bot-gh.sh` — never bare `gh`; only `git` commits/pushes stay as the owner. +> Run one tick of the autonomous PR loop. Resolve the repo with `bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/bot-gh.sh repo view --json nameWithOwner -q .nameWithOwner`. Follow docs/USAGE.md and .claude/agents/*; reviewer lenses + consensus per .claude/gates.json. ALL `gh` interaction (yours and every agent's) MUST run as the bot via `.claude/scripts/bot-gh.sh` — never bare `gh`; only `git` commits/pushes stay as the owner. > > STEP 0 — adaptive cadence: count open PRs (base = gates.json merge.baseBranch, default main) and open issues labelled module:*. Desired cadence = FAST "* * * * *" if there is ≥1 open PR OR ≥1 open module:* issue; else IDLE "*/5 * * * *" (a responsive poll so a new PR or module:* issue flips it to FAST within minutes). If this job's current schedule != desired, CronDelete this job and CronCreate a durable replacement with this SAME prompt at the desired schedule. > > Then, in order: -> 1. POLL: run `bash .claude/scripts/notify-poll.sh`; summarize new issues / PR comments / reviews and the open-PR status section. -> 2. MERGE: run `bash .claude/scripts/merge-ready.sh`; report each PR merged or why skipped. (It only merges PRs the owner APPROVED that are CI-green & mergeable; never approves.) -> 3. ADDRESS FEEDBACK: run `bash .claude/scripts/pr-feedback.sh`; for each PR it lists (bot-authored, with unaddressed CHANGES_REQUESTED), run orchestrator→worktree implementer→reviewer-lenses on the SAME branch, push to update the PR in place, and post the `<!-- claude-addressed -->` marker comment via bot-gh.sh. Do NOT merge here. +> 1. POLL: run `bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/notify-poll.sh`; summarize new issues / PR comments / reviews and the open-PR status section. +> 2. MERGE: run `bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/merge-ready.sh`; report each PR merged or why skipped. (It only merges PRs the owner APPROVED that are CI-green & mergeable; never approves.) +> 3. ADDRESS FEEDBACK: run `bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/pr-feedback.sh`; for each PR it lists (bot-authored, with unaddressed CHANGES_REQUESTED), run orchestrator→worktree implementer→reviewer-lenses on the SAME branch, push to update the PR in place, and post the `<!-- claude-addressed -->` marker comment via bot-gh.sh. Do NOT merge here. > 4. ADVANCE: ONLY when there are ZERO open PRs — pick the lowest-numbered open module:* issue with no feat/issue-<n>-* branch; drive it through the orchestrator (scope → worktree implementer → gate.sh gates → reviewer lenses → bot PR). One issue in flight at a time. > 5. If nothing actionable, reply exactly one line: "No actionable activity." diff --git a/.claude/commands/setup-orchestrator.md b/.claude/commands/setup-orchestrator.md index 20b5290..135e80e 100644 --- a/.claude/commands/setup-orchestrator.md +++ b/.claude/commands/setup-orchestrator.md @@ -13,7 +13,7 @@ and defer to them on any detail. Be conversational but efficient. Use the `AskUserQuestion` tool for discrete choices; ask for free-text (names, paths, shell commands) in plain prose. **Never invent values** — if you don't know a command or path, ask. **Propose the final files and get an explicit "yes" before writing.** All `gh` runs through -`bash .claude/scripts/bot-gh.sh`, never bare `gh`. +`bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/bot-gh.sh`, never bare `gh`. Do these in order. Stop and report if a step genuinely can't proceed. @@ -25,7 +25,7 @@ Do these in order. Stop and report if a step genuinely can't proceed. labels. Then **ask the user whether to continue or stop** (use `AskUserQuestion`). If they choose stop, end the command cleanly with no changes. If they continue, proceed with the flow. (You still confirm before each file write in later steps, so a re-run can't clobber silently.) -- Resolve the repo: `bash .claude/scripts/bot-gh.sh repo view --json nameWithOwner -q .nameWithOwner`. +- Resolve the repo: `bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/bot-gh.sh repo view --json nameWithOwner -q .nameWithOwner`. ## 2. Explain the model up front (so answers are informed) Briefly tell the user how the loop decides what to build: @@ -66,12 +66,12 @@ Ensure these are gitignored (append if missing, don't duplicate): `.env` (holds `.claude/state/` (the notify-poll cursor). Verify with `git check-ignore <path>`. ## 6. Create the module labels -For every module `name`: `bash .claude/scripts/bot-gh.sh label create "module:<name>" --description "<desc>" --force`. +For every module `name`: `bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/bot-gh.sh label create "module:<name>" --description "<desc>" --force`. Report created vs already-existing. Remind: **an issue is only loop-eligible once it carries a `module:*` label.** ## 7. Verify the bot account - Confirm `.env` has `GH_BOT_TOKEN` and the bot can see the repo: - `bash .claude/scripts/bot-gh.sh api user --jq .login` and a `repo view` on the resolved repo. + `bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/bot-gh.sh api user --jq .login` and a `repo view` on the resolved repo. - If missing/no access, DON'T fail the whole setup — point at the one-time setup notes in `.claude/scripts/bot-gh.sh` (create machine account → add as **write** collaborator → classic `repo`-scope token → `.env`) and mark this step "action needed". diff --git a/.claude/commands/test-pr.md b/.claude/commands/test-pr.md index 8f9fa5e..f01d7a4 100644 --- a/.claude/commands/test-pr.md +++ b/.claude/commands/test-pr.md @@ -8,7 +8,7 @@ working tree. The PR number is: **$ARGUMENTS** Do this: -1. Run `bash .claude/scripts/prepare-pr.sh $ARGUMENTS`. It resolves the PR's head branch, fetches it, creates (or +1. Run `bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/prepare-pr.sh $ARGUMENTS`. It resolves the PR's head branch, fetches it, creates (or refreshes) a **detached** worktree at `<humanTest.worktreeDir>/pr-$ARGUMENTS` (default `.worktrees/pr-<n>`), and runs the project's `humanTest.prepare` command (install + build) inside it. The script is idempotent — re-running it on the same PR just fast-forwards the worktree to the latest pushed commit and rebuilds. diff --git a/.claude/hooks/hooks.json b/.claude/hooks/hooks.json new file mode 100644 index 0000000..e2b9a69 --- /dev/null +++ b/.claude/hooks/hooks.json @@ -0,0 +1,23 @@ +{ + "PostToolUse": [ + { + "matcher": "Edit|Write", + "hooks": [ + { + "type": "command", + "command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/gate.sh lint" + } + ] + } + ], + "Stop": [ + { + "hooks": [ + { + "type": "command", + "command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/gate.sh test_affected" + } + ] + } + ] +} diff --git a/.claude/self/checks.sh b/.claude/self/checks.sh index 0a1bade..bb74a6e 100644 --- a/.claude/self/checks.sh +++ b/.claude/self/checks.sh @@ -16,7 +16,7 @@ json_parse() { node -e "JSON.parse(require('fs').readFileSync(process.argv[1],'u do_build() { local rc=0 - for f in .claude/gates.json .claude/self/gates.json .claude/settings.json; do + for f in .claude/gates.json .claude/self/gates.json .claude/settings.json .claude/.claude-plugin/plugin.json .claude/hooks/hooks.json; do if [ ! -f "$f" ]; then echo "build: missing $f"; rc=1; continue; fi if ! json_parse "$f" 2>/dev/null; then echo "build: invalid JSON — $f"; rc=1; fi done