ghreview 1.0.0 → 2.0.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/dependabot.yml +6 -2
- package/.github/workflows/test-and-release.yml +11 -9
- package/CHANGELOG.md +41 -0
- package/README.md +20 -2
- package/lib/config.js +72 -25
- package/lib/github.js +71 -9
- package/package.json +7 -6
- package/.claude/settings.local.json +0 -10
package/.github/dependabot.yml
CHANGED
|
@@ -3,14 +3,18 @@ updates:
|
|
|
3
3
|
- package-ecosystem: "github-actions"
|
|
4
4
|
directory: "/"
|
|
5
5
|
schedule:
|
|
6
|
-
interval: "
|
|
6
|
+
interval: "weekly"
|
|
7
7
|
commit-message:
|
|
8
8
|
prefix: "chore"
|
|
9
9
|
include: "scope"
|
|
10
|
+
cooldown:
|
|
11
|
+
default-days: 5
|
|
10
12
|
- package-ecosystem: "npm"
|
|
11
13
|
directory: "/"
|
|
12
14
|
schedule:
|
|
13
|
-
interval: "
|
|
15
|
+
interval: "weekly"
|
|
14
16
|
commit-message:
|
|
15
17
|
prefix: "chore"
|
|
16
18
|
include: "scope"
|
|
19
|
+
cooldown:
|
|
20
|
+
default-days: 5
|
|
@@ -5,14 +5,14 @@ jobs:
|
|
|
5
5
|
strategy:
|
|
6
6
|
fail-fast: false
|
|
7
7
|
matrix:
|
|
8
|
-
node: [
|
|
8
|
+
node: [lts/*, current]
|
|
9
9
|
os: [macos-latest, ubuntu-latest, windows-latest]
|
|
10
10
|
runs-on: ${{ matrix.os }}
|
|
11
11
|
steps:
|
|
12
12
|
- name: Checkout Repository
|
|
13
|
-
uses: actions/checkout@
|
|
13
|
+
uses: actions/checkout@v6.0.1
|
|
14
14
|
- name: Use Node.js ${{ matrix.node }}
|
|
15
|
-
uses: actions/setup-node@
|
|
15
|
+
uses: actions/setup-node@v6.1.0
|
|
16
16
|
with:
|
|
17
17
|
node-version: ${{ matrix.node }}
|
|
18
18
|
- name: Install Dependencies
|
|
@@ -27,24 +27,26 @@ jobs:
|
|
|
27
27
|
needs: test
|
|
28
28
|
runs-on: ubuntu-latest
|
|
29
29
|
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
|
30
|
+
permissions:
|
|
31
|
+
contents: write
|
|
32
|
+
issues: write
|
|
33
|
+
pull-requests: write
|
|
34
|
+
id-token: write
|
|
30
35
|
steps:
|
|
31
36
|
- name: Checkout
|
|
32
|
-
uses: actions/checkout@
|
|
37
|
+
uses: actions/checkout@v6.0.1
|
|
33
38
|
with:
|
|
34
39
|
fetch-depth: 0
|
|
35
40
|
- name: Setup Node.js
|
|
36
|
-
uses: actions/setup-node@
|
|
41
|
+
uses: actions/setup-node@v6.1.0
|
|
37
42
|
with:
|
|
38
43
|
node-version: lts/*
|
|
39
44
|
- name: Install dependencies
|
|
40
45
|
run: |
|
|
41
46
|
npm install --no-progress --no-save
|
|
42
|
-
- name: Build
|
|
43
|
-
run: |
|
|
44
|
-
npm run build
|
|
45
47
|
- name: Release
|
|
46
48
|
env:
|
|
47
49
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
48
|
-
|
|
50
|
+
NPM_CONFIG_PROVENANCE: true
|
|
49
51
|
run: npx semantic-release
|
|
50
52
|
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
## [2.0.0](https://github.com/rvagg/ghreview/compare/v1.0.1...v2.0.0) (2026-01-24)
|
|
2
|
+
|
|
3
|
+
### ⚠ BREAKING CHANGES
|
|
4
|
+
|
|
5
|
+
* Config location changed from ~/.ghreview/config.json
|
|
6
|
+
to XDG paths (~/.config/ghreview/config.json on Linux). Move your
|
|
7
|
+
existing config to the new location.
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* add interactive auth and filter resolved comments ([29a8196](https://github.com/rvagg/ghreview/commit/29a81961b5c9ee636552d048f96cc9d59c7ace81))
|
|
12
|
+
|
|
13
|
+
### Trivial Changes
|
|
14
|
+
|
|
15
|
+
* **ci:** oidc publishing ([4cef06b](https://github.com/rvagg/ghreview/commit/4cef06b9f64ec067b0363fc230babca74e224395))
|
|
16
|
+
* **deps-dev:** bump @semantic-release/github from 11.0.6 to 12.0.0 ([cef9450](https://github.com/rvagg/ghreview/commit/cef94502b1d00ced4c653b67b0af7b6ff350948b))
|
|
17
|
+
* **deps-dev:** bump @semantic-release/npm from 12.0.2 to 13.1.1 ([c63e419](https://github.com/rvagg/ghreview/commit/c63e419188b913ed866ddf48b7bbbe4c2d2b8f00))
|
|
18
|
+
* **deps-dev:** bump nock from 14.0.7 to 14.0.10 ([a7c98df](https://github.com/rvagg/ghreview/commit/a7c98dfe7840fbd26a5c9c81ed21acd5bfcf7240))
|
|
19
|
+
* **deps:** bump actions/checkout from 4.2.2 to 6.0.1 ([170b48c](https://github.com/rvagg/ghreview/commit/170b48c47bfd6a601037d362b9c3b4e084a83d05))
|
|
20
|
+
* **deps:** bump actions/setup-node from 4.4.0 to 6.1.0 ([61ce2d9](https://github.com/rvagg/ghreview/commit/61ce2d9f083b83562d139de8bcfba985c9017a4a))
|
|
21
|
+
* **deps:** bump chalk from 5.4.1 to 5.6.2 ([948fa9d](https://github.com/rvagg/ghreview/commit/948fa9d78c11b527c4483276e7426b6207084751))
|
|
22
|
+
* **deps:** bump ora from 8.2.0 to 9.0.0 ([8dff5ca](https://github.com/rvagg/ghreview/commit/8dff5ca3d61eb056739422b647d8e7c260967924))
|
|
23
|
+
* fix dependabot config ([4d2b670](https://github.com/rvagg/ghreview/commit/4d2b670cda6b6c975d760ec822f0ce2ca1b337cd))
|
|
24
|
+
|
|
25
|
+
## [1.0.1](https://github.com/rvagg/ghreview/compare/v1.0.0...v1.0.1) (2025-07-28)
|
|
26
|
+
|
|
27
|
+
### Trivial Changes
|
|
28
|
+
|
|
29
|
+
* bump ([ed0f6d9](https://github.com/rvagg/ghreview/commit/ed0f6d9b16c52566b1238f4b5ccdc362f5660d82))
|
|
30
|
+
|
|
31
|
+
## 1.0.0 (2025-07-28)
|
|
32
|
+
|
|
33
|
+
### Bug Fixes
|
|
34
|
+
|
|
35
|
+
* publish ([b405eb4](https://github.com/rvagg/ghreview/commit/b405eb4cff0d13e856da0aeee895d7601d9f3763))
|
|
36
|
+
* update publish perms ([62009ef](https://github.com/rvagg/ghreview/commit/62009ef718d06664efb3ac5a001e5b84fe5364a0))
|
|
37
|
+
|
|
38
|
+
### Trivial Changes
|
|
39
|
+
|
|
40
|
+
* **docs:** expand workflow description ([5dd5671](https://github.com/rvagg/ghreview/commit/5dd5671f455dfa83bda88a1f8e0fab2fa9d836af))
|
|
41
|
+
* initial release ([2a1891d](https://github.com/rvagg/ghreview/commit/2a1891d9c7661f3bb761a8d2dfa36da1287fcea3))
|
package/README.md
CHANGED
|
@@ -1,10 +1,28 @@
|
|
|
1
1
|
# ghreview
|
|
2
2
|
|
|
3
|
+
[](https://nodei.co/npm/ghreview/)
|
|
4
|
+
|
|
3
5
|
GitHub PR-based code review workflow for AI-assisted development.
|
|
4
6
|
|
|
5
7
|
## Overview
|
|
6
8
|
|
|
7
|
-
`ghreview`
|
|
9
|
+
`ghreview` bridges the gap between AI-assisted coding and GitHub's superior code review interface. It allows you to review AI-generated code changes using GitHub's familiar PR review tools while keeping your local repository pristine.
|
|
10
|
+
|
|
11
|
+
### Why?
|
|
12
|
+
|
|
13
|
+
- **Clean local state**: Your working directory remains exactly as it was - no commits, no branch switches, no git operations visible in your project
|
|
14
|
+
- **Familiar workflow**: Use GitHub's excellent PR review interface with inline comments, suggestions, and discussions
|
|
15
|
+
- **Private reviews**: Use a dedicated private repository for reviews, keeping your AI interactions separate from your main project
|
|
16
|
+
- **Zero footprint**: Creates temporary branches and commits in a separate review repository, immediately cleaning up after pushing
|
|
17
|
+
- **AI-friendly output**: Collects all feedback in a structured format that's easy to paste back to your AI assistant
|
|
18
|
+
|
|
19
|
+
### How?
|
|
20
|
+
|
|
21
|
+
1. You work with your AI assistant and have uncommitted changes in your project
|
|
22
|
+
2. `ghreview init` pushes your current state to a dedicated review repository without affecting your local git state
|
|
23
|
+
3. You review the changes on GitHub using all the familiar tools
|
|
24
|
+
4. `ghreview collect` gathers your feedback in a format optimized for AI consumption
|
|
25
|
+
5. Your local repository remains untouched throughout - as if git was never involved
|
|
8
26
|
|
|
9
27
|
## Quick Start
|
|
10
28
|
|
|
@@ -182,4 +200,4 @@ npm test
|
|
|
182
200
|
|
|
183
201
|
## License
|
|
184
202
|
|
|
185
|
-
Apache-2.0
|
|
203
|
+
Apache-2.0
|
package/lib/config.js
CHANGED
|
@@ -1,42 +1,89 @@
|
|
|
1
1
|
import fs from 'fs/promises'
|
|
2
2
|
import path from 'path'
|
|
3
3
|
import os from 'os'
|
|
4
|
+
import ghauth from 'ghauth'
|
|
5
|
+
import { read } from 'read'
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
+
function getConfigPath () {
|
|
8
|
+
const home = os.homedir()
|
|
9
|
+
switch (os.platform()) {
|
|
10
|
+
case 'darwin':
|
|
11
|
+
return path.join(home, 'Library', 'Application Support', 'ghreview', 'config.json')
|
|
12
|
+
case 'win32':
|
|
13
|
+
return path.join(process.env.APPDATA || path.join(home, 'AppData', 'Roaming'), 'ghreview', 'config.json')
|
|
14
|
+
default:
|
|
15
|
+
return path.join(process.env.XDG_CONFIG_HOME || path.join(home, '.config'), 'ghreview', 'config.json')
|
|
16
|
+
}
|
|
17
|
+
}
|
|
7
18
|
|
|
8
|
-
|
|
19
|
+
const CONFIG_FILE = getConfigPath()
|
|
20
|
+
const CONFIG_DIR = path.dirname(CONFIG_FILE)
|
|
21
|
+
|
|
22
|
+
async function readConfig () {
|
|
9
23
|
try {
|
|
10
24
|
const configData = await fs.readFile(CONFIG_FILE, 'utf8')
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
throw new Error('Missing required config field: reviewRepo')
|
|
25
|
+
return JSON.parse(configData)
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (error.code === 'ENOENT') {
|
|
28
|
+
return {}
|
|
16
29
|
}
|
|
30
|
+
throw error
|
|
31
|
+
}
|
|
32
|
+
}
|
|
17
33
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
34
|
+
async function writeConfig (config) {
|
|
35
|
+
await fs.mkdir(CONFIG_DIR, { recursive: true, mode: 0o700 })
|
|
36
|
+
await fs.writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 0o600 })
|
|
37
|
+
}
|
|
21
38
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
39
|
+
export async function loadConfig () {
|
|
40
|
+
const config = await readConfig()
|
|
41
|
+
let configChanged = false
|
|
26
42
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
43
|
+
// Check if we need to acquire a token
|
|
44
|
+
if (!config.githubToken) {
|
|
45
|
+
console.log('GitHub authentication required.\n')
|
|
46
|
+
const authData = await ghauth({
|
|
47
|
+
configName: 'ghreview',
|
|
48
|
+
scopes: ['repo'],
|
|
49
|
+
noSave: true,
|
|
50
|
+
noDeviceFlow: true
|
|
51
|
+
})
|
|
52
|
+
config.githubToken = authData.token
|
|
53
|
+
config.user = authData.user
|
|
54
|
+
configChanged = true
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Check if we need a reviewRepo
|
|
58
|
+
if (!config.reviewRepo) {
|
|
59
|
+
const defaultRepo = config.user ? `${config.user}/reviews` : null
|
|
60
|
+
const prompt = defaultRepo
|
|
61
|
+
? `Review repository (owner/repo) [${defaultRepo}]: `
|
|
62
|
+
: 'Review repository (owner/repo): '
|
|
63
|
+
|
|
64
|
+
const input = await read({ prompt })
|
|
65
|
+
config.reviewRepo = input || defaultRepo
|
|
66
|
+
|
|
67
|
+
if (!config.reviewRepo) {
|
|
68
|
+
throw new Error('Review repository is required')
|
|
31
69
|
}
|
|
32
|
-
|
|
70
|
+
configChanged = true
|
|
33
71
|
}
|
|
72
|
+
|
|
73
|
+
// Validate repo format
|
|
74
|
+
if (!config.reviewRepo.includes('/')) {
|
|
75
|
+
throw new Error('Invalid reviewRepo format. Expected: owner/repo')
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Save config if anything changed
|
|
79
|
+
if (configChanged) {
|
|
80
|
+
await writeConfig(config)
|
|
81
|
+
console.log(`\nConfig saved to ${CONFIG_FILE}\n`)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return config
|
|
34
85
|
}
|
|
35
86
|
|
|
36
87
|
export async function ensureConfigDir () {
|
|
37
|
-
|
|
38
|
-
await fs.mkdir(CONFIG_DIR, { recursive: true })
|
|
39
|
-
} catch (error) {
|
|
40
|
-
// Directory might already exist, that's fine
|
|
41
|
-
}
|
|
88
|
+
await fs.mkdir(CONFIG_DIR, { recursive: true })
|
|
42
89
|
}
|
package/lib/github.js
CHANGED
|
@@ -60,12 +60,73 @@ All comments will be collected with line numbers and context for AI consumption.
|
|
|
60
60
|
export async function fetchReviewComments (owner, repo, prNumber, token) {
|
|
61
61
|
const octokit = createOctokit(token)
|
|
62
62
|
|
|
63
|
-
//
|
|
64
|
-
const
|
|
65
|
-
owner
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
// Use GraphQL to fetch review threads with resolution status
|
|
64
|
+
const query = `
|
|
65
|
+
query($owner: String!, $repo: String!, $prNumber: Int!, $cursor: String) {
|
|
66
|
+
repository(owner: $owner, name: $repo) {
|
|
67
|
+
pullRequest(number: $prNumber) {
|
|
68
|
+
reviewThreads(first: 100, after: $cursor) {
|
|
69
|
+
pageInfo {
|
|
70
|
+
hasNextPage
|
|
71
|
+
endCursor
|
|
72
|
+
}
|
|
73
|
+
nodes {
|
|
74
|
+
isResolved
|
|
75
|
+
isOutdated
|
|
76
|
+
path
|
|
77
|
+
line
|
|
78
|
+
startLine
|
|
79
|
+
comments(first: 100) {
|
|
80
|
+
nodes {
|
|
81
|
+
id
|
|
82
|
+
body
|
|
83
|
+
path
|
|
84
|
+
line: originalLine
|
|
85
|
+
startLine: originalStartLine
|
|
86
|
+
position: originalPosition
|
|
87
|
+
user: author {
|
|
88
|
+
login
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
`
|
|
98
|
+
|
|
99
|
+
// Fetch all review threads with pagination
|
|
100
|
+
const allThreads = []
|
|
101
|
+
let cursor = null
|
|
102
|
+
let hasNextPage = true
|
|
103
|
+
|
|
104
|
+
while (hasNextPage) {
|
|
105
|
+
const result = await octokit.graphql(query, {
|
|
106
|
+
owner,
|
|
107
|
+
repo,
|
|
108
|
+
prNumber,
|
|
109
|
+
cursor
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
const threads = result.repository.pullRequest.reviewThreads
|
|
113
|
+
allThreads.push(...threads.nodes)
|
|
114
|
+
hasNextPage = threads.pageInfo.hasNextPage
|
|
115
|
+
cursor = threads.pageInfo.endCursor
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Filter to only unresolved threads and flatten comments
|
|
119
|
+
const unresolvedComments = allThreads
|
|
120
|
+
.filter(thread => !thread.isResolved)
|
|
121
|
+
.flatMap(thread => thread.comments.nodes.map(comment => ({
|
|
122
|
+
...comment,
|
|
123
|
+
path: thread.path,
|
|
124
|
+
line: comment.line || thread.line,
|
|
125
|
+
start_line: comment.startLine || thread.startLine,
|
|
126
|
+
original_line: comment.line || thread.line,
|
|
127
|
+
original_start_line: comment.startLine || thread.startLine,
|
|
128
|
+
position: comment.position
|
|
129
|
+
})))
|
|
69
130
|
|
|
70
131
|
// Fetch PR reviews (general comments)
|
|
71
132
|
const { data: reviews } = await octokit.rest.pulls.listReviews({
|
|
@@ -82,7 +143,7 @@ export async function fetchReviewComments (owner, repo, prNumber, token) {
|
|
|
82
143
|
})
|
|
83
144
|
|
|
84
145
|
return {
|
|
85
|
-
inline:
|
|
146
|
+
inline: unresolvedComments,
|
|
86
147
|
reviews: reviews.filter(r => r.body), // Only reviews with comments
|
|
87
148
|
discussion: issueComments
|
|
88
149
|
}
|
|
@@ -93,10 +154,11 @@ export function formatFeedback (comments, filterBots = true) {
|
|
|
93
154
|
|
|
94
155
|
// Filter out bot comments if requested
|
|
95
156
|
const isBot = (comment) => {
|
|
157
|
+
const login = comment.user?.login || comment.author?.login
|
|
96
158
|
return filterBots && (
|
|
97
159
|
comment.user?.type === 'Bot' ||
|
|
98
|
-
|
|
99
|
-
|
|
160
|
+
login?.includes('[bot]') ||
|
|
161
|
+
login?.includes('-bot')
|
|
100
162
|
)
|
|
101
163
|
}
|
|
102
164
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ghreview",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "GitHub PR-based code review workflow for AI-assisted development",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -25,8 +25,9 @@
|
|
|
25
25
|
"license": "Apache-2.0",
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@octokit/rest": "^22.0.0",
|
|
28
|
-
"chalk": "^5.
|
|
29
|
-
"
|
|
28
|
+
"chalk": "^5.6.2",
|
|
29
|
+
"ghauth": "^7.0.0",
|
|
30
|
+
"ora": "^9.0.0",
|
|
30
31
|
"simple-git": "^3.28.0",
|
|
31
32
|
"yargs": "^18.0.0"
|
|
32
33
|
},
|
|
@@ -34,11 +35,11 @@
|
|
|
34
35
|
"@semantic-release/changelog": "^6.0.3",
|
|
35
36
|
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
36
37
|
"@semantic-release/git": "^10.0.1",
|
|
37
|
-
"@semantic-release/github": "^
|
|
38
|
-
"@semantic-release/npm": "^
|
|
38
|
+
"@semantic-release/github": "^12.0.0",
|
|
39
|
+
"@semantic-release/npm": "^13.1.1",
|
|
39
40
|
"@semantic-release/release-notes-generator": "^14.0.3",
|
|
40
41
|
"conventional-changelog-conventionalcommits": "^9.0.0",
|
|
41
|
-
"nock": "^14.0.
|
|
42
|
+
"nock": "^14.0.10",
|
|
42
43
|
"standard": "^17.1.2",
|
|
43
44
|
"vitest": "^3.2.4"
|
|
44
45
|
},
|