@jonit-dev/night-watch-cli 1.8.11 → 1.8.12-beta.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/dist/cli.js
CHANGED
|
@@ -9825,10 +9825,56 @@ function parseFinalReviewScore(raw) {
|
|
|
9825
9825
|
return parsed;
|
|
9826
9826
|
}
|
|
9827
9827
|
function postReadyForHumanReviewComment(prNumber, finalScore, cwd) {
|
|
9828
|
+
const markerName = "night-watch-ready-for-review";
|
|
9829
|
+
let headRefOid = "";
|
|
9830
|
+
try {
|
|
9831
|
+
headRefOid = execFileSync5(
|
|
9832
|
+
"gh",
|
|
9833
|
+
["pr", "view", String(prNumber), "--json", "headRefOid", "--jq", ".headRefOid"],
|
|
9834
|
+
{
|
|
9835
|
+
cwd,
|
|
9836
|
+
encoding: "utf-8",
|
|
9837
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
9838
|
+
}
|
|
9839
|
+
).trim();
|
|
9840
|
+
} catch {
|
|
9841
|
+
headRefOid = "";
|
|
9842
|
+
}
|
|
9843
|
+
if (headRefOid) {
|
|
9844
|
+
try {
|
|
9845
|
+
const existingComments = execFileSync5(
|
|
9846
|
+
"gh",
|
|
9847
|
+
["api", `repos/{owner}/{repo}/issues/${prNumber}/comments`, "--jq", ".[].body"],
|
|
9848
|
+
{
|
|
9849
|
+
cwd,
|
|
9850
|
+
encoding: "utf-8",
|
|
9851
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
9852
|
+
}
|
|
9853
|
+
);
|
|
9854
|
+
const marker2 = `<!-- ${markerName} headRefOid:${headRefOid} -->`;
|
|
9855
|
+
if (existingComments.includes(marker2)) {
|
|
9856
|
+
try {
|
|
9857
|
+
execFileSync5("gh", ["pr", "edit", String(prNumber), "--add-label", "ready-for-review"], {
|
|
9858
|
+
cwd,
|
|
9859
|
+
encoding: "utf-8",
|
|
9860
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
9861
|
+
});
|
|
9862
|
+
} catch {
|
|
9863
|
+
}
|
|
9864
|
+
return;
|
|
9865
|
+
}
|
|
9866
|
+
} catch {
|
|
9867
|
+
}
|
|
9868
|
+
}
|
|
9828
9869
|
const scoreNote = finalScore !== void 0 ? ` (score: ${finalScore}/100)` : "";
|
|
9829
|
-
const
|
|
9870
|
+
const shortSha = headRefOid ? headRefOid.slice(0, 12) : "";
|
|
9871
|
+
const marker = headRefOid ? `<!-- ${markerName} headRefOid:${headRefOid} -->
|
|
9872
|
+
|
|
9873
|
+
` : "";
|
|
9874
|
+
const shaNote = shortSha ? ` at commit \`${shortSha}\`` : "";
|
|
9875
|
+
const body = `${marker}## \u2705 Ready for Human Review
|
|
9830
9876
|
|
|
9831
|
-
Night Watch has reviewed this PR${scoreNote} and found no issues requiring automated fixes.
|
|
9877
|
+
Night Watch has reviewed this PR${scoreNote}${shaNote} and found no issues requiring automated fixes for the current head.
|
|
9832
9878
|
|
|
9833
9879
|
This PR is ready for human code review and merge.`;
|
|
9834
9880
|
try {
|
|
@@ -10049,8 +10095,12 @@ ${stderr}`);
|
|
|
10049
10095
|
const reviewedPrNumbers = parseReviewedPrNumbers(scriptResult?.data.prs);
|
|
10050
10096
|
const noChangesPrNumbers = parseReviewedPrNumbers(scriptResult?.data.no_changes_prs);
|
|
10051
10097
|
const fallbackPrNumber = fallbackPrDetails?.number;
|
|
10098
|
+
let notificationPrNumbers = reviewedPrNumbers;
|
|
10099
|
+
if (notificationPrNumbers.length === 0 && fallbackPrNumber !== void 0) {
|
|
10100
|
+
notificationPrNumbers = [fallbackPrNumber];
|
|
10101
|
+
}
|
|
10052
10102
|
const notificationTargets = buildReviewNotificationTargets(
|
|
10053
|
-
|
|
10103
|
+
notificationPrNumbers,
|
|
10054
10104
|
noChangesPrNumbers,
|
|
10055
10105
|
legacyNoChangesNeeded
|
|
10056
10106
|
);
|
|
@@ -10082,7 +10132,10 @@ ${stderr}`);
|
|
|
10082
10132
|
event: reviewEvent,
|
|
10083
10133
|
projectName: path23.basename(projectDir),
|
|
10084
10134
|
exitCode,
|
|
10085
|
-
provider: formatProviderDisplay(
|
|
10135
|
+
provider: formatProviderDisplay(
|
|
10136
|
+
envVars.NW_PROVIDER_CMD,
|
|
10137
|
+
envVars.NW_PROVIDER_LABEL
|
|
10138
|
+
),
|
|
10086
10139
|
prUrl: prDetails?.url,
|
|
10087
10140
|
prTitle: prDetails?.title,
|
|
10088
10141
|
prBody: prDetails?.body,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"review.d.ts","sourceRoot":"","sources":["../../src/commands/review.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAEL,iBAAiB,EAgBlB,MAAM,mBAAmB,CAAC;AAU3B;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,4BAA4B,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAQ3E;AAED;;;GAGG;AACH,wBAAgB,sCAAsC,CACpD,QAAQ,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAUT;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAQ/D;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAiB7D;AAED;;;GAGG;AACH,wBAAgB,8BAA8B,CAC5C,iBAAiB,EAAE,MAAM,EAAE,EAC3B,kBAAkB,EAAE,MAAM,EAAE,EAC5B,qBAAqB,UAAQ,GAC5B,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,OAAO,CAAA;CAAE,CAAC,CAYvD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAMvD;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAStE;AAED;;;GAGG;AACH,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,GAAG,EAAE,MAAM,GACV,IAAI,
|
|
1
|
+
{"version":3,"file":"review.d.ts","sourceRoot":"","sources":["../../src/commands/review.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAEL,iBAAiB,EAgBlB,MAAM,mBAAmB,CAAC;AAU3B;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,4BAA4B,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAQ3E;AAED;;;GAGG;AACH,wBAAgB,sCAAsC,CACpD,QAAQ,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAUT;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAQ/D;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAiB7D;AAED;;;GAGG;AACH,wBAAgB,8BAA8B,CAC5C,iBAAiB,EAAE,MAAM,EAAE,EAC3B,kBAAkB,EAAE,MAAM,EAAE,EAC5B,qBAAqB,UAAQ,GAC5B,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,OAAO,CAAA;CAAE,CAAC,CAYvD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAMvD;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAStE;AAED;;;GAGG;AACH,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,GAAG,EAAE,MAAM,GACV,IAAI,CA2EN;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,iBAAiB,EACzB,OAAO,EAAE,cAAc,GACtB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAgBxB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,iBAAiB,EACzB,OAAO,EAAE,cAAc,GACtB,iBAAiB,CAgBnB;AAED,UAAU,YAAY;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAmB3D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CA0C7D;AAiCD;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAmQpD"}
|
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review command - executes the PR reviewer cron script
|
|
3
|
+
*/
|
|
4
|
+
import { CLAUDE_MODEL_IDS, PROVIDER_COMMANDS, createSpinner, createTable, dim, executeScriptWithOutput, fetchPrDetailsByNumber, fetchReviewedPrDetails, getScriptPath, header, info, loadConfig, parseScriptResult, resolveJobProvider, sendNotifications, error as uiError, } from '@night-watch/core';
|
|
5
|
+
import { buildBaseEnvVars, formatProviderDisplay, maybeApplyCronSchedulingDelay, } from './shared/env-builder.js';
|
|
6
|
+
import { execFileSync } from 'child_process';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
/**
|
|
9
|
+
* Review notifications should not fire for script-level skip/no-op outcomes.
|
|
10
|
+
*/
|
|
11
|
+
export function shouldSendReviewNotification(scriptStatus) {
|
|
12
|
+
if (!scriptStatus) {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
if (scriptStatus === 'queued') {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
return !scriptStatus.startsWith('skip_');
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Review completion notifications are only valid for successful reviewer runs.
|
|
22
|
+
* Guard against both non-zero exits and mismatched legacy status markers.
|
|
23
|
+
*/
|
|
24
|
+
export function shouldSendReviewCompletionNotification(exitCode, scriptStatus) {
|
|
25
|
+
if (exitCode !== 0) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
if (scriptStatus === 'failure' || scriptStatus === 'timeout') {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
return shouldSendReviewNotification(scriptStatus);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Parse comma-separated PR numbers like "#12,#34" into numeric IDs.
|
|
35
|
+
*/
|
|
36
|
+
export function parseAutoMergedPrNumbers(raw) {
|
|
37
|
+
if (!raw || raw.trim().length === 0) {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
return raw
|
|
41
|
+
.split(',')
|
|
42
|
+
.map((token) => parseInt(token.trim().replace(/^#/, ''), 10))
|
|
43
|
+
.filter((value) => !Number.isNaN(value));
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Parse comma-separated PR numbers like "#12,#34" into numeric IDs.
|
|
47
|
+
* Deduplicates while preserving order.
|
|
48
|
+
*/
|
|
49
|
+
export function parseReviewedPrNumbers(raw) {
|
|
50
|
+
if (!raw || raw.trim().length === 0) {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
const seen = new Set();
|
|
54
|
+
return raw
|
|
55
|
+
.split(',')
|
|
56
|
+
.map((token) => parseInt(token.trim().replace(/^#/, ''), 10))
|
|
57
|
+
.filter((value) => !Number.isNaN(value))
|
|
58
|
+
.filter((value) => {
|
|
59
|
+
if (seen.has(value)) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
seen.add(value);
|
|
63
|
+
return true;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Build per-PR review notification targets from the script result payload.
|
|
68
|
+
* Legacy no_changes_needed is only trustworthy when exactly one PR was reviewed.
|
|
69
|
+
*/
|
|
70
|
+
export function buildReviewNotificationTargets(reviewedPrNumbers, noChangesPrNumbers, legacyNoChangesNeeded = false) {
|
|
71
|
+
const uniqueReviewedPrNumbers = Array.from(new Set(reviewedPrNumbers));
|
|
72
|
+
const noChangesSet = new Set(noChangesPrNumbers);
|
|
73
|
+
if (legacyNoChangesNeeded && uniqueReviewedPrNumbers.length === 1) {
|
|
74
|
+
noChangesSet.add(uniqueReviewedPrNumbers[0]);
|
|
75
|
+
}
|
|
76
|
+
return uniqueReviewedPrNumbers.map((prNumber) => ({
|
|
77
|
+
prNumber,
|
|
78
|
+
noChangesNeeded: noChangesSet.has(prNumber),
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Parse retry attempts from script result data.
|
|
83
|
+
* Returns the number of attempts (defaults to 1 if not present or invalid).
|
|
84
|
+
*/
|
|
85
|
+
export function parseRetryAttempts(raw) {
|
|
86
|
+
if (!raw) {
|
|
87
|
+
return 1;
|
|
88
|
+
}
|
|
89
|
+
const parsed = parseInt(raw, 10);
|
|
90
|
+
return Number.isNaN(parsed) || parsed < 1 ? 1 : parsed;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Parse final review score from script result data.
|
|
94
|
+
* Returns undefined when missing or invalid.
|
|
95
|
+
*/
|
|
96
|
+
export function parseFinalReviewScore(raw) {
|
|
97
|
+
if (!raw) {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
const parsed = parseInt(raw, 10);
|
|
101
|
+
if (Number.isNaN(parsed)) {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
return parsed;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Post a "ready for human review" comment and add a label to the PR.
|
|
108
|
+
* Silently ignores failures — gh CLI may not be available.
|
|
109
|
+
*/
|
|
110
|
+
export function postReadyForHumanReviewComment(prNumber, finalScore, cwd) {
|
|
111
|
+
const markerName = 'night-watch-ready-for-review';
|
|
112
|
+
let headRefOid = '';
|
|
113
|
+
try {
|
|
114
|
+
headRefOid = execFileSync('gh', ['pr', 'view', String(prNumber), '--json', 'headRefOid', '--jq', '.headRefOid'], {
|
|
115
|
+
cwd,
|
|
116
|
+
encoding: 'utf-8',
|
|
117
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
118
|
+
}).trim();
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
headRefOid = '';
|
|
122
|
+
}
|
|
123
|
+
if (headRefOid) {
|
|
124
|
+
try {
|
|
125
|
+
const existingComments = execFileSync('gh', ['api', `repos/{owner}/{repo}/issues/${prNumber}/comments`, '--jq', '.[].body'], {
|
|
126
|
+
cwd,
|
|
127
|
+
encoding: 'utf-8',
|
|
128
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
129
|
+
});
|
|
130
|
+
const marker = `<!-- ${markerName} headRefOid:${headRefOid} -->`;
|
|
131
|
+
if (existingComments.includes(marker)) {
|
|
132
|
+
try {
|
|
133
|
+
execFileSync('gh', ['pr', 'edit', String(prNumber), '--add-label', 'ready-for-review'], {
|
|
134
|
+
cwd,
|
|
135
|
+
encoding: 'utf-8',
|
|
136
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// Label may not exist yet — ignore
|
|
141
|
+
}
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
// Ignore comment lookup failures and try to post the comment below.
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const scoreNote = finalScore !== undefined ? ` (score: ${finalScore}/100)` : '';
|
|
150
|
+
const shortSha = headRefOid ? headRefOid.slice(0, 12) : '';
|
|
151
|
+
const marker = headRefOid ? `<!-- ${markerName} headRefOid:${headRefOid} -->\n\n` : '';
|
|
152
|
+
const shaNote = shortSha ? ` at commit \`${shortSha}\`` : '';
|
|
153
|
+
const body = `${marker}## ✅ Ready for Human Review\n\n` +
|
|
154
|
+
`Night Watch has reviewed this PR${scoreNote}${shaNote} and found no issues requiring automated fixes for the current head.\n\n` +
|
|
155
|
+
`This PR is ready for human code review and merge.`;
|
|
156
|
+
try {
|
|
157
|
+
execFileSync('gh', ['pr', 'comment', String(prNumber), '--body', body], {
|
|
158
|
+
cwd,
|
|
159
|
+
encoding: 'utf-8',
|
|
160
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// gh CLI unavailable or not authenticated — ignore
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
execFileSync('gh', ['pr', 'edit', String(prNumber), '--add-label', 'ready-for-review'], {
|
|
168
|
+
cwd,
|
|
169
|
+
encoding: 'utf-8',
|
|
170
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// Label may not exist yet — ignore
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Build environment variables map from config and CLI options for reviewer
|
|
179
|
+
*/
|
|
180
|
+
export function buildEnvVars(config, options) {
|
|
181
|
+
// Start with base env vars shared by all job types
|
|
182
|
+
const env = buildBaseEnvVars(config, 'reviewer', options.dryRun);
|
|
183
|
+
// Runtime for reviewer (uses NW_REVIEWER_* variables)
|
|
184
|
+
env.NW_REVIEWER_MAX_RUNTIME = String(config.reviewerMaxRuntime);
|
|
185
|
+
env.NW_REVIEWER_MAX_RETRIES = String(config.reviewerMaxRetries);
|
|
186
|
+
env.NW_REVIEWER_RETRY_DELAY = String(config.reviewerRetryDelay);
|
|
187
|
+
env.NW_REVIEWER_MAX_PRS_PER_RUN = String(config.reviewerMaxPrsPerRun);
|
|
188
|
+
env.NW_MIN_REVIEW_SCORE = String(config.minReviewScore);
|
|
189
|
+
env.NW_BRANCH_PATTERNS = config.branchPatterns.join(',');
|
|
190
|
+
env.NW_PRD_DIR = config.prdDir;
|
|
191
|
+
env.NW_CLAUDE_MODEL_ID =
|
|
192
|
+
CLAUDE_MODEL_IDS[config.primaryFallbackModel ?? config.claudeModel ?? 'sonnet'];
|
|
193
|
+
return env;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Apply CLI flag overrides to the config for reviewer
|
|
197
|
+
*/
|
|
198
|
+
export function applyCliOverrides(config, options) {
|
|
199
|
+
const overridden = { ...config };
|
|
200
|
+
if (options.timeout) {
|
|
201
|
+
const timeout = parseInt(options.timeout, 10);
|
|
202
|
+
if (!isNaN(timeout)) {
|
|
203
|
+
overridden.reviewerMaxRuntime = timeout;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (options.provider) {
|
|
207
|
+
// Use _cliProviderOverride to ensure CLI flag takes precedence over jobProviders
|
|
208
|
+
overridden._cliProviderOverride = options.provider;
|
|
209
|
+
}
|
|
210
|
+
return overridden;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Whether a GitHub check entry should be treated as failing/action-required.
|
|
214
|
+
*/
|
|
215
|
+
export function isFailingCheck(check) {
|
|
216
|
+
const bucket = (check.bucket ?? '').toLowerCase();
|
|
217
|
+
const state = (check.state ?? '').toLowerCase();
|
|
218
|
+
const conclusion = (check.conclusion ?? '').toLowerCase();
|
|
219
|
+
return (bucket === 'fail' ||
|
|
220
|
+
bucket === 'cancel' ||
|
|
221
|
+
state === 'failure' ||
|
|
222
|
+
state === 'error' ||
|
|
223
|
+
state === 'cancelled' ||
|
|
224
|
+
conclusion === 'failure' ||
|
|
225
|
+
conclusion === 'error' ||
|
|
226
|
+
conclusion === 'cancelled' ||
|
|
227
|
+
conclusion === 'timed_out' ||
|
|
228
|
+
conclusion === 'action_required' ||
|
|
229
|
+
conclusion === 'startup_failure' ||
|
|
230
|
+
conclusion === 'stale');
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Get a human-readable list of failing checks for a PR.
|
|
234
|
+
*/
|
|
235
|
+
export function getPrFailingChecks(prNumber) {
|
|
236
|
+
try {
|
|
237
|
+
const result = execFileSync('gh', ['pr', 'checks', String(prNumber), '--json', 'name,bucket,state,conclusion'], {
|
|
238
|
+
encoding: 'utf-8',
|
|
239
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
240
|
+
});
|
|
241
|
+
const checks = JSON.parse(result.trim() || '[]');
|
|
242
|
+
const failing = checks
|
|
243
|
+
.filter((check) => isFailingCheck(check))
|
|
244
|
+
.map((check) => `${check.name ?? 'unknown'} [state=${check.state ?? 'unknown'}, conclusion=${check.conclusion ?? 'unknown'}]`);
|
|
245
|
+
if (failing.length > 0) {
|
|
246
|
+
return failing;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
// Fall through to text-mode fallback.
|
|
251
|
+
}
|
|
252
|
+
try {
|
|
253
|
+
const result = execFileSync('gh', ['pr', 'checks', String(prNumber)], {
|
|
254
|
+
encoding: 'utf-8',
|
|
255
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
256
|
+
});
|
|
257
|
+
return result
|
|
258
|
+
.split('\n')
|
|
259
|
+
.map((line) => line.trim())
|
|
260
|
+
.filter((line) => line.length > 0)
|
|
261
|
+
.filter((line) => /fail|error|cancel|timed[_ -]?out|action_required|startup_failure|stale/i.test(line));
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
return [];
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Get open PRs that need work (matching branch patterns)
|
|
269
|
+
*/
|
|
270
|
+
function getOpenPrsNeedingWork(branchPatterns) {
|
|
271
|
+
try {
|
|
272
|
+
// Build args array for safe shell execution
|
|
273
|
+
const args = ['pr', 'list', '--state', 'open', '--json', 'number,title,headRefName'];
|
|
274
|
+
for (const pattern of branchPatterns) {
|
|
275
|
+
args.push('--head', pattern);
|
|
276
|
+
}
|
|
277
|
+
// Get open PRs as JSON using execFileSync for safe argument handling
|
|
278
|
+
const result = execFileSync('gh', args, {
|
|
279
|
+
encoding: 'utf-8',
|
|
280
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
281
|
+
});
|
|
282
|
+
const prs = JSON.parse(result.trim() || '[]');
|
|
283
|
+
return prs.map((pr) => ({
|
|
284
|
+
number: pr.number,
|
|
285
|
+
title: pr.title,
|
|
286
|
+
branch: pr.headRefName,
|
|
287
|
+
}));
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
// gh CLI not available or not authenticated
|
|
291
|
+
return [];
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Register the review command with the program
|
|
296
|
+
*/
|
|
297
|
+
export function reviewCommand(program) {
|
|
298
|
+
program
|
|
299
|
+
.command('review')
|
|
300
|
+
.description('Run PR reviewer now')
|
|
301
|
+
.option('--dry-run', 'Show what would be executed without running')
|
|
302
|
+
.option('--timeout <seconds>', 'Override max runtime in seconds for reviewer')
|
|
303
|
+
.option('--provider <string>', 'AI provider to use (claude or codex)')
|
|
304
|
+
.action(async (options) => {
|
|
305
|
+
// Get the project directory (current working directory)
|
|
306
|
+
const projectDir = process.cwd();
|
|
307
|
+
// Load config from file and environment
|
|
308
|
+
let config = loadConfig(projectDir);
|
|
309
|
+
// Apply CLI flag overrides
|
|
310
|
+
config = applyCliOverrides(config, options);
|
|
311
|
+
if (!config.reviewerEnabled && !options.dryRun) {
|
|
312
|
+
info('Reviewer is disabled in config; skipping review.');
|
|
313
|
+
process.exit(0);
|
|
314
|
+
}
|
|
315
|
+
// Build environment variables
|
|
316
|
+
const envVars = buildEnvVars(config, options);
|
|
317
|
+
// Get the script path
|
|
318
|
+
const scriptPath = getScriptPath('night-watch-pr-reviewer-cron.sh');
|
|
319
|
+
if (options.dryRun) {
|
|
320
|
+
header('Dry Run: PR Reviewer');
|
|
321
|
+
// Resolve reviewer-specific provider
|
|
322
|
+
const reviewerProvider = resolveJobProvider(config, 'reviewer');
|
|
323
|
+
// Configuration section with table
|
|
324
|
+
header('Configuration');
|
|
325
|
+
const configTable = createTable({ head: ['Setting', 'Value'] });
|
|
326
|
+
configTable.push(['Provider', reviewerProvider]);
|
|
327
|
+
configTable.push(['Provider CLI', PROVIDER_COMMANDS[reviewerProvider]]);
|
|
328
|
+
configTable.push([
|
|
329
|
+
'Max Runtime',
|
|
330
|
+
`${config.reviewerMaxRuntime}s (${Math.floor(config.reviewerMaxRuntime / 60)}min)`,
|
|
331
|
+
]);
|
|
332
|
+
configTable.push(['Min Review Score', `${config.minReviewScore}/100`]);
|
|
333
|
+
configTable.push(['Branch Patterns', config.branchPatterns.join(', ')]);
|
|
334
|
+
configTable.push(['Max Retry Attempts', String(config.reviewerMaxRetries)]);
|
|
335
|
+
configTable.push(['Retry Delay', `${config.reviewerRetryDelay}s`]);
|
|
336
|
+
configTable.push([
|
|
337
|
+
'Max PRs Per Run',
|
|
338
|
+
config.reviewerMaxPrsPerRun === 0 ? 'Unlimited' : String(config.reviewerMaxPrsPerRun),
|
|
339
|
+
]);
|
|
340
|
+
console.log(configTable.toString());
|
|
341
|
+
// Check for open PRs needing work
|
|
342
|
+
header('Open PRs Needing Work');
|
|
343
|
+
const openPrs = getOpenPrsNeedingWork(config.branchPatterns);
|
|
344
|
+
if (openPrs.length === 0) {
|
|
345
|
+
dim(' (no open PRs matching branch patterns)');
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
for (const pr of openPrs) {
|
|
349
|
+
info(`#${pr.number}: ${pr.title}`);
|
|
350
|
+
dim(` Branch: ${pr.branch}`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// Provider invocation command
|
|
354
|
+
header('Provider Invocation');
|
|
355
|
+
if (reviewerProvider === 'claude') {
|
|
356
|
+
dim(' claude -p "/night-watch-pr-reviewer" --dangerously-skip-permissions');
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
dim(' codex exec --yolo "/night-watch-pr-reviewer"');
|
|
360
|
+
}
|
|
361
|
+
// Environment variables
|
|
362
|
+
header('Environment Variables');
|
|
363
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
364
|
+
dim(` ${key}=${value}`);
|
|
365
|
+
}
|
|
366
|
+
// Full command that would be executed
|
|
367
|
+
header('Command');
|
|
368
|
+
dim(` bash ${scriptPath} ${projectDir}`);
|
|
369
|
+
console.log();
|
|
370
|
+
process.exit(0);
|
|
371
|
+
}
|
|
372
|
+
// Preflight visibility: show currently failing checks before the fixer runs.
|
|
373
|
+
const preflightOpenPrs = getOpenPrsNeedingWork(config.branchPatterns);
|
|
374
|
+
const preflightFailures = preflightOpenPrs
|
|
375
|
+
.map((pr) => ({
|
|
376
|
+
prNumber: pr.number,
|
|
377
|
+
title: pr.title,
|
|
378
|
+
failingChecks: getPrFailingChecks(pr.number),
|
|
379
|
+
}))
|
|
380
|
+
.filter((entry) => entry.failingChecks.length > 0);
|
|
381
|
+
if (preflightFailures.length > 0) {
|
|
382
|
+
header('Preflight Failing Checks');
|
|
383
|
+
for (const entry of preflightFailures) {
|
|
384
|
+
info(`#${entry.prNumber}: ${entry.title}`);
|
|
385
|
+
for (const check of entry.failingChecks) {
|
|
386
|
+
dim(` ${check}`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
// Execute the script with spinner
|
|
391
|
+
const spinner = createSpinner('Running PR reviewer...');
|
|
392
|
+
spinner.start();
|
|
393
|
+
try {
|
|
394
|
+
await maybeApplyCronSchedulingDelay(config, 'reviewer', projectDir);
|
|
395
|
+
const { exitCode, stdout, stderr } = await executeScriptWithOutput(scriptPath, [projectDir], envVars);
|
|
396
|
+
const scriptResult = parseScriptResult(`${stdout}\n${stderr}`);
|
|
397
|
+
if (exitCode === 0) {
|
|
398
|
+
if (scriptResult?.status === 'queued') {
|
|
399
|
+
spinner.succeed('PR reviewer queued — another job is currently running');
|
|
400
|
+
}
|
|
401
|
+
else if (scriptResult?.status?.startsWith('skip_')) {
|
|
402
|
+
spinner.succeed('PR reviewer completed (no PRs needed review)');
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
405
|
+
spinner.succeed('PR reviewer completed successfully');
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
spinner.fail(`PR reviewer exited with code ${exitCode}`);
|
|
410
|
+
}
|
|
411
|
+
// Send notifications (fire-and-forget, failures do not affect exit code)
|
|
412
|
+
if (!options.dryRun) {
|
|
413
|
+
const shouldNotifyCompletion = shouldSendReviewCompletionNotification(exitCode, scriptResult?.status);
|
|
414
|
+
if (!shouldNotifyCompletion) {
|
|
415
|
+
info('Skipping review completion notification (review did not complete successfully)');
|
|
416
|
+
}
|
|
417
|
+
// Enrich with PR details (graceful — null if gh fails)
|
|
418
|
+
let fallbackPrDetails = null;
|
|
419
|
+
if (shouldNotifyCompletion) {
|
|
420
|
+
const reviewedPrNumbers = parseReviewedPrNumbers(scriptResult?.data.prs);
|
|
421
|
+
const firstReviewedPrNumber = reviewedPrNumbers[0];
|
|
422
|
+
if (firstReviewedPrNumber !== undefined) {
|
|
423
|
+
fallbackPrDetails = fetchPrDetailsByNumber(firstReviewedPrNumber, projectDir);
|
|
424
|
+
}
|
|
425
|
+
if (!fallbackPrDetails) {
|
|
426
|
+
fallbackPrDetails = fetchReviewedPrDetails(config.branchPatterns, projectDir);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (shouldNotifyCompletion) {
|
|
430
|
+
// Extract retry attempts from script result
|
|
431
|
+
const attempts = parseRetryAttempts(scriptResult?.data.attempts);
|
|
432
|
+
const finalScore = parseFinalReviewScore(scriptResult?.data.final_score);
|
|
433
|
+
const legacyNoChangesNeeded = scriptResult?.data.no_changes_needed === '1';
|
|
434
|
+
const reviewedPrNumbers = parseReviewedPrNumbers(scriptResult?.data.prs);
|
|
435
|
+
const noChangesPrNumbers = parseReviewedPrNumbers(scriptResult?.data.no_changes_prs);
|
|
436
|
+
const fallbackPrNumber = fallbackPrDetails?.number;
|
|
437
|
+
let notificationPrNumbers = reviewedPrNumbers;
|
|
438
|
+
if (notificationPrNumbers.length === 0 && fallbackPrNumber !== undefined) {
|
|
439
|
+
notificationPrNumbers = [fallbackPrNumber];
|
|
440
|
+
}
|
|
441
|
+
const notificationTargets = buildReviewNotificationTargets(notificationPrNumbers, noChangesPrNumbers, legacyNoChangesNeeded);
|
|
442
|
+
if (notificationTargets.length === 0) {
|
|
443
|
+
const reviewEvent = legacyNoChangesNeeded
|
|
444
|
+
? 'review_ready_for_human'
|
|
445
|
+
: 'review_completed';
|
|
446
|
+
await sendNotifications(config, {
|
|
447
|
+
event: reviewEvent,
|
|
448
|
+
projectName: path.basename(projectDir),
|
|
449
|
+
exitCode,
|
|
450
|
+
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
|
|
451
|
+
prUrl: fallbackPrDetails?.url,
|
|
452
|
+
prTitle: fallbackPrDetails?.title,
|
|
453
|
+
prBody: fallbackPrDetails?.body,
|
|
454
|
+
prNumber: fallbackPrDetails?.number,
|
|
455
|
+
filesChanged: fallbackPrDetails?.changedFiles,
|
|
456
|
+
additions: fallbackPrDetails?.additions,
|
|
457
|
+
deletions: fallbackPrDetails?.deletions,
|
|
458
|
+
attempts,
|
|
459
|
+
finalScore,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
for (const target of notificationTargets) {
|
|
464
|
+
const prDetails = fallbackPrDetails?.number === target.prNumber
|
|
465
|
+
? fallbackPrDetails
|
|
466
|
+
: fetchPrDetailsByNumber(target.prNumber, projectDir);
|
|
467
|
+
if (target.noChangesNeeded && prDetails?.number) {
|
|
468
|
+
postReadyForHumanReviewComment(prDetails.number, finalScore, projectDir);
|
|
469
|
+
}
|
|
470
|
+
const reviewEvent = target.noChangesNeeded
|
|
471
|
+
? 'review_ready_for_human'
|
|
472
|
+
: 'review_completed';
|
|
473
|
+
await sendNotifications(config, {
|
|
474
|
+
event: reviewEvent,
|
|
475
|
+
projectName: path.basename(projectDir),
|
|
476
|
+
exitCode,
|
|
477
|
+
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
|
|
478
|
+
prUrl: prDetails?.url,
|
|
479
|
+
prTitle: prDetails?.title,
|
|
480
|
+
prBody: prDetails?.body,
|
|
481
|
+
prNumber: prDetails?.number ?? target.prNumber,
|
|
482
|
+
filesChanged: prDetails?.changedFiles,
|
|
483
|
+
additions: prDetails?.additions,
|
|
484
|
+
deletions: prDetails?.deletions,
|
|
485
|
+
attempts,
|
|
486
|
+
finalScore,
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
const autoMergedPrNumbers = parseAutoMergedPrNumbers(scriptResult?.data.auto_merged);
|
|
492
|
+
if (autoMergedPrNumbers.length > 0) {
|
|
493
|
+
const autoMergedPrNumber = autoMergedPrNumbers[0];
|
|
494
|
+
const autoMergedPrDetails = fetchPrDetailsByNumber(autoMergedPrNumber, projectDir);
|
|
495
|
+
const _mergeCtx = {
|
|
496
|
+
event: 'pr_auto_merged',
|
|
497
|
+
projectName: path.basename(projectDir),
|
|
498
|
+
exitCode,
|
|
499
|
+
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
|
|
500
|
+
prNumber: autoMergedPrDetails?.number ?? autoMergedPrNumber,
|
|
501
|
+
prUrl: autoMergedPrDetails?.url,
|
|
502
|
+
prTitle: autoMergedPrDetails?.title,
|
|
503
|
+
prBody: autoMergedPrDetails?.body,
|
|
504
|
+
filesChanged: autoMergedPrDetails?.changedFiles,
|
|
505
|
+
additions: autoMergedPrDetails?.additions,
|
|
506
|
+
deletions: autoMergedPrDetails?.deletions,
|
|
507
|
+
};
|
|
508
|
+
await sendNotifications(config, _mergeCtx);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
process.exit(exitCode);
|
|
512
|
+
}
|
|
513
|
+
catch (err) {
|
|
514
|
+
spinner.fail('Failed to execute review command');
|
|
515
|
+
uiError(`${err instanceof Error ? err.message : String(err)}`);
|
|
516
|
+
process.exit(1);
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
//# sourceMappingURL=review.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review.js","sourceRoot":"","sources":["../../src/commands/review.ts"],"names":[],"mappings":"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"}
|
|
@@ -79,6 +79,8 @@ else
|
|
|
79
79
|
fi
|
|
80
80
|
|
|
81
81
|
SCRIPT_TYPE="reviewer"
|
|
82
|
+
READY_FOR_REVIEW_LABEL="${NW_READY_FOR_REVIEW_LABEL:-ready-for-review}"
|
|
83
|
+
READY_FOR_REVIEW_MARKER_NAME="night-watch-ready-for-review"
|
|
82
84
|
|
|
83
85
|
emit_result() {
|
|
84
86
|
local status="${1:?status required}"
|
|
@@ -98,6 +100,80 @@ extract_review_score_from_text() {
|
|
|
98
100
|
| grep -oP '\d+(?=/100)' || echo ""
|
|
99
101
|
}
|
|
100
102
|
|
|
103
|
+
build_ready_for_review_marker() {
|
|
104
|
+
local head_sha="${1:-}"
|
|
105
|
+
[ -z "${head_sha}" ] && return 1
|
|
106
|
+
printf '<!-- %s headRefOid:%s -->' "${READY_FOR_REVIEW_MARKER_NAME}" "${head_sha}"
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
has_ready_for_human_review_marker() {
|
|
110
|
+
local comments_text="${1:-}"
|
|
111
|
+
local head_sha="${2:-}"
|
|
112
|
+
local marker=""
|
|
113
|
+
|
|
114
|
+
marker=$(build_ready_for_review_marker "${head_sha}" || true)
|
|
115
|
+
[ -z "${marker}" ] && return 1
|
|
116
|
+
|
|
117
|
+
printf '%s\n' "${comments_text}" | grep -Fq "${marker}"
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
get_pr_comments() {
|
|
121
|
+
local pr_number="${1:?PR number required}"
|
|
122
|
+
|
|
123
|
+
{
|
|
124
|
+
gh pr view "${pr_number}" --json comments --jq '.comments[].body' 2>/dev/null || true
|
|
125
|
+
if [ -n "${REPO:-}" ]; then
|
|
126
|
+
gh api "repos/${REPO}/issues/${pr_number}/comments" --jq '.[].body' 2>/dev/null || true
|
|
127
|
+
fi
|
|
128
|
+
} | awk '!seen[$0]++'
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
get_pr_head_ref_oid() {
|
|
132
|
+
local pr_number="${1:?PR number required}"
|
|
133
|
+
gh pr view "${pr_number}" --json headRefOid --jq '.headRefOid' 2>/dev/null || echo ""
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
clear_ready_for_human_review_label() {
|
|
137
|
+
local pr_number="${1:?PR number required}"
|
|
138
|
+
gh pr edit "${pr_number}" --remove-label "${READY_FOR_REVIEW_LABEL}" 2>/dev/null || true
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
ensure_ready_for_human_review_comment() {
|
|
142
|
+
local pr_number="${1:?PR number required}"
|
|
143
|
+
local final_score="${2:-}"
|
|
144
|
+
local head_sha="${3:-}"
|
|
145
|
+
local comments_text=""
|
|
146
|
+
local marker=""
|
|
147
|
+
local score_note=""
|
|
148
|
+
local short_sha=""
|
|
149
|
+
local body=""
|
|
150
|
+
|
|
151
|
+
[ -z "${head_sha}" ] && return 0
|
|
152
|
+
|
|
153
|
+
comments_text=$(get_pr_comments "${pr_number}")
|
|
154
|
+
if has_ready_for_human_review_marker "${comments_text}" "${head_sha}"; then
|
|
155
|
+
gh pr edit "${pr_number}" --add-label "${READY_FOR_REVIEW_LABEL}" 2>/dev/null || true
|
|
156
|
+
return 0
|
|
157
|
+
fi
|
|
158
|
+
|
|
159
|
+
marker=$(build_ready_for_review_marker "${head_sha}" || true)
|
|
160
|
+
short_sha=$(printf '%s' "${head_sha}" | cut -c1-12)
|
|
161
|
+
if [ -n "${final_score}" ]; then
|
|
162
|
+
score_note=" (score: ${final_score}/100)"
|
|
163
|
+
fi
|
|
164
|
+
|
|
165
|
+
body="${marker}
|
|
166
|
+
|
|
167
|
+
## ✅ Ready for Human Review
|
|
168
|
+
|
|
169
|
+
Night Watch reviewed this PR${score_note} at commit \`${short_sha}\` and found no automated fixes to apply for the current head.
|
|
170
|
+
|
|
171
|
+
This PR is ready for human review and merge."
|
|
172
|
+
|
|
173
|
+
gh pr comment "${pr_number}" --body "${body}" 2>/dev/null || true
|
|
174
|
+
gh pr edit "${pr_number}" --add-label "${READY_FOR_REVIEW_LABEL}" 2>/dev/null || true
|
|
175
|
+
}
|
|
176
|
+
|
|
101
177
|
# ── Global Job Queue Gate ────────────────────────────────────────────────────
|
|
102
178
|
# Atomically claim a DB slot or enqueue for later dispatch — no flock needed.
|
|
103
179
|
if [ "${NW_QUEUE_ENABLED:-0}" = "1" ]; then
|
|
@@ -399,14 +475,7 @@ build_prd_context_prompt() {
|
|
|
399
475
|
get_pr_score() {
|
|
400
476
|
local pr_number="${1:?PR number required}"
|
|
401
477
|
local all_comments
|
|
402
|
-
all_comments=$(
|
|
403
|
-
{
|
|
404
|
-
gh pr view "${pr_number}" --json comments --jq '.comments[].body' 2>/dev/null || true
|
|
405
|
-
if [ -n "${REPO:-}" ]; then
|
|
406
|
-
gh api "repos/${REPO}/issues/${pr_number}/comments" --jq '.[].body' 2>/dev/null || true
|
|
407
|
-
fi
|
|
408
|
-
} | awk '!seen[$0]++'
|
|
409
|
-
)
|
|
478
|
+
all_comments=$(get_pr_comments "${pr_number}")
|
|
410
479
|
extract_review_score_from_text "${all_comments}"
|
|
411
480
|
}
|
|
412
481
|
|
|
@@ -612,8 +681,14 @@ fi
|
|
|
612
681
|
NEEDS_WORK=0
|
|
613
682
|
REPO=$(gh repo view --json nameWithOwner --jq '.nameWithOwner' 2>/dev/null || echo "")
|
|
614
683
|
PRS_NEEDING_WORK=""
|
|
684
|
+
SKIPPED_ALREADY_REVIEWED_CURRENT_HEAD=0
|
|
615
685
|
|
|
616
686
|
while IFS=$'\t' read -r pr_number pr_branch pr_labels; do
|
|
687
|
+
local_ready_for_review_label_present=0
|
|
688
|
+
current_head_sha=""
|
|
689
|
+
all_comments=""
|
|
690
|
+
latest_score=""
|
|
691
|
+
|
|
617
692
|
if [ -z "${pr_number}" ] || [ -z "${pr_branch}" ]; then
|
|
618
693
|
continue
|
|
619
694
|
fi
|
|
@@ -636,17 +711,58 @@ while IFS=$'\t' read -r pr_number pr_branch pr_labels; do
|
|
|
636
711
|
continue
|
|
637
712
|
fi
|
|
638
713
|
|
|
714
|
+
if csv_has_label "${pr_labels:-}" "${READY_FOR_REVIEW_LABEL}"; then
|
|
715
|
+
local_ready_for_review_label_present=1
|
|
716
|
+
fi
|
|
717
|
+
|
|
639
718
|
# Merge-conflict signal: this PR needs action even if CI and score look fine.
|
|
640
719
|
MERGE_STATE=$(gh pr view "${pr_number}" --json mergeStateStatus --jq '.mergeStateStatus' 2>/dev/null || echo "")
|
|
641
720
|
if [ "${MERGE_STATE}" = "DIRTY" ] || [ "${MERGE_STATE}" = "CONFLICTING" ]; then
|
|
721
|
+
if [ "${local_ready_for_review_label_present}" -eq 1 ]; then
|
|
722
|
+
log "INFO: PR #${pr_number} (${pr_branch}) is actionable again; removing stale ${READY_FOR_REVIEW_LABEL} label"
|
|
723
|
+
clear_ready_for_human_review_label "${pr_number}"
|
|
724
|
+
fi
|
|
642
725
|
log "INFO: PR #${pr_number} (${pr_branch}) has merge conflicts (${MERGE_STATE})"
|
|
643
726
|
NEEDS_WORK=1
|
|
644
727
|
PRS_NEEDING_WORK="${PRS_NEEDING_WORK} #${pr_number}"
|
|
645
728
|
continue
|
|
646
729
|
fi
|
|
647
730
|
|
|
731
|
+
current_head_sha=$(get_pr_head_ref_oid "${pr_number}")
|
|
732
|
+
all_comments=$(get_pr_comments "${pr_number}")
|
|
733
|
+
latest_score=$(extract_review_score_from_text "${all_comments}")
|
|
734
|
+
if [ -z "${latest_score}" ]; then
|
|
735
|
+
if [ "${local_ready_for_review_label_present}" -eq 1 ]; then
|
|
736
|
+
log "INFO: PR #${pr_number} (${pr_branch}) needs a fresh review; removing stale ${READY_FOR_REVIEW_LABEL} label"
|
|
737
|
+
clear_ready_for_human_review_label "${pr_number}"
|
|
738
|
+
fi
|
|
739
|
+
log "INFO: PR #${pr_number} (${pr_branch}) has no review score yet — needs initial review"
|
|
740
|
+
NEEDS_WORK=1
|
|
741
|
+
PRS_NEEDING_WORK="${PRS_NEEDING_WORK} #${pr_number}"
|
|
742
|
+
continue
|
|
743
|
+
elif [ "${latest_score}" -lt "${MIN_REVIEW_SCORE}" ]; then
|
|
744
|
+
if [ "${local_ready_for_review_label_present}" -eq 1 ]; then
|
|
745
|
+
log "INFO: PR #${pr_number} (${pr_branch}) fell below review threshold; removing stale ${READY_FOR_REVIEW_LABEL} label"
|
|
746
|
+
clear_ready_for_human_review_label "${pr_number}"
|
|
747
|
+
fi
|
|
748
|
+
log "INFO: PR #${pr_number} (${pr_branch}) has review score ${latest_score}/100 (threshold: ${MIN_REVIEW_SCORE})"
|
|
749
|
+
NEEDS_WORK=1
|
|
750
|
+
PRS_NEEDING_WORK="${PRS_NEEDING_WORK} #${pr_number}"
|
|
751
|
+
continue
|
|
752
|
+
fi
|
|
753
|
+
|
|
754
|
+
if has_ready_for_human_review_marker "${all_comments}" "${current_head_sha}"; then
|
|
755
|
+
SKIPPED_ALREADY_REVIEWED_CURRENT_HEAD=1
|
|
756
|
+
log "INFO: PR #${pr_number} (${pr_branch}) is already marked ready for human review at head ${current_head_sha:0:12}; skipping repeat automated review"
|
|
757
|
+
continue
|
|
758
|
+
fi
|
|
759
|
+
|
|
648
760
|
FAILED_CHECKS=$(get_pr_failed_ci_checks "${pr_number}")
|
|
649
761
|
if [ "${FAILED_CHECKS}" -gt 0 ]; then
|
|
762
|
+
if [ "${local_ready_for_review_label_present}" -eq 1 ]; then
|
|
763
|
+
log "INFO: PR #${pr_number} (${pr_branch}) is actionable again; removing stale ${READY_FOR_REVIEW_LABEL} label"
|
|
764
|
+
clear_ready_for_human_review_label "${pr_number}"
|
|
765
|
+
fi
|
|
650
766
|
FAILED_SUMMARY=$(get_pr_failed_ci_summary "${pr_number}")
|
|
651
767
|
if [ -n "${FAILED_SUMMARY}" ]; then
|
|
652
768
|
log "INFO: PR #${pr_number} (${pr_branch}) has ${FAILED_CHECKS} failed CI check(s): ${FAILED_SUMMARY}"
|
|
@@ -655,26 +771,6 @@ while IFS=$'\t' read -r pr_number pr_branch pr_labels; do
|
|
|
655
771
|
fi
|
|
656
772
|
NEEDS_WORK=1
|
|
657
773
|
PRS_NEEDING_WORK="${PRS_NEEDING_WORK} #${pr_number}"
|
|
658
|
-
continue
|
|
659
|
-
fi
|
|
660
|
-
|
|
661
|
-
ALL_COMMENTS=$(
|
|
662
|
-
{
|
|
663
|
-
gh pr view "${pr_number}" --json comments --jq '.comments[].body' 2>/dev/null || true
|
|
664
|
-
if [ -n "${REPO}" ]; then
|
|
665
|
-
gh api "repos/${REPO}/issues/${pr_number}/comments" --jq '.[].body' 2>/dev/null || true
|
|
666
|
-
fi
|
|
667
|
-
} | awk '!seen[$0]++'
|
|
668
|
-
)
|
|
669
|
-
LATEST_SCORE=$(extract_review_score_from_text "${ALL_COMMENTS}")
|
|
670
|
-
if [ -z "${LATEST_SCORE}" ]; then
|
|
671
|
-
log "INFO: PR #${pr_number} (${pr_branch}) has no review score yet — needs initial review"
|
|
672
|
-
NEEDS_WORK=1
|
|
673
|
-
PRS_NEEDING_WORK="${PRS_NEEDING_WORK} #${pr_number}"
|
|
674
|
-
elif [ "${LATEST_SCORE}" -lt "${MIN_REVIEW_SCORE}" ]; then
|
|
675
|
-
log "INFO: PR #${pr_number} (${pr_branch}) has review score ${LATEST_SCORE}/100 (threshold: ${MIN_REVIEW_SCORE})"
|
|
676
|
-
NEEDS_WORK=1
|
|
677
|
-
PRS_NEEDING_WORK="${PRS_NEEDING_WORK} #${pr_number}"
|
|
678
774
|
fi
|
|
679
775
|
done < <(
|
|
680
776
|
gh pr list --state open --json number,headRefName,labels \
|
|
@@ -682,14 +778,25 @@ done < <(
|
|
|
682
778
|
)
|
|
683
779
|
|
|
684
780
|
if [ "${NEEDS_WORK}" -eq 0 ]; then
|
|
685
|
-
|
|
781
|
+
if [ "${SKIPPED_ALREADY_REVIEWED_CURRENT_HEAD}" -eq 1 ]; then
|
|
782
|
+
log "SKIP: All ${OPEN_PRS} open PR(s) already pass review threshold or have already been reviewed for their current head"
|
|
686
783
|
|
|
687
|
-
|
|
688
|
-
|
|
784
|
+
if [ "${WORKER_MODE}" != "1" ]; then
|
|
785
|
+
send_telegram_status_message "🔍 Night Watch Reviewer: nothing actionable" "Project: ${PROJECT_NAME}
|
|
786
|
+
Provider (model): ${PROVIDER_MODEL_DISPLAY}
|
|
787
|
+
Result: all ${OPEN_PRS} matching PRs either already pass review threshold or were already reviewed for their current head."
|
|
788
|
+
fi
|
|
789
|
+
emit_result "skip_no_actionable_prs"
|
|
790
|
+
else
|
|
791
|
+
log "SKIP: All ${OPEN_PRS} open PR(s) have passing CI and review score >= ${MIN_REVIEW_SCORE}"
|
|
792
|
+
|
|
793
|
+
if [ "${WORKER_MODE}" != "1" ]; then
|
|
794
|
+
send_telegram_status_message "🔍 Night Watch Reviewer: nothing to do" "Project: ${PROJECT_NAME}
|
|
689
795
|
Provider (model): ${PROVIDER_MODEL_DISPLAY}
|
|
690
796
|
Result: all ${OPEN_PRS} matching PRs already pass CI and review threshold (${MIN_REVIEW_SCORE})."
|
|
797
|
+
fi
|
|
798
|
+
emit_result "skip_all_passing"
|
|
691
799
|
fi
|
|
692
|
-
emit_result "skip_all_passing"
|
|
693
800
|
exit 0
|
|
694
801
|
fi
|
|
695
802
|
|
|
@@ -1215,6 +1322,7 @@ if [ "${EXIT_CODE}" -eq 0 ] && [ -n "${TARGET_PR}" ] && [ -n "${PR_BRANCH_HEAD_B
|
|
|
1215
1322
|
NO_CHANGES_NEEDED=1
|
|
1216
1323
|
NO_CHANGES_PRS="#${TARGET_PR}"
|
|
1217
1324
|
log "INFO: PR #${TARGET_PR} — reviewer made no commits; marking as ready for human review"
|
|
1325
|
+
ensure_ready_for_human_review_comment "${TARGET_PR}" "${FINAL_SCORE}" "${PR_BRANCH_HEAD_AFTER}"
|
|
1218
1326
|
fi
|
|
1219
1327
|
fi
|
|
1220
1328
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jonit-dev/night-watch-cli",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.12-beta.0",
|
|
4
4
|
"description": "AI agent that implements your specs, opens PRs, and reviews code overnight. Queue GitHub issues or PRDs, wake up to pull requests.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|