Review mr
Review a GitLab merge request like a senior engineer and post findings as a draft review. Use when the user supplies a GitLab MR URL and asks to review it. GitHub not supported.From its SKILL.md
npx -y skills add sleeyax/skills --skill review-mrAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
10.5 KB, as published. Nobody here has run it
Review a Merge Request
MR: $ARGUMENTS
Review a GitLab MR like a senior engineer on the team and post findings as draft notes so the user publishes/discards manually.
This skill fetches the MR's context and places the comments. The reviewing itself is delegated to the review-changes skill in report-only mode, which picks the reviewers (two agents by default), runs them, and returns one merged, deduplicated set of findings.
Hard rules
- Never
publishdraft notes — leave them for the user. - Every gate — dirty-tree, no-checkout, diff-size, pre-submission — is a real stop: ask, then wait for the answer. Use the
AskUserQuestiontool when it's available in the session; where it isn't (e.g. Codex), ask in plain text with the same numbered options and stop until the user replies. Never assume an answer. - Never push, commit, or modify the cwd repo's working tree without an explicit confirm.
- Every finding cites a real
+(added) line in the actual diff. Removed-line comments are out of scope. - Posting is gated by exactly one explicit confirm covering the whole batch.
- Don't review the diff here. Delegate to
review-changesand work from the findings it returns; the only exception is diff-only mode, where that skill can't run — and the user has to pick it at the step 5 gate. - One defect is one draft note.
review-changesmerges and deduplicates before returning — never re-split a merged finding, and never post the same defect twice because both agents found it. - The agent writes exactly one
findings.jsonper run (matchingfindings.schema.json) and invokespost-draft-note.shexactly once. No per-finding shell calls. Rollback on partial failure is the helper's job. - GitLab MCP server first if available; fall back to
glab. If neither, stop. - Never dump the full diff to a file or to chat, or hand it to a subagent as one blob. When the cwd repo matches the MR, read the diff incrementally from
git(per-file, on demand) — not fromglab api .../diffs. The APIdiffsendpoint is only used when there is no local checkout, and even then read it page-by-page, one file at a time, never as one bulk write. To measure size up front, use/merge_requests/<iid>/changes(metadata + per-file stats), not/diffs. This rule still holds when the user picks Review whole diff anyway — that answer authorizes the scope, not bulk-dumping.
Workflow
1. Parse the URL
Require a full GitLab MR URL in $ARGUMENTS. Extract:
host(e.g.gitlab.com,gitlab.example.com)project_path(group/subgroup/project)mr_iid(integer after/merge_requests/)
If missing or malformed, stop and ask the user for a URL.
2. Detect tooling
- Prefer the GitLab MCP server's tools if present in the session.
- Else require
glabon PATH (command -v glab). Verify auth for the MR's host:glab auth status --hostname <host>. - If neither is available, stop and tell the user to install
glabfrom https://gitlab.com/gitlab-org/cli.
3. Resolve numeric project ID
Some GitLab versions reject URL-encoded paths on /draft_notes. Resolve once:
glab api "projects/<urlencoded-path>"
Capture .id and use that numeric project_id for every later call.
4. Fetch MR metadata only
Get only title, description, source/target branch, state, diff_refs (base_sha, start_sha, head_sha), and the changed-files list with stats (paths + per-file additions/deletions). Do not fetch full diff text yet.
- MCP: corresponding tools (metadata only).
- CLI:
glab api "projects/<id>/merge_requests/<iid>"— metadata +diff_refsglab api "projects/<id>/merge_requests/<iid>/changes" --paginate— file list + stats. Parse paths and counts; do not save the response to a file.
Bail early with a clear message if:
stateisclosedormergeddiff_refsis missing or null
5. Local checkout (preferred)
If git remote -v in cwd matches the MR's project, check out before reading any diff content:
git status --porcelainclean check. If dirty, ask: Stash (git stash push -u -m "review: pre-checkout") / Abort.git fetch originglab mr checkout <iid>to land on the MR's source branch.
From this point, the diff source is git, not the API:
- Per-file diff:
git diff <base_sha>...<head_sha> -- <path>on demand, one path at a time. - File contents at HEAD: read the file directly from the working tree.
If cwd is unrelated to the project, stop and ask before reviewing anything. Diff-only is a fallback, not a default, and this is the last point where getting a proper checkout is still cheap:
- Point me at a checkout (Recommended) — the user gives the path to an existing clone. Verify its remote matches the MR's project, then run this step from the top there.
- Clone it —
git clonethe project into a temp dir and continue from 5.1 there. Report the path in step 11 so the user can keep or delete it. - Review diff-only anyway — accept diff-only mode. Say what it costs before they choose: one agent instead of two, diff hunks with no surrounding file, and no
review-changes. - Cancel — exit without reviewing.
6. Diff-size guardrail
Using only the file list + stats from step 4 (no full diff text needed), if the changeset exceeds 800 changed lines or 30 changed files, ask:
- Pick files (Recommended) — user supplies a subset of paths to review
- Review whole diff anyway
- Cancel
The chosen subset is the only set of paths the reviewers will pull diffs for in step 7.
7. Review the diff
Invoke the review-changes skill in report-only mode. It owns everything about the review — which reviewers run (two agents by default), running them simultaneously, the read-only constraints, and merging their findings into one deduplicated set. Pass it:
report-only- the scope:
<base_sha>...<head_sha> -- <path> …when step 6 narrowed the file set
It returns the merged findings in its finding shape (severity, file, line, title, explanation, optional suggestion), the reviewers + models it used, and the raw per-reviewer and merged counts. Keep all of it — the counts and reviewer names go in the summary note. Don't re-review the diff yourself, and don't re-litigate its merge decisions.
Its severity vocabulary — blocker, concern, suggestion, nit — is the one this skill posts with, so it carries over unchanged.
Then check each returned finding before it becomes a draft note:
- The anchor must be a real
+(added) line in this MR's diff. Re-anchor it to the right added line if it's off; drop the finding if it has no added line to attach to, and say so. - Findings outside the reviewed path set are out of scope — drop them.
- A
suggestionbecomes asuggestion:-N+Mblock in step 8 (Nlines before the anchor,Mafter;-0+0= anchor only), with the original indentation preserved.
Diff-only mode. review-changes reads the diff from git, so it needs the local checkout from step 5. Reached only when the user chose Review diff-only anyway at that step's gate; review here instead: walk the file list one path at a time via glab api "projects/<id>/merge_requests/<iid>/diffs?page=N&per_page=1", read-only, holding nothing on disk, and produce the same finding shape yourself. Say in the summary that the review was single-agent and diff-only.
8. Build the in-memory findings list
For each finding, fill the body field by rendering COMMENT_TEMPLATE.md (inline) or SUMMARY_TEMPLATE.md (summary). Hold the working set as an in-memory list of objects shaped like the schema in findings.schema.json:
{ severity, path, oldPath, line, title, body } // per inline finding
{ body } // summary (optional)
title is for the pre-submission UI only — it doesn't get posted (it's already inside body). severity is metadata for sorting; not posted.
9. Pre-submission review loop
- Print numbered list to chat:
[N] <severity> · <path>:<line> · <title>+ 1-line body preview, carrying over the reviewer tagreview-changesreturned —[both],[codex],[claude]— plus its raw and merged counts (e.g. "14 findings from 2 reviewers → 9 unique, 5 agreed").- Summary as
[S]
- Ask: Post all / Drop some / Edit some / Cancel.
- Drop some → ask which numbers, remove them, loop back to step 1.
- Edit some → ask which single number, then free-form chat about that comment. Editable:
title,body,path,line. After each round, ask: Done / Keep editing / Discard this comment. Only Done locks in changes; only then loop back to step 1. - Post all → step 10.
- Cancel → drop everything, exit without posting.
10. Post (single batch)
-
Serialize the in-memory list to a
findings.jsonfile atmktemp -t review-mr-findings-XXXXXX.json. Top-level shape:{ "diffRefs": { "baseSha": "...", "startSha": "...", "headSha": "..." }, "summary": { "body": "..." }, "findings": [ { "severity": "...", "path": "...", "oldPath": "...", "line": N, "title": "...", "body": "..." } ] }Drop the
titlefrom the JSON if it adds noise — it's optional. Schema lives next to this file atfindings.schema.json; consult it for the authoritative shape. -
Invoke the helper exactly once:
./post-draft-note.sh --project <id> --mr <iid> --findings <tmp.json>The helper script lives next to this file. It validates the JSON against
findings.schema.json, idempotency-checks against existing drafts, posts the batch, and rolls back viaDELETEon any HTTP failure. On success it prints one line of JSON:{"posted":N,"skipped":M,"ids":[...]}to stdout. On failure it forwards the error to stderr and exits non-zero. -
On success, delete the findings file. On failure, leave it in place and tell the user the path so they can inspect or retry.
11. Report
Print:
- MR title + URL
<mr_url>#draftsdirect link- Reviewers used + models, and how each ran (inline, in-process subagent, or CLI)
posted/skippedcounts from the helper's success JSON- Reminder: review is unpublished. The user publishes or discards via the GitLab UI or
glab.
Then stop.