@rtorcato/repo-tooling 3.37.0 → 3.37.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rtorcato/repo-tooling",
3
- "version": "3.37.0",
3
+ "version": "3.37.2",
4
4
  "description": "One CLI to scaffold, audit and fix your repo's whole toolchain — linting, tests, commits, releases & CI.",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -4,12 +4,13 @@ model: sonnet
4
4
  description: |
5
5
  **The engine behind `/ai-workflow` — normally you do not invoke this
6
6
  directly.** One stateless tick over the GitHub label state: answer
7
- `ai-changes` with a fix round, merge Dependabot PRs, hand passed issue PRs to
8
- the human, clean up merged worktrees, reap stalled agents, and pick up any
9
- remaining `ai-ready` issues. `/ai-workflow` is the entry point and schedules
10
- this itself via `/loop 15m /ai-issue-loop`; reach for it directly only to
11
- force a tick early — "run one tick", "babysit the AI PRs" — or when the user
12
- invokes `/ai-issue-loop`. Only Dependabot PRs ever merge unattended.
7
+ `ai-changes` with a fix round, hand passed issue PRs to the human, clean up
8
+ merged worktrees, reap stalled agents, and pick up any remaining `ai-ready`
9
+ issues. `/ai-workflow` is the entry point and schedules this itself via
10
+ `/loop 15m /ai-issue-loop`; reach for it directly only to force a tick early —
11
+ "run one tick", "babysit the AI PRs" — or when the user invokes
12
+ `/ai-issue-loop`. It never merges; Dependabot PRs are handled by their own
13
+ workflow, outside this loop.
13
14
  GitHub only (`gh`) — not GitLab.
14
15
  ---
15
16
 
@@ -17,9 +18,10 @@ description: |
17
18
 
18
19
  One **tick** of an unattended pipeline: `ai-ready` issue → worktree → PR → two
19
20
  agent reviews → **assigned to you to merge** → worktree removed on the next tick.
20
- Only Dependabot PRs merge themselves, plus — on a repo whose `release` environment
21
- requires reviewers — a fully-passed issue PR. See Pass 1. Whenever the loop
22
- declines to merge, it says why in a comment on the PR.
21
+ Nothing merges here except, on a repo whose `release` environment requires
22
+ reviewers, a fully-passed issue PR. See Pass 1. Dependabot PRs are outside this
23
+ loop entirely — their own workflow merges them (#593). Whenever the loop declines
24
+ to merge, it says why in a comment on the PR.
23
25
 
24
26
  **All state lives in GitHub labels.** A tick is a stateless, idempotent pass over
25
27
  that state, so a missed tick, a crash, or a restart costs nothing. Never keep
@@ -82,7 +84,7 @@ drift with a second copy to maintain.
82
84
  | `ai-reviewing-sec` | PR | `security-expert` claimed and running. Cleared with its verdict. |
83
85
  | `ai-ok-code` | PR | `code-reviewer` passed. In-flight only — Pass 1 strips it at handoff. |
84
86
  | `ai-ok-sec` | PR | `security-expert` passed. In-flight only — Pass 1 strips it at handoff. |
85
- | `ai-changes` | PR | A reviewer requested changes, **or** Pass 1 sent the PR back over CI. Reviewers never apply it to a Dependabot PR. |
87
+ | `ai-changes` | PR | A reviewer requested changes, **or** Pass 1 sent the PR back over CI. Issue PRs only — this loop does not label Dependabot PRs. |
86
88
  | `ai-fixing` | PR | Fix-round implementer claimed and running. Cleared with its push. |
87
89
  | `ai-notes` | PR | Passed, but a reviewer left something to read before merging. |
88
90
  | `merge-ready` | PR | Both agent reviews passed and the PR is mergeable — waiting on a human. Derived state; Pass 1 applies and strips it, and it **supersedes** the `ai-ok-*` pair rather than joining it. |
@@ -91,9 +93,7 @@ drift with a second copy to maintain.
91
93
 
92
94
  **`ai-notes` is advisory and never blocks.** It rides *alongside* a pass label,
93
95
  never instead of one, and it never sends a PR back — a finding that should block
94
- an issue PR is `ai-changes`. On a Dependabot PR there is nothing to send back to,
95
- so `ai-notes` is the hold itself: it suppresses auto-merge and routes the PR to
96
- the human. It exists because a pass label currently means both "clean" and
96
+ an issue PR is `ai-changes`. It exists because a pass label currently means both "clean" and
97
97
  "I found something real but would not hold the PR over it", and those two are
98
98
  indistinguishable in the *Assigned to you* view where merges actually happen.
99
99
  The bar is a finding that **changes what a human would do at merge time**: a
@@ -152,10 +152,8 @@ grep -qxF '.claude/ai-loop-status' .gitignore || echo '.claude/ai-loop-status' >
152
152
 
153
153
  ```
154
154
  issue: ai-ready ─pickup─> ai-wip ─> PR opened, labelled ai-review
155
- PR: ai-review ─> ai-reviewing-* ─┬─> ai-ok-code + ai-ok-sec ─┬─ issue PR ─> merge-ready, assigned to you (ai-review + both ai-ok-* dropped)
156
- │ (± ai-notes) │ ─> YOU merge ─> worktree removed
157
- │ └─ dependabot ─┬─ no ai-notes ─> auto-merge ─> worktree removed
158
- │ └─ ai-notes ───> merge-ready, assigned to you
155
+ PR: ai-review ─> ai-reviewing-* ─┬─> ai-ok-code + ai-ok-sec ──> merge-ready, assigned to you (ai-review + both ai-ok-* dropped)
156
+ │ (± ai-notes) ─> YOU merge ─> worktree removed
159
157
  └─> ai-changes (issue PRs only) ─> ai-fixing (max 2) ─> ai-review
160
158
  ▲ └─ round 3 ─> ai-blocked
161
159
  └─ Pass 1 sends back: not CLEAN, or a required check FAILED
@@ -167,10 +165,11 @@ alongside the label it ends on — a verdict for a reviewer, `ai-review` for the
167
165
  round. They are transient — a claim outliving its agent means it died, which is
168
166
  Pass 2's stall reaping, not a state of the PR.
169
167
 
170
- Only the Dependabot arm merges itself, and only when no reviewer left `ai-notes`.
171
- The one exception is a repo gated by a `release` environment with
172
- `required_reviewers`, where the issue arm may also auto-merge under the same
173
- conditions — see Pass 1.
168
+ Nothing in this diagram merges itself. Dependabot PRs are absent from it on
169
+ purpose — their own workflow merges them, outside this loop entirely (#593). The
170
+ one arm that can merge unattended is a repo gated by a `release` environment with
171
+ `required_reviewers`, where a human still stands between the merge and the
172
+ registry — see Pass 1.
174
173
  On an ungated repo an issue PR ends at *assigned to you* and waits there —
175
174
  `merge-ready` is the loop's way of saying done. Add `ai-notes` and it means
176
175
  done, but open the comments first.
@@ -262,6 +261,20 @@ reviewable, and carried forward by `fix lockfile`:
262
261
  Every later use is `${AGENT_USER:+--add-assignee "$AGENT_USER"}`, which expands
263
262
  to nothing when it is empty — so there is one code path, not two.
264
263
 
264
+ **Resolve `HUMAN_USER` too — the person work is handed back to.** Needs no
265
+ config: on a personal repo the owner *is* the person. On an organisation repo
266
+ `.owner.login` is the org, which is not a human, so it resolves to empty and
267
+ every handoff below assigns nobody rather than something meaningless.
268
+
269
+ ```bash
270
+ HUMAN_USER=$(gh api "repos/$OWNER_REPO" --jq 'if .owner.type == "User" then .owner.login else "" end')
271
+ ```
272
+
273
+ Later uses are `${HUMAN_USER:+--add-assignee "$HUMAN_USER"}`, the same shape as
274
+ `AGENT_USER`. **A `gh … edit` whose every expansion is empty has no flags and
275
+ errors — skip the call entirely in that case** rather than letting it fail the
276
+ tick.
277
+
265
278
  **The point is that assignee answers "whose turn is it", which no label does
266
279
  well.** Today an issue an agent is mid-way through and an issue nobody has
267
280
  touched are both assigned to no one, so the *Assigned to you* view is only ever
@@ -275,9 +288,11 @@ half the story:
275
288
  | PR passed both reviews, waiting to merge | the human |
276
289
  | `ai-blocked`, declined, or held | the human |
277
290
 
278
- `@me` cannot express this: it resolves to whichever token is running, and the
279
- agents authenticate as the owner, so `@me` is *always* the human. That is why
280
- this is a separate name rather than a reuse.
291
+ `@me` cannot express either end: it resolves to whichever token is running, and
292
+ the identity check above *requires* that token to be `AGENT_USER` whenever an
293
+ agent account is declared — so `@me` is the agent precisely where the last two
294
+ rows want the human (#606). Both are therefore named explicitly, and `@me`
295
+ appears nowhere in this skill.
281
296
 
282
297
  Note the web UI's assignee picker can show a stale list that omits a
283
298
  freshly-added collaborator; `repos/{repo}/assignees` is the authority.
@@ -361,27 +376,28 @@ from an earlier session. Call `ExitWorktree({action: "keep"})` — **`keep`, nev
361
376
  `remove`**, an implementer may still be working in there — and carry on with the rest
362
377
  of the tick.
363
378
 
364
- **Adopt unlabelled Dependabot PRs.** Any open PR authored by `dependabot[bot]`
365
- carrying no `ai-*` label joins the pipeline — label it `ai-review` so Pass 3
366
- reviews it:
379
+ **Leave Dependabot PRs alone.** They are not adopted, not labelled, not reviewed
380
+ and not merged by this loop.
367
381
 
368
- ```bash
369
- gh pr list --state open --json number,author,labels \
370
- --jq '.[] | select(.author.login=="app/dependabot")
371
- | select([.labels[].name] | any(startswith("ai-")) | not) | .number'
372
- ```
382
+ **Why, because it reads as a gap:** `dependabot-automerge.yml` arms auto-merge when
383
+ the PR *opens*, and GitHub merges the moment checks go green. A tick runs up to 15
384
+ minutes later, so on any repo where CI beats the next tick the merge already
385
+ happened — the review arm was decorative on every repo that scaffolds the workflow
386
+ (#593, observed on `js-common` #271).
373
387
 
374
- **Order is load-bearing.** Review only gates a merge if nothing armed auto-merge
375
- first — GitHub merges the moment checks go green, labels be damned. Observed on
376
- `js-common` #148: auto-merge was armed by hand at 15:54, so a review would have had
377
- to beat CI to matter at all. If a Dependabot PR already has `autoMergeRequest != null`
378
- and lacks either `ai-ok-*`, disarm it before labelling:
388
+ Arming auto-merge from this loop instead would fix the race and cost more than it
389
+ buys: dependency updates would then only land while the loop is alive, and a loop
390
+ that is merely unscheduled would stall every bump with nothing reporting why.
379
391
 
380
- ```bash
381
- gh pr merge <N> --disable-auto
382
- ```
392
+ The gate that remains is stronger than the reviewer was. The workflow's own
393
+ predicate refuses anything appearing in a non-private package's `dependencies`,
394
+ `optionalDependencies` or `peerDependencies`, allows only the `dev-minor` group or
395
+ the `github-actions` ecosystem at patch or minor, and fails closed when no
396
+ dependency names are reported. It computes that from the checked-out manifests,
397
+ where the reviewer had to infer it from a PR body GitHub truncates at 65535
398
+ characters — the same policy, derived more reliably.
383
399
 
384
- **Adopt agent-opened PRs the same way.** A PR an agent opens outside Pass 4 — one
400
+ **Adopt agent-opened PRs.** A PR an agent opens outside Pass 4 — one
385
401
  with no `ai-ready` issue behind it — carries no `ai-*` label, so it matches no pass
386
402
  and is therefore assigned by nothing: it never reaches *Assigned to you*, which is
387
403
  the view where merges actually happen. Observed on #548, which passed all five
@@ -432,12 +448,11 @@ leaks is disk, an issue list that reads as though agents are still working, and
432
448
 
433
449
  ### Pass 1 — merge
434
450
 
435
- **Only Dependabot PRs merge unattended, unless the repo has a real publish gate.**
436
- Everything else — every PR this loop opened from an `ai-ready` issue — stops here
437
- for a human even when both reviewers pass, because merging `main` fires
438
- semantic-release and publishes to npm. A `chore(deps)` squash subject cuts no
439
- release, which is what makes the Dependabot case safe. Count human-gated PRs as
440
- `ready` for Pass 5.
451
+ **Nothing merges unattended here, unless the repo has a real publish gate.**
452
+ Every PR this loop opened from an `ai-ready` issue stops for a human even when
453
+ both reviewers pass, because merging `main` fires semantic-release and publishes
454
+ to npm. Count human-gated PRs as `ready` for Pass 5. (Dependabot PRs do merge
455
+ unattended, but by their own workflow — this pass does not touch them.)
441
456
 
442
457
  **The exception is a `release` environment with `required_reviewers`.** There a
443
458
  human still stands between the merge and npm, so an unattended merge costs a
@@ -539,7 +554,7 @@ makes `merge-ready` assert more than the `ai-ok-*` pair ever did: reviews passed
539
554
  *and* GitHub will accept the merge.
540
555
 
541
556
  ```bash
542
- gh pr edit <N> --add-assignee @me --add-label merge-ready \
557
+ gh pr edit <N> ${HUMAN_USER:+--add-assignee "$HUMAN_USER"} --add-label merge-ready \
543
558
  --remove-label ai-review --remove-label ai-ok-code --remove-label ai-ok-sec \
544
559
  ${AGENT_USER:+--remove-assignee "$AGENT_USER"}
545
560
  ```
@@ -552,7 +567,7 @@ reviews passed **and** `CLEAN`), so the pair carries no information once it is
552
567
  applied — a ready PR's whole vocabulary is the two-row table below.
553
568
 
554
569
  Consequently **`merge-ready` satisfies every later test for the `ai-ok-*` pair** —
555
- the gated-repo auto-merge arm above, the Dependabot arm below, and this pass's own
570
+ the gated-repo auto-merge arm above and this pass's own
556
571
  selector on the next tick. The pair stays the in-flight signal Pass 3 writes and
557
572
  reads; it is only at the handoff that it stops being the thing anyone looks at.
558
573
 
@@ -627,34 +642,29 @@ gh pr edit <N> --add-label ai-changes \
627
642
 
628
643
  Count it as `rev`, not `ready`. A merge conflict (`DIRTY`) takes the same route.
629
644
 
630
- **Assign any Dependabot PR carrying `ai-changes`.** Pass 3 never spawns a fix
631
- round for one, so it is waiting on a human from the moment the label lands — and
632
- no other branch of this pass assigns it, which leaves it in no *Assigned to you*
633
- view at all:
645
+ **Assign any Dependabot PR carrying `ai-changes`.** Nothing produces that state
646
+ any more — this loop stopped labelling bot PRs (#593) — but a tick from before
647
+ that change can have stranded one, and it is waiting on a human from the moment
648
+ the label landed, in no *Assigned to you* view at all. A legacy sweep, cheap to
649
+ keep and self-retiring once the last one is handled:
634
650
 
635
651
  ```bash
636
- gh pr edit <N> --add-assignee @me ${AGENT_USER:+--remove-assignee "$AGENT_USER"}
652
+ # Both empty (org repo, no agentUser) would leave `gh pr edit <N>` with no flags,
653
+ # which errors — so guard the call rather than trusting the reader to skip it.
654
+ 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"}
657
+ fi
637
658
  ```
638
659
 
639
660
  Count it as `rev`. Idempotent, so it also picks up ones an earlier tick stranded.
640
661
 
641
- So: every open PR **authored by `dependabot[bot]`**, labelled both `ai-ok-code`
642
- and `ai-ok-sec` (or `merge-ready`), **not** `ai-changes`, **not** `ai-notes`, that
643
- has no `autoMergeRequest` yet:
644
-
645
- ```bash
646
- gh pr merge <N> --auto --squash --delete-branch
647
- ```
648
-
649
- GitHub holds it until the required checks pass. Do not poll CI — a later tick
650
- picks up the merged state.
651
-
652
- A Dependabot PR carrying `ai-notes` is **not** auto-merged — assign it to the
653
- human exactly like an issue PR, `merge-ready` included (same `CLEAN` gate), and
654
- count it as `ready`, not `merge`. An auto-merge-armed one never needs the label —
655
- no human picks it up. Merging
656
- unattended when a reviewer flagged something for a human writes the note into the
657
- void, which is the one way this label can be worse than useless.
662
+ **This pass never merges a Dependabot PR.** `dependabot-automerge.yml` arms
663
+ auto-merge at PR-open for the bumps its predicate allows — dev-only, non-shipping,
664
+ patch or minor. Everything it declines (a major, anything reaching consumers) is
665
+ declined *because* a human should look, so a second unattended merger here would
666
+ only re-open the hole the predicate exists to close. Count a Dependabot PR as
667
+ `merge` when a later tick finds it merged; otherwise leave it for the human.
658
668
 
659
669
  **CI red on an issue PR is a send-back, not a wait.** Reviewers are diff-scoped
660
670
  and never see CI, so both arms happily pass a PR whose `build` failed two minutes
@@ -723,11 +733,11 @@ if telling a review-rejected PR from a CI-rejected one in the list view ever
723
733
  matters, add a `ci-failing` rider on top of `ai-changes` then, not speculatively
724
734
  now.
725
735
 
726
- **A Dependabot PR is the exception — flag it, never send it back.** There is no
727
- fix round for one (Pass 3 treats `ai-changes` on a bot PR as terminal), so a red
728
- one that already armed auto-merge will sit queued forever and only a human can
729
- choose between a fix and a close. Count these as `ci-red` too; take no other
730
- action:
736
+ **A Dependabot PR is the exception — flag it, never send it back.** This loop
737
+ does not review, label or merge bot PRs, but a red one that its own workflow
738
+ already armed will sit queued forever, and only a human can choose between a fix
739
+ and a close. Reporting it is the one thing this loop still does for Dependabot.
740
+ Count these as `ci-red`; take no other action:
731
741
 
732
742
  ```bash
733
743
  gh pr list --state open --json number,autoMergeRequest,statusCheckRollup \
@@ -790,8 +800,8 @@ git -C "$ROOT" worktree remove --force "$WT_DIR" # the path found above, not a
790
800
  git -C "$ROOT" branch -D "$BRANCH" 2>/dev/null
791
801
  gh issue edit <N> --remove-label ai-wip ${AGENT_USER:+--remove-assignee "$AGENT_USER"} 2>/dev/null
792
802
  # Still OPEN means the PR said only `Refs #N`; a `Closes #N` issue is already closed.
793
- if [ "$(gh issue view <N> --json state -q .state)" = OPEN ]; then
794
- gh issue edit <N> --add-assignee @me
803
+ if [ -n "$HUMAN_USER" ] && [ "$(gh issue view <N> --json state -q .state)" = OPEN ]; then
804
+ gh issue edit <N> --add-assignee "$HUMAN_USER"
795
805
  fi
796
806
  ```
797
807
 
@@ -822,7 +832,7 @@ work must never be reaped out from under itself.
822
832
 
823
833
  | Stalled | Condition | Do |
824
834
  |---|---|---|
825
- | 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 --add-assignee @me ${AGENT_USER:+--remove-assignee "$AGENT_USER"}`, comment, remove the worktree (and set `REMOVED=1`) |
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`) |
826
836
  | 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 |
827
837
  | 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 |
828
838
  | 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`) |
@@ -1192,117 +1202,26 @@ Reviewer prompt template:
1192
1202
  > the question stated in `### Before merging`.
1193
1203
  >
1194
1204
  > That is not a weaker gate than blocking. An issue PR never auto-merges, so the
1195
- > human is already the merge gate, and `ai-notes` is what reaches them there. On
1196
- > a Dependabot PR it suppresses auto-merge outright. Use `ai-changes` only when
1197
- > you can name a concrete change an agent could make.
1205
+ > human is already the merge gate, and `ai-notes` is what reaches them there.
1206
+ > Use `ai-changes` only when you can name a concrete change an agent could make.
1198
1207
  >
1199
1208
  > Say nothing else, and **do not restate your verdict in your reply** — the
1200
1209
  > marker in the posted comment is the only place it is read from, so a reply that
1201
1210
  > disagreed with it would be a second source for one fact. One line back to the
1202
1211
  > orchestrator is plenty; the comment body is capped separately, above.
1203
1212
 
1204
- **Dependabot PRs use a different prompt** — the one above would burn the tick on a
1205
- lockfile. `js-common` #148 bumps 20 packages and its *entire* diff is
1206
- `pnpm-lock.yaml`: thousands of lines that tell a reviewer nothing. The signal lives
1207
- in the PR body, where Dependabot writes a package/from/to table at the top and
1208
- per-package `update-type:`/`dependency-type:` trailers at the bottom.
1209
-
1210
- **Never judge from the trailers alone — they are the first thing GitHub truncates.**
1211
- A PR body caps at 65535 characters, and a group update large enough to be worth
1212
- gating is exactly the one that blows the cap. #148 measured 65535 bytes on the nose,
1213
- ended in `_Description has been truncated_`, and contained **zero** `dependency-type`
1214
- lines. A reviewer told to judge the trailers finds nothing to trip on and applies the
1215
- *pass* label — the rule fails open, in the one direction that matters. The
1216
- package/from/to table survives because it sits at the top; classify from that.
1217
-
1218
- > Review Dependabot PR #`<N>` in `<OWNER_REPO>`. Read `gh pr view <N>` — the body
1219
- > only. **Do not run `gh pr diff`**; the diff is a lockfile and reading it wastes
1220
- > the budget without informing the verdict. You may run
1221
- > `gh pr checks <N>` to see whether CI is green.
1222
- >
1223
- > The body is very likely **truncated** — check whether it ends in
1224
- > `_Description has been truncated_`, and never assume an absent
1225
- > `updated-dependencies:` trailer block means "nothing to flag". Work from the
1226
- > package/from/to table at the top of the body, which is not truncated, and
1227
- > resolve each package's type yourself:
1228
- >
1229
- > ```bash
1230
- > gh api "repos/<OWNER_REPO>/contents/package.json" --jq '.content' | base64 -d \
1231
- > | jq '{ships: ((.dependencies // {}) + (.optionalDependencies // {}) + (.peerDependencies // {}) | keys),
1232
- > dev: (.devDependencies // {} | keys)}'
1233
- > ```
1234
- >
1235
- > **`dependencies` is not the whole of what ships.** npm installs
1236
- > `optionalDependencies` for consumers too, so they are production by any
1237
- > meaningful definition — in `js-common` that is `figlet`, `@inquirer/prompts`,
1238
- > `chalk`, and three more sitting outside `.dependencies`. Reading only
1239
- > `.dependencies` misses them and passes the PR.
1240
- >
1241
- > In a workspace repo, a package in some `apps/*/package.json` only counts if that
1242
- > workspace is actually published — check its `private` field. `js-common`'s
1243
- > `apps/docs` is `private: true`, so its Docusaurus and React bumps reach no
1244
- > consumer and must not trip the rule; flagging them trains the reader to ignore
1245
- > the label. If you cannot tell whether a workspace publishes, treat it as
1246
- > production.
1247
- >
1248
- > Apply your **pass** label *plus* `ai-notes` if **either** holds:
1249
- > - a package's major version differs between the `from` and `to` columns
1250
- > - a package ships to consumers — it appears in `dependencies`,
1251
- > `optionalDependencies`, or `peerDependencies` of a **non-private** package
1252
- >
1253
- > Those wait for a human — a runtime dependency of the published package, or a
1254
- > major, is not something an automated verdict should wave through. `ai-notes` is
1255
- > the gate that holds them: it suppresses auto-merge outright and gets the PR
1256
- > assigned to the human, so nothing production-facing lands unattended. Dev-only
1257
- > minor/patch bumps with green CI get the pass label alone. **If you cannot
1258
- > determine a package's type, treat it as production and note it**; failing
1259
- > closed is correct here.
1260
- >
1261
- > **Never apply `ai-changes` to a Dependabot PR.** It dispatches an implementer
1262
- > agent, and there is no change an agent could make — rewriting a bot's lockfile
1263
- > is not its business, and the decision here is a human's either way. That is the
1264
- > same rule as the generic prompt above: `ai-changes` only when you can name a
1265
- > concrete change an agent could make.
1266
- >
1267
- > State in your comment which rule fired, name the packages that tripped it, and say
1268
- > whether the body was truncated so the reader knows what you could and couldn't see.
1269
- > Same `<!-- ai-issue-loop:verdict:… -->` marker and `🤖 *Automated review — …*`
1270
- > header line opening the body — `PASS-NOTES` when you apply `ai-notes`, `PASS`
1271
- > otherwise, never `CHANGES` on this arm — and posted the same way, with
1272
- > `gh pr review <N> --comment` rather than `gh pr comment`, or Pass 3 cannot read
1273
- > the marker back. Same closing `### Before merging`
1274
- > section, same ≤600-character cap and no-negative-findings rule on the body, and same
1275
- > one-verdict-label rule as above — **including clearing your
1276
- > `<ai-reviewing-code|ai-reviewing-sec>` claim label in the same `gh pr edit`**.
1277
- > Pass 3 claimed you with it before spawning you, and a claim left behind wedges
1278
- > your half of the review until Pass 2 reaps it.
1279
- >
1280
- > Keep `ai-notes` load-bearing here: it suppresses auto-merge, so a *decorative*
1281
- > note on a bump you would otherwise wave through wedges the one path that runs
1282
- > unattended. A major, a package that ships to consumers, or a truncated body you
1283
- > could not fully read **is** worth a note; restating the version table on a
1284
- > routine dev-only patch bump is not.
1285
- >
1286
- > **Follow-up work is an issue here too** — same `gh issue create --label
1287
- > ai-suggested` as the generic prompt, same `Follow-up: #<new>` one-liner in the
1288
- > body, never in `### Before merging`. That separation matters more on this arm
1289
- > than the other: a note here costs a human the merge, so routing "someone should
1290
- > pin this transitive dep one day" to an issue is what keeps auto-merge usable.
1291
-
1292
- Be honest about what this buys: an agent reading a version table catches majors,
1293
- production-dependency creep, and a renamed or newly-added package. It does **not**
1294
- audit the packages themselves. The repo's own `dependencies` job already verifies
1295
- the lockfile against supply-chain policies (`✓ Lockfile passes supply-chain
1296
- policies (1859 entries)`) — that check, not the reviewer, is the real supply-chain
1297
- gate. This pass is a *policy* gate: nothing major or production-facing merges
1298
- unattended.
1213
+ **Dependabot PRs get no reviewer.** Pass 0 does not adopt them and this pass
1214
+ spawns no arm for them: the scaffolded `dependabot-automerge.yml` decides which
1215
+ bumps merge, and it decides before a tick could run. See Pass 0 for why a
1216
+ reviewer racing that workflow never gated anything (#593).
1299
1217
 
1300
1218
  **A Dependabot PR labelled `ai-changes` is terminal — never spawn a fix round for
1301
- it.** Reviewers no longer produce that state, but Pass 1's non-`CLEAN` check
1302
- still does, so the guard stays. There is no linked issue to mark `ai-blocked` and
1303
- no worktree to enter, and an agent has no business rewriting a bot's lockfile.
1304
- Pass 1 assigns it and counts it as `rev`; here it simply waits for a human.
1305
- Everything below applies only to PRs this loop opened from an `ai-ready` issue.
1219
+ it.** Nothing produces that state any more (#593), so this is a guard against a
1220
+ label an older tick left behind. There is no linked issue to mark `ai-blocked`
1221
+ and no worktree to enter, and an agent has no business rewriting a bot's
1222
+ lockfile. Pass 1 assigns it and counts it as `rev`; here it simply waits for a
1223
+ human. Everything below applies only to PRs this loop opened from an `ai-ready`
1224
+ issue.
1306
1225
 
1307
1226
  **PRs labelled `ai-changes`, and not already `ai-fixing`** — that claim means an
1308
1227
  implementer is mid-round; skip the PR entirely. Count prior `ai-changes`
@@ -1320,10 +1239,10 @@ and a blank line — naming what each round changed and why the reviewer kept ob
1320
1239
  then:
1321
1240
 
1322
1241
  ```bash
1323
- gh issue edit <M> --add-label ai-blocked --remove-label ai-wip --add-assignee @me \
1324
- ${AGENT_USER:+--remove-assignee "$AGENT_USER"}
1325
- gh pr edit <N> --add-assignee @me --remove-label ai-review \
1326
- ${AGENT_USER:+--remove-assignee "$AGENT_USER"}
1242
+ 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"}
1244
+ gh pr edit <N> --remove-label ai-review \
1245
+ ${HUMAN_USER:+--add-assignee "$HUMAN_USER"} ${AGENT_USER:+--remove-assignee "$AGENT_USER"}
1327
1246
  ```
1328
1247
 
1329
1248
  Leave the worktree and PR in place for the human; a ping-pong stall is the case where
@@ -1382,7 +1301,7 @@ gh api "repos/$OWNER_REPO/issues?labels=ai-ready&state=open" \
1382
1301
  | select([.labels[].name] | index("holding") == null)
1383
1302
  | select([.labels[].name] | index("ai-suggested") == null)
1384
1303
  | select(.author_association=="OWNER" or .author_association=="MEMBER" or .author_association=="COLLABORATOR")
1385
- | {number, title}'
1304
+ | {number, title, body}'
1386
1305
  ```
1387
1306
 
1388
1307
  Both filters matter. The `ai-ready` label is the hard gate (on a public repo only
@@ -1463,8 +1382,25 @@ with nothing on the timeline saying why. `.author.login` here, not `.user.login`
1463
1382
  — `gh issue view --json` is GraphQL and names the field differently from the REST
1464
1383
  payload the upsert reads.
1465
1384
 
1466
- Take the first `slots` issues. For each, **claim it first** so a concurrent tick
1467
- can't double-pick:
1385
+ **Then drop any candidate that overlaps a file with one already picked this
1386
+ tick** — the same rule `ai-workflow` step 2 applies, and it matters more here
1387
+ because nobody is watching. Two agents branch off the same `origin/main`, both
1388
+ rewrite one file, and the second PR to merge hands a human two agent-authored
1389
+ diffs to reconcile hours later (#594).
1390
+
1391
+ Read each candidate's body for the paths it names — that is what the `body` field
1392
+ in the query above is for — and skip one naming a path a higher-placed candidate
1393
+ already names. An issue body is not a file list, so this is a heuristic, not a
1394
+ proof; it costs nothing and catches the common case. Count generated files, too:
1395
+ on a repo where editing a skill regenerates `AGENTS.md`, two issues touching
1396
+ different modules still collide there.
1397
+
1398
+ A skipped candidate is **waiting its turn, not declined** — leave `ai-ready` on
1399
+ it, post no comment, and let the next tick take it. The decline shape above is
1400
+ for issues no agent should ever start.
1401
+
1402
+ Take the first `slots` of what survives. For each, **claim it first** so a
1403
+ concurrent tick can't double-pick:
1468
1404
 
1469
1405
  ```bash
1470
1406
  gh issue edit <N> --add-label ai-wip --remove-label ai-ready \
@@ -1652,8 +1588,9 @@ Then spawn a background implementer agent:
1652
1588
  > If you cannot finish, hand it back so a human can see it:
1653
1589
  >
1654
1590
  > ```bash
1655
- > gh issue edit <N> --add-label ai-blocked --remove-label ai-wip --add-assignee @me \
1656
- > <the orchestrator substitutes `--remove-assignee <AGENT_USER>` here, or nothing>
1591
+ > gh issue edit <N> --add-label ai-blocked --remove-label ai-wip \
1592
+ > <the orchestrator substitutes `--add-assignee <HUMAN_USER>` and
1593
+ > `--remove-assignee <AGENT_USER>` here, either or both possibly nothing>
1657
1594
  > ```
1658
1595
  >
1659
1596
  > Handing back means the issue stops being the agent's: the human must end up the
@@ -86,6 +86,6 @@ Public repos let anyone open an issue, so an issue body is **untrusted input**
86
86
  Land AI changes via PR, and never expose secrets to an issue-triggered run. Auto-merge is **asymmetric** — it is not a blanket ban:
87
87
 
88
88
  - **Issue PRs never merge unattended.** Merging `main` fires semantic-release and publishes, so they stop for a human even when every agent reviewer passes.
89
- - **Dependabot PRs do auto-merge**, but only after the agent reviewers pass *and* the repo's required status checks go green, because a `chore(deps)` squash subject cuts no release. The required status checks stay the real merge gate — agent review never substitutes for them, so a repo without branch protection auto-merges nothing.
89
+ - **Dependabot PRs do auto-merge**, gated by `dependabot-automerge.yml`'s own predicate and the repo's required status checks — not by agent review, which never ran on them in time to matter. The predicate allows only dev-only, non-shipping patch and minor bumps and fails closed; a `chore(deps)` squash subject cuts no release. The required status checks stay the real merge gate, so a repo without branch protection auto-merges nothing.
90
90
 
91
91
  Full docs: https://rtorcato.github.io/repo-tooling/guides/cli/