ira-review 1.2.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/LICENSE +17 -18
- package/NOTICE +1 -28
- package/README.github.md +2 -2
- package/README.md +69 -18
- package/README.npm.md +69 -18
- package/dist/{bitbucket-TSFJF5KS.js → bitbucket-TQU3E6SG.js} +1 -1
- package/dist/{chunk-NVWGRWNH.js → chunk-NIAFIXU4.js} +41 -0
- package/dist/{chunk-6G34PNVI.js → chunk-XOR6EFZE.js} +56 -0
- package/dist/cli.js +1242 -179
- package/dist/{github-736KUSMV.js → github-Q46RU3UR.js} +1 -1
- package/dist/index.cjs +1079 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +117 -7
- package/dist/index.d.ts +117 -7
- package/dist/index.js +1068 -67
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/scripts/postinstall.js +7 -24
package/LICENSE
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2024-present Mayur Patil (patilmayur5572@gmail.com)
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
4. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
-
IMPLIED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
20
|
-
OTHER LIABILITY ARISING FROM THE USE OF THE SOFTWARE.
|
|
21
|
-
|
|
22
|
-
For licensing inquiries: patilmayur5572@gmail.com
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/NOTICE
CHANGED
|
@@ -1,34 +1,7 @@
|
|
|
1
1
|
IRA — Intelligent Review Assistant
|
|
2
2
|
Copyright (c) 2024-present Mayur Patil (patilmayur5572@gmail.com)
|
|
3
3
|
|
|
4
|
-
This software is licensed under the
|
|
5
|
-
|
|
6
|
-
=== AGPL-3.0 OBLIGATIONS ===
|
|
7
|
-
|
|
8
|
-
By using, modifying, or distributing this software, you agree to the following:
|
|
9
|
-
|
|
10
|
-
1. SOURCE CODE DISCLOSURE
|
|
11
|
-
If you modify this software or use it as part of a network service,
|
|
12
|
-
you MUST make the complete source code of your modified version available
|
|
13
|
-
to all users under the AGPL-3.0 license.
|
|
14
|
-
|
|
15
|
-
2. LICENSE PRESERVATION
|
|
16
|
-
You must retain all copyright notices, this NOTICE file, and the full
|
|
17
|
-
AGPL-3.0 license text in any copies or derivative works.
|
|
18
|
-
|
|
19
|
-
3. NETWORK USE IS DISTRIBUTION
|
|
20
|
-
Under AGPL-3.0, providing this software as a network service (e.g., SaaS,
|
|
21
|
-
CI/CD pipeline service) counts as distribution. You must offer the source
|
|
22
|
-
code to all users who interact with the service.
|
|
23
|
-
|
|
24
|
-
4. NO PROPRIETARY USE WITHOUT COMMERCIAL LICENSE
|
|
25
|
-
Using this software in proprietary/closed-source projects without releasing
|
|
26
|
-
your source code under AGPL-3.0 requires a commercial license.
|
|
27
|
-
|
|
28
|
-
=== COMMERCIAL LICENSING ===
|
|
29
|
-
|
|
30
|
-
For commercial licensing (use IRA in proprietary projects without AGPL obligations),
|
|
31
|
-
contact: patilmayur5572@gmail.com
|
|
4
|
+
This software is licensed under the MIT License. See LICENSE file for details.
|
|
32
5
|
|
|
33
6
|
=== THIRD-PARTY NOTICES ===
|
|
34
7
|
|
package/README.github.md
CHANGED
|
@@ -214,7 +214,7 @@ IRA is not a SaaS product. There is no hosted service, no telemetry, no analytic
|
|
|
214
214
|
| **Auth** | Environment variables or CLI flags | VS Code OAuth + OS keychain |
|
|
215
215
|
| **Output** | Terminal + PR comments | Inline diagnostics, CodeLens, TreeView, risk badge |
|
|
216
216
|
| **JIRA/Sonar** | CLI flags or env vars | VS Code settings |
|
|
217
|
-
| **
|
|
217
|
+
| **Extension features** | Not applicable | Auto-review on save, one-click fix, history/trends |
|
|
218
218
|
|
|
219
219
|
Both surfaces use the same core review engine. The review logic, risk scoring, and AI prompts are identical.
|
|
220
220
|
|
|
@@ -425,7 +425,7 @@ CLI flags override environment variables, which override the config file. Token
|
|
|
425
425
|
|
|
426
426
|
## License
|
|
427
427
|
|
|
428
|
-
[
|
|
428
|
+
[MIT](LICENSE)
|
|
429
429
|
|
|
430
430
|
Full CLI reference: `npx ira-review review --help`
|
|
431
431
|
|
package/README.md
CHANGED
|
@@ -15,28 +15,66 @@ No install required. Drop `--dry-run` to post comments directly on the PR. For B
|
|
|
15
15
|
## What You Get
|
|
16
16
|
|
|
17
17
|
```
|
|
18
|
-
IRA
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
18
|
+
🔍 IRA — Scanning PR before your reviewers do
|
|
19
|
+
|
|
20
|
+
✓ Config loaded — AI-only mode, openai, PR #42
|
|
21
|
+
✓ Diff loaded — 4 files changed
|
|
22
|
+
✓ Review complete — 3 issues found
|
|
23
|
+
|
|
24
|
+
────────────────────────────────────────────────────────────
|
|
25
|
+
📄 src/routes/auth.ts:31
|
|
26
|
+
Rule: IRA/security (CRITICAL)
|
|
27
|
+
Message: User input passed directly to SQL query
|
|
28
|
+
Explain: The username parameter is concatenated into a SQL string,
|
|
29
|
+
creating a SQL injection vector.
|
|
30
|
+
Impact: Attacker could execute arbitrary SQL and gain database control.
|
|
31
|
+
Fix: BEFORE: `db.query(`SELECT * FROM users WHERE name = ${username}`)`
|
|
32
|
+
→ AFTER: `db.query('SELECT * FROM users WHERE name = $1', [username])`
|
|
33
|
+
|
|
34
|
+
────────────────────────────────────────────────────────────
|
|
35
|
+
📄 src/middleware/cors.ts:8
|
|
36
|
+
Rule: IRA/error-handling (MAJOR)
|
|
37
|
+
Message: Empty catch block swallows CORS validation errors
|
|
38
|
+
Explain: fetch() failure in CORS preflight is caught and ignored,
|
|
39
|
+
leaving the request in an undefined state.
|
|
40
|
+
Impact: Silent CORS failures in production with no logging.
|
|
41
|
+
Fix: BEFORE: `} catch {}`
|
|
42
|
+
→ AFTER: `} catch (err) { logger.error('CORS preflight failed', err); throw err; }`
|
|
43
|
+
|
|
44
|
+
# 🔍 IRA Review Summary
|
|
45
|
+
|
|
46
|
+
## 🟡 Risk: MEDIUM (38/100)
|
|
47
|
+
|
|
48
|
+
| Metric | Value |
|
|
49
|
+
|---------------|----------|
|
|
50
|
+
| Review mode | AI-only |
|
|
51
|
+
| Total issues | 3 |
|
|
52
|
+
| Reviewed (AI) | 3 |
|
|
53
|
+
| Framework | react |
|
|
54
|
+
|
|
55
|
+
## ✅ Requirements: AUTH-234 — 83% Complete (5/6)
|
|
56
|
+
|
|
57
|
+
✅ OAuth2 login flow implemented with Google provider
|
|
58
|
+
✅ JWT tokens generated on successful authentication
|
|
59
|
+
✅ Refresh token rotation with 7-day expiry
|
|
60
|
+
❌ Input validation on login endpoint — no email format check
|
|
61
|
+
✅ Logout endpoint clears session and revokes token
|
|
62
|
+
✅ Rate limiting on login attempts
|
|
63
|
+
|
|
64
|
+
⚠️ Edge Cases Not Covered
|
|
65
|
+
- What happens when Google OAuth is unreachable?
|
|
66
|
+
- Token refresh during concurrent requests?
|
|
31
67
|
```
|
|
32
68
|
|
|
33
|
-
Each issue is posted as an inline comment on the exact PR line with explanation, impact, and
|
|
69
|
+
Each issue is posted as an inline comment on the exact PR line with explanation, impact, and a minimal BEFORE → AFTER fix.
|
|
34
70
|
|
|
35
71
|
**Features:**
|
|
36
72
|
|
|
73
|
+
- Evidence-based reviews — 7 categories (security, business logic, race conditions, data consistency, async, error handling, defensive coding), each with explicit false-positive exclusions. Issues without concrete evidence are filtered out.
|
|
37
74
|
- Risk scoring (0-100) with severity breakdown and PR labels
|
|
38
|
-
- Inline AI comments with explanation, impact, and
|
|
39
|
-
- JIRA acceptance criteria validation with per-criterion pass/fail
|
|
75
|
+
- Inline AI comments with explanation, impact, and minimal BEFORE → AFTER fix
|
|
76
|
+
- JIRA acceptance criteria validation with per-criterion pass/fail and edge case detection
|
|
77
|
+
- JIRA AC auto-detection — finds AC from custom field or description automatically
|
|
40
78
|
- Custom team review rules via `.ira-rules.json` (see below)
|
|
41
79
|
- Test case generation from JIRA tickets (Jest, Vitest, Playwright, etc.)
|
|
42
80
|
- Comment deduplication across re-runs
|
|
@@ -53,6 +91,8 @@ Commit a `.ira-rules.json` to your repo root. Rules are injected into the AI pro
|
|
|
53
91
|
"rules": [
|
|
54
92
|
{
|
|
55
93
|
"message": "Use parameterized queries for all SQL operations",
|
|
94
|
+
"bad": "db.query(`SELECT * FROM users WHERE id = ${userId}`)",
|
|
95
|
+
"good": "db.query('SELECT * FROM users WHERE id = $1', [userId])",
|
|
56
96
|
"severity": "CRITICAL",
|
|
57
97
|
"paths": ["src/db/**", "src/api/**"]
|
|
58
98
|
},
|
|
@@ -62,16 +102,27 @@ Commit a `.ira-rules.json` to your repo root. Rules are injected into the AI pro
|
|
|
62
102
|
"good": "logger.info('User created', { userId: user.id });",
|
|
63
103
|
"severity": "MINOR"
|
|
64
104
|
}
|
|
105
|
+
],
|
|
106
|
+
"sensitiveAreas": [
|
|
107
|
+
"src/services/payment/**",
|
|
108
|
+
"**/auth/**",
|
|
109
|
+
"src/config/database.*"
|
|
65
110
|
]
|
|
66
111
|
}
|
|
67
112
|
```
|
|
68
113
|
|
|
114
|
+
**Rules:**
|
|
69
115
|
- `message` + `severity` required. `bad`/`good` examples and `paths` are optional.
|
|
70
116
|
- Rules without `paths` apply to all files. Rules with `paths` match only those directories.
|
|
71
|
-
- Maximum
|
|
117
|
+
- Maximum 50 rules. Deterministic checks (naming, formatting) belong in ESLint.
|
|
72
118
|
- Invalid rules are skipped with a warning, not a crash.
|
|
73
119
|
- No license gating. Works in CLI, CI/CD, and VS Code extension.
|
|
74
120
|
|
|
121
|
+
**Sensitive Areas:**
|
|
122
|
+
- Files matching a sensitive area glob get extra scrutiny during review and Apply Fix.
|
|
123
|
+
- Labels are derived from the glob automatically (`src/services/payment/**` → "payment").
|
|
124
|
+
- Sensitive file findings get a higher weight in risk scoring.
|
|
125
|
+
|
|
75
126
|
---
|
|
76
127
|
|
|
77
128
|
## Use Cases
|
|
@@ -170,7 +221,7 @@ Tokens are read from environment variables or CLI flags at runtime. Nothing is w
|
|
|
170
221
|
|
|
171
222
|
## License
|
|
172
223
|
|
|
173
|
-
[
|
|
224
|
+
[MIT](LICENSE)
|
|
174
225
|
|
|
175
226
|
---
|
|
176
227
|
|
package/README.npm.md
CHANGED
|
@@ -15,28 +15,66 @@ No install required. Drop `--dry-run` to post comments directly on the PR. For B
|
|
|
15
15
|
## What You Get
|
|
16
16
|
|
|
17
17
|
```
|
|
18
|
-
IRA
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
18
|
+
🔍 IRA — Scanning PR before your reviewers do
|
|
19
|
+
|
|
20
|
+
✓ Config loaded — AI-only mode, openai, PR #42
|
|
21
|
+
✓ Diff loaded — 4 files changed
|
|
22
|
+
✓ Review complete — 3 issues found
|
|
23
|
+
|
|
24
|
+
────────────────────────────────────────────────────────────
|
|
25
|
+
📄 src/routes/auth.ts:31
|
|
26
|
+
Rule: IRA/security (CRITICAL)
|
|
27
|
+
Message: User input passed directly to SQL query
|
|
28
|
+
Explain: The username parameter is concatenated into a SQL string,
|
|
29
|
+
creating a SQL injection vector.
|
|
30
|
+
Impact: Attacker could execute arbitrary SQL and gain database control.
|
|
31
|
+
Fix: BEFORE: `db.query(`SELECT * FROM users WHERE name = ${username}`)`
|
|
32
|
+
→ AFTER: `db.query('SELECT * FROM users WHERE name = $1', [username])`
|
|
33
|
+
|
|
34
|
+
────────────────────────────────────────────────────────────
|
|
35
|
+
📄 src/middleware/cors.ts:8
|
|
36
|
+
Rule: IRA/error-handling (MAJOR)
|
|
37
|
+
Message: Empty catch block swallows CORS validation errors
|
|
38
|
+
Explain: fetch() failure in CORS preflight is caught and ignored,
|
|
39
|
+
leaving the request in an undefined state.
|
|
40
|
+
Impact: Silent CORS failures in production with no logging.
|
|
41
|
+
Fix: BEFORE: `} catch {}`
|
|
42
|
+
→ AFTER: `} catch (err) { logger.error('CORS preflight failed', err); throw err; }`
|
|
43
|
+
|
|
44
|
+
# 🔍 IRA Review Summary
|
|
45
|
+
|
|
46
|
+
## 🟡 Risk: MEDIUM (38/100)
|
|
47
|
+
|
|
48
|
+
| Metric | Value |
|
|
49
|
+
|---------------|----------|
|
|
50
|
+
| Review mode | AI-only |
|
|
51
|
+
| Total issues | 3 |
|
|
52
|
+
| Reviewed (AI) | 3 |
|
|
53
|
+
| Framework | react |
|
|
54
|
+
|
|
55
|
+
## ✅ Requirements: AUTH-234 — 83% Complete (5/6)
|
|
56
|
+
|
|
57
|
+
✅ OAuth2 login flow implemented with Google provider
|
|
58
|
+
✅ JWT tokens generated on successful authentication
|
|
59
|
+
✅ Refresh token rotation with 7-day expiry
|
|
60
|
+
❌ Input validation on login endpoint — no email format check
|
|
61
|
+
✅ Logout endpoint clears session and revokes token
|
|
62
|
+
✅ Rate limiting on login attempts
|
|
63
|
+
|
|
64
|
+
⚠️ Edge Cases Not Covered
|
|
65
|
+
- What happens when Google OAuth is unreachable?
|
|
66
|
+
- Token refresh during concurrent requests?
|
|
31
67
|
```
|
|
32
68
|
|
|
33
|
-
Each issue is posted as an inline comment on the exact PR line with explanation, impact, and
|
|
69
|
+
Each issue is posted as an inline comment on the exact PR line with explanation, impact, and a minimal BEFORE → AFTER fix.
|
|
34
70
|
|
|
35
71
|
**Features:**
|
|
36
72
|
|
|
73
|
+
- Evidence-based reviews — 7 categories (security, business logic, race conditions, data consistency, async, error handling, defensive coding), each with explicit false-positive exclusions. Issues without concrete evidence are filtered out.
|
|
37
74
|
- Risk scoring (0-100) with severity breakdown and PR labels
|
|
38
|
-
- Inline AI comments with explanation, impact, and
|
|
39
|
-
- JIRA acceptance criteria validation with per-criterion pass/fail
|
|
75
|
+
- Inline AI comments with explanation, impact, and minimal BEFORE → AFTER fix
|
|
76
|
+
- JIRA acceptance criteria validation with per-criterion pass/fail and edge case detection
|
|
77
|
+
- JIRA AC auto-detection — finds AC from custom field or description automatically
|
|
40
78
|
- Custom team review rules via `.ira-rules.json` (see below)
|
|
41
79
|
- Test case generation from JIRA tickets (Jest, Vitest, Playwright, etc.)
|
|
42
80
|
- Comment deduplication across re-runs
|
|
@@ -53,6 +91,8 @@ Commit a `.ira-rules.json` to your repo root. Rules are injected into the AI pro
|
|
|
53
91
|
"rules": [
|
|
54
92
|
{
|
|
55
93
|
"message": "Use parameterized queries for all SQL operations",
|
|
94
|
+
"bad": "db.query(`SELECT * FROM users WHERE id = ${userId}`)",
|
|
95
|
+
"good": "db.query('SELECT * FROM users WHERE id = $1', [userId])",
|
|
56
96
|
"severity": "CRITICAL",
|
|
57
97
|
"paths": ["src/db/**", "src/api/**"]
|
|
58
98
|
},
|
|
@@ -62,16 +102,27 @@ Commit a `.ira-rules.json` to your repo root. Rules are injected into the AI pro
|
|
|
62
102
|
"good": "logger.info('User created', { userId: user.id });",
|
|
63
103
|
"severity": "MINOR"
|
|
64
104
|
}
|
|
105
|
+
],
|
|
106
|
+
"sensitiveAreas": [
|
|
107
|
+
"src/services/payment/**",
|
|
108
|
+
"**/auth/**",
|
|
109
|
+
"src/config/database.*"
|
|
65
110
|
]
|
|
66
111
|
}
|
|
67
112
|
```
|
|
68
113
|
|
|
114
|
+
**Rules:**
|
|
69
115
|
- `message` + `severity` required. `bad`/`good` examples and `paths` are optional.
|
|
70
116
|
- Rules without `paths` apply to all files. Rules with `paths` match only those directories.
|
|
71
|
-
- Maximum
|
|
117
|
+
- Maximum 50 rules. Deterministic checks (naming, formatting) belong in ESLint.
|
|
72
118
|
- Invalid rules are skipped with a warning, not a crash.
|
|
73
119
|
- No license gating. Works in CLI, CI/CD, and VS Code extension.
|
|
74
120
|
|
|
121
|
+
**Sensitive Areas:**
|
|
122
|
+
- Files matching a sensitive area glob get extra scrutiny during review and Apply Fix.
|
|
123
|
+
- Labels are derived from the glob automatically (`src/services/payment/**` → "payment").
|
|
124
|
+
- Sensitive file findings get a higher weight in risk scoring.
|
|
125
|
+
|
|
75
126
|
---
|
|
76
127
|
|
|
77
128
|
## Use Cases
|
|
@@ -170,7 +221,7 @@ Tokens are read from environment variables or CLI flags at runtime. Nothing is w
|
|
|
170
221
|
|
|
171
222
|
## License
|
|
172
223
|
|
|
173
|
-
[
|
|
224
|
+
[MIT](LICENSE)
|
|
174
225
|
|
|
175
226
|
---
|
|
176
227
|
|
|
@@ -54,6 +54,21 @@ var GitHubClient = class {
|
|
|
54
54
|
}
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
|
+
async getPRState(pullRequestId) {
|
|
58
|
+
const url = `${this.baseUrl}/repos/${this.owner}/${this.repo}/pulls/${pullRequestId}`;
|
|
59
|
+
const response = await fetchWithTimeout(url, { headers: this.headers });
|
|
60
|
+
if (response.status === 404) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
`PR #${pullRequestId} was not found \u2014 it may have been deleted.
|
|
63
|
+
\u{1F4A1} Double-check the PR number and try again.`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
if (!response.ok) return "unknown";
|
|
67
|
+
const data = await response.json();
|
|
68
|
+
if (data.merged) return "merged";
|
|
69
|
+
if (data.state === "closed") return "closed";
|
|
70
|
+
return "open";
|
|
71
|
+
}
|
|
57
72
|
async getDiff(pullRequestId) {
|
|
58
73
|
const url = `${this.baseUrl}/repos/${this.owner}/${this.repo}/pulls/${pullRequestId}`;
|
|
59
74
|
return withRetry(async () => {
|
|
@@ -73,6 +88,32 @@ var GitHubClient = class {
|
|
|
73
88
|
return response.text();
|
|
74
89
|
});
|
|
75
90
|
}
|
|
91
|
+
async getDiffPerFile(pullRequestId) {
|
|
92
|
+
const fileMap = /* @__PURE__ */ new Map();
|
|
93
|
+
let page = 1;
|
|
94
|
+
while (true) {
|
|
95
|
+
const url = `${this.baseUrl}/repos/${this.owner}/${this.repo}/pulls/${pullRequestId}/files?per_page=100&page=${page}`;
|
|
96
|
+
const data = await withRetry(async () => {
|
|
97
|
+
const response = await fetchWithTimeout(url, { headers: this.headers });
|
|
98
|
+
if (!response.ok) {
|
|
99
|
+
const text = await response.text();
|
|
100
|
+
throw new RetryableError(`GitHub API error (${response.status}): ${text}`, response.status);
|
|
101
|
+
}
|
|
102
|
+
return response.json();
|
|
103
|
+
});
|
|
104
|
+
for (const file of data) {
|
|
105
|
+
if (file.status === "removed" || !file.patch) continue;
|
|
106
|
+
const unified = `diff --git a/${file.filename} b/${file.filename}
|
|
107
|
+
--- a/${file.filename}
|
|
108
|
+
+++ b/${file.filename}
|
|
109
|
+
${file.patch}`;
|
|
110
|
+
fileMap.set(file.filename, unified);
|
|
111
|
+
}
|
|
112
|
+
if (data.length < 100) break;
|
|
113
|
+
page++;
|
|
114
|
+
}
|
|
115
|
+
return fileMap;
|
|
116
|
+
}
|
|
76
117
|
async getFileContent(filePath, pullRequestId) {
|
|
77
118
|
const sha = await this.getHeadSha(pullRequestId);
|
|
78
119
|
const encodedPath = filePath.split("/").map(encodeURIComponent).join("/");
|
|
@@ -106,6 +106,22 @@ var BitbucketClient = class {
|
|
|
106
106
|
return response.text();
|
|
107
107
|
});
|
|
108
108
|
}
|
|
109
|
+
async getPRState(pullRequestId) {
|
|
110
|
+
const url = `${this.baseUrl}/repositories/${this.workspace}/${this.repoSlug}/pullrequests/${pullRequestId}`;
|
|
111
|
+
const response = await fetchWithTimeout(url, { headers: this.headers });
|
|
112
|
+
if (response.status === 404) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`PR #${pullRequestId} was not found \u2014 it may have been deleted.
|
|
115
|
+
\u{1F4A1} Double-check the PR number and try again.`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
if (!response.ok) return "unknown";
|
|
119
|
+
const data = await response.json();
|
|
120
|
+
const state = data.state?.toUpperCase();
|
|
121
|
+
if (state === "MERGED") return "merged";
|
|
122
|
+
if (state === "DECLINED" || state === "SUPERSEDED") return "declined";
|
|
123
|
+
return "open";
|
|
124
|
+
}
|
|
109
125
|
async getDiff(pullRequestId) {
|
|
110
126
|
const url = `${this.baseUrl}/repositories/${this.workspace}/${this.repoSlug}/pullrequests/${pullRequestId}/diff`;
|
|
111
127
|
return withRetry(async () => {
|
|
@@ -122,6 +138,46 @@ var BitbucketClient = class {
|
|
|
122
138
|
return response.text();
|
|
123
139
|
});
|
|
124
140
|
}
|
|
141
|
+
async getDiffPerFile(pullRequestId) {
|
|
142
|
+
const fileMap = /* @__PURE__ */ new Map();
|
|
143
|
+
let nextUrl = `${this.baseUrl}/repositories/${this.workspace}/${this.repoSlug}/pullrequests/${pullRequestId}/diffstat?pagelen=100`;
|
|
144
|
+
const changedFiles = [];
|
|
145
|
+
while (nextUrl) {
|
|
146
|
+
const data = await withRetry(async () => {
|
|
147
|
+
const response = await fetchWithTimeout(nextUrl, { headers: this.headers });
|
|
148
|
+
if (!response.ok) {
|
|
149
|
+
const text = await response.text();
|
|
150
|
+
throw new RetryableError(`Bitbucket API error (${response.status}): ${text}`, response.status);
|
|
151
|
+
}
|
|
152
|
+
return response.json();
|
|
153
|
+
});
|
|
154
|
+
for (const entry of data.values) {
|
|
155
|
+
if (entry.status === "removed") continue;
|
|
156
|
+
const path = entry.new?.path ?? entry.old?.path;
|
|
157
|
+
if (path) changedFiles.push(path);
|
|
158
|
+
}
|
|
159
|
+
nextUrl = data.next ?? null;
|
|
160
|
+
}
|
|
161
|
+
for (const filePath of changedFiles) {
|
|
162
|
+
try {
|
|
163
|
+
const encodedPath = filePath.split("/").map(encodeURIComponent).join("/");
|
|
164
|
+
const url = `${this.baseUrl}/repositories/${this.workspace}/${this.repoSlug}/pullrequests/${pullRequestId}/diff?path=${encodedPath}`;
|
|
165
|
+
const diff = await withRetry(async () => {
|
|
166
|
+
const response = await fetchWithTimeout(url, { headers: this.headers }, 15e3);
|
|
167
|
+
if (!response.ok) {
|
|
168
|
+
const text = await response.text();
|
|
169
|
+
throw new RetryableError(`Bitbucket API error (${response.status}): ${text}`, response.status);
|
|
170
|
+
}
|
|
171
|
+
return response.text();
|
|
172
|
+
});
|
|
173
|
+
if (diff.trim()) {
|
|
174
|
+
fileMap.set(filePath, diff);
|
|
175
|
+
}
|
|
176
|
+
} catch {
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return fileMap;
|
|
180
|
+
}
|
|
125
181
|
getSourceHash(pullRequestId) {
|
|
126
182
|
const cached = this.shaCache.get(pullRequestId);
|
|
127
183
|
if (cached) return cached;
|