@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 body = `## \u2705 Ready for Human Review
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
- reviewedPrNumbers.length > 0 ? reviewedPrNumbers : fallbackPrNumber !== void 0 ? [fallbackPrNumber] : [],
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(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
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,CA2BN;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,CAgQpD"}
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
- log "SKIP: All ${OPEN_PRS} open PR(s) have passing CI and review score >= ${MIN_REVIEW_SCORE}"
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
- if [ "${WORKER_MODE}" != "1" ]; then
688
- send_telegram_status_message "🔍 Night Watch Reviewer: nothing to do" "Project: ${PROJECT_NAME}
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.11",
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": {