@jonit-dev/night-watch-cli 1.8.8-beta.0 → 1.8.8-beta.10
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 +640 -24
- package/dist/cli.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +38 -6
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/install.d.ts +4 -0
- package/dist/commands/install.d.ts.map +1 -1
- package/dist/commands/install.js +25 -0
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/qa.d.ts.map +1 -1
- package/dist/commands/qa.js +5 -0
- package/dist/commands/qa.js.map +1 -1
- package/dist/commands/queue.d.ts.map +1 -1
- package/dist/commands/queue.js +27 -4
- package/dist/commands/queue.js.map +1 -1
- package/dist/commands/resolve.d.ts +26 -0
- package/dist/commands/resolve.d.ts.map +1 -0
- package/dist/commands/resolve.js +186 -0
- package/dist/commands/resolve.js.map +1 -0
- package/dist/commands/review.d.ts +5 -0
- package/dist/commands/review.d.ts.map +1 -1
- package/dist/commands/review.js +18 -5
- package/dist/commands/review.js.map +1 -1
- package/dist/commands/summary.d.ts +14 -0
- package/dist/commands/summary.d.ts.map +1 -0
- package/dist/commands/summary.js +193 -0
- package/dist/commands/summary.js.map +1 -0
- package/dist/commands/uninstall.d.ts.map +1 -1
- package/dist/commands/uninstall.js +14 -2
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/scripts/night-watch-helpers.sh +10 -1
- package/dist/scripts/night-watch-pr-resolver-cron.sh +402 -0
- package/dist/scripts/night-watch-pr-reviewer-cron.sh +22 -5
- package/dist/scripts/night-watch-qa-cron.sh +107 -38
- package/dist/scripts/test-helpers.bats +45 -0
- package/dist/templates/night-watch-pr-reviewer.md +2 -1
- package/dist/templates/pr-reviewer.md +2 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -201,6 +201,35 @@ var init_job_registry = __esm({
|
|
|
201
201
|
maxRuntime: 3600
|
|
202
202
|
}
|
|
203
203
|
},
|
|
204
|
+
{
|
|
205
|
+
id: "pr-resolver",
|
|
206
|
+
name: "PR Conflict Solver",
|
|
207
|
+
description: "Resolves merge conflicts via AI rebase; optionally addresses review comments and labels PRs ready-to-merge",
|
|
208
|
+
cliCommand: "resolve",
|
|
209
|
+
logName: "pr-resolver",
|
|
210
|
+
lockSuffix: "-pr-resolver.lock",
|
|
211
|
+
queuePriority: 35,
|
|
212
|
+
envPrefix: "NW_PR_RESOLVER",
|
|
213
|
+
extraFields: [
|
|
214
|
+
{ name: "branchPatterns", type: "string[]", defaultValue: [] },
|
|
215
|
+
{ name: "maxPrsPerRun", type: "number", defaultValue: 0 },
|
|
216
|
+
{ name: "perPrTimeout", type: "number", defaultValue: 600 },
|
|
217
|
+
{ name: "aiConflictResolution", type: "boolean", defaultValue: true },
|
|
218
|
+
{ name: "aiReviewResolution", type: "boolean", defaultValue: false },
|
|
219
|
+
{ name: "readyLabel", type: "string", defaultValue: "ready-to-merge" }
|
|
220
|
+
],
|
|
221
|
+
defaultConfig: {
|
|
222
|
+
enabled: true,
|
|
223
|
+
schedule: "15 6,14,22 * * *",
|
|
224
|
+
maxRuntime: 3600,
|
|
225
|
+
branchPatterns: [],
|
|
226
|
+
maxPrsPerRun: 0,
|
|
227
|
+
perPrTimeout: 600,
|
|
228
|
+
aiConflictResolution: true,
|
|
229
|
+
aiReviewResolution: false,
|
|
230
|
+
readyLabel: "ready-to-merge"
|
|
231
|
+
}
|
|
232
|
+
},
|
|
204
233
|
{
|
|
205
234
|
id: "slicer",
|
|
206
235
|
name: "Slicer",
|
|
@@ -234,7 +263,8 @@ var init_job_registry = __esm({
|
|
|
234
263
|
defaultValue: "both"
|
|
235
264
|
},
|
|
236
265
|
{ name: "skipLabel", type: "string", defaultValue: "skip-qa" },
|
|
237
|
-
{ name: "autoInstallPlaywright", type: "boolean", defaultValue: true }
|
|
266
|
+
{ name: "autoInstallPlaywright", type: "boolean", defaultValue: true },
|
|
267
|
+
{ name: "validatedLabel", type: "string", defaultValue: "e2e-validated" }
|
|
238
268
|
],
|
|
239
269
|
defaultConfig: {
|
|
240
270
|
enabled: true,
|
|
@@ -243,7 +273,8 @@ var init_job_registry = __esm({
|
|
|
243
273
|
branchPatterns: [],
|
|
244
274
|
artifacts: "both",
|
|
245
275
|
skipLabel: "skip-qa",
|
|
246
|
-
autoInstallPlaywright: true
|
|
276
|
+
autoInstallPlaywright: true,
|
|
277
|
+
validatedLabel: "e2e-validated"
|
|
247
278
|
}
|
|
248
279
|
},
|
|
249
280
|
{
|
|
@@ -308,13 +339,14 @@ function resolveProviderBucketKey(provider, providerEnv) {
|
|
|
308
339
|
return `claude-proxy:${baseUrl}`;
|
|
309
340
|
}
|
|
310
341
|
}
|
|
311
|
-
var DEFAULT_DEFAULT_BRANCH, DEFAULT_PRD_DIR, DEFAULT_MAX_RUNTIME, DEFAULT_REVIEWER_MAX_RUNTIME, DEFAULT_CRON_SCHEDULE, DEFAULT_REVIEWER_SCHEDULE, DEFAULT_CRON_SCHEDULE_OFFSET, DEFAULT_MAX_RETRIES, DEFAULT_REVIEWER_MAX_RETRIES, DEFAULT_REVIEWER_RETRY_DELAY, DEFAULT_REVIEWER_MAX_PRS_PER_RUN, DEFAULT_BRANCH_PREFIX, DEFAULT_BRANCH_PATTERNS, DEFAULT_MIN_REVIEW_SCORE, DEFAULT_MAX_LOG_SIZE, DEFAULT_PROVIDER, DEFAULT_EXECUTOR_ENABLED, DEFAULT_REVIEWER_ENABLED, DEFAULT_PROVIDER_ENV, DEFAULT_FALLBACK_ON_RATE_LIMIT, DEFAULT_CLAUDE_MODEL, DEFAULT_PRIMARY_FALLBACK_MODEL, DEFAULT_SECONDARY_FALLBACK_MODEL, VALID_CLAUDE_MODELS, CLAUDE_MODEL_IDS, DEFAULT_NOTIFICATIONS, DEFAULT_PRD_PRIORITY, DEFAULT_SLICER_SCHEDULE, DEFAULT_SLICER_MAX_RUNTIME, DEFAULT_ROADMAP_SCANNER, DEFAULT_TEMPLATES_DIR, DEFAULT_BOARD_PROVIDER, DEFAULT_LOCAL_BOARD_INFO, DEFAULT_AUTO_MERGE, DEFAULT_AUTO_MERGE_METHOD, VALID_MERGE_METHODS, DEFAULT_QA_ENABLED, DEFAULT_QA_SCHEDULE, DEFAULT_QA_MAX_RUNTIME, DEFAULT_QA_ARTIFACTS, DEFAULT_QA_SKIP_LABEL, DEFAULT_QA_AUTO_INSTALL_PLAYWRIGHT, DEFAULT_QA, QA_LOG_NAME, DEFAULT_AUDIT_ENABLED, DEFAULT_AUDIT_SCHEDULE, DEFAULT_AUDIT_MAX_RUNTIME, DEFAULT_AUDIT, DEFAULT_ANALYTICS_ENABLED, DEFAULT_ANALYTICS_SCHEDULE, DEFAULT_ANALYTICS_MAX_RUNTIME, DEFAULT_ANALYTICS_LOOKBACK_DAYS, DEFAULT_ANALYTICS_TARGET_COLUMN, DEFAULT_ANALYTICS_PROMPT, DEFAULT_ANALYTICS, AUDIT_LOG_NAME, PLANNER_LOG_NAME, ANALYTICS_LOG_NAME, VALID_PROVIDERS, VALID_JOB_TYPES, DEFAULT_JOB_PROVIDERS, DEFAULT_PROVIDER_SCHEDULE_OVERRIDES, BUILT_IN_PRESETS, BUILT_IN_PRESET_IDS, PROVIDER_COMMANDS, CONFIG_FILE_NAME, LOCK_FILE_PREFIX, LOG_DIR, CLAIM_FILE_EXTENSION, EXECUTOR_LOG_NAME, REVIEWER_LOG_NAME, EXECUTOR_LOG_FILE, REVIEWER_LOG_FILE, LOG_FILE_NAMES, GLOBAL_CONFIG_DIR, REGISTRY_FILE_NAME, HISTORY_FILE_NAME, PRD_STATES_FILE_NAME, STATE_DB_FILE_NAME, GLOBAL_NOTIFICATIONS_FILE_NAME, MAX_HISTORY_RECORDS_PER_PRD, DEFAULT_QUEUE_ENABLED, DEFAULT_QUEUE_MODE, DEFAULT_QUEUE_MAX_CONCURRENCY, DEFAULT_QUEUE_MAX_WAIT_TIME, DEFAULT_QUEUE_PRIORITY, DEFAULT_QUEUE, DEFAULT_SCHEDULING_PRIORITY;
|
|
342
|
+
var DEFAULT_DEFAULT_BRANCH, DEFAULT_PRD_DIR, DEFAULT_SUMMARY_WINDOW_HOURS, DEFAULT_MAX_RUNTIME, DEFAULT_REVIEWER_MAX_RUNTIME, DEFAULT_CRON_SCHEDULE, DEFAULT_REVIEWER_SCHEDULE, DEFAULT_CRON_SCHEDULE_OFFSET, DEFAULT_MAX_RETRIES, DEFAULT_REVIEWER_MAX_RETRIES, DEFAULT_REVIEWER_RETRY_DELAY, DEFAULT_REVIEWER_MAX_PRS_PER_RUN, DEFAULT_BRANCH_PREFIX, DEFAULT_BRANCH_PATTERNS, DEFAULT_MIN_REVIEW_SCORE, DEFAULT_MAX_LOG_SIZE, DEFAULT_PROVIDER, DEFAULT_EXECUTOR_ENABLED, DEFAULT_REVIEWER_ENABLED, DEFAULT_PROVIDER_ENV, DEFAULT_FALLBACK_ON_RATE_LIMIT, DEFAULT_CLAUDE_MODEL, DEFAULT_PRIMARY_FALLBACK_MODEL, DEFAULT_SECONDARY_FALLBACK_MODEL, VALID_CLAUDE_MODELS, CLAUDE_MODEL_IDS, DEFAULT_NOTIFICATIONS, DEFAULT_PRD_PRIORITY, DEFAULT_SLICER_SCHEDULE, DEFAULT_SLICER_MAX_RUNTIME, DEFAULT_ROADMAP_SCANNER, DEFAULT_TEMPLATES_DIR, DEFAULT_BOARD_PROVIDER, DEFAULT_LOCAL_BOARD_INFO, DEFAULT_AUTO_MERGE, DEFAULT_AUTO_MERGE_METHOD, VALID_MERGE_METHODS, DEFAULT_QA_ENABLED, DEFAULT_QA_SCHEDULE, DEFAULT_QA_MAX_RUNTIME, DEFAULT_QA_ARTIFACTS, DEFAULT_QA_SKIP_LABEL, DEFAULT_QA_AUTO_INSTALL_PLAYWRIGHT, DEFAULT_QA_VALIDATED_LABEL, DEFAULT_QA, QA_LOG_NAME, DEFAULT_AUDIT_ENABLED, DEFAULT_AUDIT_SCHEDULE, DEFAULT_AUDIT_MAX_RUNTIME, DEFAULT_AUDIT, DEFAULT_ANALYTICS_ENABLED, DEFAULT_ANALYTICS_SCHEDULE, DEFAULT_ANALYTICS_MAX_RUNTIME, DEFAULT_ANALYTICS_LOOKBACK_DAYS, DEFAULT_ANALYTICS_TARGET_COLUMN, DEFAULT_ANALYTICS_PROMPT, DEFAULT_ANALYTICS, DEFAULT_PR_RESOLVER_ENABLED, DEFAULT_PR_RESOLVER_SCHEDULE, DEFAULT_PR_RESOLVER_MAX_RUNTIME, DEFAULT_PR_RESOLVER_MAX_PRS_PER_RUN, DEFAULT_PR_RESOLVER_PER_PR_TIMEOUT, DEFAULT_PR_RESOLVER_AI_CONFLICT_RESOLUTION, DEFAULT_PR_RESOLVER_AI_REVIEW_RESOLUTION, DEFAULT_PR_RESOLVER_READY_LABEL, DEFAULT_PR_RESOLVER, AUDIT_LOG_NAME, PLANNER_LOG_NAME, ANALYTICS_LOG_NAME, PR_RESOLVER_LOG_NAME, VALID_PROVIDERS, VALID_JOB_TYPES, DEFAULT_JOB_PROVIDERS, DEFAULT_PROVIDER_SCHEDULE_OVERRIDES, BUILT_IN_PRESETS, BUILT_IN_PRESET_IDS, PROVIDER_COMMANDS, CONFIG_FILE_NAME, LOCK_FILE_PREFIX, LOG_DIR, CLAIM_FILE_EXTENSION, EXECUTOR_LOG_NAME, REVIEWER_LOG_NAME, EXECUTOR_LOG_FILE, REVIEWER_LOG_FILE, LOG_FILE_NAMES, GLOBAL_CONFIG_DIR, REGISTRY_FILE_NAME, HISTORY_FILE_NAME, PRD_STATES_FILE_NAME, STATE_DB_FILE_NAME, GLOBAL_NOTIFICATIONS_FILE_NAME, MAX_HISTORY_RECORDS_PER_PRD, DEFAULT_QUEUE_ENABLED, DEFAULT_QUEUE_MODE, DEFAULT_QUEUE_MAX_CONCURRENCY, DEFAULT_QUEUE_MAX_WAIT_TIME, DEFAULT_QUEUE_PRIORITY, DEFAULT_QUEUE, DEFAULT_SCHEDULING_PRIORITY;
|
|
312
343
|
var init_constants = __esm({
|
|
313
344
|
"../core/dist/constants.js"() {
|
|
314
345
|
"use strict";
|
|
315
346
|
init_job_registry();
|
|
316
347
|
DEFAULT_DEFAULT_BRANCH = "";
|
|
317
348
|
DEFAULT_PRD_DIR = "docs/prds";
|
|
349
|
+
DEFAULT_SUMMARY_WINDOW_HOURS = 12;
|
|
318
350
|
DEFAULT_MAX_RUNTIME = 7200;
|
|
319
351
|
DEFAULT_REVIEWER_MAX_RUNTIME = 3600;
|
|
320
352
|
DEFAULT_CRON_SCHEDULE = "5 * * * *";
|
|
@@ -369,6 +401,7 @@ var init_constants = __esm({
|
|
|
369
401
|
DEFAULT_QA_ARTIFACTS = "both";
|
|
370
402
|
DEFAULT_QA_SKIP_LABEL = "skip-qa";
|
|
371
403
|
DEFAULT_QA_AUTO_INSTALL_PLAYWRIGHT = true;
|
|
404
|
+
DEFAULT_QA_VALIDATED_LABEL = "e2e-validated";
|
|
372
405
|
DEFAULT_QA = {
|
|
373
406
|
enabled: DEFAULT_QA_ENABLED,
|
|
374
407
|
schedule: DEFAULT_QA_SCHEDULE,
|
|
@@ -376,7 +409,8 @@ var init_constants = __esm({
|
|
|
376
409
|
branchPatterns: [],
|
|
377
410
|
artifacts: DEFAULT_QA_ARTIFACTS,
|
|
378
411
|
skipLabel: DEFAULT_QA_SKIP_LABEL,
|
|
379
|
-
autoInstallPlaywright: DEFAULT_QA_AUTO_INSTALL_PLAYWRIGHT
|
|
412
|
+
autoInstallPlaywright: DEFAULT_QA_AUTO_INSTALL_PLAYWRIGHT,
|
|
413
|
+
validatedLabel: DEFAULT_QA_VALIDATED_LABEL
|
|
380
414
|
};
|
|
381
415
|
QA_LOG_NAME = "night-watch-qa";
|
|
382
416
|
DEFAULT_AUDIT_ENABLED = true;
|
|
@@ -405,9 +439,29 @@ If no issues are warranted, output an empty array: []`;
|
|
|
405
439
|
targetColumn: DEFAULT_ANALYTICS_TARGET_COLUMN,
|
|
406
440
|
analysisPrompt: DEFAULT_ANALYTICS_PROMPT
|
|
407
441
|
};
|
|
442
|
+
DEFAULT_PR_RESOLVER_ENABLED = true;
|
|
443
|
+
DEFAULT_PR_RESOLVER_SCHEDULE = "15 6,14,22 * * *";
|
|
444
|
+
DEFAULT_PR_RESOLVER_MAX_RUNTIME = 3600;
|
|
445
|
+
DEFAULT_PR_RESOLVER_MAX_PRS_PER_RUN = 0;
|
|
446
|
+
DEFAULT_PR_RESOLVER_PER_PR_TIMEOUT = 600;
|
|
447
|
+
DEFAULT_PR_RESOLVER_AI_CONFLICT_RESOLUTION = true;
|
|
448
|
+
DEFAULT_PR_RESOLVER_AI_REVIEW_RESOLUTION = false;
|
|
449
|
+
DEFAULT_PR_RESOLVER_READY_LABEL = "ready-to-merge";
|
|
450
|
+
DEFAULT_PR_RESOLVER = {
|
|
451
|
+
enabled: DEFAULT_PR_RESOLVER_ENABLED,
|
|
452
|
+
schedule: DEFAULT_PR_RESOLVER_SCHEDULE,
|
|
453
|
+
maxRuntime: DEFAULT_PR_RESOLVER_MAX_RUNTIME,
|
|
454
|
+
branchPatterns: [],
|
|
455
|
+
maxPrsPerRun: DEFAULT_PR_RESOLVER_MAX_PRS_PER_RUN,
|
|
456
|
+
perPrTimeout: DEFAULT_PR_RESOLVER_PER_PR_TIMEOUT,
|
|
457
|
+
aiConflictResolution: DEFAULT_PR_RESOLVER_AI_CONFLICT_RESOLUTION,
|
|
458
|
+
aiReviewResolution: DEFAULT_PR_RESOLVER_AI_REVIEW_RESOLUTION,
|
|
459
|
+
readyLabel: DEFAULT_PR_RESOLVER_READY_LABEL
|
|
460
|
+
};
|
|
408
461
|
AUDIT_LOG_NAME = "audit";
|
|
409
462
|
PLANNER_LOG_NAME = "slicer";
|
|
410
463
|
ANALYTICS_LOG_NAME = "analytics";
|
|
464
|
+
PR_RESOLVER_LOG_NAME = "pr-resolver";
|
|
411
465
|
VALID_PROVIDERS = ["claude", "codex"];
|
|
412
466
|
VALID_JOB_TYPES = getValidJobTypes();
|
|
413
467
|
DEFAULT_JOB_PROVIDERS = {};
|
|
@@ -689,6 +743,13 @@ function normalizeConfig(rawConfig) {
|
|
|
689
743
|
normalized[jobId] = normalizeJobConfig(rawJob, jobDef);
|
|
690
744
|
}
|
|
691
745
|
}
|
|
746
|
+
const prResolverDef = getJobDef("pr-resolver");
|
|
747
|
+
if (prResolverDef) {
|
|
748
|
+
const rawJob = readObject(rawConfig.prResolver);
|
|
749
|
+
if (rawJob) {
|
|
750
|
+
normalized.prResolver = normalizeJobConfig(rawJob, prResolverDef);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
692
753
|
const rawJobProviders = readObject(rawConfig.jobProviders);
|
|
693
754
|
if (rawJobProviders) {
|
|
694
755
|
const jobProviders = {};
|
|
@@ -996,6 +1057,17 @@ function buildEnvOverrideConfig(fileConfig) {
|
|
|
996
1057
|
env[jobId] = overrides;
|
|
997
1058
|
}
|
|
998
1059
|
}
|
|
1060
|
+
const prResolverDef = getJobDef("pr-resolver");
|
|
1061
|
+
if (prResolverDef) {
|
|
1062
|
+
const currentBase = (
|
|
1063
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1064
|
+
env.prResolver ?? fileConfig?.prResolver ?? prResolverDef.defaultConfig
|
|
1065
|
+
);
|
|
1066
|
+
const overrides = buildJobEnvOverrides(prResolverDef.envPrefix, currentBase, prResolverDef.extraFields);
|
|
1067
|
+
if (overrides) {
|
|
1068
|
+
env.prResolver = overrides;
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
999
1071
|
const jobProvidersEnv = {};
|
|
1000
1072
|
for (const jobType of VALID_JOB_TYPES) {
|
|
1001
1073
|
const val = process.env[`NW_JOB_PROVIDER_${jobType.toUpperCase()}`];
|
|
@@ -1090,6 +1162,7 @@ function getDefaultConfig() {
|
|
|
1090
1162
|
qa: { ...DEFAULT_QA },
|
|
1091
1163
|
audit: { ...DEFAULT_AUDIT },
|
|
1092
1164
|
analytics: { ...DEFAULT_ANALYTICS },
|
|
1165
|
+
prResolver: { ...DEFAULT_PR_RESOLVER },
|
|
1093
1166
|
jobProviders: { ...DEFAULT_JOB_PROVIDERS },
|
|
1094
1167
|
providerScheduleOverrides: [...DEFAULT_PROVIDER_SCHEDULE_OVERRIDES],
|
|
1095
1168
|
queue: { ...DEFAULT_QUEUE }
|
|
@@ -1157,7 +1230,7 @@ function mergeConfigLayer(base, layer) {
|
|
|
1157
1230
|
...layerQueue,
|
|
1158
1231
|
providerBuckets: { ...baseQueue.providerBuckets, ...layerQueue.providerBuckets }
|
|
1159
1232
|
};
|
|
1160
|
-
} else if (_key === "providerEnv" || _key === "boardProvider" || _key === "qa" || _key === "audit" || _key === "analytics") {
|
|
1233
|
+
} else if (_key === "providerEnv" || _key === "boardProvider" || _key === "qa" || _key === "audit" || _key === "analytics" || _key === "prResolver") {
|
|
1161
1234
|
base[_key] = {
|
|
1162
1235
|
...base[_key],
|
|
1163
1236
|
...value
|
|
@@ -2936,6 +3009,11 @@ var init_labels = __esm({
|
|
|
2936
3009
|
name: "analytics",
|
|
2937
3010
|
description: "Created by the analytics job for Amplitude findings",
|
|
2938
3011
|
color: "1d76db"
|
|
3012
|
+
},
|
|
3013
|
+
{
|
|
3014
|
+
name: "e2e-validated",
|
|
3015
|
+
description: "PR acceptance requirements validated by e2e/integration tests",
|
|
3016
|
+
color: "0e8a16"
|
|
2939
3017
|
}
|
|
2940
3018
|
];
|
|
2941
3019
|
}
|
|
@@ -3509,6 +3587,9 @@ function plannerLockPath(projectDir) {
|
|
|
3509
3587
|
function analyticsLockPath(projectDir) {
|
|
3510
3588
|
return `${LOCK_FILE_PREFIX}analytics-${projectRuntimeKey(projectDir)}.lock`;
|
|
3511
3589
|
}
|
|
3590
|
+
function prResolverLockPath(projectDir) {
|
|
3591
|
+
return `${LOCK_FILE_PREFIX}pr-resolver-${projectRuntimeKey(projectDir)}.lock`;
|
|
3592
|
+
}
|
|
3512
3593
|
function isProcessRunning(pid) {
|
|
3513
3594
|
try {
|
|
3514
3595
|
process.kill(pid, 0);
|
|
@@ -3809,7 +3890,7 @@ async function collectPrInfo(projectDir, branchPatterns) {
|
|
|
3809
3890
|
} catch {
|
|
3810
3891
|
return [];
|
|
3811
3892
|
}
|
|
3812
|
-
const { stdout: output } = await execAsync("gh pr list --state open --json headRefName,number,title,url,statusCheckRollup,reviewDecision", {
|
|
3893
|
+
const { stdout: output } = await execAsync("gh pr list --state open --json headRefName,number,title,url,statusCheckRollup,reviewDecision,labels", {
|
|
3813
3894
|
cwd: projectDir,
|
|
3814
3895
|
encoding: "utf-8"
|
|
3815
3896
|
});
|
|
@@ -3833,7 +3914,8 @@ async function collectPrInfo(projectDir, branchPatterns) {
|
|
|
3833
3914
|
branch: pr.headRefName,
|
|
3834
3915
|
url: pr.url,
|
|
3835
3916
|
ciStatus: deriveCiStatus(pr.statusCheckRollup),
|
|
3836
|
-
reviewScore: deriveReviewScore(pr.reviewDecision)
|
|
3917
|
+
reviewScore: deriveReviewScore(pr.reviewDecision),
|
|
3918
|
+
labels: (pr.labels ?? []).map((l) => l.name)
|
|
3837
3919
|
};
|
|
3838
3920
|
});
|
|
3839
3921
|
} catch {
|
|
@@ -4812,6 +4894,12 @@ function getEventEmoji(event) {
|
|
|
4812
4894
|
return "\u{1F500}";
|
|
4813
4895
|
case "qa_completed":
|
|
4814
4896
|
return "\u{1F9EA}";
|
|
4897
|
+
case "pr_resolver_completed":
|
|
4898
|
+
return "\u{1F527}";
|
|
4899
|
+
case "pr_resolver_conflict_resolved":
|
|
4900
|
+
return "\u2705";
|
|
4901
|
+
case "pr_resolver_failed":
|
|
4902
|
+
return "\u274C";
|
|
4815
4903
|
}
|
|
4816
4904
|
}
|
|
4817
4905
|
function getEventTitle(event) {
|
|
@@ -4836,6 +4924,12 @@ function getEventTitle(event) {
|
|
|
4836
4924
|
return "PR Auto-Merged";
|
|
4837
4925
|
case "qa_completed":
|
|
4838
4926
|
return "QA Completed";
|
|
4927
|
+
case "pr_resolver_completed":
|
|
4928
|
+
return "PR Resolver Completed";
|
|
4929
|
+
case "pr_resolver_conflict_resolved":
|
|
4930
|
+
return "PR Conflict Resolved";
|
|
4931
|
+
case "pr_resolver_failed":
|
|
4932
|
+
return "PR Resolver Failed";
|
|
4839
4933
|
}
|
|
4840
4934
|
}
|
|
4841
4935
|
function getEventColor(event) {
|
|
@@ -4860,6 +4954,12 @@ function getEventColor(event) {
|
|
|
4860
4954
|
return 10181046;
|
|
4861
4955
|
case "qa_completed":
|
|
4862
4956
|
return 3066993;
|
|
4957
|
+
case "pr_resolver_completed":
|
|
4958
|
+
return 51283;
|
|
4959
|
+
case "pr_resolver_conflict_resolved":
|
|
4960
|
+
return 65280;
|
|
4961
|
+
case "pr_resolver_failed":
|
|
4962
|
+
return 16711680;
|
|
4863
4963
|
}
|
|
4864
4964
|
}
|
|
4865
4965
|
function buildDescription(ctx) {
|
|
@@ -6178,6 +6278,15 @@ function isJobTypeEnabled(config, jobType) {
|
|
|
6178
6278
|
return true;
|
|
6179
6279
|
}
|
|
6180
6280
|
}
|
|
6281
|
+
function getJobSchedule(config, jobType) {
|
|
6282
|
+
switch (jobType) {
|
|
6283
|
+
case "reviewer":
|
|
6284
|
+
return config.reviewerSchedule ?? "";
|
|
6285
|
+
case "executor":
|
|
6286
|
+
default:
|
|
6287
|
+
return config.cronSchedule ?? "";
|
|
6288
|
+
}
|
|
6289
|
+
}
|
|
6181
6290
|
function loadPeerConfig(projectPath) {
|
|
6182
6291
|
if (!fs17.existsSync(projectPath) || !fs17.existsSync(path16.join(projectPath, CONFIG_FILE_NAME))) {
|
|
6183
6292
|
return null;
|
|
@@ -6191,11 +6300,15 @@ function loadPeerConfig(projectPath) {
|
|
|
6191
6300
|
function collectSchedulingPeers(currentProjectDir, currentConfig, jobType) {
|
|
6192
6301
|
const peers = /* @__PURE__ */ new Map();
|
|
6193
6302
|
const currentPath = path16.resolve(currentProjectDir);
|
|
6303
|
+
const currentSchedule = getJobSchedule(currentConfig, jobType);
|
|
6194
6304
|
const addPeer = (projectPath, config) => {
|
|
6195
6305
|
const resolvedPath = path16.resolve(projectPath);
|
|
6196
6306
|
if (!isJobTypeEnabled(config, jobType)) {
|
|
6197
6307
|
return;
|
|
6198
6308
|
}
|
|
6309
|
+
if (getJobSchedule(config, jobType) !== currentSchedule) {
|
|
6310
|
+
return;
|
|
6311
|
+
}
|
|
6199
6312
|
peers.set(resolvedPath, {
|
|
6200
6313
|
path: resolvedPath,
|
|
6201
6314
|
config,
|
|
@@ -6532,6 +6645,8 @@ function getLockPathForJob(projectPath, jobType) {
|
|
|
6532
6645
|
return plannerLockPath(projectPath);
|
|
6533
6646
|
case "analytics":
|
|
6534
6647
|
return analyticsLockPath(projectPath);
|
|
6648
|
+
case "pr-resolver":
|
|
6649
|
+
return prResolverLockPath(projectPath);
|
|
6535
6650
|
}
|
|
6536
6651
|
}
|
|
6537
6652
|
function reconcileStaleRunningJobs(db) {
|
|
@@ -6621,6 +6736,15 @@ function getJobPriority(jobType, config) {
|
|
|
6621
6736
|
function enqueueJob(projectPath, projectName, jobType, envVars, config, providerKey) {
|
|
6622
6737
|
const db = openDb();
|
|
6623
6738
|
try {
|
|
6739
|
+
const existing = db.prepare(`SELECT id FROM job_queue WHERE project_path = ? AND job_type = ? AND status IN ('pending', 'dispatched', 'running')`).get(projectPath, jobType);
|
|
6740
|
+
if (existing) {
|
|
6741
|
+
logger2.info("Skipping duplicate enqueue \u2014 active entry already exists", {
|
|
6742
|
+
id: existing.id,
|
|
6743
|
+
jobType,
|
|
6744
|
+
project: projectName
|
|
6745
|
+
});
|
|
6746
|
+
return existing.id;
|
|
6747
|
+
}
|
|
6624
6748
|
const priority = getJobPriority(jobType, config);
|
|
6625
6749
|
const now = Math.floor(Date.now() / 1e3);
|
|
6626
6750
|
const envJson = JSON.stringify(envVars);
|
|
@@ -7086,6 +7210,88 @@ var init_job_queue = __esm({
|
|
|
7086
7210
|
}
|
|
7087
7211
|
});
|
|
7088
7212
|
|
|
7213
|
+
// ../core/dist/utils/summary.js
|
|
7214
|
+
function computeCounts(runs) {
|
|
7215
|
+
const counts = {
|
|
7216
|
+
total: runs.length,
|
|
7217
|
+
succeeded: 0,
|
|
7218
|
+
failed: 0,
|
|
7219
|
+
timedOut: 0,
|
|
7220
|
+
rateLimited: 0,
|
|
7221
|
+
skipped: 0
|
|
7222
|
+
};
|
|
7223
|
+
for (const run2 of runs) {
|
|
7224
|
+
switch (run2.status) {
|
|
7225
|
+
case "success":
|
|
7226
|
+
counts.succeeded++;
|
|
7227
|
+
break;
|
|
7228
|
+
case "failure":
|
|
7229
|
+
counts.failed++;
|
|
7230
|
+
break;
|
|
7231
|
+
case "timeout":
|
|
7232
|
+
counts.timedOut++;
|
|
7233
|
+
break;
|
|
7234
|
+
case "rate_limited":
|
|
7235
|
+
counts.rateLimited++;
|
|
7236
|
+
break;
|
|
7237
|
+
case "skipped":
|
|
7238
|
+
counts.skipped++;
|
|
7239
|
+
break;
|
|
7240
|
+
}
|
|
7241
|
+
}
|
|
7242
|
+
return counts;
|
|
7243
|
+
}
|
|
7244
|
+
function buildActionItems(counts, prs, pendingItems) {
|
|
7245
|
+
const items = [];
|
|
7246
|
+
if (counts.failed > 0) {
|
|
7247
|
+
items.push(`${counts.failed} failed job${counts.failed > 1 ? "s" : ""} \u2014 run \`night-watch logs\` to investigate`);
|
|
7248
|
+
}
|
|
7249
|
+
if (counts.timedOut > 0) {
|
|
7250
|
+
items.push(`${counts.timedOut} timed out job${counts.timedOut > 1 ? "s" : ""} \u2014 check logs for details`);
|
|
7251
|
+
}
|
|
7252
|
+
if (counts.rateLimited > 0) {
|
|
7253
|
+
items.push(`${counts.rateLimited} rate-limited job${counts.rateLimited > 1 ? "s" : ""} \u2014 consider adjusting schedule`);
|
|
7254
|
+
}
|
|
7255
|
+
const failingCiPrs = prs.filter((pr) => pr.ciStatus === "fail");
|
|
7256
|
+
for (const pr of failingCiPrs) {
|
|
7257
|
+
items.push(`PR #${pr.number} has failing CI \u2014 check ${pr.url}`);
|
|
7258
|
+
}
|
|
7259
|
+
const readyToMergePrs = prs.filter((pr) => pr.labels.includes("ready-to-merge"));
|
|
7260
|
+
if (readyToMergePrs.length > 0) {
|
|
7261
|
+
items.push(`${readyToMergePrs.length} PR${readyToMergePrs.length > 1 ? "s" : ""} marked ready-to-merge \u2014 review and merge`);
|
|
7262
|
+
}
|
|
7263
|
+
if (pendingItems.length > 0) {
|
|
7264
|
+
const jobTypes = [...new Set(pendingItems.map((item) => item.jobType))];
|
|
7265
|
+
items.push(`${pendingItems.length} job${pendingItems.length > 1 ? "s" : ""} pending in queue (${jobTypes.join(", ")})`);
|
|
7266
|
+
}
|
|
7267
|
+
return items;
|
|
7268
|
+
}
|
|
7269
|
+
async function getSummaryData(projectDir, windowHours = DEFAULT_SUMMARY_WINDOW_HOURS, branchPatterns = []) {
|
|
7270
|
+
const analytics = getJobRunsAnalytics(windowHours);
|
|
7271
|
+
const jobRuns = analytics.recentRuns;
|
|
7272
|
+
const counts = computeCounts(jobRuns);
|
|
7273
|
+
const openPrs = await collectPrInfo(projectDir, branchPatterns);
|
|
7274
|
+
const queueStatus = getQueueStatus();
|
|
7275
|
+
const pendingQueueItems = queueStatus.items.filter((item) => item.status === "pending");
|
|
7276
|
+
const actionItems = buildActionItems(counts, openPrs, pendingQueueItems);
|
|
7277
|
+
return {
|
|
7278
|
+
windowHours,
|
|
7279
|
+
jobRuns,
|
|
7280
|
+
counts,
|
|
7281
|
+
openPrs,
|
|
7282
|
+
pendingQueueItems,
|
|
7283
|
+
actionItems
|
|
7284
|
+
};
|
|
7285
|
+
}
|
|
7286
|
+
var init_summary = __esm({
|
|
7287
|
+
"../core/dist/utils/summary.js"() {
|
|
7288
|
+
"use strict";
|
|
7289
|
+
init_constants();
|
|
7290
|
+
init_status_data();
|
|
7291
|
+
init_job_queue();
|
|
7292
|
+
}
|
|
7293
|
+
});
|
|
7294
|
+
|
|
7089
7295
|
// ../core/dist/analytics/amplitude-client.js
|
|
7090
7296
|
function buildAuthHeader(apiKey, secretKey) {
|
|
7091
7297
|
return `Basic ${Buffer.from(`${apiKey}:${secretKey}`).toString("base64")}`;
|
|
@@ -7516,6 +7722,15 @@ __export(dist_exports, {
|
|
|
7516
7722
|
DEFAULT_PROVIDER: () => DEFAULT_PROVIDER,
|
|
7517
7723
|
DEFAULT_PROVIDER_ENV: () => DEFAULT_PROVIDER_ENV,
|
|
7518
7724
|
DEFAULT_PROVIDER_SCHEDULE_OVERRIDES: () => DEFAULT_PROVIDER_SCHEDULE_OVERRIDES,
|
|
7725
|
+
DEFAULT_PR_RESOLVER: () => DEFAULT_PR_RESOLVER,
|
|
7726
|
+
DEFAULT_PR_RESOLVER_AI_CONFLICT_RESOLUTION: () => DEFAULT_PR_RESOLVER_AI_CONFLICT_RESOLUTION,
|
|
7727
|
+
DEFAULT_PR_RESOLVER_AI_REVIEW_RESOLUTION: () => DEFAULT_PR_RESOLVER_AI_REVIEW_RESOLUTION,
|
|
7728
|
+
DEFAULT_PR_RESOLVER_ENABLED: () => DEFAULT_PR_RESOLVER_ENABLED,
|
|
7729
|
+
DEFAULT_PR_RESOLVER_MAX_PRS_PER_RUN: () => DEFAULT_PR_RESOLVER_MAX_PRS_PER_RUN,
|
|
7730
|
+
DEFAULT_PR_RESOLVER_MAX_RUNTIME: () => DEFAULT_PR_RESOLVER_MAX_RUNTIME,
|
|
7731
|
+
DEFAULT_PR_RESOLVER_PER_PR_TIMEOUT: () => DEFAULT_PR_RESOLVER_PER_PR_TIMEOUT,
|
|
7732
|
+
DEFAULT_PR_RESOLVER_READY_LABEL: () => DEFAULT_PR_RESOLVER_READY_LABEL,
|
|
7733
|
+
DEFAULT_PR_RESOLVER_SCHEDULE: () => DEFAULT_PR_RESOLVER_SCHEDULE,
|
|
7519
7734
|
DEFAULT_QA: () => DEFAULT_QA,
|
|
7520
7735
|
DEFAULT_QA_ARTIFACTS: () => DEFAULT_QA_ARTIFACTS,
|
|
7521
7736
|
DEFAULT_QA_AUTO_INSTALL_PLAYWRIGHT: () => DEFAULT_QA_AUTO_INSTALL_PLAYWRIGHT,
|
|
@@ -7523,6 +7738,7 @@ __export(dist_exports, {
|
|
|
7523
7738
|
DEFAULT_QA_MAX_RUNTIME: () => DEFAULT_QA_MAX_RUNTIME,
|
|
7524
7739
|
DEFAULT_QA_SCHEDULE: () => DEFAULT_QA_SCHEDULE,
|
|
7525
7740
|
DEFAULT_QA_SKIP_LABEL: () => DEFAULT_QA_SKIP_LABEL,
|
|
7741
|
+
DEFAULT_QA_VALIDATED_LABEL: () => DEFAULT_QA_VALIDATED_LABEL,
|
|
7526
7742
|
DEFAULT_QUEUE: () => DEFAULT_QUEUE,
|
|
7527
7743
|
DEFAULT_QUEUE_ENABLED: () => DEFAULT_QUEUE_ENABLED,
|
|
7528
7744
|
DEFAULT_QUEUE_MAX_CONCURRENCY: () => DEFAULT_QUEUE_MAX_CONCURRENCY,
|
|
@@ -7540,6 +7756,7 @@ __export(dist_exports, {
|
|
|
7540
7756
|
DEFAULT_SECONDARY_FALLBACK_MODEL: () => DEFAULT_SECONDARY_FALLBACK_MODEL,
|
|
7541
7757
|
DEFAULT_SLICER_MAX_RUNTIME: () => DEFAULT_SLICER_MAX_RUNTIME,
|
|
7542
7758
|
DEFAULT_SLICER_SCHEDULE: () => DEFAULT_SLICER_SCHEDULE,
|
|
7759
|
+
DEFAULT_SUMMARY_WINDOW_HOURS: () => DEFAULT_SUMMARY_WINDOW_HOURS,
|
|
7543
7760
|
DEFAULT_TEMPLATES_DIR: () => DEFAULT_TEMPLATES_DIR,
|
|
7544
7761
|
EXECUTOR_LOG_FILE: () => EXECUTOR_LOG_FILE,
|
|
7545
7762
|
EXECUTOR_LOG_NAME: () => EXECUTOR_LOG_NAME,
|
|
@@ -7562,6 +7779,7 @@ __export(dist_exports, {
|
|
|
7562
7779
|
PRIORITY_LABELS: () => PRIORITY_LABELS,
|
|
7563
7780
|
PRIORITY_LABEL_INFO: () => PRIORITY_LABEL_INFO,
|
|
7564
7781
|
PROVIDER_COMMANDS: () => PROVIDER_COMMANDS,
|
|
7782
|
+
PR_RESOLVER_LOG_NAME: () => PR_RESOLVER_LOG_NAME,
|
|
7565
7783
|
QA_LOG_NAME: () => QA_LOG_NAME,
|
|
7566
7784
|
REGISTRY_FILE_NAME: () => REGISTRY_FILE_NAME,
|
|
7567
7785
|
REVIEWER_LOG_FILE: () => REVIEWER_LOG_FILE,
|
|
@@ -7690,6 +7908,7 @@ __export(dist_exports, {
|
|
|
7690
7908
|
getScriptPath: () => getScriptPath,
|
|
7691
7909
|
getStateFilePath: () => getStateFilePath,
|
|
7692
7910
|
getStateItem: () => getStateItem,
|
|
7911
|
+
getSummaryData: () => getSummaryData,
|
|
7693
7912
|
getUncheckedItems: () => getUncheckedItems,
|
|
7694
7913
|
getValidJobTypes: () => getValidJobTypes,
|
|
7695
7914
|
groupBySection: () => groupBySection2,
|
|
@@ -7726,6 +7945,7 @@ __export(dist_exports, {
|
|
|
7726
7945
|
parseTimeToMinutes: () => parseTimeToMinutes,
|
|
7727
7946
|
performCancel: () => performCancel,
|
|
7728
7947
|
plannerLockPath: () => plannerLockPath,
|
|
7948
|
+
prResolverLockPath: () => prResolverLockPath,
|
|
7729
7949
|
prepareBranchWorktree: () => prepareBranchWorktree,
|
|
7730
7950
|
prepareDetachedWorktree: () => prepareDetachedWorktree,
|
|
7731
7951
|
projectRuntimeKey: () => projectRuntimeKey,
|
|
@@ -7822,6 +8042,7 @@ var init_dist = __esm({
|
|
|
7822
8042
|
init_webhook_validator();
|
|
7823
8043
|
init_worktree_manager();
|
|
7824
8044
|
init_job_queue();
|
|
8045
|
+
init_summary();
|
|
7825
8046
|
init_analytics();
|
|
7826
8047
|
init_prd_template();
|
|
7827
8048
|
init_slicer_prompt();
|
|
@@ -8098,6 +8319,7 @@ function buildInitConfig(params) {
|
|
|
8098
8319
|
},
|
|
8099
8320
|
audit: { ...defaults.audit },
|
|
8100
8321
|
analytics: { ...defaults.analytics },
|
|
8322
|
+
prResolver: { ...defaults.prResolver },
|
|
8101
8323
|
jobProviders: { ...defaults.jobProviders },
|
|
8102
8324
|
queue: {
|
|
8103
8325
|
...defaults.queue,
|
|
@@ -8221,7 +8443,7 @@ function initCommand(program2) {
|
|
|
8221
8443
|
const cwd = process.cwd();
|
|
8222
8444
|
const force = options.force || false;
|
|
8223
8445
|
const prdDir = options.prdDir || DEFAULT_PRD_DIR;
|
|
8224
|
-
const totalSteps =
|
|
8446
|
+
const totalSteps = 14;
|
|
8225
8447
|
const interactive = isInteractiveInitSession();
|
|
8226
8448
|
console.log();
|
|
8227
8449
|
header("Night Watch CLI - Initializing");
|
|
@@ -8476,7 +8698,35 @@ function initCommand(program2) {
|
|
|
8476
8698
|
}
|
|
8477
8699
|
}
|
|
8478
8700
|
}
|
|
8479
|
-
step(11, totalSteps, "
|
|
8701
|
+
step(11, totalSteps, "Syncing Night Watch labels to GitHub...");
|
|
8702
|
+
let labelSyncStatus = "Skipped";
|
|
8703
|
+
if (!remoteStatus.hasGitHubRemote || !ghAuthenticated) {
|
|
8704
|
+
labelSyncStatus = !remoteStatus.hasGitHubRemote ? "Skipped (no GitHub remote)" : "Skipped (gh auth required)";
|
|
8705
|
+
info("Skipping label sync (no GitHub remote or gh not authenticated).");
|
|
8706
|
+
} else {
|
|
8707
|
+
try {
|
|
8708
|
+
const { NIGHT_WATCH_LABELS: NIGHT_WATCH_LABELS2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
|
|
8709
|
+
let created = 0;
|
|
8710
|
+
for (const label2 of NIGHT_WATCH_LABELS2) {
|
|
8711
|
+
try {
|
|
8712
|
+
execSync3(
|
|
8713
|
+
`gh label create "${label2.name}" --description "${label2.description}" --color "${label2.color}" --force`,
|
|
8714
|
+
{ cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
8715
|
+
);
|
|
8716
|
+
created++;
|
|
8717
|
+
} catch {
|
|
8718
|
+
}
|
|
8719
|
+
}
|
|
8720
|
+
labelSyncStatus = `Synced ${created}/${NIGHT_WATCH_LABELS2.length} labels`;
|
|
8721
|
+
success(`Synced ${created}/${NIGHT_WATCH_LABELS2.length} labels to GitHub`);
|
|
8722
|
+
} catch (labelErr) {
|
|
8723
|
+
labelSyncStatus = "Failed";
|
|
8724
|
+
warn(
|
|
8725
|
+
`Could not sync labels: ${labelErr instanceof Error ? labelErr.message : String(labelErr)}`
|
|
8726
|
+
);
|
|
8727
|
+
}
|
|
8728
|
+
}
|
|
8729
|
+
step(12, totalSteps, "Registering project in global registry...");
|
|
8480
8730
|
try {
|
|
8481
8731
|
const { registerProject: registerProject2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
|
|
8482
8732
|
const entry = registerProject2(cwd);
|
|
@@ -8486,7 +8736,7 @@ function initCommand(program2) {
|
|
|
8486
8736
|
` Warning: Could not register in global registry: ${regErr instanceof Error ? regErr.message : String(regErr)}`
|
|
8487
8737
|
);
|
|
8488
8738
|
}
|
|
8489
|
-
step(
|
|
8739
|
+
step(13, totalSteps, "Installing Night Watch skills...");
|
|
8490
8740
|
const skillsResult = installSkills(cwd, selectedProvider, force, TEMPLATES_DIR);
|
|
8491
8741
|
if (skillsResult.installed > 0) {
|
|
8492
8742
|
success(`Installed ${skillsResult.installed} skills to ${skillsResult.location}`);
|
|
@@ -8498,7 +8748,7 @@ function initCommand(program2) {
|
|
|
8498
8748
|
} else if (skillsResult.type === "none") {
|
|
8499
8749
|
info("No compatible AI skills directory detected \u2014 skipping.");
|
|
8500
8750
|
}
|
|
8501
|
-
step(
|
|
8751
|
+
step(14, totalSteps, "Initialization complete!");
|
|
8502
8752
|
header("Initialization Complete");
|
|
8503
8753
|
const filesTable = createTable({ head: ["Created Files", ""] });
|
|
8504
8754
|
filesTable.push(["PRD Directory", `${prdDir}/done/`]);
|
|
@@ -8511,6 +8761,7 @@ function initCommand(program2) {
|
|
|
8511
8761
|
filesTable.push(["", `instructions/prd-creator.md (${templateSources[5].source})`]);
|
|
8512
8762
|
filesTable.push(["Config File", CONFIG_FILE_NAME]);
|
|
8513
8763
|
filesTable.push(["Board Setup", boardSetupStatus]);
|
|
8764
|
+
filesTable.push(["Label Sync", labelSyncStatus]);
|
|
8514
8765
|
filesTable.push(["Global Registry", "~/.night-watch/projects.json"]);
|
|
8515
8766
|
let skillsSummary;
|
|
8516
8767
|
if (skillsResult.installed > 0) {
|
|
@@ -9058,6 +9309,15 @@ function shouldSendReviewNotification(scriptStatus) {
|
|
|
9058
9309
|
}
|
|
9059
9310
|
return !scriptStatus.startsWith("skip_");
|
|
9060
9311
|
}
|
|
9312
|
+
function shouldSendReviewCompletionNotification(exitCode, scriptStatus) {
|
|
9313
|
+
if (exitCode !== 0) {
|
|
9314
|
+
return false;
|
|
9315
|
+
}
|
|
9316
|
+
if (scriptStatus === "failure" || scriptStatus === "timeout") {
|
|
9317
|
+
return false;
|
|
9318
|
+
}
|
|
9319
|
+
return shouldSendReviewNotification(scriptStatus);
|
|
9320
|
+
}
|
|
9061
9321
|
function parseAutoMergedPrNumbers(raw) {
|
|
9062
9322
|
if (!raw || raw.trim().length === 0) {
|
|
9063
9323
|
return [];
|
|
@@ -9316,12 +9576,15 @@ ${stderr}`);
|
|
|
9316
9576
|
spinner.fail(`PR reviewer exited with code ${exitCode}`);
|
|
9317
9577
|
}
|
|
9318
9578
|
if (!options.dryRun) {
|
|
9319
|
-
const
|
|
9320
|
-
|
|
9321
|
-
|
|
9579
|
+
const shouldNotifyCompletion = shouldSendReviewCompletionNotification(
|
|
9580
|
+
exitCode,
|
|
9581
|
+
scriptResult?.status
|
|
9582
|
+
);
|
|
9583
|
+
if (!shouldNotifyCompletion) {
|
|
9584
|
+
info("Skipping review completion notification (review did not complete successfully)");
|
|
9322
9585
|
}
|
|
9323
9586
|
let fallbackPrDetails = null;
|
|
9324
|
-
if (
|
|
9587
|
+
if (shouldNotifyCompletion) {
|
|
9325
9588
|
const reviewedPrNumbers = parseReviewedPrNumbers(scriptResult?.data.prs);
|
|
9326
9589
|
const firstReviewedPrNumber = reviewedPrNumbers[0];
|
|
9327
9590
|
if (firstReviewedPrNumber !== void 0) {
|
|
@@ -9331,7 +9594,7 @@ ${stderr}`);
|
|
|
9331
9594
|
fallbackPrDetails = fetchReviewedPrDetails(config.branchPatterns, projectDir);
|
|
9332
9595
|
}
|
|
9333
9596
|
}
|
|
9334
|
-
if (
|
|
9597
|
+
if (shouldNotifyCompletion) {
|
|
9335
9598
|
const attempts = parseRetryAttempts(scriptResult?.data.attempts);
|
|
9336
9599
|
const finalScore = parseFinalReviewScore(scriptResult?.data.final_score);
|
|
9337
9600
|
const legacyNoChangesNeeded = scriptResult?.data.no_changes_needed === "1";
|
|
@@ -9453,6 +9716,7 @@ function buildEnvVars3(config, options) {
|
|
|
9453
9716
|
const branchPatterns = config.qa.branchPatterns.length > 0 ? config.qa.branchPatterns : config.branchPatterns;
|
|
9454
9717
|
env.NW_BRANCH_PATTERNS = branchPatterns.join(",");
|
|
9455
9718
|
env.NW_QA_SKIP_LABEL = config.qa.skipLabel;
|
|
9719
|
+
env.NW_QA_VALIDATED_LABEL = config.qa.validatedLabel;
|
|
9456
9720
|
env.NW_QA_ARTIFACTS = config.qa.artifacts;
|
|
9457
9721
|
env.NW_QA_AUTO_INSTALL_PLAYWRIGHT = config.qa.autoInstallPlaywright ? "1" : "0";
|
|
9458
9722
|
env.NW_CLAUDE_MODEL_ID = CLAUDE_MODEL_IDS[config.primaryFallbackModel ?? config.claudeModel ?? "sonnet"];
|
|
@@ -9502,6 +9766,7 @@ function qaCommand(program2) {
|
|
|
9502
9766
|
const branchPatterns = config.qa.branchPatterns.length > 0 ? config.qa.branchPatterns : config.branchPatterns;
|
|
9503
9767
|
configTable.push(["Branch Patterns", branchPatterns.join(", ")]);
|
|
9504
9768
|
configTable.push(["Skip Label", config.qa.skipLabel]);
|
|
9769
|
+
configTable.push(["Validated Label", config.qa.validatedLabel]);
|
|
9505
9770
|
configTable.push(["Artifacts", config.qa.artifacts]);
|
|
9506
9771
|
configTable.push([
|
|
9507
9772
|
"Auto-install Playwright",
|
|
@@ -9533,6 +9798,8 @@ ${stderr}`);
|
|
|
9533
9798
|
spinner.succeed("QA process queued \u2014 another job is currently running");
|
|
9534
9799
|
} else if (scriptResult?.status?.startsWith("skip_")) {
|
|
9535
9800
|
spinner.succeed("QA process completed (no PRs needed QA)");
|
|
9801
|
+
} else if (scriptResult?.status === "warning_qa") {
|
|
9802
|
+
spinner.succeed("QA process completed with warnings");
|
|
9536
9803
|
} else {
|
|
9537
9804
|
spinner.succeed("QA process completed successfully");
|
|
9538
9805
|
}
|
|
@@ -9864,6 +10131,14 @@ function performInstall(projectDir, config, options) {
|
|
|
9864
10131
|
const analyticsEntry = `${analyticsSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} analytics >> ${shellQuote(analyticsLog)} 2>&1 ${marker}`;
|
|
9865
10132
|
entries.push(analyticsEntry);
|
|
9866
10133
|
}
|
|
10134
|
+
const disablePrResolver = options?.noPrResolver === true || options?.prResolver === false;
|
|
10135
|
+
const installPrResolver = disablePrResolver ? false : config.prResolver.enabled;
|
|
10136
|
+
if (installPrResolver) {
|
|
10137
|
+
const prResolverSchedule = config.prResolver.schedule;
|
|
10138
|
+
const prResolverLog = path25.join(logDir, "pr-resolver.log");
|
|
10139
|
+
const prResolverEntry = `${prResolverSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} resolve >> ${shellQuote(prResolverLog)} 2>&1 ${marker}`;
|
|
10140
|
+
entries.push(prResolverEntry);
|
|
10141
|
+
}
|
|
9867
10142
|
const existingEntries = new Set(
|
|
9868
10143
|
Array.from(/* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)]))
|
|
9869
10144
|
);
|
|
@@ -9882,7 +10157,7 @@ function performInstall(projectDir, config, options) {
|
|
|
9882
10157
|
}
|
|
9883
10158
|
}
|
|
9884
10159
|
function installCommand(program2) {
|
|
9885
|
-
program2.command("install").description("Add crontab entries for automated execution").option("-s, --schedule <cron>", "Cron schedule for PRD executor").option("--reviewer-schedule <cron>", "Cron schedule for reviewer").option("--no-reviewer", "Skip installing reviewer cron").option("--no-slicer", "Skip installing slicer cron").option("--no-qa", "Skip installing QA cron").option("--no-audit", "Skip installing audit cron").option("--no-analytics", "Skip installing analytics cron").option("-f, --force", "Replace existing cron entries for this project").action(async (options) => {
|
|
10160
|
+
program2.command("install").description("Add crontab entries for automated execution").option("-s, --schedule <cron>", "Cron schedule for PRD executor").option("--reviewer-schedule <cron>", "Cron schedule for reviewer").option("--no-reviewer", "Skip installing reviewer cron").option("--no-slicer", "Skip installing slicer cron").option("--no-qa", "Skip installing QA cron").option("--no-audit", "Skip installing audit cron").option("--no-analytics", "Skip installing analytics cron").option("--no-pr-resolver", "Skip installing PR resolver cron").option("-f, --force", "Replace existing cron entries for this project").action(async (options) => {
|
|
9886
10161
|
try {
|
|
9887
10162
|
const projectDir = process.cwd();
|
|
9888
10163
|
const config = loadConfig(projectDir);
|
|
@@ -9964,6 +10239,15 @@ function installCommand(program2) {
|
|
|
9964
10239
|
const analyticsEntry = `${analyticsSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} analytics >> ${shellQuote(analyticsLog)} 2>&1 ${marker}`;
|
|
9965
10240
|
entries.push(analyticsEntry);
|
|
9966
10241
|
}
|
|
10242
|
+
const disablePrResolver = options.noPrResolver === true || options.prResolver === false;
|
|
10243
|
+
const installPrResolver = disablePrResolver ? false : config.prResolver.enabled;
|
|
10244
|
+
let prResolverLog;
|
|
10245
|
+
if (installPrResolver) {
|
|
10246
|
+
prResolverLog = path25.join(logDir, "pr-resolver.log");
|
|
10247
|
+
const prResolverSchedule = config.prResolver.schedule;
|
|
10248
|
+
const prResolverEntry = `${prResolverSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} resolve >> ${shellQuote(prResolverLog)} 2>&1 ${marker}`;
|
|
10249
|
+
entries.push(prResolverEntry);
|
|
10250
|
+
}
|
|
9967
10251
|
const existingEntrySet = new Set(existingEntries);
|
|
9968
10252
|
const currentCrontab = readCrontab();
|
|
9969
10253
|
const baseCrontab = options.force ? currentCrontab.filter((line) => !existingEntrySet.has(line) && !line.includes(marker)) : currentCrontab;
|
|
@@ -9993,6 +10277,9 @@ function installCommand(program2) {
|
|
|
9993
10277
|
if (installAnalytics && analyticsLog) {
|
|
9994
10278
|
dim(` Analytics: ${analyticsLog}`);
|
|
9995
10279
|
}
|
|
10280
|
+
if (installPrResolver && prResolverLog) {
|
|
10281
|
+
dim(` PR Resolver: ${prResolverLog}`);
|
|
10282
|
+
}
|
|
9996
10283
|
console.log();
|
|
9997
10284
|
dim("To uninstall, run: night-watch uninstall");
|
|
9998
10285
|
dim("To check status, run: night-watch status");
|
|
@@ -10025,7 +10312,13 @@ function performUninstall(projectDir, options) {
|
|
|
10025
10312
|
if (!options?.keepLogs) {
|
|
10026
10313
|
const logDir = path26.join(projectDir, "logs");
|
|
10027
10314
|
if (fs24.existsSync(logDir)) {
|
|
10028
|
-
const logFiles = [
|
|
10315
|
+
const logFiles = [
|
|
10316
|
+
"executor.log",
|
|
10317
|
+
"reviewer.log",
|
|
10318
|
+
"slicer.log",
|
|
10319
|
+
"audit.log",
|
|
10320
|
+
"pr-resolver.log"
|
|
10321
|
+
];
|
|
10029
10322
|
logFiles.forEach((logFile) => {
|
|
10030
10323
|
const logPath = path26.join(logDir, logFile);
|
|
10031
10324
|
if (fs24.existsSync(logPath)) {
|
|
@@ -10070,7 +10363,13 @@ function uninstallCommand(program2) {
|
|
|
10070
10363
|
if (!options.keepLogs) {
|
|
10071
10364
|
const logDir = path26.join(projectDir, "logs");
|
|
10072
10365
|
if (fs24.existsSync(logDir)) {
|
|
10073
|
-
const logFiles = [
|
|
10366
|
+
const logFiles = [
|
|
10367
|
+
"executor.log",
|
|
10368
|
+
"reviewer.log",
|
|
10369
|
+
"slicer.log",
|
|
10370
|
+
"audit.log",
|
|
10371
|
+
"pr-resolver.log"
|
|
10372
|
+
];
|
|
10074
10373
|
let logsRemoved = 0;
|
|
10075
10374
|
logFiles.forEach((logFile) => {
|
|
10076
10375
|
const logPath = path26.join(logDir, logFile);
|
|
@@ -16740,8 +17039,9 @@ function createQueueCommand() {
|
|
|
16740
17039
|
}
|
|
16741
17040
|
process.exit(0);
|
|
16742
17041
|
});
|
|
16743
|
-
queue.command("dispatch").description("Dispatch the next pending job (used by cron scripts)").option("--log <file>", "Log file to write dispatch output").action((_opts) => {
|
|
16744
|
-
const
|
|
17042
|
+
queue.command("dispatch").description("Dispatch the next pending job (used by cron scripts)").option("--log <file>", "Log file to write dispatch output").option("--project-dir <dir>", "Project directory to load queue config from (defaults to cwd)").action((_opts) => {
|
|
17043
|
+
const configDir = _opts.projectDir ?? process.cwd();
|
|
17044
|
+
const entry = dispatchNextJob(loadConfig(configDir).queue);
|
|
16745
17045
|
if (!entry) {
|
|
16746
17046
|
logger5.info("No pending jobs to dispatch");
|
|
16747
17047
|
return;
|
|
@@ -16819,8 +17119,9 @@ function createQueueCommand() {
|
|
|
16819
17119
|
}
|
|
16820
17120
|
removeJob(queueId);
|
|
16821
17121
|
});
|
|
16822
|
-
queue.command("can-start").description("Return a zero exit status when the global queue has an available slot").action(() => {
|
|
16823
|
-
const
|
|
17122
|
+
queue.command("can-start").description("Return a zero exit status when the global queue has an available slot").option("--project-dir <dir>", "Project directory to load queue config from (defaults to cwd)").action((opts) => {
|
|
17123
|
+
const configDir = opts.projectDir ?? process.cwd();
|
|
17124
|
+
const queueConfig = loadConfig(configDir).queue;
|
|
16824
17125
|
process.exit(canStartJob(queueConfig) ? 0 : 1);
|
|
16825
17126
|
});
|
|
16826
17127
|
queue.command("expire").description("Expire stale queued jobs").option(
|
|
@@ -16842,7 +17143,26 @@ function createQueueCommand() {
|
|
|
16842
17143
|
});
|
|
16843
17144
|
return queue;
|
|
16844
17145
|
}
|
|
16845
|
-
var QUEUE_MARKER_KEYS = /* @__PURE__ */ new Set([
|
|
17146
|
+
var QUEUE_MARKER_KEYS = /* @__PURE__ */ new Set([
|
|
17147
|
+
"NW_DRY_RUN",
|
|
17148
|
+
"NW_CRON_TRIGGER",
|
|
17149
|
+
"NW_DEFAULT_BRANCH",
|
|
17150
|
+
"NW_TARGET_PR",
|
|
17151
|
+
"NW_REVIEWER_WORKER_MODE",
|
|
17152
|
+
"NW_REVIEWER_PARALLEL",
|
|
17153
|
+
"NW_REVIEWER_WORKER_STAGGER",
|
|
17154
|
+
"NW_REVIEWER_MAX_RUNTIME",
|
|
17155
|
+
"NW_REVIEWER_MAX_RETRIES",
|
|
17156
|
+
"NW_REVIEWER_RETRY_DELAY",
|
|
17157
|
+
"NW_REVIEWER_MAX_PRS_PER_RUN",
|
|
17158
|
+
"NW_MIN_REVIEW_SCORE",
|
|
17159
|
+
"NW_BRANCH_PATTERNS",
|
|
17160
|
+
"NW_PRD_DIR",
|
|
17161
|
+
"NW_AUTO_MERGE",
|
|
17162
|
+
"NW_AUTO_MERGE_METHOD",
|
|
17163
|
+
"NW_MAX_RUNTIME",
|
|
17164
|
+
"NW_QA_MAX_RUNTIME"
|
|
17165
|
+
]);
|
|
16846
17166
|
function filterQueueMarkers(envJson) {
|
|
16847
17167
|
const result = {};
|
|
16848
17168
|
for (const [key, value] of Object.entries(envJson)) {
|
|
@@ -16911,6 +17231,300 @@ function notifyCommand(program2) {
|
|
|
16911
17231
|
);
|
|
16912
17232
|
}
|
|
16913
17233
|
|
|
17234
|
+
// src/commands/summary.ts
|
|
17235
|
+
init_dist();
|
|
17236
|
+
import path42 from "path";
|
|
17237
|
+
import chalk8 from "chalk";
|
|
17238
|
+
function formatDuration2(seconds) {
|
|
17239
|
+
if (seconds === null) return "-";
|
|
17240
|
+
const mins = Math.floor(seconds / 60);
|
|
17241
|
+
const secs = seconds % 60;
|
|
17242
|
+
if (mins === 0) return `${secs}s`;
|
|
17243
|
+
return `${mins}m ${secs}s`;
|
|
17244
|
+
}
|
|
17245
|
+
function formatCiStatus2(status) {
|
|
17246
|
+
if (status === "pass") return chalk8.green("pass");
|
|
17247
|
+
if (status === "fail") return chalk8.red("fail");
|
|
17248
|
+
if (status === "pending") return chalk8.yellow("pending");
|
|
17249
|
+
return chalk8.dim("unknown");
|
|
17250
|
+
}
|
|
17251
|
+
function formatReviewScore2(score) {
|
|
17252
|
+
if (score === null) return chalk8.dim("-");
|
|
17253
|
+
if (score >= 80) return chalk8.green(String(score));
|
|
17254
|
+
if (score >= 60) return chalk8.yellow(String(score));
|
|
17255
|
+
return chalk8.red(String(score));
|
|
17256
|
+
}
|
|
17257
|
+
function formatJobStatus(status) {
|
|
17258
|
+
if (status === "success") return chalk8.green("success");
|
|
17259
|
+
if (status === "failure") return chalk8.red("failure");
|
|
17260
|
+
if (status === "timeout") return chalk8.yellow("timeout");
|
|
17261
|
+
if (status === "rate_limited") return chalk8.magenta("rate_limited");
|
|
17262
|
+
if (status === "skipped") return chalk8.dim("skipped");
|
|
17263
|
+
return chalk8.dim(status);
|
|
17264
|
+
}
|
|
17265
|
+
function getProjectName2(projectPath) {
|
|
17266
|
+
return path42.basename(projectPath) || projectPath;
|
|
17267
|
+
}
|
|
17268
|
+
function formatProvider(providerKey) {
|
|
17269
|
+
return providerKey.split(":")[0] || providerKey;
|
|
17270
|
+
}
|
|
17271
|
+
function summaryCommand(program2) {
|
|
17272
|
+
program2.command("summary").description("Show a summary of recent Night Watch activity").option(
|
|
17273
|
+
"--hours <n>",
|
|
17274
|
+
"Time window in hours (default: 12)",
|
|
17275
|
+
String(DEFAULT_SUMMARY_WINDOW_HOURS)
|
|
17276
|
+
).option("--json", "Output summary as JSON").action(async (options) => {
|
|
17277
|
+
try {
|
|
17278
|
+
const projectDir = process.cwd();
|
|
17279
|
+
const config = loadConfig(projectDir);
|
|
17280
|
+
const hours = parseInt(options.hours || String(DEFAULT_SUMMARY_WINDOW_HOURS), 10);
|
|
17281
|
+
if (isNaN(hours) || hours <= 0) {
|
|
17282
|
+
console.error("Error: --hours must be a positive integer");
|
|
17283
|
+
process.exit(1);
|
|
17284
|
+
}
|
|
17285
|
+
const data = await getSummaryData(projectDir, hours, config.branchPatterns);
|
|
17286
|
+
if (options.json) {
|
|
17287
|
+
console.log(JSON.stringify(data, null, 2));
|
|
17288
|
+
return;
|
|
17289
|
+
}
|
|
17290
|
+
console.log();
|
|
17291
|
+
console.log(chalk8.bold.cyan(`Night Watch Summary (last ${data.windowHours}h)`));
|
|
17292
|
+
console.log(chalk8.dim("\u2500".repeat(40)));
|
|
17293
|
+
console.log();
|
|
17294
|
+
if (data.jobRuns.length === 0) {
|
|
17295
|
+
info("No recent activity in this time window.");
|
|
17296
|
+
console.log();
|
|
17297
|
+
} else {
|
|
17298
|
+
const countParts = [];
|
|
17299
|
+
if (data.counts.succeeded > 0) {
|
|
17300
|
+
countParts.push(chalk8.green(`\u2713 ${data.counts.succeeded} succeeded`));
|
|
17301
|
+
}
|
|
17302
|
+
if (data.counts.failed > 0) {
|
|
17303
|
+
countParts.push(chalk8.red(`\u2717 ${data.counts.failed} failed`));
|
|
17304
|
+
}
|
|
17305
|
+
if (data.counts.timedOut > 0) {
|
|
17306
|
+
countParts.push(chalk8.yellow(`\u23F1 ${data.counts.timedOut} timed out`));
|
|
17307
|
+
}
|
|
17308
|
+
if (data.counts.rateLimited > 0) {
|
|
17309
|
+
countParts.push(chalk8.magenta(`\u23F3 ${data.counts.rateLimited} rate limited`));
|
|
17310
|
+
}
|
|
17311
|
+
if (data.counts.skipped > 0) {
|
|
17312
|
+
countParts.push(chalk8.dim(`${data.counts.skipped} skipped`));
|
|
17313
|
+
}
|
|
17314
|
+
console.log(`Jobs Executed: ${data.counts.total}`);
|
|
17315
|
+
if (countParts.length > 0) {
|
|
17316
|
+
console.log(` ${countParts.join(" ")}`);
|
|
17317
|
+
}
|
|
17318
|
+
console.log();
|
|
17319
|
+
const table = createTable({
|
|
17320
|
+
head: ["Job", "Status", "Project", "Provider", "Duration"],
|
|
17321
|
+
colWidths: [12, 12, 20, 12, 12]
|
|
17322
|
+
});
|
|
17323
|
+
for (const run2 of data.jobRuns.slice(0, 10)) {
|
|
17324
|
+
table.push([
|
|
17325
|
+
run2.jobType,
|
|
17326
|
+
formatJobStatus(run2.status),
|
|
17327
|
+
getProjectName2(run2.projectPath),
|
|
17328
|
+
formatProvider(run2.providerKey),
|
|
17329
|
+
formatDuration2(run2.durationSeconds)
|
|
17330
|
+
]);
|
|
17331
|
+
}
|
|
17332
|
+
console.log(table.toString());
|
|
17333
|
+
if (data.jobRuns.length > 10) {
|
|
17334
|
+
dim(` ... and ${data.jobRuns.length - 10} more`);
|
|
17335
|
+
}
|
|
17336
|
+
console.log();
|
|
17337
|
+
}
|
|
17338
|
+
if (data.openPrs.length > 0) {
|
|
17339
|
+
header(`Open PRs (${data.openPrs.length})`);
|
|
17340
|
+
const prTable = createTable({
|
|
17341
|
+
head: ["#", "Title", "CI", "Score"],
|
|
17342
|
+
colWidths: [6, 40, 10, 8]
|
|
17343
|
+
});
|
|
17344
|
+
for (const pr of data.openPrs) {
|
|
17345
|
+
const title = pr.title.length > 37 ? pr.title.substring(0, 34) + "..." : pr.title;
|
|
17346
|
+
prTable.push([
|
|
17347
|
+
String(pr.number),
|
|
17348
|
+
title,
|
|
17349
|
+
formatCiStatus2(pr.ciStatus),
|
|
17350
|
+
formatReviewScore2(pr.reviewScore)
|
|
17351
|
+
]);
|
|
17352
|
+
}
|
|
17353
|
+
console.log(prTable.toString());
|
|
17354
|
+
console.log();
|
|
17355
|
+
}
|
|
17356
|
+
if (data.pendingQueueItems.length > 0) {
|
|
17357
|
+
const jobTypes = [...new Set(data.pendingQueueItems.map((item) => item.jobType))];
|
|
17358
|
+
const projectNames = [...new Set(data.pendingQueueItems.map((item) => item.projectName))];
|
|
17359
|
+
dim(
|
|
17360
|
+
`Queue: ${data.pendingQueueItems.length} pending (${jobTypes.join(", ")}) for ${projectNames.join(", ")}`
|
|
17361
|
+
);
|
|
17362
|
+
console.log();
|
|
17363
|
+
}
|
|
17364
|
+
if (data.actionItems.length > 0) {
|
|
17365
|
+
console.log(chalk8.yellow("\u26A0 Action needed:"));
|
|
17366
|
+
for (const item of data.actionItems) {
|
|
17367
|
+
console.log(` \u2022 ${item}`);
|
|
17368
|
+
}
|
|
17369
|
+
} else {
|
|
17370
|
+
console.log(chalk8.green("\u2713 No action needed \u2014 all jobs healthy."));
|
|
17371
|
+
}
|
|
17372
|
+
console.log();
|
|
17373
|
+
} catch (error2) {
|
|
17374
|
+
console.error(
|
|
17375
|
+
`Error getting summary: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
17376
|
+
);
|
|
17377
|
+
process.exit(1);
|
|
17378
|
+
}
|
|
17379
|
+
});
|
|
17380
|
+
}
|
|
17381
|
+
|
|
17382
|
+
// src/commands/resolve.ts
|
|
17383
|
+
init_dist();
|
|
17384
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
17385
|
+
import * as path43 from "path";
|
|
17386
|
+
function buildEnvVars6(config, options) {
|
|
17387
|
+
const env = buildBaseEnvVars(config, "pr-resolver", options.dryRun);
|
|
17388
|
+
env.NW_PR_RESOLVER_MAX_RUNTIME = String(config.prResolver.maxRuntime);
|
|
17389
|
+
env.NW_PR_RESOLVER_MAX_PRS_PER_RUN = String(config.prResolver.maxPrsPerRun);
|
|
17390
|
+
env.NW_PR_RESOLVER_PER_PR_TIMEOUT = String(config.prResolver.perPrTimeout);
|
|
17391
|
+
env.NW_PR_RESOLVER_AI_CONFLICT_RESOLUTION = config.prResolver.aiConflictResolution ? "1" : "0";
|
|
17392
|
+
env.NW_PR_RESOLVER_AI_REVIEW_RESOLUTION = config.prResolver.aiReviewResolution ? "1" : "0";
|
|
17393
|
+
env.NW_PR_RESOLVER_READY_LABEL = config.prResolver.readyLabel;
|
|
17394
|
+
env.NW_PR_RESOLVER_BRANCH_PATTERNS = config.prResolver.branchPatterns.join(",");
|
|
17395
|
+
return env;
|
|
17396
|
+
}
|
|
17397
|
+
function applyCliOverrides5(config, options) {
|
|
17398
|
+
const overridden = { ...config, prResolver: { ...config.prResolver } };
|
|
17399
|
+
if (options.timeout) {
|
|
17400
|
+
const timeout = parseInt(options.timeout, 10);
|
|
17401
|
+
if (!isNaN(timeout)) {
|
|
17402
|
+
overridden.prResolver.maxRuntime = timeout;
|
|
17403
|
+
}
|
|
17404
|
+
}
|
|
17405
|
+
if (options.provider) {
|
|
17406
|
+
overridden._cliProviderOverride = options.provider;
|
|
17407
|
+
}
|
|
17408
|
+
return overridden;
|
|
17409
|
+
}
|
|
17410
|
+
function getOpenPrs() {
|
|
17411
|
+
try {
|
|
17412
|
+
const args = ["pr", "list", "--state", "open", "--json", "number,title,headRefName,mergeable"];
|
|
17413
|
+
const result = execFileSync7("gh", args, {
|
|
17414
|
+
encoding: "utf-8",
|
|
17415
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
17416
|
+
});
|
|
17417
|
+
const prs = JSON.parse(result.trim() || "[]");
|
|
17418
|
+
return prs.map(
|
|
17419
|
+
(pr) => ({
|
|
17420
|
+
number: pr.number,
|
|
17421
|
+
title: pr.title,
|
|
17422
|
+
branch: pr.headRefName,
|
|
17423
|
+
mergeable: pr.mergeable
|
|
17424
|
+
})
|
|
17425
|
+
);
|
|
17426
|
+
} catch {
|
|
17427
|
+
return [];
|
|
17428
|
+
}
|
|
17429
|
+
}
|
|
17430
|
+
function resolveCommand(program2) {
|
|
17431
|
+
program2.command("resolve").description("Run PR conflict resolver now").option("--dry-run", "Show what would be executed without running").option("--timeout <seconds>", "Override max runtime").option("--provider <string>", "AI provider to use").action(async (options) => {
|
|
17432
|
+
const projectDir = process.cwd();
|
|
17433
|
+
let config = loadConfig(projectDir);
|
|
17434
|
+
config = applyCliOverrides5(config, options);
|
|
17435
|
+
if (!config.prResolver.enabled && !options.dryRun) {
|
|
17436
|
+
info("PR resolver is disabled in config; skipping.");
|
|
17437
|
+
process.exit(0);
|
|
17438
|
+
}
|
|
17439
|
+
const envVars = buildEnvVars6(config, options);
|
|
17440
|
+
const scriptPath = getScriptPath("night-watch-pr-resolver-cron.sh");
|
|
17441
|
+
if (options.dryRun) {
|
|
17442
|
+
header("Dry Run: PR Resolver");
|
|
17443
|
+
const resolverProvider = resolveJobProvider(config, "pr-resolver");
|
|
17444
|
+
header("Configuration");
|
|
17445
|
+
const configTable = createTable({ head: ["Setting", "Value"] });
|
|
17446
|
+
configTable.push(["Provider", resolverProvider]);
|
|
17447
|
+
configTable.push([
|
|
17448
|
+
"Max Runtime",
|
|
17449
|
+
`${config.prResolver.maxRuntime}s (${Math.floor(config.prResolver.maxRuntime / 60)}min)`
|
|
17450
|
+
]);
|
|
17451
|
+
configTable.push([
|
|
17452
|
+
"Max PRs Per Run",
|
|
17453
|
+
config.prResolver.maxPrsPerRun === 0 ? "Unlimited" : String(config.prResolver.maxPrsPerRun)
|
|
17454
|
+
]);
|
|
17455
|
+
configTable.push(["Per-PR Timeout", `${config.prResolver.perPrTimeout}s`]);
|
|
17456
|
+
configTable.push([
|
|
17457
|
+
"AI Conflict Resolution",
|
|
17458
|
+
config.prResolver.aiConflictResolution ? "Enabled" : "Disabled"
|
|
17459
|
+
]);
|
|
17460
|
+
configTable.push([
|
|
17461
|
+
"AI Review Resolution",
|
|
17462
|
+
config.prResolver.aiReviewResolution ? "Enabled" : "Disabled"
|
|
17463
|
+
]);
|
|
17464
|
+
configTable.push(["Ready Label", config.prResolver.readyLabel]);
|
|
17465
|
+
configTable.push([
|
|
17466
|
+
"Branch Patterns",
|
|
17467
|
+
config.prResolver.branchPatterns.length > 0 ? config.prResolver.branchPatterns.join(", ") : "(all)"
|
|
17468
|
+
]);
|
|
17469
|
+
console.log(configTable.toString());
|
|
17470
|
+
header("Open PRs");
|
|
17471
|
+
const openPrs = getOpenPrs();
|
|
17472
|
+
if (openPrs.length === 0) {
|
|
17473
|
+
dim(" (no open PRs found)");
|
|
17474
|
+
} else {
|
|
17475
|
+
for (const pr of openPrs) {
|
|
17476
|
+
const conflictStatus = pr.mergeable === "CONFLICTING" ? " [CONFLICT]" : "";
|
|
17477
|
+
info(`#${pr.number}: ${pr.title}${conflictStatus}`);
|
|
17478
|
+
dim(` Branch: ${pr.branch}`);
|
|
17479
|
+
}
|
|
17480
|
+
}
|
|
17481
|
+
header("Environment Variables");
|
|
17482
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
17483
|
+
dim(` ${key}=${value}`);
|
|
17484
|
+
}
|
|
17485
|
+
header("Command");
|
|
17486
|
+
dim(` bash ${scriptPath} ${projectDir}`);
|
|
17487
|
+
console.log();
|
|
17488
|
+
process.exit(0);
|
|
17489
|
+
}
|
|
17490
|
+
const spinner = createSpinner("Running PR resolver...");
|
|
17491
|
+
spinner.start();
|
|
17492
|
+
try {
|
|
17493
|
+
await maybeApplyCronSchedulingDelay(config, "pr-resolver", projectDir);
|
|
17494
|
+
const { exitCode, stdout, stderr } = await executeScriptWithOutput(
|
|
17495
|
+
scriptPath,
|
|
17496
|
+
[projectDir],
|
|
17497
|
+
envVars
|
|
17498
|
+
);
|
|
17499
|
+
const scriptResult = parseScriptResult(`${stdout}
|
|
17500
|
+
${stderr}`);
|
|
17501
|
+
if (exitCode === 0) {
|
|
17502
|
+
if (scriptResult?.status === "queued") {
|
|
17503
|
+
spinner.succeed("PR resolver queued \u2014 another job is currently running");
|
|
17504
|
+
} else if (scriptResult?.status?.startsWith("skip_")) {
|
|
17505
|
+
spinner.succeed("PR resolver completed (no PRs needed resolution)");
|
|
17506
|
+
} else {
|
|
17507
|
+
spinner.succeed("PR resolver completed successfully");
|
|
17508
|
+
}
|
|
17509
|
+
} else {
|
|
17510
|
+
spinner.fail(`PR resolver exited with code ${exitCode}`);
|
|
17511
|
+
}
|
|
17512
|
+
const notificationEvent = exitCode === 0 ? "pr_resolver_completed" : "pr_resolver_failed";
|
|
17513
|
+
await sendNotifications(config, {
|
|
17514
|
+
event: notificationEvent,
|
|
17515
|
+
projectName: path43.basename(projectDir),
|
|
17516
|
+
exitCode,
|
|
17517
|
+
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL)
|
|
17518
|
+
});
|
|
17519
|
+
process.exit(exitCode);
|
|
17520
|
+
} catch (err) {
|
|
17521
|
+
spinner.fail("Failed to execute resolve command");
|
|
17522
|
+
error(`${err instanceof Error ? err.message : String(err)}`);
|
|
17523
|
+
process.exit(1);
|
|
17524
|
+
}
|
|
17525
|
+
});
|
|
17526
|
+
}
|
|
17527
|
+
|
|
16914
17528
|
// src/cli.ts
|
|
16915
17529
|
var __filename5 = fileURLToPath6(import.meta.url);
|
|
16916
17530
|
var __dirname5 = dirname12(__filename5);
|
|
@@ -16952,4 +17566,6 @@ program.addCommand(createStateCommand());
|
|
|
16952
17566
|
boardCommand(program);
|
|
16953
17567
|
queueCommand(program);
|
|
16954
17568
|
notifyCommand(program);
|
|
17569
|
+
summaryCommand(program);
|
|
17570
|
+
resolveCommand(program);
|
|
16955
17571
|
program.parse();
|