pr-prism 1.0.5 → 1.1.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/.github/workflows/ci.yml +0 -2
- package/.github/workflows/publish.yml +114 -0
- package/.github/workflows/release.yml +123 -0
- package/.github/workflows/security.yml +0 -2
- package/.pr-prism.example.json +3 -0
- package/README.md +79 -76
- package/agents/scrape-pr-reviews.md +8 -3
- package/docs/plans/2026-03-14-auto-resolve-design.md +166 -0
- package/docs/plans/2026-03-14-auto-resolve-plan.md +570 -0
- package/llms.txt +8 -3
- package/package.json +23 -11
- package/scripts/resolve-pr-threads.ts +293 -1
- package/scripts/scrape-pr-reviews.ts +48 -8
- package/.github/workflows/labeler.yml +0 -21
- package/.github/workflows/version-bump.yml +0 -35
package/.github/workflows/ci.yml
CHANGED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
name: Release & Publish
|
|
2
|
+
|
|
3
|
+
# Auto-releases on every push to main.
|
|
4
|
+
#
|
|
5
|
+
# Version bump is determined by conventional commit prefix:
|
|
6
|
+
# feat!: / BREAKING CHANGE → major
|
|
7
|
+
# feat: → minor
|
|
8
|
+
# everything else → patch
|
|
9
|
+
#
|
|
10
|
+
# If package.json already has an un-tagged version (manual bump),
|
|
11
|
+
# that version is used as-is — no auto-bump.
|
|
12
|
+
#
|
|
13
|
+
# Skip release: add [skip release] to the commit message.
|
|
14
|
+
|
|
15
|
+
on:
|
|
16
|
+
push:
|
|
17
|
+
branches: [main]
|
|
18
|
+
|
|
19
|
+
concurrency:
|
|
20
|
+
group: release
|
|
21
|
+
cancel-in-progress: false
|
|
22
|
+
|
|
23
|
+
permissions:
|
|
24
|
+
id-token: write
|
|
25
|
+
contents: write
|
|
26
|
+
|
|
27
|
+
jobs:
|
|
28
|
+
release:
|
|
29
|
+
if: >
|
|
30
|
+
!startsWith(github.event.head_commit.message, 'chore(release):') &&
|
|
31
|
+
!contains(github.event.head_commit.message, '[skip release]')
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/checkout@v4
|
|
36
|
+
with:
|
|
37
|
+
fetch-depth: 0
|
|
38
|
+
|
|
39
|
+
- uses: pnpm/action-setup@v4
|
|
40
|
+
|
|
41
|
+
- uses: actions/setup-node@v4
|
|
42
|
+
with:
|
|
43
|
+
node-version: 22
|
|
44
|
+
registry-url: https://registry.npmjs.org
|
|
45
|
+
cache: pnpm
|
|
46
|
+
|
|
47
|
+
- name: Upgrade npm for Trusted Publishing
|
|
48
|
+
run: npm install -g npm@latest
|
|
49
|
+
|
|
50
|
+
- name: Install dependencies
|
|
51
|
+
run: pnpm install --frozen-lockfile
|
|
52
|
+
|
|
53
|
+
- name: Configure Git
|
|
54
|
+
run: |
|
|
55
|
+
git config user.name "github-actions[bot]"
|
|
56
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
57
|
+
|
|
58
|
+
- name: Determine version bump type
|
|
59
|
+
id: bump
|
|
60
|
+
run: |
|
|
61
|
+
MSG=$(git log -1 --pretty=format:'%s')
|
|
62
|
+
|
|
63
|
+
if echo "$MSG" | grep -qE '^[a-z]+(\(.*\))?!:' || echo "$MSG" | grep -q 'BREAKING CHANGE'; then
|
|
64
|
+
echo "type=major" >> "$GITHUB_OUTPUT"
|
|
65
|
+
elif echo "$MSG" | grep -qE '^feat(\(.*\))?:'; then
|
|
66
|
+
echo "type=minor" >> "$GITHUB_OUTPUT"
|
|
67
|
+
else
|
|
68
|
+
echo "type=patch" >> "$GITHUB_OUTPUT"
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
echo "Commit: $MSG"
|
|
72
|
+
echo "Bump type: $(grep type= "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)"
|
|
73
|
+
|
|
74
|
+
- name: Resolve version
|
|
75
|
+
id: version
|
|
76
|
+
run: |
|
|
77
|
+
CURRENT=$(node -p "require('./package.json').version")
|
|
78
|
+
TAG="v${CURRENT}"
|
|
79
|
+
|
|
80
|
+
if git rev-parse "${TAG}" >/dev/null 2>&1; then
|
|
81
|
+
echo "v${CURRENT} already tagged — auto-bumping ${{ steps.bump.outputs.type }}"
|
|
82
|
+
npm version ${{ steps.bump.outputs.type }} --no-git-tag-version
|
|
83
|
+
else
|
|
84
|
+
echo "v${CURRENT} is a new version — using as-is"
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
VERSION=$(node -p "require('./package.json').version")
|
|
88
|
+
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
|
89
|
+
echo "tag=v${VERSION}" >> "$GITHUB_OUTPUT"
|
|
90
|
+
echo "Releasing v${VERSION}"
|
|
91
|
+
|
|
92
|
+
- name: Type check
|
|
93
|
+
run: pnpm run typecheck
|
|
94
|
+
|
|
95
|
+
- name: Commit version bump and tag
|
|
96
|
+
run: |
|
|
97
|
+
if ! git diff --quiet package.json 2>/dev/null; then
|
|
98
|
+
git add package.json pnpm-lock.yaml 2>/dev/null || git add package.json
|
|
99
|
+
git commit -m "chore(release): v${{ steps.version.outputs.version }}"
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
git tag -a "v${{ steps.version.outputs.version }}" \
|
|
103
|
+
-m "Release v${{ steps.version.outputs.version }}"
|
|
104
|
+
git push origin main --follow-tags
|
|
105
|
+
|
|
106
|
+
- name: Publish to npm
|
|
107
|
+
run: pnpm publish --no-git-checks --access public --provenance
|
|
108
|
+
|
|
109
|
+
- name: Create GitHub Release
|
|
110
|
+
uses: softprops/action-gh-release@v2
|
|
111
|
+
with:
|
|
112
|
+
tag_name: v${{ steps.version.outputs.version }}
|
|
113
|
+
name: v${{ steps.version.outputs.version }}
|
|
114
|
+
generate_release_notes: true
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Automated Release Workflow
|
|
2
|
+
# ─────────────────────────
|
|
3
|
+
# Bumps version, creates Git tag, and publishes a GitHub Release
|
|
4
|
+
# when a PR is merged into `main`.
|
|
5
|
+
#
|
|
6
|
+
# Version bump is determined by PR labels:
|
|
7
|
+
# - `major` → breaking changes (1.0.0 → 2.0.0)
|
|
8
|
+
# - `minor` → new features (1.0.0 → 1.1.0)
|
|
9
|
+
# - `patch` → bug fixes (1.0.0 → 1.0.1) [default]
|
|
10
|
+
# - `no-release` → skip entirely
|
|
11
|
+
#
|
|
12
|
+
# Template Note:
|
|
13
|
+
# - Ensure labels `major`, `minor`, `patch`, `no-release` exist in your repo
|
|
14
|
+
# - For npm publish: uncomment the publish step and add NPM_TOKEN secret
|
|
15
|
+
# - Works with dev → main merge flow out of the box
|
|
16
|
+
# - GITHUB_TOKEN won't trigger downstream workflows; use a PAT if needed
|
|
17
|
+
|
|
18
|
+
name: Release
|
|
19
|
+
|
|
20
|
+
on:
|
|
21
|
+
pull_request:
|
|
22
|
+
types: [closed]
|
|
23
|
+
branches: [main]
|
|
24
|
+
|
|
25
|
+
jobs:
|
|
26
|
+
release:
|
|
27
|
+
if: >
|
|
28
|
+
github.event.pull_request.merged == true &&
|
|
29
|
+
!contains(github.event.pull_request.labels.*.name, 'no-release')
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
permissions:
|
|
32
|
+
contents: write
|
|
33
|
+
|
|
34
|
+
steps:
|
|
35
|
+
- name: Checkout
|
|
36
|
+
uses: actions/checkout@v6
|
|
37
|
+
with:
|
|
38
|
+
ref: main
|
|
39
|
+
fetch-depth: 0
|
|
40
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
41
|
+
|
|
42
|
+
- name: Setup Node.js
|
|
43
|
+
uses: actions/setup-node@v6
|
|
44
|
+
with:
|
|
45
|
+
node-version: 22
|
|
46
|
+
|
|
47
|
+
- name: Configure Git
|
|
48
|
+
run: |
|
|
49
|
+
git config user.name "github-actions[bot]"
|
|
50
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
51
|
+
|
|
52
|
+
- name: Determine version bump
|
|
53
|
+
id: bump
|
|
54
|
+
env:
|
|
55
|
+
LABELS: ${{ toJson(github.event.pull_request.labels.*.name) }}
|
|
56
|
+
run: |
|
|
57
|
+
if echo "$LABELS" | grep -q '"major"'; then
|
|
58
|
+
echo "type=major" >> "$GITHUB_OUTPUT"
|
|
59
|
+
elif echo "$LABELS" | grep -q '"minor"'; then
|
|
60
|
+
echo "type=minor" >> "$GITHUB_OUTPUT"
|
|
61
|
+
else
|
|
62
|
+
echo "type=patch" >> "$GITHUB_OUTPUT"
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
- name: Bump version in package.json
|
|
66
|
+
id: version
|
|
67
|
+
run: |
|
|
68
|
+
if ! node -e "const p = require('./package.json'); if (!p.version) process.exit(1);" 2>/dev/null; then
|
|
69
|
+
echo "No version found in package.json — initializing to 0.1.0"
|
|
70
|
+
npm pkg set version=0.1.0
|
|
71
|
+
fi
|
|
72
|
+
NEW_VERSION=$(npm version ${{ steps.bump.outputs.type }} --no-git-tag-version)
|
|
73
|
+
echo "version=$NEW_VERSION" >> "$GITHUB_OUTPUT"
|
|
74
|
+
echo "Bumped to $NEW_VERSION (${{ steps.bump.outputs.type }})"
|
|
75
|
+
|
|
76
|
+
- name: Build release notes from commits
|
|
77
|
+
id: notes
|
|
78
|
+
env:
|
|
79
|
+
PR_TITLE: ${{ github.event.pull_request.title }}
|
|
80
|
+
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
81
|
+
PR_URL: ${{ github.event.pull_request.html_url }}
|
|
82
|
+
BUMP_TYPE: ${{ steps.bump.outputs.type }}
|
|
83
|
+
run: |
|
|
84
|
+
PREV_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
|
85
|
+
if [ -n "$PREV_TAG" ]; then
|
|
86
|
+
COMMITS=$(git log "$PREV_TAG"..HEAD --pretty=format:"- %s (%h)" --no-merges)
|
|
87
|
+
else
|
|
88
|
+
COMMITS=$(git log --pretty=format:"- %s (%h)" --no-merges -20)
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
{
|
|
92
|
+
echo "body<<RELEASE_EOF"
|
|
93
|
+
echo "## What Changed"
|
|
94
|
+
echo ""
|
|
95
|
+
echo "**PR:** [#${PR_NUMBER} — ${PR_TITLE}](${PR_URL})"
|
|
96
|
+
echo "**Bump:** \`${BUMP_TYPE}\`"
|
|
97
|
+
echo ""
|
|
98
|
+
echo "### Commits"
|
|
99
|
+
echo ""
|
|
100
|
+
echo "$COMMITS"
|
|
101
|
+
echo ""
|
|
102
|
+
echo "RELEASE_EOF"
|
|
103
|
+
} >> "$GITHUB_OUTPUT"
|
|
104
|
+
|
|
105
|
+
- name: Commit version bump and create tag
|
|
106
|
+
run: |
|
|
107
|
+
git add package.json package-lock.json 2>/dev/null || git add package.json
|
|
108
|
+
git commit -m "chore(release): ${{ steps.version.outputs.version }}"
|
|
109
|
+
git tag -a "${{ steps.version.outputs.version }}" \
|
|
110
|
+
-m "Release ${{ steps.version.outputs.version }} — ${{ github.event.pull_request.title }}"
|
|
111
|
+
git push origin main --follow-tags
|
|
112
|
+
|
|
113
|
+
- name: Create GitHub Release
|
|
114
|
+
env:
|
|
115
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
116
|
+
TAG: ${{ steps.version.outputs.version }}
|
|
117
|
+
run: |
|
|
118
|
+
gh release create "$TAG" \
|
|
119
|
+
--title "$TAG — ${{ github.event.pull_request.title }}" \
|
|
120
|
+
--notes "${{ steps.notes.outputs.body }}" \
|
|
121
|
+
--latest
|
|
122
|
+
|
|
123
|
+
# npm publish is handled by publish.yml (triggered by the tag created above)
|
package/README.md
CHANGED
|
@@ -1,114 +1,119 @@
|
|
|
1
1
|
# pr-prism
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
<div align="center">
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/pr-prism)
|
|
6
|
+
[](https://www.npmjs.com/package/pr-prism)
|
|
7
|
+
[](https://github.com/YosefHayim/pr-prism/actions/workflows/ci.yml)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
[](https://github.com/YosefHayim/pr-prism/issues)
|
|
6
10
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
## The Problem
|
|
11
|
+
> Stateful GitHub PR review scraper for AI agent workflows. Filter noise, cache seen comments, deliver signal.
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
Built with [grimoire-wizard](https://github.com/YosefHayim/grimoire) for interactive CLI prompts.
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
</div>
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
pr-prism solves this with a two-command workflow:
|
|
17
|
+
---
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
2. **`pr-resolve`** — resolves handled threads via GraphQL and tags AI agents for re-review
|
|
19
|
+
AI agents re-reading the same resolved comments, outdated threads, and bot spam on every loop — wasting tokens and losing context. pr-prism solves this with two commands: scrape only what's new, then resolve what's been addressed.
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
## Why
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
AI coding agents loop on PR review comments — re-reading the same resolved threads, outdated feedback, and bot noise on every iteration. This wastes tokens and loses context fast.
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|---|---|
|
|
28
|
-
| **ID cache** | `pr-reviews/.scraped-ids.json` — re-runs emit only new comments |
|
|
29
|
-
| **Resolved threads** | Silently skipped via GraphQL `isResolved` |
|
|
30
|
-
| **Outdated threads** | Flagged with `⚠️ OUTDATED` so agents don't chase dead feedback |
|
|
31
|
-
| **Bot filter** | Authors ending in `[bot]` or matching `KNOWN_BOTS` are skipped |
|
|
32
|
-
| **Suggested changes** | ` ```suggestion ` blocks rendered as clean `diff` (REMOVE / ADD) |
|
|
33
|
-
| **File path** | Each inline comment prefixed with `📄 File: path/to/file.ts` |
|
|
34
|
-
| **Noise domains** | Social sharing links (Twitter, Reddit, LinkedIn, CodeAnt) stripped |
|
|
35
|
-
| **Thread resolution** | `pr-resolve` closes threads via `resolveReviewThread` mutation |
|
|
36
|
-
| **Agent tagging** | `--tag-agents` posts a configurable @mention comment after resolving |
|
|
25
|
+
pr-prism solves this with two focused commands: scrape only what's new since the last run, then auto-resolve threads that have been addressed. Your agent gets clean signal, not stale noise.
|
|
37
26
|
|
|
38
27
|
---
|
|
39
28
|
|
|
40
29
|
## Requirements
|
|
41
30
|
|
|
42
31
|
- Node.js ≥ 20
|
|
43
|
-
- [
|
|
44
|
-
- [gh CLI](https://cli.github.com) authenticated: `gh auth login`
|
|
32
|
+
- [gh CLI](https://cli.github.com) authenticated (`gh auth login`)
|
|
45
33
|
|
|
46
34
|
---
|
|
47
35
|
|
|
48
36
|
## Installation
|
|
49
37
|
|
|
38
|
+
**One-time use (no install):**
|
|
39
|
+
```bash
|
|
40
|
+
npx pr-prism pr-review -- 42
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Global install:**
|
|
44
|
+
```bash
|
|
45
|
+
npm install -g pr-prism
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Local to your project:**
|
|
50
49
|
```bash
|
|
51
|
-
|
|
52
|
-
cd pr-prism
|
|
53
|
-
pnpm install
|
|
50
|
+
npm install --save-dev pr-prism
|
|
54
51
|
```
|
|
55
52
|
|
|
56
53
|
---
|
|
57
54
|
|
|
58
55
|
## Usage
|
|
59
56
|
|
|
60
|
-
### `pr-review` — scrape
|
|
57
|
+
### `pr-review` — scrape new comments
|
|
61
58
|
|
|
62
59
|
```bash
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
60
|
+
pr-review # list open PRs, pick interactively
|
|
61
|
+
pr-review 42 # process PR #42 directly
|
|
62
|
+
pr-review <url> # process by full GitHub PR URL
|
|
66
63
|
```
|
|
67
64
|
|
|
68
|
-
|
|
65
|
+
On first run, automatically appends output patterns to your `.gitignore`.
|
|
66
|
+
|
|
67
|
+
### `pr-resolve` — resolve threads
|
|
69
68
|
|
|
70
69
|
```bash
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
70
|
+
pr-resolve 42 # resolve all threads from last scrape
|
|
71
|
+
pr-resolve 42 --auto # smart resolve: addressed threads only
|
|
72
|
+
pr-resolve 42 --auto --dry-run # preview what would be resolved
|
|
73
|
+
pr-resolve 42 --auto --tag-agents # resolve + tag agents for re-review
|
|
74
|
+
pr-resolve 42 --tag-agents --comment "msg" # custom message
|
|
75
|
+
pr-resolve 42 --unresolve # re-open resolved threads
|
|
77
76
|
```
|
|
78
77
|
|
|
79
|
-
|
|
78
|
+
**`--auto` detection signals:**
|
|
79
|
+
- `isOutdated` — GitHub detected the commented lines were changed
|
|
80
|
+
- Suggestion applied — `` ```suggestion `` block content found in current file at HEAD
|
|
80
81
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
| File | Description |
|
|
84
|
-
|---|---|
|
|
85
|
-
| `pr-reviews/new-<timestamp>.md` | New actionable comments since last run |
|
|
86
|
-
| `pr-reviews/.scraped-ids.json` | Persistent ID cache — **commit this file** |
|
|
87
|
-
| `pr-reviews/.threads-<pr>.json` | Thread IDs consumed by `pr-resolve` |
|
|
82
|
+
Skips threads with no signal. Posts a summary comment on the PR.
|
|
88
83
|
|
|
89
84
|
---
|
|
90
85
|
|
|
91
|
-
## The
|
|
86
|
+
## The Agent Loop
|
|
92
87
|
|
|
93
88
|
```
|
|
94
|
-
1.
|
|
95
|
-
|
|
96
|
-
→ pr-reviews/.threads-<pr>.json (thread IDs for resolve)
|
|
89
|
+
1. pr-review 42
|
|
90
|
+
→ pr-reviews/new-<timestamp>.md (new comments only)
|
|
97
91
|
|
|
98
|
-
2.
|
|
92
|
+
2. Agent reads markdown, implements fixes, commits, pushes
|
|
99
93
|
|
|
100
|
-
3.
|
|
101
|
-
|
|
102
|
-
|
|
94
|
+
3. pr-resolve 42 --auto --tag-agents
|
|
95
|
+
→ Auto-resolves addressed threads
|
|
96
|
+
→ Posts: "Auto-resolved 6/8 threads. 2 remain."
|
|
97
|
+
→ Tags agents for re-review
|
|
103
98
|
|
|
104
|
-
4.
|
|
99
|
+
4. Repeat — only new replies appear
|
|
105
100
|
```
|
|
106
101
|
|
|
107
102
|
---
|
|
108
103
|
|
|
109
|
-
##
|
|
104
|
+
## Output Files
|
|
105
|
+
|
|
106
|
+
| File | Description |
|
|
107
|
+
|---|---|
|
|
108
|
+
| `pr-reviews/new-<timestamp>.md` | New actionable comments since last run |
|
|
109
|
+
| `pr-reviews/.scraped-ids.json` | Persistent ID cache — **commit this** |
|
|
110
|
+
| `pr-reviews/.threads-<pr>.json` | Thread IDs for `pr-resolve` |
|
|
111
|
+
|
|
112
|
+
---
|
|
110
113
|
|
|
111
|
-
|
|
114
|
+
## Config
|
|
115
|
+
|
|
116
|
+
Copy `.pr-prism.example.json` to `.pr-prism.json` in your repo root:
|
|
112
117
|
|
|
113
118
|
```json
|
|
114
119
|
{
|
|
@@ -116,28 +121,26 @@ Create `.pr-prism.json` in your repo root to customise agent mentions without ed
|
|
|
116
121
|
}
|
|
117
122
|
```
|
|
118
123
|
|
|
119
|
-
|
|
124
|
+
`agentMentions` — GitHub handles (without `@`) tagged in the post-resolve comment. Defaults to `["cubic-dev-ai"]`.
|
|
120
125
|
|
|
121
|
-
|
|
126
|
+
---
|
|
122
127
|
|
|
123
|
-
|
|
128
|
+
## What Gets Filtered
|
|
124
129
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
130
|
+
| Signal | Behaviour |
|
|
131
|
+
|---|---|
|
|
132
|
+
| Already-seen comment ID | Silently skipped |
|
|
133
|
+
| Resolved thread | Skipped + cached |
|
|
134
|
+
| Bot author (`[bot]`, `KNOWN_BOTS`) | Skipped + cached |
|
|
135
|
+
| Outdated thread | Shown with `⚠️ OUTDATED` warning |
|
|
136
|
+
| Social sharing links | Stripped from body |
|
|
137
|
+
| `` ```suggestion `` blocks | Rendered as `diff` |
|
|
128
138
|
|
|
129
|
-
|
|
139
|
+
---
|
|
130
140
|
|
|
131
|
-
|
|
141
|
+
## Contributing
|
|
132
142
|
|
|
133
|
-
|
|
134
|
-
const NOISE_DOMAINS = [
|
|
135
|
-
"twitter.com/intent", "x.com/intent",
|
|
136
|
-
"reddit.com/submit",
|
|
137
|
-
"linkedin.com/sharing",
|
|
138
|
-
"app.codeant.ai", "codeant.ai/feedback",
|
|
139
|
-
];
|
|
140
|
-
```
|
|
143
|
+
PRs and issues welcome. Fork the repo, create a feature branch, and open a pull request.
|
|
141
144
|
|
|
142
145
|
---
|
|
143
146
|
|
|
@@ -32,6 +32,9 @@ pnpm run pr-review -- https://github.com/owner/repo/pull/42
|
|
|
32
32
|
|
|
33
33
|
# Resolve handled threads + tag agents
|
|
34
34
|
pnpm run pr-resolve -- 42 # resolve all threads from last scrape
|
|
35
|
+
pnpm run pr-resolve -- 42 --auto # smart auto-resolve (detects addressed threads)
|
|
36
|
+
pnpm run pr-resolve -- 42 --auto --dry-run # preview auto-resolve classifications
|
|
37
|
+
pnpm run pr-resolve -- 42 --auto --tag-agents # auto-resolve + tag agents
|
|
35
38
|
pnpm run pr-resolve -- 42 --dry-run # preview without mutating
|
|
36
39
|
pnpm run pr-resolve -- 42 --tag-agents # resolve + post @mention comment
|
|
37
40
|
pnpm run pr-resolve -- 42 --tag-agents --comment "Fixed in abc123"
|
|
@@ -69,9 +72,11 @@ pnpm run pr-resolve -- 42 --unresolve # re-open threads if needed
|
|
|
69
72
|
1. pnpm run pr-review -- <PR number>
|
|
70
73
|
2. Agent reads pr-reviews/new-<timestamp>.md
|
|
71
74
|
3. Agent implements fixes, commits, pushes
|
|
72
|
-
4. pnpm run pr-resolve -- <PR number> --tag-agents
|
|
73
|
-
→
|
|
74
|
-
→
|
|
75
|
+
4. pnpm run pr-resolve -- <PR number> --auto --tag-agents
|
|
76
|
+
→ Re-fetches live thread state from GitHub
|
|
77
|
+
→ Auto-resolves: isOutdated threads + matched suggestions
|
|
78
|
+
→ Posts: "Auto-resolved 6/8 threads. 2 remain."
|
|
79
|
+
→ Tags agents for re-review
|
|
75
80
|
5. Repeat from step 1 — only new reviewer replies appear
|
|
76
81
|
```
|
|
77
82
|
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Auto-Resolve Design — GitHub Signals + Suggestion Matching
|
|
2
|
+
|
|
3
|
+
**Date**: 2026-03-14
|
|
4
|
+
**Status**: Approved
|
|
5
|
+
**Approach**: B (GitHub-native signals + suggestion block comparison)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Problem
|
|
10
|
+
|
|
11
|
+
`pr-resolve` resolves ALL threads from the sidecar indiscriminately. Agents must manually invoke it after pushing fixes, and every thread gets resolved regardless of whether the underlying comment was actually addressed. This creates noise — reviewers see resolved threads that weren't truly fixed, eroding trust.
|
|
12
|
+
|
|
13
|
+
## Solution
|
|
14
|
+
|
|
15
|
+
Extend `pr-resolve` with an `--auto` flag that re-fetches live thread state from GitHub, classifies each thread by confidence signals, and resolves only threads with strong evidence of being addressed. Posts a transparent summary comment on the PR.
|
|
16
|
+
|
|
17
|
+
## Detection Signals
|
|
18
|
+
|
|
19
|
+
### Signal 1: `isOutdated` (GitHub-native)
|
|
20
|
+
|
|
21
|
+
GitHub automatically sets `isOutdated: true` on a `PullRequestReviewThread` when the exact lines the comment refers to are modified by a subsequent commit. This is the primary, highest-confidence signal.
|
|
22
|
+
|
|
23
|
+
Additionally, when `line: null` on a thread (or `position: null` via REST), the diff can no longer anchor the comment — the code has changed enough that the comment's location is gone.
|
|
24
|
+
|
|
25
|
+
### Signal 2: Suggestion Matching
|
|
26
|
+
|
|
27
|
+
Review comments may contain ` ```suggestion ` blocks — explicit code change requests. After the agent pushes, fetch the current file content at HEAD and check if the suggested code is now present in the file. If all suggestion lines match, the comment was applied.
|
|
28
|
+
|
|
29
|
+
## Classification Buckets
|
|
30
|
+
|
|
31
|
+
| Bucket | Signal | Action |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| **auto-resolve** | `isOutdated: true` OR `line: null` | Resolve + tag as "lines changed" |
|
|
34
|
+
| **auto-resolve** | Has suggestion block AND content matches current file | Resolve + tag as "suggestion applied" |
|
|
35
|
+
| **skip** | None of the above | Leave unresolved, report count |
|
|
36
|
+
|
|
37
|
+
## Extended GraphQL Query
|
|
38
|
+
|
|
39
|
+
```graphql
|
|
40
|
+
query($owner: String!, $repo: String!, $prNumber: Int!) {
|
|
41
|
+
repository(owner: $owner, name: $repo) {
|
|
42
|
+
pullRequest(number: $prNumber) {
|
|
43
|
+
headRefOid
|
|
44
|
+
reviewThreads(first: 100) {
|
|
45
|
+
nodes {
|
|
46
|
+
id isResolved isOutdated
|
|
47
|
+
path line originalLine
|
|
48
|
+
comments(first: 20) {
|
|
49
|
+
nodes {
|
|
50
|
+
databaseId
|
|
51
|
+
author { login }
|
|
52
|
+
body
|
|
53
|
+
path
|
|
54
|
+
outdated
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Key additions vs current query: `headRefOid`, `path`/`line`/`originalLine` on thread, `outdated` on comment.
|
|
65
|
+
|
|
66
|
+
## Suggestion Matching Algorithm
|
|
67
|
+
|
|
68
|
+
1. Extract suggestion content from comment body using existing regex: `` /```suggestion\n([\s\S]*?)```/g ``
|
|
69
|
+
2. Fetch file at HEAD: `gh api repos/{owner}/{repo}/contents/{path}?ref={headSha}` — base64 decode response
|
|
70
|
+
3. Normalize both suggestion lines and file lines (trim whitespace, collapse spaces)
|
|
71
|
+
4. Check if suggestion lines appear as a contiguous block in the file
|
|
72
|
+
5. If ALL suggestion blocks in a comment match → thread is addressed
|
|
73
|
+
|
|
74
|
+
### Edge cases
|
|
75
|
+
|
|
76
|
+
- **Multiple suggestion blocks in one comment**: All must match for auto-resolve
|
|
77
|
+
- **Whitespace differences**: Normalize before comparing (trim, collapse)
|
|
78
|
+
- **File deleted**: Treat as addressed (the code was removed)
|
|
79
|
+
- **Partial application**: NOT handled — if agent applied 3 of 5 suggested lines, thread is not auto-resolved (conservative)
|
|
80
|
+
- **Reformatted suggestions**: NOT handled — exact content match only (conservative)
|
|
81
|
+
|
|
82
|
+
## Command Interface
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# Existing (unchanged) — resolve ALL threads from sidecar
|
|
86
|
+
pnpm run pr-resolve -- 42
|
|
87
|
+
|
|
88
|
+
# NEW — smart auto-resolve
|
|
89
|
+
pnpm run pr-resolve -- 42 --auto
|
|
90
|
+
|
|
91
|
+
# Combine with existing flags
|
|
92
|
+
pnpm run pr-resolve -- 42 --auto --tag-agents
|
|
93
|
+
pnpm run pr-resolve -- 42 --auto --dry-run
|
|
94
|
+
pnpm run pr-resolve -- 42 --auto --tag-agents --comment "Fixes pushed"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
When `--auto` is used:
|
|
98
|
+
- Sidecar file is **not required** — threads fetched directly from GitHub
|
|
99
|
+
- If sidecar exists, used as hint but GitHub's live state takes precedence
|
|
100
|
+
- `--dry-run` shows classification without mutating
|
|
101
|
+
|
|
102
|
+
## Summary Comment Format
|
|
103
|
+
|
|
104
|
+
Posted on the PR after auto-resolution:
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
:robot: **pr-prism auto-resolve** — 6 of 8 threads resolved
|
|
108
|
+
|
|
109
|
+
**Auto-resolved (lines changed):** 4 threads
|
|
110
|
+
**Auto-resolved (suggestion applied):** 2 threads
|
|
111
|
+
**Remaining (needs manual review):** 2 threads
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
When combined with `--tag-agents`, the agent mentions are appended to this comment instead of the existing default message.
|
|
115
|
+
|
|
116
|
+
## Data Flow
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
Agent pushes fixes
|
|
120
|
+
|
|
|
121
|
+
v
|
|
122
|
+
pr-resolve -- 42 --auto --tag-agents
|
|
123
|
+
|
|
|
124
|
+
+---> Fetch live threads from GitHub GraphQL
|
|
125
|
+
| (isOutdated, line, path, comments.body, headRefOid)
|
|
126
|
+
|
|
|
127
|
+
+---> For each unresolved thread:
|
|
128
|
+
| +-- isOutdated=true OR line=null?
|
|
129
|
+
| | -> BUCKET: auto-resolve (lines changed)
|
|
130
|
+
| +-- Has suggestion block?
|
|
131
|
+
| | -> Fetch file at HEAD, normalize, compare
|
|
132
|
+
| | -> match? -> BUCKET: auto-resolve (suggestion applied)
|
|
133
|
+
| +-- Neither?
|
|
134
|
+
| -> BUCKET: skip
|
|
135
|
+
|
|
|
136
|
+
+---> Execute resolveReviewThread mutations for auto-resolve bucket
|
|
137
|
+
|
|
|
138
|
+
+---> Post summary comment on PR
|
|
139
|
+
|
|
|
140
|
+
+---> Tag agents if --tag-agents
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## File Changes
|
|
144
|
+
|
|
145
|
+
| File | Change |
|
|
146
|
+
|---|---|
|
|
147
|
+
| `scripts/resolve-pr-threads.ts` | Add `--auto` flag, extended GraphQL query for auto mode, `classifyThreads()` function, `extractSuggestions()` function, `fetchFileAtHead()` function, `matchSuggestion()` function, summary comment logic |
|
|
148
|
+
|
|
149
|
+
No new files. No new dependencies. All logic fits in the existing resolve script (~80-100 lines added).
|
|
150
|
+
|
|
151
|
+
## Updated Agent Loop
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
1. pnpm run pr-review -- <PR number>
|
|
155
|
+
-> pr-reviews/new-<timestamp>.md (new comments only)
|
|
156
|
+
|
|
157
|
+
2. Agent reads the markdown, implements fixes, commits, pushes
|
|
158
|
+
|
|
159
|
+
3. pnpm run pr-resolve -- <PR number> --auto --tag-agents
|
|
160
|
+
-> Re-fetches live thread state from GitHub
|
|
161
|
+
-> Auto-resolves: isOutdated threads + matched suggestions
|
|
162
|
+
-> Posts: "Auto-resolved 6/8 threads. 2 remain."
|
|
163
|
+
-> Tags agents for re-review
|
|
164
|
+
|
|
165
|
+
4. Repeat from step 1 — only new reviewer replies appear
|
|
166
|
+
```
|