@rtorcato/repo-tooling 3.37.2 → 3.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/ai-issue-loop/SKILL.md +69 -40
- package/skills/ai-workflow/SKILL.md +18 -9
package/package.json
CHANGED
|
@@ -147,7 +147,7 @@ this block matches it, so the two cannot diverge.
|
|
|
147
147
|
Also once per repo, keep the status file out of git:
|
|
148
148
|
|
|
149
149
|
```bash
|
|
150
|
-
grep -qxF '.claude/ai-loop-status'
|
|
150
|
+
grep -qxF '.claude/ai-loop-status' "$ROOT/.gitignore" || echo '.claude/ai-loop-status' >> "$ROOT/.gitignore"
|
|
151
151
|
```
|
|
152
152
|
|
|
153
153
|
```
|
|
@@ -258,8 +258,12 @@ reviewable, and carried forward by `fix lockfile`:
|
|
|
258
258
|
{ "rules": { "aiLoop": { "agentUser": "your-bot-account" } } }
|
|
259
259
|
```
|
|
260
260
|
|
|
261
|
-
Every later use is `${AGENT_USER:+--add-assignee "$AGENT_USER"}`, which expands
|
|
262
|
-
to nothing when it is empty — so there is one code path, not two.
|
|
261
|
+
Every later use is `${AGENT_USER:+--add-assignee} ${AGENT_USER:+"$AGENT_USER"}`, which expands
|
|
262
|
+
to nothing when it is empty — so there is one code path, not two. **Keep the flag
|
|
263
|
+
and the value in separate expansions.** The one-expansion form
|
|
264
|
+
`${AGENT_USER:+--add-assignee "$AGENT_USER"}` (#624) word-splits in bash but not
|
|
265
|
+
in zsh, where `gh` receives `--add-assignee bot` as a single argument and
|
|
266
|
+
rejects it.
|
|
263
267
|
|
|
264
268
|
**Resolve `HUMAN_USER` too — the person work is handed back to.** Needs no
|
|
265
269
|
config: on a personal repo the owner *is* the person. On an organisation repo
|
|
@@ -270,7 +274,7 @@ every handoff below assigns nobody rather than something meaningless.
|
|
|
270
274
|
HUMAN_USER=$(gh api "repos/$OWNER_REPO" --jq 'if .owner.type == "User" then .owner.login else "" end')
|
|
271
275
|
```
|
|
272
276
|
|
|
273
|
-
Later uses are `${HUMAN_USER:+--add-assignee "$HUMAN_USER"}`, the same shape as
|
|
277
|
+
Later uses are `${HUMAN_USER:+--add-assignee} ${HUMAN_USER:+"$HUMAN_USER"}`, the same shape as
|
|
274
278
|
`AGENT_USER`. **A `gh … edit` whose every expansion is empty has no flags and
|
|
275
279
|
errors — skip the call entirely in that case** rather than letting it fail the
|
|
276
280
|
tick.
|
|
@@ -490,7 +494,11 @@ Three things the gate does **not** change:
|
|
|
490
494
|
- **`ai-notes` still blocks an unattended merge.** A reviewer who passed but left
|
|
491
495
|
something to read means a human reads it.
|
|
492
496
|
- **Order is still load-bearing.** If `autoMergeRequest != null` the merge can beat
|
|
493
|
-
the review
|
|
497
|
+
the review. Nowhere but this arm does the loop let an issue PR auto-merge, and
|
|
498
|
+
only after both verdicts, so one found already armed without both `ai-ok-*`
|
|
499
|
+
labels was armed by someone else — run `gh pr merge <N> --disable-auto` before anything
|
|
500
|
+
else touches it. (#605 removed the Pass 0 disarm step this line used to point
|
|
501
|
+
at, along with the Dependabot arm it served.)
|
|
494
502
|
|
|
495
503
|
Be plain about the residual risk: even gated, this lands code on `main` unattended,
|
|
496
504
|
and the only quality signal is two reviewers that — per the limits above — see the
|
|
@@ -554,9 +562,9 @@ makes `merge-ready` assert more than the `ai-ok-*` pair ever did: reviews passed
|
|
|
554
562
|
*and* GitHub will accept the merge.
|
|
555
563
|
|
|
556
564
|
```bash
|
|
557
|
-
gh pr edit <N> ${HUMAN_USER:+--add-assignee "$HUMAN_USER"} --add-label merge-ready \
|
|
565
|
+
gh pr edit <N> ${HUMAN_USER:+--add-assignee} ${HUMAN_USER:+"$HUMAN_USER"} --add-label merge-ready \
|
|
558
566
|
--remove-label ai-review --remove-label ai-ok-code --remove-label ai-ok-sec \
|
|
559
|
-
${AGENT_USER:+--remove-assignee "$AGENT_USER"}
|
|
567
|
+
${AGENT_USER:+--remove-assignee} ${AGENT_USER:+"$AGENT_USER"}
|
|
560
568
|
```
|
|
561
569
|
|
|
562
570
|
**`merge-ready` replaces the pass pair — it does not join it.** A handed-off PR
|
|
@@ -652,8 +660,8 @@ keep and self-retiring once the last one is handled:
|
|
|
652
660
|
# Both empty (org repo, no agentUser) would leave `gh pr edit <N>` with no flags,
|
|
653
661
|
# which errors — so guard the call rather than trusting the reader to skip it.
|
|
654
662
|
if [ -n "$HUMAN_USER" ] || [ -n "$AGENT_USER" ]; then
|
|
655
|
-
gh pr edit <N> ${HUMAN_USER:+--add-assignee "$HUMAN_USER"} \
|
|
656
|
-
${AGENT_USER:+--remove-assignee "$AGENT_USER"}
|
|
663
|
+
gh pr edit <N> ${HUMAN_USER:+--add-assignee} ${HUMAN_USER:+"$HUMAN_USER"} \
|
|
664
|
+
${AGENT_USER:+--remove-assignee} ${AGENT_USER:+"$AGENT_USER"}
|
|
657
665
|
fi
|
|
658
666
|
```
|
|
659
667
|
|
|
@@ -798,7 +806,7 @@ Only then:
|
|
|
798
806
|
REMOVED=1 # every removal in this pass sets this
|
|
799
807
|
git -C "$ROOT" worktree remove --force "$WT_DIR" # the path found above, not a rebuilt one
|
|
800
808
|
git -C "$ROOT" branch -D "$BRANCH" 2>/dev/null
|
|
801
|
-
gh issue edit <N> --remove-label ai-wip ${AGENT_USER:+--remove-assignee "$AGENT_USER"} 2>/dev/null
|
|
809
|
+
gh issue edit <N> --remove-label ai-wip ${AGENT_USER:+--remove-assignee} ${AGENT_USER:+"$AGENT_USER"} 2>/dev/null
|
|
802
810
|
# Still OPEN means the PR said only `Refs #N`; a `Closes #N` issue is already closed.
|
|
803
811
|
if [ -n "$HUMAN_USER" ] && [ "$(gh issue view <N> --json state -q .state)" = OPEN ]; then
|
|
804
812
|
gh issue edit <N> --add-assignee "$HUMAN_USER"
|
|
@@ -832,7 +840,7 @@ work must never be reaped out from under itself.
|
|
|
832
840
|
|
|
833
841
|
| Stalled | Condition | Do |
|
|
834
842
|
|---|---|---|
|
|
835
|
-
| Implementer died | issue `ai-wip` ≥45min, **and no PR exists** for `ai-<N>-<slug>` | `gh issue edit <N> --add-label ai-blocked --remove-label ai-wip ${HUMAN_USER:+--add-assignee "$HUMAN_USER"} ${AGENT_USER:+--remove-assignee "$AGENT_USER"}`, comment, remove the worktree (and set `REMOVED=1`) |
|
|
843
|
+
| Implementer died | issue `ai-wip` ≥45min, **and no PR exists** for `ai-<N>-<slug>` | `gh issue edit <N> --add-label ai-blocked --remove-label ai-wip ${HUMAN_USER:+--add-assignee} ${HUMAN_USER:+"$HUMAN_USER"} ${AGENT_USER:+--remove-assignee} ${AGENT_USER:+"$AGENT_USER"}`, comment, remove the worktree (and set `REMOVED=1`) |
|
|
836
844
|
| Reviewer died | PR `ai-reviewing-code` (or `ai-reviewing-sec`) ≥45min with no matching `ai-ok-*` and no `ai-changes` | `gh pr edit <N> --remove-label <the claim that stalled>` — drop **that** label, not a fixed one; a stalled `ai-reviewing-sec` cleared as `ai-reviewing-code` leaves the dead claim in place and the reviewer never re-spawns. Dropping the claim is what lets Pass 3 re-spawn it, and they're cheap and diff-scoped. If that claim has been applied ≥3 times, `ai-blocked` instead |
|
|
837
845
|
| Fix implementer died | PR `ai-fixing` ≥45min and still `ai-changes` — it never got as far as relabelling to `ai-review` | `gh pr edit <N> --remove-label ai-fixing`, which is what lets Pass 3 dispatch the round again. If `ai-fixing` has been applied ≥3 times, `ai-blocked` on the linked issue instead — a round that dies every time is not one more spawn away from working. Leave the worktree: it holds whatever the dead implementer committed |
|
|
838
846
|
| Orphan worktree | `"$WT_ROOT"/ai-<N>-*` whose issue is not `ai-wip` and has no open PR | remove the worktree and branch (and set `REMOVED=1`) |
|
|
@@ -983,6 +991,13 @@ whose pass-label is missing — `code-reviewer` if no `ai-ok-code`,
|
|
|
983
991
|
carries `ai-reviewing-sec`. Both can run concurrently; launch them in a single
|
|
984
992
|
message.
|
|
985
993
|
|
|
994
|
+
**`code-reviewer` and `security-expert` name the two *arms*, not agent types this
|
|
995
|
+
package ships.** Spawn each with that `subagent_type` when your Agent tool lists
|
|
996
|
+
it; otherwise spawn `general-purpose`, which always exists. The prompt template
|
|
997
|
+
below carries the whole review lens and the verdict protocol, so a named agent
|
|
998
|
+
only adds its own system prompt on top. Never skip a review because the named
|
|
999
|
+
type is missing (#611).
|
|
1000
|
+
|
|
986
1001
|
**Before spawning either, check whether it already posted.** A missing verdict
|
|
987
1002
|
label does not mean the review is missing: on #497 both reviewers posted
|
|
988
1003
|
complete reviews and then went idle, labelling nothing. Every review comment
|
|
@@ -1065,8 +1080,8 @@ intended; one that died after posting is now recovered instead of duplicated.
|
|
|
1065
1080
|
issue. Apply the label immediately before the spawn, not after:
|
|
1066
1081
|
|
|
1067
1082
|
```bash
|
|
1068
|
-
gh pr edit <N> --add-label ai-reviewing-code ${AGENT_USER:+--add-assignee "$AGENT_USER"} # then spawn code-reviewer
|
|
1069
|
-
gh pr edit <N> --add-label ai-reviewing-sec ${AGENT_USER:+--add-assignee "$AGENT_USER"} # then spawn security-expert
|
|
1083
|
+
gh pr edit <N> --add-label ai-reviewing-code ${AGENT_USER:+--add-assignee} ${AGENT_USER:+"$AGENT_USER"} # then spawn code-reviewer
|
|
1084
|
+
gh pr edit <N> --add-label ai-reviewing-sec ${AGENT_USER:+--add-assignee} ${AGENT_USER:+"$AGENT_USER"} # then spawn security-expert
|
|
1070
1085
|
```
|
|
1071
1086
|
|
|
1072
1087
|
Assigning `AGENT_USER` on the claim is idempotent — both arms adding the same
|
|
@@ -1240,9 +1255,9 @@ then:
|
|
|
1240
1255
|
|
|
1241
1256
|
```bash
|
|
1242
1257
|
gh issue edit <M> --add-label ai-blocked --remove-label ai-wip \
|
|
1243
|
-
${HUMAN_USER:+--add-assignee "$HUMAN_USER"} ${AGENT_USER:+--remove-assignee "$AGENT_USER"}
|
|
1258
|
+
${HUMAN_USER:+--add-assignee} ${HUMAN_USER:+"$HUMAN_USER"} ${AGENT_USER:+--remove-assignee} ${AGENT_USER:+"$AGENT_USER"}
|
|
1244
1259
|
gh pr edit <N> --remove-label ai-review \
|
|
1245
|
-
${HUMAN_USER:+--add-assignee "$HUMAN_USER"} ${AGENT_USER:+--remove-assignee "$AGENT_USER"}
|
|
1260
|
+
${HUMAN_USER:+--add-assignee} ${HUMAN_USER:+"$HUMAN_USER"} ${AGENT_USER:+--remove-assignee} ${AGENT_USER:+"$AGENT_USER"}
|
|
1246
1261
|
```
|
|
1247
1262
|
|
|
1248
1263
|
Leave the worktree and PR in place for the human; a ping-pong stall is the case where
|
|
@@ -1252,7 +1267,7 @@ Otherwise **claim first, then spawn** — same shape as the reviewer claims abov
|
|
|
1252
1267
|
and for the same reason. Apply the label immediately before the spawn, not after:
|
|
1253
1268
|
|
|
1254
1269
|
```bash
|
|
1255
|
-
gh pr edit <N> --add-label ai-fixing ${AGENT_USER:+--add-assignee "$AGENT_USER"} # then spawn the implementer
|
|
1270
|
+
gh pr edit <N> --add-label ai-fixing ${AGENT_USER:+--add-assignee} ${AGENT_USER:+"$AGENT_USER"} # then spawn the implementer
|
|
1256
1271
|
```
|
|
1257
1272
|
|
|
1258
1273
|
A fix round runs longer than a 15-minute tick — on #565, `ai-changes` at 17:35 and
|
|
@@ -1299,7 +1314,6 @@ gh api "repos/$OWNER_REPO/issues?labels=ai-ready&state=open" \
|
|
|
1299
1314
|
| select([.labels[].name] | index("ai-wip") == null)
|
|
1300
1315
|
| select([.labels[].name] | index("ai-blocked") == null)
|
|
1301
1316
|
| select([.labels[].name] | index("holding") == null)
|
|
1302
|
-
| select([.labels[].name] | index("ai-suggested") == null)
|
|
1303
1317
|
| select(.author_association=="OWNER" or .author_association=="MEMBER" or .author_association=="COLLABORATOR")
|
|
1304
1318
|
| {number, title, body}'
|
|
1305
1319
|
```
|
|
@@ -1314,9 +1328,11 @@ the first place, but then mislabelling it costs nothing. Unlike `ai-blocked` (an
|
|
|
1314
1328
|
agent tried and got stuck), `holding` says *no agent should ever start*, and it
|
|
1315
1329
|
shows up in the issue list so a human triaging does not re-litigate it either.
|
|
1316
1330
|
|
|
1317
|
-
`ai-suggested` is
|
|
1318
|
-
so
|
|
1319
|
-
|
|
1331
|
+
`ai-suggested` is deliberately *not* filtered. An agent's own suggestion carries
|
|
1332
|
+
only `ai-suggested`, so it never matches `labels=ai-ready` — the loop cannot feed
|
|
1333
|
+
itself work. Promoting one is a human adding `ai-ready`, and the item keeps
|
|
1334
|
+
`ai-suggested` (Pass 2 relies on that), so excluding the label here would strand
|
|
1335
|
+
every promoted issue in the queue forever (#608).
|
|
1320
1336
|
|
|
1321
1337
|
**Declining an issue is a visible act — comment, never just skip.** Whenever an
|
|
1322
1338
|
agent decides an issue should *not* go to the pipeline — triaging which issues to
|
|
@@ -1404,7 +1420,7 @@ concurrent tick can't double-pick:
|
|
|
1404
1420
|
|
|
1405
1421
|
```bash
|
|
1406
1422
|
gh issue edit <N> --add-label ai-wip --remove-label ai-ready \
|
|
1407
|
-
${AGENT_USER:+--add-assignee "$AGENT_USER"}
|
|
1423
|
+
${AGENT_USER:+--add-assignee} ${AGENT_USER:+"$AGENT_USER"}
|
|
1408
1424
|
```
|
|
1409
1425
|
|
|
1410
1426
|
Assigning here is what makes the issue list honest: from this moment an agent
|
|
@@ -1453,7 +1469,7 @@ done)
|
|
|
1453
1469
|
**Iterate line by line — never `for d in $DIRS`.** Your shell may be zsh, which
|
|
1454
1470
|
does not word-split an unquoted expansion: `$DIRS` arrives as *one* word with
|
|
1455
1471
|
embedded newlines, `[ -d ]` fails against that nonsense path, and the loop links
|
|
1456
|
-
**nothing** (#585). Same class as the Pass 2 glob hazard
|
|
1472
|
+
**nothing** (#585). Same class as the Pass 2 glob hazard above, and just as
|
|
1457
1473
|
silent — the `pnpm install` fallback is gated on `$DIRS` being *empty*, which it
|
|
1458
1474
|
is not, so the worktree gets neither links nor an install, and the implementer
|
|
1459
1475
|
meets `Cannot find module` on its first test run, reading as the issue's fault
|
|
@@ -1622,7 +1638,8 @@ worktree in this pass. Never fall back to `EnterWorktree`.
|
|
|
1622
1638
|
Never skip this pass, **including on an idle tick**. An unobservable loop is
|
|
1623
1639
|
indistinguishable from a dead one.
|
|
1624
1640
|
|
|
1625
|
-
Compose `SUMMARY` from what Passes 1–4 already counted — no extra `gh` calls
|
|
1641
|
+
Compose `SUMMARY` from what Passes 1–4 already counted — no extra `gh` calls
|
|
1642
|
+
(the triage digest's one `gh issue list` below is the only exception).
|
|
1626
1643
|
Middle dot separated, zero segments omitted, stall counts first with a `⚠`:
|
|
1627
1644
|
|
|
1628
1645
|
| State | `SUMMARY` |
|
|
@@ -1635,11 +1652,19 @@ Middle dot separated, zero segments omitted, stall counts first with a `⚠`:
|
|
|
1635
1652
|
Then diff against last tick and decide whether to notify:
|
|
1636
1653
|
|
|
1637
1654
|
```bash
|
|
1638
|
-
STATUS="
|
|
1655
|
+
STATUS="$ROOT/.claude/ai-loop-status" # absolute — a pinned tick's cwd is a worktree
|
|
1639
1656
|
PREV=$(head -1 "$STATUS" 2>/dev/null)
|
|
1640
|
-
IDLE=$(sed -n 2p "$STATUS" 2>/dev/null
|
|
1657
|
+
IDLE=$(sed -n 2p "$STATUS" 2>/dev/null); IDLE=${IDLE:-0}
|
|
1658
|
+
PREV_SUGGESTED=$(sed -n 3p "$STATUS" 2>/dev/null)
|
|
1659
|
+
DIGEST=$(gh issue list -R "$OWNER_REPO" --label ai-suggested --state open --limit 100 \
|
|
1660
|
+
--json number,title --jq 'sort_by(.number) | .[] | "#\(.number) \(.title)"')
|
|
1661
|
+
SUGGESTED=$(printf '%s\n' "$DIGEST" | grep -o '^#[0-9]*' | tr -d '#' | paste -sd, -)
|
|
1641
1662
|
```
|
|
1642
1663
|
|
|
1664
|
+
`IDLE=${IDLE:-0}` rather than `|| echo 0`: `sed` on a file shorter than two
|
|
1665
|
+
lines exits 0 with no output, so the `||` branch never fires and `IDLE+1` would
|
|
1666
|
+
run on an empty string.
|
|
1667
|
+
|
|
1643
1668
|
- **`SUMMARY` != `PREV`** → notify, and `IDLE=0`.
|
|
1644
1669
|
- **`SUMMARY` == `idle`** → `IDLE=$((IDLE+1))`; notify **only when `IDLE` is
|
|
1645
1670
|
exactly 4** (≈1h quiet), with `idle 1h — no ai-ready issues`. Exactly, not
|
|
@@ -1648,20 +1673,25 @@ IDLE=$(sed -n 2p "$STATUS" 2>/dev/null || echo 0)
|
|
|
1648
1673
|
|
|
1649
1674
|
One notification per tick, maximum — the summary already says everything.
|
|
1650
1675
|
|
|
1676
|
+
Send it with the **`PushNotification`** tool — `message`: `"$OWNER_REPO: $SUMMARY"`
|
|
1677
|
+
(one line, under 200 characters, `⚠` segments first so a truncated phone banner
|
|
1678
|
+
still leads with the stall). It works on every platform, reaches the phone when
|
|
1679
|
+
Remote Control is connected, and skips itself when the user is already at the
|
|
1680
|
+
terminal — so a tick the user is watching costs no toast. A "not sent" result is
|
|
1681
|
+
normal; never retry it.
|
|
1682
|
+
|
|
1683
|
+
Only when the tool is not available in this session, fall back to a desktop toast
|
|
1684
|
+
that cannot fail the tick:
|
|
1685
|
+
|
|
1651
1686
|
```bash
|
|
1652
|
-
osascript -e "display notification \"$SUMMARY\" with title \"ai-issue-loop\" subtitle \"$OWNER_REPO\"" 2>/dev/null
|
|
1687
|
+
osascript -e "display notification \"$SUMMARY\" with title \"ai-issue-loop\" subtitle \"$OWNER_REPO\"" 2>/dev/null \
|
|
1688
|
+
|| notify-send "ai-issue-loop" "$OWNER_REPO: $SUMMARY" 2>/dev/null || true
|
|
1653
1689
|
```
|
|
1654
1690
|
|
|
1655
|
-
|
|
1656
|
-
elsewhere the tick still completes and only loses the desktop toast. On Linux
|
|
1657
|
-
swap in `notify-send "ai-issue-loop" "$SUMMARY"` behind the same `|| true`. The
|
|
1658
|
-
statusline file below is plain text and works anywhere.
|
|
1659
|
-
|
|
1660
|
-
When `SUMMARY` carries a `⚠` (anything `blocked`, `ci-red`, or `rebuild`), append
|
|
1661
|
-
`sound name "Basso"` so a stall is audibly different from routine progress.
|
|
1691
|
+
The statusline file below is plain text and works anywhere.
|
|
1662
1692
|
|
|
1663
1693
|
Write the file **last** — summary, idle counter, and the sorted `ai-suggested`
|
|
1664
|
-
numbers the digest rule
|
|
1694
|
+
numbers the digest rule below compares against:
|
|
1665
1695
|
|
|
1666
1696
|
```bash
|
|
1667
1697
|
printf '%s\n%s\n%s\n' "$SUMMARY" "$IDLE" "$SUGGESTED" > "$STATUS"
|
|
@@ -1680,12 +1710,11 @@ cleaned up, sent to review, picked up, blocked. Nothing else; this repeats every
|
|
|
1680
1710
|
which ones need reading before they are merged — that is the one place the notes
|
|
1681
1711
|
reach a human who is not already looking at GitHub.
|
|
1682
1712
|
|
|
1683
|
-
**End with the triage digest** — the open `ai-suggested`
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
sorted issue numbers).
|
|
1713
|
+
**End with the triage digest** — print `$DIGEST` (the open `ai-suggested`
|
|
1714
|
+
queue, one line per issue, fetched above). No new state, no extra prose: a list
|
|
1715
|
+
scanned in one glance is what makes a human promote or close something. Skip the
|
|
1716
|
+
digest when `$SUGGESTED` is empty or equals `$PREV_SUGGESTED` (line 3 of
|
|
1717
|
+
`$STATUS` from the last tick).
|
|
1689
1718
|
|
|
1690
1719
|
**The digest is a deadline, not an archive** — Pass 2 closes any item untouched
|
|
1691
1720
|
for 30 days, so anything listed here that nobody engages with will expire on its
|
|
@@ -55,6 +55,8 @@ git -C "$ROOT" fetch --prune
|
|
|
55
55
|
# ai-issue-loop skill's Pass 0 for why this is repo config rather than an env var.
|
|
56
56
|
AGENT_USER="${AI_LOOP_AGENT:-$(jq -r '.rules.aiLoop.agentUser // .aiLoop.agentUser // empty' "$ROOT/.repo-tooling.json" 2>/dev/null)}"
|
|
57
57
|
[ -n "$AGENT_USER" ] && { gh api "repos/$R/assignees/$AGENT_USER" --silent 2>/dev/null || AGENT_USER=""; }
|
|
58
|
+
# The human a given-up issue is handed back to — the repo owner, when that is a user.
|
|
59
|
+
HUMAN_USER=$(gh api "repos/$R" --jq 'if .owner.type == "User" then .owner.login else "" end')
|
|
58
60
|
```
|
|
59
61
|
|
|
60
62
|
`R` comes from the working directory's remote and is the only repo touched —
|
|
@@ -79,7 +81,6 @@ gh api "repos/$R/issues?labels=ai-ready&state=open" \
|
|
|
79
81
|
| select([.labels[].name] | index("ai-wip") == null)
|
|
80
82
|
| select([.labels[].name] | index("ai-blocked") == null)
|
|
81
83
|
| select([.labels[].name] | index("holding") == null)
|
|
82
|
-
| select([.labels[].name] | index("ai-suggested") == null)
|
|
83
84
|
| select(.author_association=="OWNER" or .author_association=="MEMBER" or .author_association=="COLLABORATOR")
|
|
84
85
|
| {number, title, body}'
|
|
85
86
|
```
|
|
@@ -121,7 +122,7 @@ carrying both re-enters the queue the instant `ai-wip` clears):
|
|
|
121
122
|
```bash
|
|
122
123
|
for n in <numbers>; do
|
|
123
124
|
gh issue edit -R "$R" $n --add-label ai-wip --remove-label ai-ready \
|
|
124
|
-
${AGENT_USER:+--add-assignee "$AGENT_USER"}
|
|
125
|
+
${AGENT_USER:+--add-assignee} ${AGENT_USER:+"$AGENT_USER"}
|
|
125
126
|
SLUG="ai-$n-<3-4 kebab words from the title>"
|
|
126
127
|
mkdir -p "$WT_ROOT"
|
|
127
128
|
git -C "$ROOT" worktree add "$WT_ROOT/$SLUG" -b "$SLUG" origin/main
|
|
@@ -143,11 +144,17 @@ and why.
|
|
|
143
144
|
Call `Workflow` with the script below, passing the selected issues as `args`:
|
|
144
145
|
|
|
145
146
|
```
|
|
146
|
-
Workflow({args: {repo: R, agentUser: AGENT_USER, issues: [{number, title, slug, worktree}, …]}, script: …})
|
|
147
|
+
Workflow({args: {repo: R, agentUser: AGENT_USER, humanUser: HUMAN_USER, namedReviewers, issues: [{number, title, slug, worktree}, …]}, script: …})
|
|
147
148
|
```
|
|
148
149
|
|
|
149
|
-
Pass `
|
|
150
|
-
|
|
150
|
+
Pass `namedReviewers: true` only when **both** `code-reviewer` and
|
|
151
|
+
`security-expert` appear in your Agent tool's list of agent types. They are not
|
|
152
|
+
shipped by this package, and a Workflow `agentType` that does not exist fails the
|
|
153
|
+
spawn. Otherwise pass `false`, and the reviewers run as `general-purpose` with the
|
|
154
|
+
same prompt, which carries the whole lens and verdict protocol (#611).
|
|
155
|
+
|
|
156
|
+
Pass `agentUser` / `humanUser` as the empty string when unset — the script
|
|
157
|
+
tests each, so an empty value simply drops that assign.
|
|
151
158
|
|
|
152
159
|
```js
|
|
153
160
|
export const meta = {
|
|
@@ -211,8 +218,9 @@ const results = await pipeline(
|
|
|
211
218
|
|
|
212
219
|
Give up early rather than grinding: if a build or test command hangs or fails
|
|
213
220
|
twice the same way, stop. If you cannot finish, \`gh issue edit ${i.number}
|
|
214
|
-
--add-label ai-blocked --remove-label ai-wip
|
|
215
|
-
leave the worktree in place, and return pr: null
|
|
221
|
+
--add-label ai-blocked --remove-label ai-wip${args.humanUser ? ` --add-assignee ${args.humanUser}` : ''}${args.agentUser ? ` --remove-assignee ${args.agentUser}` : ''}\`,
|
|
222
|
+
comment why (🤖 header first), leave the worktree in place, and return pr: null.
|
|
223
|
+
Handing back means the human ends up the only assignee.`,
|
|
216
224
|
{ label: `impl:#${i.number}`, phase: 'Implement', schema: PR }
|
|
217
225
|
),
|
|
218
226
|
|
|
@@ -251,7 +259,7 @@ Then apply exactly one verdict label, clearing your claim in the same command:
|
|
|
251
259
|
\`gh pr edit ${r.pr} --add-label ai-changes --remove-label ai-review --remove-label ${v.claim}\`
|
|
252
260
|
Plus \`--add-label ai-notes\` if and only if your section is not Nothing.
|
|
253
261
|
A question only a human can answer → pass + ai-notes, never ai-changes.`,
|
|
254
|
-
{ label: `${v.type}:#${i.number}`, phase: 'Review', schema: VERDICT, agentType: v.type }
|
|
262
|
+
{ label: `${v.type}:#${i.number}`, phase: 'Review', schema: VERDICT, agentType: args.namedReviewers ? v.type : 'general-purpose' }
|
|
255
263
|
)))
|
|
256
264
|
)
|
|
257
265
|
|
|
@@ -268,7 +276,8 @@ Notes on the script, so it doesn't get "tidied" into breakage:
|
|
|
268
276
|
- **No `EnterWorktree` anywhere** — `{path}` is rejected for sibling worktrees
|
|
269
277
|
and `{name}` relocates the orchestrator's own session. Implementers work via
|
|
270
278
|
`git -C` and absolute paths.
|
|
271
|
-
- Reviewers use `agentType`
|
|
279
|
+
- Reviewers use `agentType` (the named type when installed, else
|
|
280
|
+
`general-purpose`) so they get their real system prompts, and post the
|
|
272
281
|
same verdict markers the loop's Pass 3 reads — so a later tick adopts their
|
|
273
282
|
verdicts instead of re-reviewing.
|
|
274
283
|
|