review-sweep — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited review-sweep (Agent Skill) and scored it 79/100 (yellow). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 5 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 5 flagged
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Scan every open PR/MR you authored across GitHub and GitLab, fetch unresolved reviewer threads, hand each comment to the abc:triage subagent for classification, present a dashboard, and apply only the fixes you confirm.
Composes with `/loop` for periodic sweeps: /loop 30m /abc:review-sweep keeps the queue swept while you work on other things.
ready to push — approve? and force-pushed only after the user approves. The skill is a triage and proposal tool, not a hands-off auto-fixer.main/master. Only push to the PR/MR's source branch.--include-drafts.--no-verify, no test deletions, no assertion widening, even when the only way to make the rebased branch pass gates is to weaken a check.**/*.test.*, **/*.spec.*, **/__tests__/**, **/test/**, **/tests/**) rejects any proposed fix that would touch a test file and escalates to the user. Test-edit requirements are never auto-applied — even when the test is the one that needs updating, the legitimate-test-update case goes to user judgment via the dashboard, not silent commit.$ARGUMENTS selects platforms:
both → scan GitHub AND GitLab (default)github → GitHub onlygitlab → GitLab onlyRun auth pre-flight in parallel for the targeted platforms:
gh auth status — if not authed, skip GitHub scan and surface the re-auth command.glab auth status --hostname <host> — same fallback. Derive <host> from the ABC_GITLAB_HOST env var if set, else parse it from git remote get-url origin; fall back to bare glab auth status if no GitLab remote resolves (never hardcode a GitLab hostname).If both fail, abort with the re-auth commands. Don't degrade silently.
In parallel per platform:
GitHub (cross-repo, via search API):
gh search prs --author=@me --state=open --json url,number,title,repository,isDraft,updatedAt --limit 50GitLab (cross-project, via MCP):
mcp__gitlab__list_user_merge_requests with state: opened. Resolve the author identity MCP-natively rather than scraping `glab auth status`: prefer the GitLab MCP server's own authenticated-user method (a current user / whoami-style call the server exposes) to obtain the username, or let list_user_merge_requests scope to the authenticated user directly if the server supports an implicit-self mode. Parsing glab auth status for the username is a fallback only — used when no MCP identity call is available. Host selection: if multiple GitLab hosts are authenticated, pick the host whose project namespace matches the cwd's git remote; if that is ambiguous, AskUserQuestion for the host rather than guessing.Filter out drafts unless --include-drafts was passed. Drop any PR/MR with no recent activity (no updates in 60+ days) — too stale to sweep cleanly.
If the resulting list is empty: print "No open PRs/MRs with reviewer activity. Inbox zero." and return.
Resolving the local working copy for a PR/MR's head-branch repo follows one rule everywhere — do not redefine it per phase:
origin remote URL to the PR/MR's repo. The one whose remote matches is the workdir.The escalation on step 3 differs only by phase:
no local clone available — rebase manually and skip Phase 2+ for that PR/MR. Never prompt — the pre-pass runs without a human watching.There is no `repo:` label in this resolution — that convention belongs to the ship-* skills, not review-sweep (a reviewer's open PRs aren't necessarily scaffolded sub-issues, so the label may not exist).
Runs once per enumerated PR/MR, before triage. The goal: a PR that's behind main/master but otherwise mergeable shouldn't appear in the dashboard as "stalled" — most parallel-work conflicts are textual (multiple PRs adding imports to the same file, JSX elements to the same component, lines to the same array) and resolvable by git's three-way merge. Phase 1.5 tries the mechanical resolution before the dashboard renders.
Per PR/MR, in parallel where possible:
gh api /repos/<owner>/<repo>/pulls/<num> and read the mergeable field. (GitHub computes this asynchronously; a null value means "still computing" — treat as true for this pass so we don't block on stale state. A subsequent sweep will catch it.)mcp__gitlab__get_merge_request and read the equivalent field.gh pr checkout <num> (or glab mr checkout <iid>). git fetch origin. git rebase origin/<base>.git rebase --abort. Surface in the dashboard as `needs manual rebase: <conflicted-file-paths>`. Skip Phase 2+ for this PR/MR.pnpm typecheck && pnpm test / package.json script invocation that Phase 5 uses post-fix.git rebase --abort (back to the pre-rebase tip). Surface in the dashboard as `rebased clean but gates failed: <top-20-lines-of-output>`. Skip Phase 2+ for this PR/MR.git push --force-with-lease. Post <!-- review-sweep:health:rebased --> as a marker-only comment on the PR/MR — the marker is the entire comment body, no trailing prose. Matches the convention of the other <!-- ship-issue:* --> / <!-- review-sweep:* --> markers (e.g. <!-- ship-issue:commit -->, <!-- ship-issue:rebase:auto -->): downstream skills grep for the marker as a yes/no signal, and free-form prose breaks the match across revisions. The rebase SHA range is already captured in the commit log for forensic reading. If a human-readable timeline note is also wanted, post it as a separate PR/MR comment that does NOT contain the marker so the two signals stay decoupled. Mark health-OK. Continue to Phase 2.Self-cheating hard stop (verbatim, applies inside Phase 1.5 too):
If the skill catches itself about to bypass a failing check rather than fix it — deleting a failing assertion so tests pass, adding--no-verifyto a commit, widening a type to suppress an error, wrapping a failing line in// @ts-expect-error,.skip()-ing a test that was failing, commenting out a lint rule that was firing,eslint-disable-ing a violation to silence it — hard stop. No self-healing, no "I'll come back and fix it properly," no silent retry. Surface the attempted-cheat in the dashboard so the user can see exactly what the skill was about to do.
In Phase 1.5 specifically: if the only way to make gates pass after a clean rebase is to delete or weaken an assertion, abort the rebase (step 7's --abort path) and surface as rebased clean but gates failed — never push a "fixed" rebase that's actually a cheat.
#### Phase 1.5b: CI repair (production-code-only, one attempt)
For PRs/MRs that came out of the rebase pre-pass health-OK (either no rebase needed, or successful auto-rebase) but have failing CI checks, attempt a one-shot production-code-only repair before the dashboard renders. Same engineering pattern as the rebase pre-pass: attempt the mechanical resolution, run the project's gates, escalate (don't auto-merge a cheat) if anything goes off-spec.
gh api /repos/<o>/<r>/commits/<sha>/check-runs --paginate (filter conclusion=failure) plus gh api /repos/<o>/<r>/commits/<sha>/status for the legacy status API.mcp__gitlab__list_merge_request_pipelines → mcp__gitlab__list_pipeline_jobs (filter to status=failed); pull a failing job's detail with mcp__gitlab__get_job and its log with mcp__gitlab__download_job_log.ship-issue/SKILL.md § Phase 3 rows 3a/3b:review-sweep is one-shot per sweep):gh api /repos/<o>/<r>/check-runs/<id>/logs (or gh run view <run-id> --log-failed).**/*.test.*, **/*.spec.*, **/__tests__/**, **/test/**, **/tests/**. If the patch touches a test file → reject the patch entirely. Surface in the dashboard as `CI red: proposed fix would modify test file (production-only auto-fix); needs your judgment`. Skip Phase 2+ for this PR/MR.--no-verify, no eslint-disable, no // @ts-expect-error to silence the failure, no widening types, no .skip()-ing tests. The verbatim hard stop above governs this step too. If the only mechanical fix is to weaken an assertion, treat the patch as rejected and surface as CI repair attempted but produced a cheat.pnpm typecheck && pnpm test or the equivalent from package.json scripts / CLAUDE.md).<!-- ship-issue:commit --> (no Co-Authored-By trailer when a reachable CLAUDE.md forbids it; see the ship-issue skills for the detection rule), so once pushed, any future /abc:ship-issue[-gh] wake on this PR finds the commit correctly.git push --force-with-lease, then post <!-- review-sweep:health:ci-fixed --> as a marker-only comment on the PR/MR — the marker is the entire comment body, no trailing prose. Matches the convention of <!-- review-sweep:health:rebased --> and the <!-- ship-issue:* --> family: downstream skills grep markers as yes/no signals, and free-form prose breaks the match across revisions. The fix-commit SHA is already in git log. If a human-readable timeline note is wanted, post it as a separate PR/MR comment that does NOT contain the marker. If the user declines, git restore . and reset the local commit, then surface as ci-fix declined.git restore . to revert the local patch (leaves the PR's pushed tip untouched). Surface in the dashboard as `CI repair attempted but gates failed: <top-20-lines-of-output>`. Skip Phase 2+ for this PR/MR.Hard rule (also listed under Phase 0 hard rules): Phase 1.5b is production-code-only by construction. Any failure that diagnoses to "the test needs updating" escalates to the user — review-sweep never silently rewrites a test to make CI green. The legitimate-test-update case (the PR intentionally changed the contract a test was asserting) is captured by the dashboard's proposed fix would modify test file line and routed to user judgment, not auto-applied.
The dashboard (Phase 4) renders health-pre-pass outcomes on their own lines so successful auto-rebases (✓ rebased, marker posted — rebases are pushed in-pass since they change no content), staged CI repairs (⏸ ci-fixed (staged) — ready to push — approve?, force-pushed only on Phase 5 approval), and health-escalations (needs manual rebase, rebased clean but gates failed, no local clone, CI red (non-assertion), proposed fix would modify test file, CI repair attempted but gates failed) are distinguishable at a glance.
For each PR/MR (in parallel where possible — gh api calls are cheap, GitLab MCP calls can run concurrently per project):
GitHub: GitHub does expose native review-thread resolution — through the GraphQL API, not REST. Use it as the primary signal. Fetch threads via:
gh api graphql -f query='
query($owner:String!,$repo:String!,$num:Int!){
repository(owner:$owner,name:$repo){
pullRequest(number:$num){
reviewThreads(first:100){
nodes{
isResolved
isOutdated
comments(first:50){ nodes{ author{login} body path line createdAt } } }
}
}
}
}' -F owner=<owner> -F repo=<repo> -F num=<num>Filter to threads with isResolved: false whose LATEST comment is from a reviewer (not the PR author). Use the REST gh api /repos/<owner>/<repo>/pulls/<num>/comments --paginate endpoint only to backfill per-comment detail the GraphQL nodes lack. The legacy "Done" / "Fixed in <sha>" author-reply heuristic is now secondary — apply it only as a fallback when GraphQL is unavailable (e.g. an old GHES host), and never to override an explicit isResolved: true.
GitLab: mcp__gitlab__discussion_list — each discussion has a resolved boolean. Filter for resolved: false. Skip discussions where the latest note is from the MR author.
Skip PRs/MRs with zero unresolved threads.
For each unresolved comment, dispatch the abc:triage subagent (use the Agent tool with subagent_type: abc:triage). Pass:
gh pr diff or mcp__gitlab__list_merge_request_diffs once per PR/MR, then slice)Parallelize — fire all triage subagents for a PR/MR at once. They're independent.
Each subagent returns the structured YAML (see triage agent contract). Collect responses per PR/MR.
Bucketing rules — apply before rendering:
confidence: low is re-mapped to judgment-required regardless of its classification field, per the triage agent contract ("the orchestrator should treat low-confidence items as judgment-required"). Do this before bucketing so a low-confidence fixable-code never lands in the auto-apply batch.noise are not rendered as individual lines — aggregate them into a single per-PR/MR skipped (noise): N count so the dashboard stays signal-dense. This makes all five triage categories (fixable-code, fixable-doc, question, judgment-required, noise) representable in the dashboard.After all triage subagents return, print a per-PR/MR dashboard. Phase 1.5 health-pre-pass outcomes are rendered as a health: line per PR/MR so successful auto-rebases and escalations are distinguishable at a glance:
/abc:review-sweep — <N> PRs/MRs, <M> unresolved threads
[1] acme/web#4521 "Add WidgetRow to dashboard"
health: ✓ rebased on origin/main (gates green; marker posted)
└─ src/WidgetRow.tsx:42 [fixable-code, high]
"Use `useMemo` for the derived rows array"
→ const rows = useMemo(() => derive(input), [input]);
└─ src/WidgetRow.module.css:18 [fixable-code, high]
"Hardcoded color, use --color-text-primary"
→ color: var(--color-text-primary);
└─ src/WidgetRow.tsx:12 [fixable-doc, high]
"JSDoc typo: `@para` should be `@param`"
→ * @param input the rows to derive from
└─ src/WidgetRow.tsx:88 [judgment-required, medium]
"Should this handle the empty state differently?"
skipped (noise): 2
[2] acme/api!231 "Auto-approve threshold tuning"
health: ✓ mergeable (no rebase needed)
└─ config/thresholds.yaml:14 [question, high]
"Why 0.6 not 0.7?"
[3] acme/web#4530 "Sidebar navigation refactor"
health: ⚠ needs manual rebase: src/Sidebar.tsx, src/routes.ts
(skipped triage)
[4] acme/web#4531 "Update form validation"
health: ⚠ rebased clean but gates failed: 2 type-errors in src/forms/Field.tsx
(skipped triage)
[5] acme/other#9 "Hot-fix typo in helper"
health: ⚠ no local clone available — rebase manually
(skipped triage)
[6] acme/web#4533 "Rename Avatar prop `size` → `dimension`"
health: ⏸ ci-fixed (staged) — ready to push — approve? (patched src/Avatar.tsx consumer; gates green locally; SHA abc1234)
└─ src/Avatar.tsx:18 [fixable-code, medium]
"Default prop value should come from theme tokens"
[7] acme/web#4534 "Refactor parseConfig to accept stream"
health: ⚠ CI red: proposed fix would modify test file (production-only auto-fix); needs your judgment
(skipped triage)
[8] acme/web#4535 "Add CSV export endpoint"
health: ⚠ CI red (non-assertion): build / install-deps
(skipped triage)
[9] acme/web#4536 "Wire shipping label printer"
health: ⚠ CI repair attempted but gates failed: 3 new type-errors after fix
(skipped triage)
Summary: 3 fixable-code, 1 fixable-doc, 1 judgment-required, 1 question, 2 noise (skipped) · 1 auto-rebased · 1 ci-fix staged (awaiting approval) · 6 health-escalationsThen ask via AskUserQuestion. Every option that mutates the remote spells out exactly what it will do — there is no posting, replying, or pushing path that isn't covered by the option text the user selects:
fixable-code + fixable-doc change, push any staged CI repairs (the ready to push — approve? items), and reply Fixed in <SHA>. to each addressed thread. Skips judgment-required and questions.fixable-doc changes and replies Fixed in <SHA>. to those threads. Leaves staged CI repairs unpushed.For each PR/MR the user chose to act on:
git fetch and gh pr checkout <num> / glab mr checkout <iid>.ci-fixed (staged) — ready to push — approve? commit and the user approved pushing it: git push --force-with-lease, then post the <!-- review-sweep:health:ci-fixed --> marker-only comment (per Phase 1.5b step 5). If the user declined, git restore . and reset the staged commit. This is the deferred half of Phase 1.5b's gate — it runs before the thread fixes so the branch tip is current.fixable-code/fixable-doc change as proposed. Use the suggestion block contents from the triage output.pnpm typecheck, pnpm test, or the equivalent from package.json scripts). Abort this PR/MR if anything fails — leave the branch as-is, report the failure, move on to the next PR/MR.Address review feedback on <thread topic> — no AI footer. Use HEREDOC.Fixed in <SHA>. Use:gh pr comment <num> --body "..." for thread replies — note GitHub's inline reply API is gh api /repos/.../pulls/comments/<id>/replies if you want to nest under the original comment.mcp__gitlab__discussion_add_note with the discussion ID.Surface each PR/MR's status after processing:
[1] acme/web#4521 ✓ 2 fixes applied, pushed <SHA>, 2 threads replied
[2] acme/api!231 — skipped (only question, no fixable items)After applying fixes, print a separate "needs your input" block:
Needs your attention:
[1] acme/web#4521 src/WidgetRow.tsx:88
"Should this handle the empty state differently?"
Triage rationale: design decision needed — depends on whether empty is a valid runtime state.
[2] acme/api!231 config/thresholds.yaml:14
"Why 0.6 not 0.7?"
Triage rationale: a reply with reasoning would close the thread.These are intentionally left for the user to handle. Do not auto-reply, even to questions — the user's voice on judgment calls matters.
Print a one-line summary:
Sweep complete: <X> PRs/MRs processed, <Y> fixes applied, <Z> threads pending your input.Return. The skill is one-shot — do NOT self-arm /loop. If the user wants periodic sweeps, they wrap with /loop 30m /abc:review-sweep.
<!-- review-sweep:health:rebased --> marker; non-trivial conflicts surface in the dashboard as needs manual rebase: <files> and skip Phase 2+ for that PR/MR.no local clone available — rebase manually and skips Phase 2+ for that PR/MR. No interactive prompt — the dashboard line is the entire signal.CI red (non-assertion) and never get an auto-fix attempt. Test-edit-requiring fixes surface as proposed fix would modify test file (production-only auto-fix); needs your judgment. A successful CI repair is committed locally and surfaced as ci-fixed (staged) — ready to push — approve?; it is force-pushed (and gets the <!-- review-sweep:health:ci-fixed --> marker) only after the user approves in Phase 4/5 — review-sweep never auto-pushes a code change.fixable-code if the rule has a mechanical fix, judgment-required if it's a design lint (architectural pattern violations).~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.