From ecbd788b7cc2b327e69e95a35f7958f5863c1d3e Mon Sep 17 00:00:00 2001 From: Roberto Cano <3525807+robercano@users.noreply.github.com> Date: Fri, 3 Jul 2026 11:59:30 +0200 Subject: [PATCH 1/2] =?UTF-8?q?feat(commands):=20/pr-loop-self=20=E2=80=94?= =?UTF-8?q?=20durable=20self-hosted=20PR=20loop=20(closes=20#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add .claude/commands/pr-loop-self.md, mirroring pr-loop.md but pointing every gate/agent at .claude/self/gates.json (self modules, correctness+tests lenses, consensus all) with a distinct "self-hosted autonomous PR loop" cron marker so it never collides with the base loop. Document the command in docs/USAGE.md and .claude/self/README.md, replacing the old "next increment on issue #11" placeholder note. --- .claude/commands/pr-loop-self.md | 32 ++++++++++++++++++++++++++++++++ .claude/self/README.md | 8 ++++++-- docs/USAGE.md | 5 +++++ 3 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 .claude/commands/pr-loop-self.md diff --git a/.claude/commands/pr-loop-self.md b/.claude/commands/pr-loop-self.md new file mode 100644 index 0000000..ac1002e --- /dev/null +++ b/.claude/commands/pr-loop-self.md @@ -0,0 +1,32 @@ +--- +description: Arm (or re-arm) the self-hosted autonomous PR-loop cron and run one tick now +--- + +You are (re)arming this project's **self-hosted** autonomous PR loop — the loop that works THIS repo's own +`.claude`/`docs`/`examples`/`.github` backlog instead of a downstream project's. 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`. 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 autonomous PR loop", leave it (do not duplicate) and report its id + schedule. (This phrase is distinct from the base loop's "autonomous PR loop" marker so the two loops never cross-match.) +- Otherwise `CronCreate` with `durable: true`, schedule `*/5 * * * *`, and the EXACT prompt below (STEP 0 will self-adjust the cadence on the first tick). + +Prompt to use (the tick logic, with adaptive STEP 0): + +> Run one tick of the self-hosted autonomous 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 `. 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 `` 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--* 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." + +## 2. Run one tick now +Execute steps 1–5 above immediately so the loop doesn't wait for the next cron fire. Report what happened (polled items, merges, feedback addressed, issue advanced — or "no actionable activity"). + +Notes: requires the bot machine account set up per docs/USAGE.md (`GH_BOT_TOKEN` in `.env`, bot is a write collaborator) so PRs are bot-authored and the owner can formally Approve them. Cadence is adaptive: FAST (every minute) whenever there's ≥1 open PR or ≥1 open self `module:*` issue, else a responsive IDLE poll (every 5 minutes) that flips to FAST within minutes of new work. For a tighter in-session cadence you can also run `/loop 5m /pr-loop-self`. diff --git a/.claude/self/README.md b/.claude/self/README.md index 935e75d..6330d20 100644 --- a/.claude/self/README.md +++ b/.claude/self/README.md @@ -32,8 +32,12 @@ To have the autonomous loop work this repo's own `module:*` backlog: **`.claude/self/gates.json`** as its adapter (module map + gates) for this repo. The generic agents/scripts otherwise behave identically — worker boundaries come from this file's `modules`, gates from its `gates`. -> A dedicated self-host loop command that wires this automatically is the next increment on issue #11; for now -> the mechanism above is explicit. +The durable, first-class way to do this is **`/pr-loop-self`** (`.claude/commands/pr-loop-self.md`) — it +mirrors `/pr-loop` exactly (arm/re-arm cron, adaptive cadence, poll → merge → address-feedback → advance) but +carries `GATES_FILE=.claude/self/gates.json` through every gate call and every spawned agent, and adapts on +the self modules (`module:docs`/`module:harness`/`module:examples`/`module:ci`) instead of the project's own +`gates.json`. It uses a distinct cron identity marker ("self-hosted autonomous PR loop") so it never collides +with a `/pr-loop` job in the same session. ## Self-hosting promotion (the one gotcha) Agent-definition / `settings.json` / hook changes only take effect on a **fresh session**. So when the loop diff --git a/docs/USAGE.md b/docs/USAGE.md index 354181d..54b3a25 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -146,6 +146,11 @@ decide what the loop actually touches: `gates.json.modules[]`, and (b) the issue carries the matching `module:*` label. Commenting "approved" on an issue does nothing — nothing watches issue text. +**Self-hosting this repo's own backlog?** **`/pr-loop-self`** (`.claude/commands/pr-loop-self.md`) runs the +same loop mechanics self-hosted, against this repo's own `.claude`/`docs`/`examples`/`.github` backlog, using +**`.claude/self/gates.json`** as the adapter (module map, gates, review lenses) instead of the placeholder +`.claude/gates.json` above. See `.claude/self/README.md` for the self-adapter contract. + New project? Wire this up with the **[new-project configuration checklist](GETTING_STARTED.md#new-project-configuration-checklist)**. From 2eb1fe1b50f8b03c4355246e2ed290a8dea5303c Mon Sep 17 00:00:00 2001 From: Roberto Cano <3525807+robercano@users.noreply.github.com> Date: Fri, 3 Jul 2026 12:05:22 +0200 Subject: [PATCH 2/2] fix(pr-loop-self): rename marker to avoid cross-match with base loop The self loop's armed prompt began "Run one tick of the self-hosted autonomous PR loop", which is a superstring of the base loop's marker "autonomous PR loop". Since /pr-loop's idempotency check matches any job whose prompt contains "autonomous PR loop", arming /pr-loop-self first would cause /pr-loop to falsely believe the base loop was already armed and silently skip creating its own cron job. Rename the self loop's identity to "self-hosted PR loop" everywhere (description, prose, armed prompt opening line, and this command's own idempotency matcher) so the exact substring "autonomous PR loop" never appears in the self loop's armed prompt, and "self-hosted PR loop" never appears in the base loop's prompt. Correct the non-collision claim to state the guarantee in both directions. --- .claude/commands/pr-loop-self.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.claude/commands/pr-loop-self.md b/.claude/commands/pr-loop-self.md index ac1002e..216b61d 100644 --- a/.claude/commands/pr-loop-self.md +++ b/.claude/commands/pr-loop-self.md @@ -1,8 +1,8 @@ --- -description: Arm (or re-arm) the self-hosted autonomous PR-loop cron and run one tick now +description: Arm (or re-arm) the self-hosted PR-loop cron and run one tick now --- -You are (re)arming this project's **self-hosted** autonomous PR loop — the loop that works THIS repo's own +You are (re)arming this project's self-hosted PR loop — the loop that works THIS repo's own `.claude`/`docs`/`examples`/`.github` backlog instead of a downstream project's. 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. @@ -10,12 +10,12 @@ the start of each new session. This command restores the whole loop in one step. 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`. ## 1. (Re)arm the cron — idempotent -- Call `CronList`. If a job already exists whose prompt mentions "self-hosted autonomous PR loop", leave it (do not duplicate) and report its id + schedule. (This phrase is distinct from the base loop's "autonomous PR loop" marker so the two loops never cross-match.) +- 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.) - Otherwise `CronCreate` with `durable: true`, schedule `*/5 * * * *`, and the EXACT prompt below (STEP 0 will self-adjust the cadence on the first tick). Prompt to use (the tick logic, with adaptive STEP 0): -> Run one tick of the self-hosted autonomous 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 `. 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/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 `. 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. >