@rtorcato/repo-tooling 3.37.1 → 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 +98 -47
- 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,26 @@ 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.
|
|
267
|
+
|
|
268
|
+
**Resolve `HUMAN_USER` too — the person work is handed back to.** Needs no
|
|
269
|
+
config: on a personal repo the owner *is* the person. On an organisation repo
|
|
270
|
+
`.owner.login` is the org, which is not a human, so it resolves to empty and
|
|
271
|
+
every handoff below assigns nobody rather than something meaningless.
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
HUMAN_USER=$(gh api "repos/$OWNER_REPO" --jq 'if .owner.type == "User" then .owner.login else "" end')
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Later uses are `${HUMAN_USER:+--add-assignee} ${HUMAN_USER:+"$HUMAN_USER"}`, the same shape as
|
|
278
|
+
`AGENT_USER`. **A `gh … edit` whose every expansion is empty has no flags and
|
|
279
|
+
errors — skip the call entirely in that case** rather than letting it fail the
|
|
280
|
+
tick.
|
|
263
281
|
|
|
264
282
|
**The point is that assignee answers "whose turn is it", which no label does
|
|
265
283
|
well.** Today an issue an agent is mid-way through and an issue nobody has
|
|
@@ -274,9 +292,11 @@ half the story:
|
|
|
274
292
|
| PR passed both reviews, waiting to merge | the human |
|
|
275
293
|
| `ai-blocked`, declined, or held | the human |
|
|
276
294
|
|
|
277
|
-
`@me` cannot express
|
|
278
|
-
|
|
279
|
-
|
|
295
|
+
`@me` cannot express either end: it resolves to whichever token is running, and
|
|
296
|
+
the identity check above *requires* that token to be `AGENT_USER` whenever an
|
|
297
|
+
agent account is declared — so `@me` is the agent precisely where the last two
|
|
298
|
+
rows want the human (#606). Both are therefore named explicitly, and `@me`
|
|
299
|
+
appears nowhere in this skill.
|
|
280
300
|
|
|
281
301
|
Note the web UI's assignee picker can show a stale list that omits a
|
|
282
302
|
freshly-added collaborator; `repos/{repo}/assignees` is the authority.
|
|
@@ -474,7 +494,11 @@ Three things the gate does **not** change:
|
|
|
474
494
|
- **`ai-notes` still blocks an unattended merge.** A reviewer who passed but left
|
|
475
495
|
something to read means a human reads it.
|
|
476
496
|
- **Order is still load-bearing.** If `autoMergeRequest != null` the merge can beat
|
|
477
|
-
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.)
|
|
478
502
|
|
|
479
503
|
Be plain about the residual risk: even gated, this lands code on `main` unattended,
|
|
480
504
|
and the only quality signal is two reviewers that — per the limits above — see the
|
|
@@ -538,9 +562,9 @@ makes `merge-ready` assert more than the `ai-ok-*` pair ever did: reviews passed
|
|
|
538
562
|
*and* GitHub will accept the merge.
|
|
539
563
|
|
|
540
564
|
```bash
|
|
541
|
-
gh pr edit <N>
|
|
565
|
+
gh pr edit <N> ${HUMAN_USER:+--add-assignee} ${HUMAN_USER:+"$HUMAN_USER"} --add-label merge-ready \
|
|
542
566
|
--remove-label ai-review --remove-label ai-ok-code --remove-label ai-ok-sec \
|
|
543
|
-
${AGENT_USER:+--remove-assignee "$AGENT_USER"}
|
|
567
|
+
${AGENT_USER:+--remove-assignee} ${AGENT_USER:+"$AGENT_USER"}
|
|
544
568
|
```
|
|
545
569
|
|
|
546
570
|
**`merge-ready` replaces the pass pair — it does not join it.** A handed-off PR
|
|
@@ -633,7 +657,12 @@ the label landed, in no *Assigned to you* view at all. A legacy sweep, cheap to
|
|
|
633
657
|
keep and self-retiring once the last one is handled:
|
|
634
658
|
|
|
635
659
|
```bash
|
|
636
|
-
gh pr edit <N
|
|
660
|
+
# Both empty (org repo, no agentUser) would leave `gh pr edit <N>` with no flags,
|
|
661
|
+
# which errors — so guard the call rather than trusting the reader to skip it.
|
|
662
|
+
if [ -n "$HUMAN_USER" ] || [ -n "$AGENT_USER" ]; then
|
|
663
|
+
gh pr edit <N> ${HUMAN_USER:+--add-assignee} ${HUMAN_USER:+"$HUMAN_USER"} \
|
|
664
|
+
${AGENT_USER:+--remove-assignee} ${AGENT_USER:+"$AGENT_USER"}
|
|
665
|
+
fi
|
|
637
666
|
```
|
|
638
667
|
|
|
639
668
|
Count it as `rev`. Idempotent, so it also picks up ones an earlier tick stranded.
|
|
@@ -777,10 +806,10 @@ Only then:
|
|
|
777
806
|
REMOVED=1 # every removal in this pass sets this
|
|
778
807
|
git -C "$ROOT" worktree remove --force "$WT_DIR" # the path found above, not a rebuilt one
|
|
779
808
|
git -C "$ROOT" branch -D "$BRANCH" 2>/dev/null
|
|
780
|
-
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
|
|
781
810
|
# Still OPEN means the PR said only `Refs #N`; a `Closes #N` issue is already closed.
|
|
782
|
-
if [ "$(gh issue view <N> --json state -q .state)" = OPEN ]; then
|
|
783
|
-
gh issue edit <N> --add-assignee
|
|
811
|
+
if [ -n "$HUMAN_USER" ] && [ "$(gh issue view <N> --json state -q .state)" = OPEN ]; then
|
|
812
|
+
gh issue edit <N> --add-assignee "$HUMAN_USER"
|
|
784
813
|
fi
|
|
785
814
|
```
|
|
786
815
|
|
|
@@ -811,7 +840,7 @@ work must never be reaped out from under itself.
|
|
|
811
840
|
|
|
812
841
|
| Stalled | Condition | Do |
|
|
813
842
|
|---|---|---|
|
|
814
|
-
| 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
|
|
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`) |
|
|
815
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 |
|
|
816
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 |
|
|
817
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`) |
|
|
@@ -962,6 +991,13 @@ whose pass-label is missing — `code-reviewer` if no `ai-ok-code`,
|
|
|
962
991
|
carries `ai-reviewing-sec`. Both can run concurrently; launch them in a single
|
|
963
992
|
message.
|
|
964
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
|
+
|
|
965
1001
|
**Before spawning either, check whether it already posted.** A missing verdict
|
|
966
1002
|
label does not mean the review is missing: on #497 both reviewers posted
|
|
967
1003
|
complete reviews and then went idle, labelling nothing. Every review comment
|
|
@@ -1044,8 +1080,8 @@ intended; one that died after posting is now recovered instead of duplicated.
|
|
|
1044
1080
|
issue. Apply the label immediately before the spawn, not after:
|
|
1045
1081
|
|
|
1046
1082
|
```bash
|
|
1047
|
-
gh pr edit <N> --add-label ai-reviewing-code ${AGENT_USER:+--add-assignee "$AGENT_USER"} # then spawn code-reviewer
|
|
1048
|
-
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
|
|
1049
1085
|
```
|
|
1050
1086
|
|
|
1051
1087
|
Assigning `AGENT_USER` on the claim is idempotent — both arms adding the same
|
|
@@ -1218,10 +1254,10 @@ and a blank line — naming what each round changed and why the reviewer kept ob
|
|
|
1218
1254
|
then:
|
|
1219
1255
|
|
|
1220
1256
|
```bash
|
|
1221
|
-
gh issue edit <M> --add-label ai-blocked --remove-label ai-wip
|
|
1222
|
-
${AGENT_USER:+--remove-assignee "$AGENT_USER"}
|
|
1223
|
-
gh pr edit <N> --
|
|
1224
|
-
${AGENT_USER:+--remove-assignee "$AGENT_USER"}
|
|
1257
|
+
gh issue edit <M> --add-label ai-blocked --remove-label ai-wip \
|
|
1258
|
+
${HUMAN_USER:+--add-assignee} ${HUMAN_USER:+"$HUMAN_USER"} ${AGENT_USER:+--remove-assignee} ${AGENT_USER:+"$AGENT_USER"}
|
|
1259
|
+
gh pr edit <N> --remove-label ai-review \
|
|
1260
|
+
${HUMAN_USER:+--add-assignee} ${HUMAN_USER:+"$HUMAN_USER"} ${AGENT_USER:+--remove-assignee} ${AGENT_USER:+"$AGENT_USER"}
|
|
1225
1261
|
```
|
|
1226
1262
|
|
|
1227
1263
|
Leave the worktree and PR in place for the human; a ping-pong stall is the case where
|
|
@@ -1231,7 +1267,7 @@ Otherwise **claim first, then spawn** — same shape as the reviewer claims abov
|
|
|
1231
1267
|
and for the same reason. Apply the label immediately before the spawn, not after:
|
|
1232
1268
|
|
|
1233
1269
|
```bash
|
|
1234
|
-
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
|
|
1235
1271
|
```
|
|
1236
1272
|
|
|
1237
1273
|
A fix round runs longer than a 15-minute tick — on #565, `ai-changes` at 17:35 and
|
|
@@ -1278,7 +1314,6 @@ gh api "repos/$OWNER_REPO/issues?labels=ai-ready&state=open" \
|
|
|
1278
1314
|
| select([.labels[].name] | index("ai-wip") == null)
|
|
1279
1315
|
| select([.labels[].name] | index("ai-blocked") == null)
|
|
1280
1316
|
| select([.labels[].name] | index("holding") == null)
|
|
1281
|
-
| select([.labels[].name] | index("ai-suggested") == null)
|
|
1282
1317
|
| select(.author_association=="OWNER" or .author_association=="MEMBER" or .author_association=="COLLABORATOR")
|
|
1283
1318
|
| {number, title, body}'
|
|
1284
1319
|
```
|
|
@@ -1293,9 +1328,11 @@ the first place, but then mislabelling it costs nothing. Unlike `ai-blocked` (an
|
|
|
1293
1328
|
agent tried and got stuck), `holding` says *no agent should ever start*, and it
|
|
1294
1329
|
shows up in the issue list so a human triaging does not re-litigate it either.
|
|
1295
1330
|
|
|
1296
|
-
`ai-suggested` is
|
|
1297
|
-
so
|
|
1298
|
-
|
|
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).
|
|
1299
1336
|
|
|
1300
1337
|
**Declining an issue is a visible act — comment, never just skip.** Whenever an
|
|
1301
1338
|
agent decides an issue should *not* go to the pipeline — triaging which issues to
|
|
@@ -1383,7 +1420,7 @@ concurrent tick can't double-pick:
|
|
|
1383
1420
|
|
|
1384
1421
|
```bash
|
|
1385
1422
|
gh issue edit <N> --add-label ai-wip --remove-label ai-ready \
|
|
1386
|
-
${AGENT_USER:+--add-assignee "$AGENT_USER"}
|
|
1423
|
+
${AGENT_USER:+--add-assignee} ${AGENT_USER:+"$AGENT_USER"}
|
|
1387
1424
|
```
|
|
1388
1425
|
|
|
1389
1426
|
Assigning here is what makes the issue list honest: from this moment an agent
|
|
@@ -1432,7 +1469,7 @@ done)
|
|
|
1432
1469
|
**Iterate line by line — never `for d in $DIRS`.** Your shell may be zsh, which
|
|
1433
1470
|
does not word-split an unquoted expansion: `$DIRS` arrives as *one* word with
|
|
1434
1471
|
embedded newlines, `[ -d ]` fails against that nonsense path, and the loop links
|
|
1435
|
-
**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
|
|
1436
1473
|
silent — the `pnpm install` fallback is gated on `$DIRS` being *empty*, which it
|
|
1437
1474
|
is not, so the worktree gets neither links nor an install, and the implementer
|
|
1438
1475
|
meets `Cannot find module` on its first test run, reading as the issue's fault
|
|
@@ -1567,8 +1604,9 @@ Then spawn a background implementer agent:
|
|
|
1567
1604
|
> If you cannot finish, hand it back so a human can see it:
|
|
1568
1605
|
>
|
|
1569
1606
|
> ```bash
|
|
1570
|
-
> gh issue edit <N> --add-label ai-blocked --remove-label ai-wip
|
|
1571
|
-
> <the orchestrator substitutes `--
|
|
1607
|
+
> gh issue edit <N> --add-label ai-blocked --remove-label ai-wip \
|
|
1608
|
+
> <the orchestrator substitutes `--add-assignee <HUMAN_USER>` and
|
|
1609
|
+
> `--remove-assignee <AGENT_USER>` here, either or both possibly nothing>
|
|
1572
1610
|
> ```
|
|
1573
1611
|
>
|
|
1574
1612
|
> Handing back means the issue stops being the agent's: the human must end up the
|
|
@@ -1600,7 +1638,8 @@ worktree in this pass. Never fall back to `EnterWorktree`.
|
|
|
1600
1638
|
Never skip this pass, **including on an idle tick**. An unobservable loop is
|
|
1601
1639
|
indistinguishable from a dead one.
|
|
1602
1640
|
|
|
1603
|
-
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).
|
|
1604
1643
|
Middle dot separated, zero segments omitted, stall counts first with a `⚠`:
|
|
1605
1644
|
|
|
1606
1645
|
| State | `SUMMARY` |
|
|
@@ -1613,11 +1652,19 @@ Middle dot separated, zero segments omitted, stall counts first with a `⚠`:
|
|
|
1613
1652
|
Then diff against last tick and decide whether to notify:
|
|
1614
1653
|
|
|
1615
1654
|
```bash
|
|
1616
|
-
STATUS="
|
|
1655
|
+
STATUS="$ROOT/.claude/ai-loop-status" # absolute — a pinned tick's cwd is a worktree
|
|
1617
1656
|
PREV=$(head -1 "$STATUS" 2>/dev/null)
|
|
1618
|
-
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, -)
|
|
1619
1662
|
```
|
|
1620
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
|
+
|
|
1621
1668
|
- **`SUMMARY` != `PREV`** → notify, and `IDLE=0`.
|
|
1622
1669
|
- **`SUMMARY` == `idle`** → `IDLE=$((IDLE+1))`; notify **only when `IDLE` is
|
|
1623
1670
|
exactly 4** (≈1h quiet), with `idle 1h — no ai-ready issues`. Exactly, not
|
|
@@ -1626,20 +1673,25 @@ IDLE=$(sed -n 2p "$STATUS" 2>/dev/null || echo 0)
|
|
|
1626
1673
|
|
|
1627
1674
|
One notification per tick, maximum — the summary already says everything.
|
|
1628
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
|
+
|
|
1629
1686
|
```bash
|
|
1630
|
-
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
|
|
1631
1689
|
```
|
|
1632
1690
|
|
|
1633
|
-
|
|
1634
|
-
elsewhere the tick still completes and only loses the desktop toast. On Linux
|
|
1635
|
-
swap in `notify-send "ai-issue-loop" "$SUMMARY"` behind the same `|| true`. The
|
|
1636
|
-
statusline file below is plain text and works anywhere.
|
|
1637
|
-
|
|
1638
|
-
When `SUMMARY` carries a `⚠` (anything `blocked`, `ci-red`, or `rebuild`), append
|
|
1639
|
-
`sound name "Basso"` so a stall is audibly different from routine progress.
|
|
1691
|
+
The statusline file below is plain text and works anywhere.
|
|
1640
1692
|
|
|
1641
1693
|
Write the file **last** — summary, idle counter, and the sorted `ai-suggested`
|
|
1642
|
-
numbers the digest rule
|
|
1694
|
+
numbers the digest rule below compares against:
|
|
1643
1695
|
|
|
1644
1696
|
```bash
|
|
1645
1697
|
printf '%s\n%s\n%s\n' "$SUMMARY" "$IDLE" "$SUGGESTED" > "$STATUS"
|
|
@@ -1658,12 +1710,11 @@ cleaned up, sent to review, picked up, blocked. Nothing else; this repeats every
|
|
|
1658
1710
|
which ones need reading before they are merged — that is the one place the notes
|
|
1659
1711
|
reach a human who is not already looking at GitHub.
|
|
1660
1712
|
|
|
1661
|
-
**End with the triage digest** — the open `ai-suggested`
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
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).
|
|
1667
1718
|
|
|
1668
1719
|
**The digest is a deadline, not an archive** — Pass 2 closes any item untouched
|
|
1669
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
|
|