@jonit-dev/night-watch-cli 1.8.8-beta.1 → 1.8.8-beta.8
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 +621 -19
- 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 +2 -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/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 +21 -5
- package/dist/scripts/night-watch-qa-cron.sh +30 -4
- 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) {
|
|
@@ -9453,6 +9704,7 @@ function buildEnvVars3(config, options) {
|
|
|
9453
9704
|
const branchPatterns = config.qa.branchPatterns.length > 0 ? config.qa.branchPatterns : config.branchPatterns;
|
|
9454
9705
|
env.NW_BRANCH_PATTERNS = branchPatterns.join(",");
|
|
9455
9706
|
env.NW_QA_SKIP_LABEL = config.qa.skipLabel;
|
|
9707
|
+
env.NW_QA_VALIDATED_LABEL = config.qa.validatedLabel;
|
|
9456
9708
|
env.NW_QA_ARTIFACTS = config.qa.artifacts;
|
|
9457
9709
|
env.NW_QA_AUTO_INSTALL_PLAYWRIGHT = config.qa.autoInstallPlaywright ? "1" : "0";
|
|
9458
9710
|
env.NW_CLAUDE_MODEL_ID = CLAUDE_MODEL_IDS[config.primaryFallbackModel ?? config.claudeModel ?? "sonnet"];
|
|
@@ -9502,6 +9754,7 @@ function qaCommand(program2) {
|
|
|
9502
9754
|
const branchPatterns = config.qa.branchPatterns.length > 0 ? config.qa.branchPatterns : config.branchPatterns;
|
|
9503
9755
|
configTable.push(["Branch Patterns", branchPatterns.join(", ")]);
|
|
9504
9756
|
configTable.push(["Skip Label", config.qa.skipLabel]);
|
|
9757
|
+
configTable.push(["Validated Label", config.qa.validatedLabel]);
|
|
9505
9758
|
configTable.push(["Artifacts", config.qa.artifacts]);
|
|
9506
9759
|
configTable.push([
|
|
9507
9760
|
"Auto-install Playwright",
|
|
@@ -9866,6 +10119,14 @@ function performInstall(projectDir, config, options) {
|
|
|
9866
10119
|
const analyticsEntry = `${analyticsSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} analytics >> ${shellQuote(analyticsLog)} 2>&1 ${marker}`;
|
|
9867
10120
|
entries.push(analyticsEntry);
|
|
9868
10121
|
}
|
|
10122
|
+
const disablePrResolver = options?.noPrResolver === true || options?.prResolver === false;
|
|
10123
|
+
const installPrResolver = disablePrResolver ? false : config.prResolver.enabled;
|
|
10124
|
+
if (installPrResolver) {
|
|
10125
|
+
const prResolverSchedule = config.prResolver.schedule;
|
|
10126
|
+
const prResolverLog = path25.join(logDir, "pr-resolver.log");
|
|
10127
|
+
const prResolverEntry = `${prResolverSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} resolve >> ${shellQuote(prResolverLog)} 2>&1 ${marker}`;
|
|
10128
|
+
entries.push(prResolverEntry);
|
|
10129
|
+
}
|
|
9869
10130
|
const existingEntries = new Set(
|
|
9870
10131
|
Array.from(/* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)]))
|
|
9871
10132
|
);
|
|
@@ -9884,7 +10145,7 @@ function performInstall(projectDir, config, options) {
|
|
|
9884
10145
|
}
|
|
9885
10146
|
}
|
|
9886
10147
|
function installCommand(program2) {
|
|
9887
|
-
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) => {
|
|
10148
|
+
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) => {
|
|
9888
10149
|
try {
|
|
9889
10150
|
const projectDir = process.cwd();
|
|
9890
10151
|
const config = loadConfig(projectDir);
|
|
@@ -9966,6 +10227,15 @@ function installCommand(program2) {
|
|
|
9966
10227
|
const analyticsEntry = `${analyticsSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} analytics >> ${shellQuote(analyticsLog)} 2>&1 ${marker}`;
|
|
9967
10228
|
entries.push(analyticsEntry);
|
|
9968
10229
|
}
|
|
10230
|
+
const disablePrResolver = options.noPrResolver === true || options.prResolver === false;
|
|
10231
|
+
const installPrResolver = disablePrResolver ? false : config.prResolver.enabled;
|
|
10232
|
+
let prResolverLog;
|
|
10233
|
+
if (installPrResolver) {
|
|
10234
|
+
prResolverLog = path25.join(logDir, "pr-resolver.log");
|
|
10235
|
+
const prResolverSchedule = config.prResolver.schedule;
|
|
10236
|
+
const prResolverEntry = `${prResolverSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} resolve >> ${shellQuote(prResolverLog)} 2>&1 ${marker}`;
|
|
10237
|
+
entries.push(prResolverEntry);
|
|
10238
|
+
}
|
|
9969
10239
|
const existingEntrySet = new Set(existingEntries);
|
|
9970
10240
|
const currentCrontab = readCrontab();
|
|
9971
10241
|
const baseCrontab = options.force ? currentCrontab.filter((line) => !existingEntrySet.has(line) && !line.includes(marker)) : currentCrontab;
|
|
@@ -9995,6 +10265,9 @@ function installCommand(program2) {
|
|
|
9995
10265
|
if (installAnalytics && analyticsLog) {
|
|
9996
10266
|
dim(` Analytics: ${analyticsLog}`);
|
|
9997
10267
|
}
|
|
10268
|
+
if (installPrResolver && prResolverLog) {
|
|
10269
|
+
dim(` PR Resolver: ${prResolverLog}`);
|
|
10270
|
+
}
|
|
9998
10271
|
console.log();
|
|
9999
10272
|
dim("To uninstall, run: night-watch uninstall");
|
|
10000
10273
|
dim("To check status, run: night-watch status");
|
|
@@ -10027,7 +10300,13 @@ function performUninstall(projectDir, options) {
|
|
|
10027
10300
|
if (!options?.keepLogs) {
|
|
10028
10301
|
const logDir = path26.join(projectDir, "logs");
|
|
10029
10302
|
if (fs24.existsSync(logDir)) {
|
|
10030
|
-
const logFiles = [
|
|
10303
|
+
const logFiles = [
|
|
10304
|
+
"executor.log",
|
|
10305
|
+
"reviewer.log",
|
|
10306
|
+
"slicer.log",
|
|
10307
|
+
"audit.log",
|
|
10308
|
+
"pr-resolver.log"
|
|
10309
|
+
];
|
|
10031
10310
|
logFiles.forEach((logFile) => {
|
|
10032
10311
|
const logPath = path26.join(logDir, logFile);
|
|
10033
10312
|
if (fs24.existsSync(logPath)) {
|
|
@@ -10072,7 +10351,13 @@ function uninstallCommand(program2) {
|
|
|
10072
10351
|
if (!options.keepLogs) {
|
|
10073
10352
|
const logDir = path26.join(projectDir, "logs");
|
|
10074
10353
|
if (fs24.existsSync(logDir)) {
|
|
10075
|
-
const logFiles = [
|
|
10354
|
+
const logFiles = [
|
|
10355
|
+
"executor.log",
|
|
10356
|
+
"reviewer.log",
|
|
10357
|
+
"slicer.log",
|
|
10358
|
+
"audit.log",
|
|
10359
|
+
"pr-resolver.log"
|
|
10360
|
+
];
|
|
10076
10361
|
let logsRemoved = 0;
|
|
10077
10362
|
logFiles.forEach((logFile) => {
|
|
10078
10363
|
const logPath = path26.join(logDir, logFile);
|
|
@@ -16742,8 +17027,9 @@ function createQueueCommand() {
|
|
|
16742
17027
|
}
|
|
16743
17028
|
process.exit(0);
|
|
16744
17029
|
});
|
|
16745
|
-
queue.command("dispatch").description("Dispatch the next pending job (used by cron scripts)").option("--log <file>", "Log file to write dispatch output").action((_opts) => {
|
|
16746
|
-
const
|
|
17030
|
+
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) => {
|
|
17031
|
+
const configDir = _opts.projectDir ?? process.cwd();
|
|
17032
|
+
const entry = dispatchNextJob(loadConfig(configDir).queue);
|
|
16747
17033
|
if (!entry) {
|
|
16748
17034
|
logger5.info("No pending jobs to dispatch");
|
|
16749
17035
|
return;
|
|
@@ -16821,8 +17107,9 @@ function createQueueCommand() {
|
|
|
16821
17107
|
}
|
|
16822
17108
|
removeJob(queueId);
|
|
16823
17109
|
});
|
|
16824
|
-
queue.command("can-start").description("Return a zero exit status when the global queue has an available slot").action(() => {
|
|
16825
|
-
const
|
|
17110
|
+
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) => {
|
|
17111
|
+
const configDir = opts.projectDir ?? process.cwd();
|
|
17112
|
+
const queueConfig = loadConfig(configDir).queue;
|
|
16826
17113
|
process.exit(canStartJob(queueConfig) ? 0 : 1);
|
|
16827
17114
|
});
|
|
16828
17115
|
queue.command("expire").description("Expire stale queued jobs").option(
|
|
@@ -16844,7 +17131,26 @@ function createQueueCommand() {
|
|
|
16844
17131
|
});
|
|
16845
17132
|
return queue;
|
|
16846
17133
|
}
|
|
16847
|
-
var QUEUE_MARKER_KEYS = /* @__PURE__ */ new Set([
|
|
17134
|
+
var QUEUE_MARKER_KEYS = /* @__PURE__ */ new Set([
|
|
17135
|
+
"NW_DRY_RUN",
|
|
17136
|
+
"NW_CRON_TRIGGER",
|
|
17137
|
+
"NW_DEFAULT_BRANCH",
|
|
17138
|
+
"NW_TARGET_PR",
|
|
17139
|
+
"NW_REVIEWER_WORKER_MODE",
|
|
17140
|
+
"NW_REVIEWER_PARALLEL",
|
|
17141
|
+
"NW_REVIEWER_WORKER_STAGGER",
|
|
17142
|
+
"NW_REVIEWER_MAX_RUNTIME",
|
|
17143
|
+
"NW_REVIEWER_MAX_RETRIES",
|
|
17144
|
+
"NW_REVIEWER_RETRY_DELAY",
|
|
17145
|
+
"NW_REVIEWER_MAX_PRS_PER_RUN",
|
|
17146
|
+
"NW_MIN_REVIEW_SCORE",
|
|
17147
|
+
"NW_BRANCH_PATTERNS",
|
|
17148
|
+
"NW_PRD_DIR",
|
|
17149
|
+
"NW_AUTO_MERGE",
|
|
17150
|
+
"NW_AUTO_MERGE_METHOD",
|
|
17151
|
+
"NW_MAX_RUNTIME",
|
|
17152
|
+
"NW_QA_MAX_RUNTIME"
|
|
17153
|
+
]);
|
|
16848
17154
|
function filterQueueMarkers(envJson) {
|
|
16849
17155
|
const result = {};
|
|
16850
17156
|
for (const [key, value] of Object.entries(envJson)) {
|
|
@@ -16913,6 +17219,300 @@ function notifyCommand(program2) {
|
|
|
16913
17219
|
);
|
|
16914
17220
|
}
|
|
16915
17221
|
|
|
17222
|
+
// src/commands/summary.ts
|
|
17223
|
+
init_dist();
|
|
17224
|
+
import path42 from "path";
|
|
17225
|
+
import chalk8 from "chalk";
|
|
17226
|
+
function formatDuration2(seconds) {
|
|
17227
|
+
if (seconds === null) return "-";
|
|
17228
|
+
const mins = Math.floor(seconds / 60);
|
|
17229
|
+
const secs = seconds % 60;
|
|
17230
|
+
if (mins === 0) return `${secs}s`;
|
|
17231
|
+
return `${mins}m ${secs}s`;
|
|
17232
|
+
}
|
|
17233
|
+
function formatCiStatus2(status) {
|
|
17234
|
+
if (status === "pass") return chalk8.green("pass");
|
|
17235
|
+
if (status === "fail") return chalk8.red("fail");
|
|
17236
|
+
if (status === "pending") return chalk8.yellow("pending");
|
|
17237
|
+
return chalk8.dim("unknown");
|
|
17238
|
+
}
|
|
17239
|
+
function formatReviewScore2(score) {
|
|
17240
|
+
if (score === null) return chalk8.dim("-");
|
|
17241
|
+
if (score >= 80) return chalk8.green(String(score));
|
|
17242
|
+
if (score >= 60) return chalk8.yellow(String(score));
|
|
17243
|
+
return chalk8.red(String(score));
|
|
17244
|
+
}
|
|
17245
|
+
function formatJobStatus(status) {
|
|
17246
|
+
if (status === "success") return chalk8.green("success");
|
|
17247
|
+
if (status === "failure") return chalk8.red("failure");
|
|
17248
|
+
if (status === "timeout") return chalk8.yellow("timeout");
|
|
17249
|
+
if (status === "rate_limited") return chalk8.magenta("rate_limited");
|
|
17250
|
+
if (status === "skipped") return chalk8.dim("skipped");
|
|
17251
|
+
return chalk8.dim(status);
|
|
17252
|
+
}
|
|
17253
|
+
function getProjectName2(projectPath) {
|
|
17254
|
+
return path42.basename(projectPath) || projectPath;
|
|
17255
|
+
}
|
|
17256
|
+
function formatProvider(providerKey) {
|
|
17257
|
+
return providerKey.split(":")[0] || providerKey;
|
|
17258
|
+
}
|
|
17259
|
+
function summaryCommand(program2) {
|
|
17260
|
+
program2.command("summary").description("Show a summary of recent Night Watch activity").option(
|
|
17261
|
+
"--hours <n>",
|
|
17262
|
+
"Time window in hours (default: 12)",
|
|
17263
|
+
String(DEFAULT_SUMMARY_WINDOW_HOURS)
|
|
17264
|
+
).option("--json", "Output summary as JSON").action(async (options) => {
|
|
17265
|
+
try {
|
|
17266
|
+
const projectDir = process.cwd();
|
|
17267
|
+
const config = loadConfig(projectDir);
|
|
17268
|
+
const hours = parseInt(options.hours || String(DEFAULT_SUMMARY_WINDOW_HOURS), 10);
|
|
17269
|
+
if (isNaN(hours) || hours <= 0) {
|
|
17270
|
+
console.error("Error: --hours must be a positive integer");
|
|
17271
|
+
process.exit(1);
|
|
17272
|
+
}
|
|
17273
|
+
const data = await getSummaryData(projectDir, hours, config.branchPatterns);
|
|
17274
|
+
if (options.json) {
|
|
17275
|
+
console.log(JSON.stringify(data, null, 2));
|
|
17276
|
+
return;
|
|
17277
|
+
}
|
|
17278
|
+
console.log();
|
|
17279
|
+
console.log(chalk8.bold.cyan(`Night Watch Summary (last ${data.windowHours}h)`));
|
|
17280
|
+
console.log(chalk8.dim("\u2500".repeat(40)));
|
|
17281
|
+
console.log();
|
|
17282
|
+
if (data.jobRuns.length === 0) {
|
|
17283
|
+
info("No recent activity in this time window.");
|
|
17284
|
+
console.log();
|
|
17285
|
+
} else {
|
|
17286
|
+
const countParts = [];
|
|
17287
|
+
if (data.counts.succeeded > 0) {
|
|
17288
|
+
countParts.push(chalk8.green(`\u2713 ${data.counts.succeeded} succeeded`));
|
|
17289
|
+
}
|
|
17290
|
+
if (data.counts.failed > 0) {
|
|
17291
|
+
countParts.push(chalk8.red(`\u2717 ${data.counts.failed} failed`));
|
|
17292
|
+
}
|
|
17293
|
+
if (data.counts.timedOut > 0) {
|
|
17294
|
+
countParts.push(chalk8.yellow(`\u23F1 ${data.counts.timedOut} timed out`));
|
|
17295
|
+
}
|
|
17296
|
+
if (data.counts.rateLimited > 0) {
|
|
17297
|
+
countParts.push(chalk8.magenta(`\u23F3 ${data.counts.rateLimited} rate limited`));
|
|
17298
|
+
}
|
|
17299
|
+
if (data.counts.skipped > 0) {
|
|
17300
|
+
countParts.push(chalk8.dim(`${data.counts.skipped} skipped`));
|
|
17301
|
+
}
|
|
17302
|
+
console.log(`Jobs Executed: ${data.counts.total}`);
|
|
17303
|
+
if (countParts.length > 0) {
|
|
17304
|
+
console.log(` ${countParts.join(" ")}`);
|
|
17305
|
+
}
|
|
17306
|
+
console.log();
|
|
17307
|
+
const table = createTable({
|
|
17308
|
+
head: ["Job", "Status", "Project", "Provider", "Duration"],
|
|
17309
|
+
colWidths: [12, 12, 20, 12, 12]
|
|
17310
|
+
});
|
|
17311
|
+
for (const run2 of data.jobRuns.slice(0, 10)) {
|
|
17312
|
+
table.push([
|
|
17313
|
+
run2.jobType,
|
|
17314
|
+
formatJobStatus(run2.status),
|
|
17315
|
+
getProjectName2(run2.projectPath),
|
|
17316
|
+
formatProvider(run2.providerKey),
|
|
17317
|
+
formatDuration2(run2.durationSeconds)
|
|
17318
|
+
]);
|
|
17319
|
+
}
|
|
17320
|
+
console.log(table.toString());
|
|
17321
|
+
if (data.jobRuns.length > 10) {
|
|
17322
|
+
dim(` ... and ${data.jobRuns.length - 10} more`);
|
|
17323
|
+
}
|
|
17324
|
+
console.log();
|
|
17325
|
+
}
|
|
17326
|
+
if (data.openPrs.length > 0) {
|
|
17327
|
+
header(`Open PRs (${data.openPrs.length})`);
|
|
17328
|
+
const prTable = createTable({
|
|
17329
|
+
head: ["#", "Title", "CI", "Score"],
|
|
17330
|
+
colWidths: [6, 40, 10, 8]
|
|
17331
|
+
});
|
|
17332
|
+
for (const pr of data.openPrs) {
|
|
17333
|
+
const title = pr.title.length > 37 ? pr.title.substring(0, 34) + "..." : pr.title;
|
|
17334
|
+
prTable.push([
|
|
17335
|
+
String(pr.number),
|
|
17336
|
+
title,
|
|
17337
|
+
formatCiStatus2(pr.ciStatus),
|
|
17338
|
+
formatReviewScore2(pr.reviewScore)
|
|
17339
|
+
]);
|
|
17340
|
+
}
|
|
17341
|
+
console.log(prTable.toString());
|
|
17342
|
+
console.log();
|
|
17343
|
+
}
|
|
17344
|
+
if (data.pendingQueueItems.length > 0) {
|
|
17345
|
+
const jobTypes = [...new Set(data.pendingQueueItems.map((item) => item.jobType))];
|
|
17346
|
+
const projectNames = [...new Set(data.pendingQueueItems.map((item) => item.projectName))];
|
|
17347
|
+
dim(
|
|
17348
|
+
`Queue: ${data.pendingQueueItems.length} pending (${jobTypes.join(", ")}) for ${projectNames.join(", ")}`
|
|
17349
|
+
);
|
|
17350
|
+
console.log();
|
|
17351
|
+
}
|
|
17352
|
+
if (data.actionItems.length > 0) {
|
|
17353
|
+
console.log(chalk8.yellow("\u26A0 Action needed:"));
|
|
17354
|
+
for (const item of data.actionItems) {
|
|
17355
|
+
console.log(` \u2022 ${item}`);
|
|
17356
|
+
}
|
|
17357
|
+
} else {
|
|
17358
|
+
console.log(chalk8.green("\u2713 No action needed \u2014 all jobs healthy."));
|
|
17359
|
+
}
|
|
17360
|
+
console.log();
|
|
17361
|
+
} catch (error2) {
|
|
17362
|
+
console.error(
|
|
17363
|
+
`Error getting summary: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
17364
|
+
);
|
|
17365
|
+
process.exit(1);
|
|
17366
|
+
}
|
|
17367
|
+
});
|
|
17368
|
+
}
|
|
17369
|
+
|
|
17370
|
+
// src/commands/resolve.ts
|
|
17371
|
+
init_dist();
|
|
17372
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
17373
|
+
import * as path43 from "path";
|
|
17374
|
+
function buildEnvVars6(config, options) {
|
|
17375
|
+
const env = buildBaseEnvVars(config, "pr-resolver", options.dryRun);
|
|
17376
|
+
env.NW_PR_RESOLVER_MAX_RUNTIME = String(config.prResolver.maxRuntime);
|
|
17377
|
+
env.NW_PR_RESOLVER_MAX_PRS_PER_RUN = String(config.prResolver.maxPrsPerRun);
|
|
17378
|
+
env.NW_PR_RESOLVER_PER_PR_TIMEOUT = String(config.prResolver.perPrTimeout);
|
|
17379
|
+
env.NW_PR_RESOLVER_AI_CONFLICT_RESOLUTION = config.prResolver.aiConflictResolution ? "1" : "0";
|
|
17380
|
+
env.NW_PR_RESOLVER_AI_REVIEW_RESOLUTION = config.prResolver.aiReviewResolution ? "1" : "0";
|
|
17381
|
+
env.NW_PR_RESOLVER_READY_LABEL = config.prResolver.readyLabel;
|
|
17382
|
+
env.NW_PR_RESOLVER_BRANCH_PATTERNS = config.prResolver.branchPatterns.join(",");
|
|
17383
|
+
return env;
|
|
17384
|
+
}
|
|
17385
|
+
function applyCliOverrides5(config, options) {
|
|
17386
|
+
const overridden = { ...config, prResolver: { ...config.prResolver } };
|
|
17387
|
+
if (options.timeout) {
|
|
17388
|
+
const timeout = parseInt(options.timeout, 10);
|
|
17389
|
+
if (!isNaN(timeout)) {
|
|
17390
|
+
overridden.prResolver.maxRuntime = timeout;
|
|
17391
|
+
}
|
|
17392
|
+
}
|
|
17393
|
+
if (options.provider) {
|
|
17394
|
+
overridden._cliProviderOverride = options.provider;
|
|
17395
|
+
}
|
|
17396
|
+
return overridden;
|
|
17397
|
+
}
|
|
17398
|
+
function getOpenPrs() {
|
|
17399
|
+
try {
|
|
17400
|
+
const args = ["pr", "list", "--state", "open", "--json", "number,title,headRefName,mergeable"];
|
|
17401
|
+
const result = execFileSync7("gh", args, {
|
|
17402
|
+
encoding: "utf-8",
|
|
17403
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
17404
|
+
});
|
|
17405
|
+
const prs = JSON.parse(result.trim() || "[]");
|
|
17406
|
+
return prs.map(
|
|
17407
|
+
(pr) => ({
|
|
17408
|
+
number: pr.number,
|
|
17409
|
+
title: pr.title,
|
|
17410
|
+
branch: pr.headRefName,
|
|
17411
|
+
mergeable: pr.mergeable
|
|
17412
|
+
})
|
|
17413
|
+
);
|
|
17414
|
+
} catch {
|
|
17415
|
+
return [];
|
|
17416
|
+
}
|
|
17417
|
+
}
|
|
17418
|
+
function resolveCommand(program2) {
|
|
17419
|
+
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) => {
|
|
17420
|
+
const projectDir = process.cwd();
|
|
17421
|
+
let config = loadConfig(projectDir);
|
|
17422
|
+
config = applyCliOverrides5(config, options);
|
|
17423
|
+
if (!config.prResolver.enabled && !options.dryRun) {
|
|
17424
|
+
info("PR resolver is disabled in config; skipping.");
|
|
17425
|
+
process.exit(0);
|
|
17426
|
+
}
|
|
17427
|
+
const envVars = buildEnvVars6(config, options);
|
|
17428
|
+
const scriptPath = getScriptPath("night-watch-pr-resolver-cron.sh");
|
|
17429
|
+
if (options.dryRun) {
|
|
17430
|
+
header("Dry Run: PR Resolver");
|
|
17431
|
+
const resolverProvider = resolveJobProvider(config, "pr-resolver");
|
|
17432
|
+
header("Configuration");
|
|
17433
|
+
const configTable = createTable({ head: ["Setting", "Value"] });
|
|
17434
|
+
configTable.push(["Provider", resolverProvider]);
|
|
17435
|
+
configTable.push([
|
|
17436
|
+
"Max Runtime",
|
|
17437
|
+
`${config.prResolver.maxRuntime}s (${Math.floor(config.prResolver.maxRuntime / 60)}min)`
|
|
17438
|
+
]);
|
|
17439
|
+
configTable.push([
|
|
17440
|
+
"Max PRs Per Run",
|
|
17441
|
+
config.prResolver.maxPrsPerRun === 0 ? "Unlimited" : String(config.prResolver.maxPrsPerRun)
|
|
17442
|
+
]);
|
|
17443
|
+
configTable.push(["Per-PR Timeout", `${config.prResolver.perPrTimeout}s`]);
|
|
17444
|
+
configTable.push([
|
|
17445
|
+
"AI Conflict Resolution",
|
|
17446
|
+
config.prResolver.aiConflictResolution ? "Enabled" : "Disabled"
|
|
17447
|
+
]);
|
|
17448
|
+
configTable.push([
|
|
17449
|
+
"AI Review Resolution",
|
|
17450
|
+
config.prResolver.aiReviewResolution ? "Enabled" : "Disabled"
|
|
17451
|
+
]);
|
|
17452
|
+
configTable.push(["Ready Label", config.prResolver.readyLabel]);
|
|
17453
|
+
configTable.push([
|
|
17454
|
+
"Branch Patterns",
|
|
17455
|
+
config.prResolver.branchPatterns.length > 0 ? config.prResolver.branchPatterns.join(", ") : "(all)"
|
|
17456
|
+
]);
|
|
17457
|
+
console.log(configTable.toString());
|
|
17458
|
+
header("Open PRs");
|
|
17459
|
+
const openPrs = getOpenPrs();
|
|
17460
|
+
if (openPrs.length === 0) {
|
|
17461
|
+
dim(" (no open PRs found)");
|
|
17462
|
+
} else {
|
|
17463
|
+
for (const pr of openPrs) {
|
|
17464
|
+
const conflictStatus = pr.mergeable === "CONFLICTING" ? " [CONFLICT]" : "";
|
|
17465
|
+
info(`#${pr.number}: ${pr.title}${conflictStatus}`);
|
|
17466
|
+
dim(` Branch: ${pr.branch}`);
|
|
17467
|
+
}
|
|
17468
|
+
}
|
|
17469
|
+
header("Environment Variables");
|
|
17470
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
17471
|
+
dim(` ${key}=${value}`);
|
|
17472
|
+
}
|
|
17473
|
+
header("Command");
|
|
17474
|
+
dim(` bash ${scriptPath} ${projectDir}`);
|
|
17475
|
+
console.log();
|
|
17476
|
+
process.exit(0);
|
|
17477
|
+
}
|
|
17478
|
+
const spinner = createSpinner("Running PR resolver...");
|
|
17479
|
+
spinner.start();
|
|
17480
|
+
try {
|
|
17481
|
+
await maybeApplyCronSchedulingDelay(config, "pr-resolver", projectDir);
|
|
17482
|
+
const { exitCode, stdout, stderr } = await executeScriptWithOutput(
|
|
17483
|
+
scriptPath,
|
|
17484
|
+
[projectDir],
|
|
17485
|
+
envVars
|
|
17486
|
+
);
|
|
17487
|
+
const scriptResult = parseScriptResult(`${stdout}
|
|
17488
|
+
${stderr}`);
|
|
17489
|
+
if (exitCode === 0) {
|
|
17490
|
+
if (scriptResult?.status === "queued") {
|
|
17491
|
+
spinner.succeed("PR resolver queued \u2014 another job is currently running");
|
|
17492
|
+
} else if (scriptResult?.status?.startsWith("skip_")) {
|
|
17493
|
+
spinner.succeed("PR resolver completed (no PRs needed resolution)");
|
|
17494
|
+
} else {
|
|
17495
|
+
spinner.succeed("PR resolver completed successfully");
|
|
17496
|
+
}
|
|
17497
|
+
} else {
|
|
17498
|
+
spinner.fail(`PR resolver exited with code ${exitCode}`);
|
|
17499
|
+
}
|
|
17500
|
+
const notificationEvent = exitCode === 0 ? "pr_resolver_completed" : "pr_resolver_failed";
|
|
17501
|
+
await sendNotifications(config, {
|
|
17502
|
+
event: notificationEvent,
|
|
17503
|
+
projectName: path43.basename(projectDir),
|
|
17504
|
+
exitCode,
|
|
17505
|
+
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL)
|
|
17506
|
+
});
|
|
17507
|
+
process.exit(exitCode);
|
|
17508
|
+
} catch (err) {
|
|
17509
|
+
spinner.fail("Failed to execute resolve command");
|
|
17510
|
+
error(`${err instanceof Error ? err.message : String(err)}`);
|
|
17511
|
+
process.exit(1);
|
|
17512
|
+
}
|
|
17513
|
+
});
|
|
17514
|
+
}
|
|
17515
|
+
|
|
16916
17516
|
// src/cli.ts
|
|
16917
17517
|
var __filename5 = fileURLToPath6(import.meta.url);
|
|
16918
17518
|
var __dirname5 = dirname12(__filename5);
|
|
@@ -16954,4 +17554,6 @@ program.addCommand(createStateCommand());
|
|
|
16954
17554
|
boardCommand(program);
|
|
16955
17555
|
queueCommand(program);
|
|
16956
17556
|
notifyCommand(program);
|
|
17557
|
+
summaryCommand(program);
|
|
17558
|
+
resolveCommand(program);
|
|
16957
17559
|
program.parse();
|