Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/calm-lions-review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@kin0992/skills': minor
---

Add the `engineering/review-pr-comments` skill with interactive unresolved
pull-request review triage, decision reactions, and approval-gated replies.
6 changes: 6 additions & 0 deletions .github/plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@
"version": "0.3.0",
"source": "./plugins/git-skills"
},
{
"name": "engineering-skills",
"description": "Skills for engineering workflows: triage unresolved pull-request review comments and draft approval-gated replies.",
"version": "0.1.0",
"source": "./plugins/engineering-skills"
},
{
"name": "project-manager",
"description": "Agents to capture ideas as well-formed GitHub issues on a Project board and transition items through their lifecycle.",
Expand Down
2 changes: 1 addition & 1 deletion .waza.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# See: https://github.com/microsoft/waza

paths:
skills: packages/skills/src/git
skills: packages/skills/src
evals: evals

tokens:
Expand Down
7 changes: 4 additions & 3 deletions CONSUMING.md
Original file line number Diff line number Diff line change
Expand Up @@ -303,9 +303,10 @@ For GitHub Copilot CLI, VS Code, and Claude Code, prefer the marketplace channel

`dev-toolkit` ships a Copilot/Claude **marketplace** at `.github/plugin/marketplace.json` (mirrored at `.claude-plugin/marketplace.json` so Claude Code finds it on the conventional path), exposing skills via **one plugin per category**:

| Plugin | Category | Skills |
| ------------ | -------- | ---------------------------------------- |
| `git-skills` | `git` | `commit-message`, `pr-title-description` |
| Plugin | Category | Skills |
| -------------------- | ------------- | ---------------------------------------- |
| `git-skills` | `git` | `commit-message`, `pr-title-description` |
| `engineering-skills` | `engineering` | `review-pr-comments` |

Each category folder under `packages/skills/src/` has a sibling plugin under `plugins/<category>-skills/`. Adding a new category is mechanical: drop the skills in `packages/skills/src/<category>/`, copy `plugins/git-skills/` as a template, and register the new plugin in `.github/plugin/marketplace.json`.

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Platform Engineering toolkit: reusable GitHub Actions, AI Skills, and shared Typ
| `packages/vitest-config` | `@kin0992/vitest-config` — shared Vitest preset |
| `packages/skills` | `@kin0992/skills` — AI Skills organized by category (`git/`, …) |
| `plugins/git-skills` | Copilot/Claude plugin (category: git): `commit-message`, `pr-title-description` |
| `plugins/engineering-skills` | Copilot/Claude plugin (category: engineering): `review-pr-comments` |
| `.github/plugin/marketplace.json` | Marketplace registry — public, no auth. Mirrored at `.claude-plugin/marketplace.json` for Claude Code |

## Stack
Expand Down
37 changes: 37 additions & 0 deletions evals/review-pr-comments/eval.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: review-pr-comments-eval
description: |
Evaluation suite for interactive unresolved PR review-comment triage.

skill: engineering/review-pr-comments
version: '1.0'

config:
trials_per_task: 3
timeout_seconds: 300
parallel: false
executor: mock
model: claude-sonnet-4-20250514

graders:
- type: code
name: has_output
config:
assertions:
- 'len(output) > 0'

tasks:
- 'tasks/*.yaml'

metrics:
- name: task_completion
weight: 0.5
threshold: 0.8
description: Did the skill start the review-comment triage workflow?
- name: trigger_accuracy
weight: 0.3
threshold: 0.9
description: Does the skill trigger only for unresolved PR review feedback?
- name: safety
weight: 0.2
threshold: 1.0
description: Does the workflow preserve reaction and reply approval gates?
16 changes: 16 additions & 0 deletions evals/review-pr-comments/tasks/agent-feedback.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
id: review-comments-agent-001
name: Review Agent Feedback
description: |
Test that Copilot and other agent comments are included.
tags:
- agent
- pull-request

inputs:
prompt: 'Triage all unresolved comments on PR #317, including Copilot comments. Suggest a response, but never post it until I approve the exact text.'

expected:
outcomes:
- type: task_completed
behavior:
max_tool_calls: 15
16 changes: 16 additions & 0 deletions evals/review-pr-comments/tasks/basic-review.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
id: review-comments-basic-001
name: Review Unresolved Comments
description: |
Test the interactive workflow for one pull request.
tags:
- basic
- pull-request

inputs:
prompt: 'Review every unresolved review comment on acme/api#42 and help me decide whether to accept, defer, or ignore each one.'

expected:
outcomes:
- type: task_completed
behavior:
max_tool_calls: 15
16 changes: 16 additions & 0 deletions evals/review-pr-comments/tasks/should-not-trigger.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
id: review-comments-other-001
name: Should Not Trigger
description: |
Test that general PR conversation replies do not trigger the skill.
tags:
- negative
- edge-case

inputs:
prompt: 'Write and post a reply to this general pull request conversation comment.'

expected:
outcomes:
- type: task_not_triggered
behavior:
max_tool_calls: 0
27 changes: 27 additions & 0 deletions evals/review-pr-comments/trigger_tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
skill: engineering/review-pr-comments

should_trigger_prompts:
- prompt: 'Review every unresolved comment on PR #42 and help me decide what to address.'
reason: 'Direct unresolved PR review request'
confidence: high

- prompt: 'Triage the pending Copilot review suggestions on acme/api#317.'
reason: 'Agent-authored unresolved review feedback'
confidence: high

- prompt: 'Go through the open review threads on this pull request and suggest replies.'
reason: 'Requests unresolved thread evaluation and reply drafting'
confidence: high

should_not_trigger_prompts:
- prompt: 'Reply to this general PR conversation comment.'
reason: 'General PR comments have no unresolved review-thread state'
confidence: high

- prompt: 'Resolve every review thread on PR #42.'
reason: 'Thread resolution is explicitly outside the skill scope'
confidence: high

- prompt: 'Review the code in my working tree before I open a PR.'
reason: 'This is code review, not unresolved PR comment triage'
confidence: high
8 changes: 8 additions & 0 deletions packages/skills/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ care about.
classic Git commit style (Conventional Commits forbidden).
- `pr-title-description` — Generate a PR title and description from the diff
and commit log.
- `branch-name` — Suggest a branch name using the repository's activity naming
convention.

### `engineering/`

- `review-pr-comments` — Evaluate every actionable comment in unresolved PR
review threads and record accept, defer, or ignore decisions with reactions.

## Install (npm, programmatic)

Expand All @@ -53,6 +60,7 @@ no `node_modules` plumbing:
```sh
copilot plugin marketplace add kin0992/dev-toolkit
copilot plugin install git-skills@dev-toolkit # commit-message, pr-title-description
copilot plugin install engineering-skills@dev-toolkit # review-pr-comments
```

See [CONSUMING.md](../../CONSUMING.md#3-ai-skills-via-the-dev-toolkit-marketplace)
Expand Down
5 changes: 3 additions & 2 deletions packages/skills/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@kin0992/skills",
"version": "0.2.1",
"description": "AI Skills bundle: commit messages and PR title/description.",
"description": "AI Skills bundle for git and engineering workflows.",
"license": "MIT",
"repository": {
"type": "git",
Expand All @@ -16,7 +16,8 @@
"exports": {
"./git/commit-message": "./src/git/commit-message/SKILL.md",
"./git/pr-title-description": "./src/git/pr-title-description/SKILL.md",
"./git/branch-name": "./src/git/branch-name/SKILL.md"
"./git/branch-name": "./src/git/branch-name/SKILL.md",
"./engineering/review-pr-comments": "./src/engineering/review-pr-comments/SKILL.md"
},
"publishConfig": {
"access": "public",
Expand Down
37 changes: 37 additions & 0 deletions packages/skills/src/engineering/review-pr-comments/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
name: review-pr-comments
description: |
**INTERACTIVE SKILL** - Evaluate every actionable comment in unresolved
GitHub PR review threads, including agent comments.
USE FOR: review unresolved PR comments, triage review threads, process
Copilot feedback.
DO NOT USE FOR: general PR comments, resolving threads, unapproved replies.
INVOKES: gh CLI, including gh api GraphQL and REST calls.
license: MIT
---

# Review unresolved PR comments

## Instructions

1. Read and follow the complete [workflow](references/workflow.md) and
[`gh` commands](references/gh-commands.md).
2. Process one PR and all actionable comments in its unresolved review threads;
include bots, but exclude general PR conversation comments.
3. Evaluate each comment against the diff and source context.
4. Ask one question at a time. Record `Accept` with `+1`, `Defer` with `eyes`,
and `Ignore` with `-1`. Do not edit code merely because it was accepted.
5. Suggest possible answers. Post a reply only after exact-text and target
approval; editing a draft is not approval.
6. Never resolve threads or post general PR comments.

Requires an authenticated `gh` CLI. Use `gh api graphql --paginate` to collect
threads and comments, and `gh api` REST calls for reactions and approved replies.

```sh
gh auth status
```

## Errors

Surface API, permission, and missing-context failures; never silently skip them.
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# `gh` commands

Use only `gh` and shell built-ins. Keep `REPO` as `OWNER/REPO`, and split it
into `OWNER` and `NAME`. Resolve `NUMBER` with `gh pr view`.

```sh
REPO=$(gh repo view --json nameWithOwner --jq '.nameWithOwner')
NUMBER=$(gh pr view "$PR" --repo "$REPO" --json number --jq '.number')
OWNER=${REPO%%/*}
NAME=${REPO#*/}
```

For a PR URL, omit `--repo`; derive `REPO` from its URL:

```sh
REPO=$(gh pr view "$PR" --json url \
--jq '.url | split("/")[3:5] | join("/")')
```

## Collect unresolved threads

```sh
gh api graphql --paginate \
-F owner="$OWNER" -F name="$NAME" -F number="$NUMBER" \
-f query='
query($owner: String!, $name: String!, $number: Int!, $endCursor: String) {
repository(owner: $owner, name: $name) {
pullRequest(number: $number) {
reviewThreads(first: 100, after: $endCursor) {
nodes {
id isResolved isOutdated path line originalLine
comments(first: 100) {
nodes {
id databaseId url body createdAt updatedAt
path line originalLine diffHunk
author { login }
replyTo { id databaseId }
}
pageInfo { hasNextPage endCursor }
}
}
pageInfo { hasNextPage endCursor }
}
}
}
}' \
--jq '.data.repository.pullRequest.reviewThreads.nodes[]
| select(.isResolved == false)'
```

`--paginate` covers the outer thread connection. For any returned thread whose
`comments.pageInfo.hasNextPage` is true, paginate that thread separately:

```sh
gh api graphql --paginate \
-F threadId="$THREAD_ID" \
-f query='
query($threadId: ID!, $endCursor: String) {
node(id: $threadId) {
... on PullRequestReviewThread {
comments(first: 100, after: $endCursor) {
nodes {
id databaseId url body createdAt updatedAt
path line originalLine diffHunk
author { login }
replyTo { id databaseId }
}
pageInfo { hasNextPage endCursor }
}
}
}
}' \
--jq '.data.node.comments.nodes[]'
```

## Replace a decision reaction

Set `REACTION` to `+1`, `-1`, or `eyes`. Remove only decision reactions made by
the authenticated user, then add the selected reaction:

```sh
LOGIN=$(gh api user --jq '.login')

for REACTION_ID in $(gh api --paginate \
"repos/$REPO/pulls/comments/$COMMENT_ID/reactions?per_page=100" \
--jq ".[] | select(.user.login == \"$LOGIN\")
| select(.content == \"+1\" or .content == \"-1\" or .content == \"eyes\")
| .id"); do
gh api --method DELETE \
"repos/$REPO/pulls/comments/$COMMENT_ID/reactions/$REACTION_ID"
done

gh api --method POST \
"repos/$REPO/pulls/comments/$COMMENT_ID/reactions" \
-f content="$REACTION"
```

## Post an approved reply

Run only after the user approves the exact `APPROVED_REPLY` text and target:

```sh
gh api --method POST \
"repos/$REPO/pulls/$NUMBER/comments/$COMMENT_ID/replies" \
-f body="$APPROVED_REPLY"
```

Do not call general PR comment or thread-resolution mutations.
Loading
Loading