Skip to content

Commit 4634a6c

Browse files
Merge pull request #72 from robercano/feat/issue-approval-labels
feat(loop): formal issue approval via backlog → planned labels (owner-only)
2 parents 57b23d2 + 8388b73 commit 4634a6c

5 files changed

Lines changed: 44 additions & 20 deletions

File tree

.claude/commands/pr-loop-self.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,18 @@ Prompt to use (the tick logic, with adaptive STEP 0):
1717

1818
> 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.
1919
>
20-
> STEP 0 — adaptive cadence (every tick is a fresh full-context session, so cadence is the loop's dominant token cost — fire fast ONLY when the loop can act): count open PRs (base = `.claude/self/gates.json` merge.baseBranch, default main), open issues labelled `module:docs`, `module:harness`, `module:examples`, or `module:ci` (the self modules), and bot PRs with unaddressed CHANGES_REQUESTED (per pr-feedback.sh). Desired cadence = FAST "* * * * *" only if the loop has something to DO right now: ≥1 PR with unaddressed feedback, OR zero open PRs AND ≥1 open self module:* issue (ready to advance). WATCH "*/5 * * * *" if PRs are open but merely waiting on human review or CI — the loop can't hurry a human. Else IDLE "*/15 * * * *". If this job's current schedule != desired, CronDelete this job and CronCreate a durable replacement with this SAME prompt at the desired schedule.
20+
> STEP 0 — adaptive cadence (every tick is a fresh full-context session, so cadence is the loop's dominant token cost — fire fast ONLY when the loop can act): count open PRs (base = `.claude/self/gates.json` merge.baseBranch, default main), open issues labelled **`planned` AND** one of `module:docs`, `module:harness`, `module:examples`, `module:ci` (the self modules) — `backlog` issues are NOT work, and only the repo OWNER may add the `planned` label; you must never add it — and bot PRs with unaddressed CHANGES_REQUESTED (per pr-feedback.sh). Desired cadence = FAST "* * * * *" only if the loop has something to DO right now: ≥1 PR with unaddressed feedback, OR zero open PRs AND ≥1 open `planned` self module:* issue (ready to advance). WATCH "*/5 * * * *" if PRs are open but merely waiting on human review or CI — the loop can't hurry a human. Else IDLE "*/15 * * * *". If this job's current schedule != desired, CronDelete this job and CronCreate a durable replacement with this SAME prompt at the desired schedule.
2121
>
2222
> Then, in order:
2323
> 1. POLL: run `bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/notify-poll.sh`; summarize new issues / PR comments / reviews and the open-PR status section.
2424
> 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.) After merging, it fast-forwards the LOCAL checkout to `main` when it is clean and already on `main` — so your terminal/IDE shows the latest without a manual pull. It never switches branches or clobbers uncommitted work; anything else is skipped (`local_sync` line reports the reason).
2525
> 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.
26-
> 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.
26+
> 4. ADVANCE: ONLY when there are ZERO open PRs — pick the lowest-numbered open issue labelled **`planned`** AND a self `module:*` label (`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. `backlog` issues are owner-unapproved: never pick them, and if you file an issue yourself, label it `backlog` — NEVER `planned` (that label is the owner's formal approval and is assigned by the owner alone; see docs/USAGE.md → "Autonomous loop & the issue queue").
2727
> 5. If nothing actionable, reply exactly one line: "No actionable activity."
2828
>
2929
> Token discipline: only read docs/USAGE.md and .claude/agents/* when a step actually orchestrates agents (3–4); poll/merge-only ticks need only the script outputs. Keep the tick report to a few lines — it is telemetry, not documentation.
3030
3131
## 2. Run one tick now
3232
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").
3333

34-
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 and biased toward cheap ticks (each tick is a fresh full-context session): FAST (every minute) only while the loop has actionable work — unaddressed PR feedback, or a self `module:*` issue ready to advance with no PR in flight; WATCH (every 5 minutes) while PRs wait on human review/CI; IDLE (every 15 minutes) otherwise. New work is picked up within one WATCH/IDLE interval. For a tighter in-session cadence you can also run `/loop 5m /pr-loop-self`.
34+
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 and biased toward cheap ticks (each tick is a fresh full-context session): FAST (every minute) only while the loop has actionable work — unaddressed PR feedback, or a `planned` self `module:*` issue ready to advance with no PR in flight; WATCH (every 5 minutes) while PRs wait on human review/CI; IDLE (every 15 minutes) otherwise. New work is picked up within one WATCH/IDLE interval. For a tighter in-session cadence you can also run `/loop 5m /pr-loop-self`.

.claude/commands/pr-loop.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,18 @@ Prompt to use (the tick logic, with adaptive STEP 0):
1414

1515
> 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.
1616
>
17-
> STEP 0 — adaptive cadence (every tick is a fresh full-context session, so cadence is the loop's dominant token cost — fire fast ONLY when the loop can act): count open PRs (base = gates.json merge.baseBranch, default main), open issues labelled module:*, and bot PRs with unaddressed CHANGES_REQUESTED (per pr-feedback.sh). Desired cadence = FAST "* * * * *" only if the loop has something to DO right now: ≥1 PR with unaddressed feedback, OR zero open PRs AND ≥1 open module:* issue (ready to advance). WATCH "*/5 * * * *" if PRs are open but merely waiting on human review or CI — the loop can't hurry a human. Else IDLE "*/15 * * * *". If this job's current schedule != desired, CronDelete this job and CronCreate a durable replacement with this SAME prompt at the desired schedule.
17+
> STEP 0 — adaptive cadence (every tick is a fresh full-context session, so cadence is the loop's dominant token cost — fire fast ONLY when the loop can act): count open PRs (base = gates.json merge.baseBranch, default main), open issues labelled **`planned` AND** module:*`backlog` issues are NOT work, and only the repo OWNER may add the `planned` label; you must never add it — and bot PRs with unaddressed CHANGES_REQUESTED (per pr-feedback.sh). Desired cadence = FAST "* * * * *" only if the loop has something to DO right now: ≥1 PR with unaddressed feedback, OR zero open PRs AND ≥1 open `planned` module:* issue (ready to advance). WATCH "*/5 * * * *" if PRs are open but merely waiting on human review or CI — the loop can't hurry a human. Else IDLE "*/15 * * * *". If this job's current schedule != desired, CronDelete this job and CronCreate a durable replacement with this SAME prompt at the desired schedule.
1818
>
1919
> Then, in order:
2020
> 1. POLL: run `bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/notify-poll.sh`; summarize new issues / PR comments / reviews and the open-PR status section.
2121
> 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.) After merging, it fast-forwards the LOCAL checkout to `main` when it is clean and already on `main` — so your terminal/IDE shows the latest without a manual pull. It never switches branches or clobbers uncommitted work; anything else is skipped (`local_sync` line reports the reason).
2222
> 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.
23-
> 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.
23+
> 4. ADVANCE: ONLY when there are ZERO open PRs — pick the lowest-numbered open issue labelled **`planned`** AND module:* 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. `backlog` issues are owner-unapproved: never pick them, and if you file an issue yourself, label it `backlog` — NEVER `planned` (that label is the owner's formal approval and is assigned by the owner alone; see docs/USAGE.md → "Autonomous loop & the issue queue").
2424
> 5. If nothing actionable, reply exactly one line: "No actionable activity."
2525
>
2626
> Token discipline: only read docs/USAGE.md and .claude/agents/* when a step actually orchestrates agents (3–4); poll/merge-only ticks need only the script outputs. Keep the tick report to a few lines — it is telemetry, not documentation.
2727
2828
## 2. Run one tick now
2929
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").
3030

31-
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 and biased toward cheap ticks (each tick is a fresh full-context session): FAST (every minute) only while the loop has actionable work — unaddressed PR feedback, or an issue ready to advance with no PR in flight; WATCH (every 5 minutes) while PRs wait on human review/CI; IDLE (every 15 minutes) otherwise. New work is picked up within one WATCH/IDLE interval. For a tighter in-session cadence you can also run `/loop 5m /pr-loop`.
31+
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 and biased toward cheap ticks (each tick is a fresh full-context session): FAST (every minute) only while the loop has actionable work — unaddressed PR feedback, or a `planned` module:* issue ready to advance with no PR in flight; WATCH (every 5 minutes) while PRs wait on human review/CI; IDLE (every 15 minutes) otherwise. New work is picked up within one WATCH/IDLE interval. For a tighter in-session cadence you can also run `/loop 5m /pr-loop`.

.claude/scripts/seed-issues.sh

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ for m in "${modules[@]}"; do
4848
done
4949
gh label create "type:feature" --color 0052cc --description "Feature work scoped to one module" --force >/dev/null 2>&1 && echo " label ✓ type:feature" || true
5050
gh label create "type:infra" --color b60205 --description "Repo-wide tooling/infra (touches root config)" --force >/dev/null 2>&1 && echo " label ✓ type:infra" || true
51+
# Approval workflow labels (docs/USAGE.md → "Autonomous loop & the issue queue"):
52+
# every issue starts `backlog`; ONLY the repo owner moves it to `planned`, which
53+
# is what makes it loop-eligible (together with a module:* label).
54+
gh label create "backlog" --color bfd4f2 --description "Filed, not yet approved by the owner — the loop must NOT pick it up" --force >/dev/null 2>&1 && echo " label ✓ backlog" || true
55+
gh label create "planned" --color 0e8a16 --description "Owner-approved for the autonomous loop (assigned ONLY by the owner)" --force >/dev/null 2>&1 && echo " label ✓ planned" || true
5156

5257
# --- helper: create an issue unless an exact-title match already exists -------
5358
existing="$(gh issue list --state all --limit 500 --json title -q '.[].title' 2>/dev/null)"
@@ -64,9 +69,11 @@ BOUND="**Module boundary:** stay within this module's path; do not edit other mo
6469
# Replace the lines below with your real backlog. One mkissue per ticket:
6570
# mkissue "<title>" "<comma,separated,labels>" "<body>"
6671
# Keep each ticket scoped to ONE module so workers get non-overlapping boundaries.
72+
# Seeded tickets are labelled `backlog` — the OWNER promotes each to `planned`
73+
# when it is approved for the loop (the loop ignores backlog issues).
6774

6875
if [ "${#modules[@]}" -gt 0 ] && [ -n "${modules[0]}" ]; then
69-
mkissue "[${modules[0]}] EXAMPLE — replace with a real ticket" "module:${modules[0]},type:feature" \
76+
mkissue "[${modules[0]}] EXAMPLE — replace with a real ticket" "module:${modules[0]},type:feature,backlog" \
7077
"This is a placeholder created by seed-issues.sh to show the pattern. Delete it and add your own.
7178
7279
$BOUND

.claude/skills/setup/SKILL.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Do these in order. Stop and report if a step genuinely can't proceed.
3535

3636
## 2. Explain the model up front (so answers are informed)
3737
Briefly tell the user how the loop decides what to build:
38-
- The loop only builds issues **labelled `module:<name>`**, one at a time, when no PRs are open — an explicit opt-in queue.
38+
- The loop only builds issues labelled **`planned` + `module:<name>`**, one at a time, when no PRs are open — an explicit, owner-gated queue. Every issue starts as `backlog`; **only the owner** promotes it to `planned` (agents never assign `planned`).
3939
- Each `module` maps to exactly one filesystem `path`, the **hard boundary** a worker may edit within. So the
4040
module list you define here is both the isolation boundary and the set of labels the loop understands. Include
4141
any non-code area you want automatable (e.g. `docs`, `.claude`, `examples`).
@@ -98,9 +98,13 @@ before writing. Do **not** touch `.claude/settings.json` or any generic agent/sc
9898
`.env`, `.claude/settings.local.json`, and `.claude/state/` to confirm they actually resolve as ignored (e.g. a
9999
repo-level override elsewhere in `.gitignore` could still un-ignore one).
100100

101-
## 6. Create the module labels
101+
## 6. Create the module + approval labels
102102
For every module `name`: `bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/bot-gh.sh label create "module:<name>" --description "<desc>" --force`.
103-
Report created vs already-existing. Remind: **an issue is only loop-eligible once it carries a `module:*` label.**
103+
Also create the approval-workflow pair (if `gh label create` is unavailable in the installed gh, use `bot-gh.sh api repos/<owner>/<repo>/labels -f name=... -f color=... -f description=...`):
104+
- `backlog` (color `bfd4f2`) — "Filed, not yet approved by the owner — the loop must NOT pick it up"
105+
- `planned` (color `0e8a16`) — "Owner-approved for the autonomous loop (assigned ONLY by the owner)"
106+
107+
Report created vs already-existing. Remind: **an issue is only loop-eligible once the OWNER labels it `planned` and it carries a `module:*` label**; issues agents file must be labelled `backlog`.
104108

105109
## 7. Verify the bot account
106110
- Confirm `.env` has `GH_BOT_TOKEN` and the bot can see the repo:

0 commit comments

Comments
 (0)