@jonit-dev/night-watch-cli 1.7.50 → 1.7.52

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.
Files changed (36) hide show
  1. package/dist/cli.js +393 -229
  2. package/dist/commands/audit.d.ts.map +1 -1
  3. package/dist/commands/audit.js +6 -24
  4. package/dist/commands/audit.js.map +1 -1
  5. package/dist/commands/init.d.ts.map +1 -1
  6. package/dist/commands/init.js +20 -23
  7. package/dist/commands/init.js.map +1 -1
  8. package/dist/commands/qa.d.ts.map +1 -1
  9. package/dist/commands/qa.js +16 -4
  10. package/dist/commands/qa.js.map +1 -1
  11. package/dist/commands/review.d.ts.map +1 -1
  12. package/dist/commands/review.js +6 -4
  13. package/dist/commands/review.js.map +1 -1
  14. package/dist/commands/shared/env-builder.d.ts +5 -0
  15. package/dist/commands/shared/env-builder.d.ts.map +1 -1
  16. package/dist/commands/shared/env-builder.js +32 -0
  17. package/dist/commands/shared/env-builder.js.map +1 -1
  18. package/dist/commands/slice.d.ts +8 -0
  19. package/dist/commands/slice.d.ts.map +1 -1
  20. package/dist/commands/slice.js +90 -2
  21. package/dist/commands/slice.js.map +1 -1
  22. package/dist/scripts/night-watch-audit-cron.sh +17 -4
  23. package/dist/scripts/night-watch-cron.sh +19 -5
  24. package/dist/scripts/night-watch-helpers.sh +137 -0
  25. package/dist/scripts/night-watch-pr-reviewer-cron.sh +268 -5
  26. package/dist/scripts/night-watch-qa-cron.sh +427 -22
  27. package/dist/scripts/night-watch-slicer-cron.sh +14 -3
  28. package/dist/templates/audit.md +87 -0
  29. package/dist/templates/executor.md +67 -0
  30. package/dist/templates/night-watch-pr-reviewer.md +33 -0
  31. package/dist/templates/night-watch.config.json +31 -1
  32. package/dist/templates/night-watch.md +31 -0
  33. package/dist/templates/pr-reviewer.md +203 -0
  34. package/dist/templates/qa.md +157 -0
  35. package/dist/templates/slicer.md +234 -0
  36. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -4,7 +4,6 @@ import 'reflect-metadata';
4
4
  // dist/cli.js
5
5
  import "reflect-metadata";
6
6
  import "reflect-metadata";
7
- import "reflect-metadata";
8
7
  import * as fs from "fs";
9
8
  import * as path from "path";
10
9
  import { fileURLToPath } from "url";
@@ -84,7 +83,7 @@ import "reflect-metadata";
84
83
  import { Command as Command2 } from "commander";
85
84
  import { existsSync as existsSync30, readFileSync as readFileSync18 } from "fs";
86
85
  import { fileURLToPath as fileURLToPath4 } from "url";
87
- import { dirname as dirname8, join as join33 } from "path";
86
+ import { dirname as dirname8, join as join34 } from "path";
88
87
  import fs18 from "fs";
89
88
  import path17 from "path";
90
89
  import { execSync as execSync3 } from "child_process";
@@ -286,7 +285,9 @@ var init_constants = __esm({
286
285
  roadmapPath: "ROADMAP.md",
287
286
  autoScanInterval: 300,
288
287
  slicerSchedule: DEFAULT_SLICER_SCHEDULE,
289
- slicerMaxRuntime: DEFAULT_SLICER_MAX_RUNTIME
288
+ slicerMaxRuntime: DEFAULT_SLICER_MAX_RUNTIME,
289
+ priorityMode: "roadmap-first",
290
+ issueColumn: "Draft"
290
291
  };
291
292
  DEFAULT_TEMPLATES_DIR = ".night-watch/templates";
292
293
  DEFAULT_BOARD_PROVIDER = {
@@ -441,6 +442,10 @@ function normalizeConfig(rawConfig) {
441
442
  normalized.provider = validateProvider(String(rawConfig.provider ?? "")) ?? void 0;
442
443
  normalized.executorEnabled = readBoolean(rawConfig.executorEnabled);
443
444
  normalized.reviewerEnabled = readBoolean(rawConfig.reviewerEnabled);
445
+ const providerLabelVal = readString(rawConfig.providerLabel);
446
+ if (providerLabelVal) {
447
+ normalized.providerLabel = providerLabelVal;
448
+ }
444
449
  const rawProviderEnv = readObject(rawConfig.providerEnv);
445
450
  if (rawProviderEnv) {
446
451
  const env = {};
@@ -474,12 +479,18 @@ function normalizeConfig(rawConfig) {
474
479
  normalized.prdPriority = readStringArray(rawConfig.prdPriority);
475
480
  const rawRoadmapScanner = readObject(rawConfig.roadmapScanner);
476
481
  if (rawRoadmapScanner) {
482
+ const priorityModeRaw = readString(rawRoadmapScanner.priorityMode);
483
+ const priorityMode = priorityModeRaw === "roadmap-first" || priorityModeRaw === "audit-first" ? priorityModeRaw : DEFAULT_ROADMAP_SCANNER.priorityMode;
484
+ const issueColumnRaw = readString(rawRoadmapScanner.issueColumn);
485
+ const issueColumn = issueColumnRaw === "Draft" || issueColumnRaw === "Ready" ? issueColumnRaw : DEFAULT_ROADMAP_SCANNER.issueColumn;
477
486
  const roadmapScanner = {
478
487
  enabled: readBoolean(rawRoadmapScanner.enabled) ?? DEFAULT_ROADMAP_SCANNER.enabled,
479
488
  roadmapPath: readString(rawRoadmapScanner.roadmapPath) ?? DEFAULT_ROADMAP_SCANNER.roadmapPath,
480
489
  autoScanInterval: readNumber(rawRoadmapScanner.autoScanInterval) ?? DEFAULT_ROADMAP_SCANNER.autoScanInterval,
481
490
  slicerSchedule: readString(rawRoadmapScanner.slicerSchedule) ?? DEFAULT_ROADMAP_SCANNER.slicerSchedule,
482
- slicerMaxRuntime: readNumber(rawRoadmapScanner.slicerMaxRuntime) ?? DEFAULT_ROADMAP_SCANNER.slicerMaxRuntime
491
+ slicerMaxRuntime: readNumber(rawRoadmapScanner.slicerMaxRuntime) ?? DEFAULT_ROADMAP_SCANNER.slicerMaxRuntime,
492
+ priorityMode,
493
+ issueColumn
483
494
  };
484
495
  if (roadmapScanner.autoScanInterval < 30) {
485
496
  roadmapScanner.autoScanInterval = 30;
@@ -601,130 +612,30 @@ function sanitizeReviewerRetryDelay(value, fallback) {
601
612
  return 300;
602
613
  return normalized;
603
614
  }
615
+ function mergeConfigLayer(base, layer) {
616
+ for (const _key of Object.keys(layer)) {
617
+ const value = layer[_key];
618
+ if (value === void 0)
619
+ continue;
620
+ if (_key === "providerEnv" || _key === "boardProvider" || _key === "qa" || _key === "audit") {
621
+ base[_key] = {
622
+ ...base[_key],
623
+ ...value
624
+ };
625
+ } else if (_key === "roadmapScanner" || _key === "jobProviders") {
626
+ base[_key] = { ...value };
627
+ } else if (_key === "branchPatterns" || _key === "prdPriority") {
628
+ base[_key] = [...value];
629
+ } else {
630
+ base[_key] = value;
631
+ }
632
+ }
633
+ }
604
634
  function mergeConfigs(base, fileConfig, envConfig) {
605
635
  const merged = { ...base };
606
- if (fileConfig) {
607
- if (fileConfig.defaultBranch !== void 0)
608
- merged.defaultBranch = fileConfig.defaultBranch;
609
- if (fileConfig.prdDir !== void 0)
610
- merged.prdDir = fileConfig.prdDir;
611
- if (fileConfig.maxRuntime !== void 0)
612
- merged.maxRuntime = fileConfig.maxRuntime;
613
- if (fileConfig.reviewerMaxRuntime !== void 0)
614
- merged.reviewerMaxRuntime = fileConfig.reviewerMaxRuntime;
615
- if (fileConfig.branchPrefix !== void 0)
616
- merged.branchPrefix = fileConfig.branchPrefix;
617
- if (fileConfig.branchPatterns !== void 0)
618
- merged.branchPatterns = [...fileConfig.branchPatterns];
619
- if (fileConfig.minReviewScore !== void 0)
620
- merged.minReviewScore = fileConfig.minReviewScore;
621
- if (fileConfig.maxLogSize !== void 0)
622
- merged.maxLogSize = fileConfig.maxLogSize;
623
- if (fileConfig.cronSchedule !== void 0)
624
- merged.cronSchedule = fileConfig.cronSchedule;
625
- if (fileConfig.reviewerSchedule !== void 0)
626
- merged.reviewerSchedule = fileConfig.reviewerSchedule;
627
- if (fileConfig.cronScheduleOffset !== void 0)
628
- merged.cronScheduleOffset = fileConfig.cronScheduleOffset;
629
- if (fileConfig.maxRetries !== void 0)
630
- merged.maxRetries = fileConfig.maxRetries;
631
- if (fileConfig.reviewerMaxRetries !== void 0)
632
- merged.reviewerMaxRetries = fileConfig.reviewerMaxRetries;
633
- if (fileConfig.reviewerRetryDelay !== void 0)
634
- merged.reviewerRetryDelay = fileConfig.reviewerRetryDelay;
635
- if (fileConfig.provider !== void 0)
636
- merged.provider = fileConfig.provider;
637
- if (fileConfig.executorEnabled !== void 0)
638
- merged.executorEnabled = fileConfig.executorEnabled;
639
- if (fileConfig.reviewerEnabled !== void 0)
640
- merged.reviewerEnabled = fileConfig.reviewerEnabled;
641
- if (fileConfig.providerEnv !== void 0)
642
- merged.providerEnv = { ...merged.providerEnv, ...fileConfig.providerEnv };
643
- if (fileConfig.notifications !== void 0)
644
- merged.notifications = fileConfig.notifications;
645
- if (fileConfig.prdPriority !== void 0)
646
- merged.prdPriority = [...fileConfig.prdPriority];
647
- if (fileConfig.roadmapScanner !== void 0)
648
- merged.roadmapScanner = { ...fileConfig.roadmapScanner };
649
- if (fileConfig.templatesDir !== void 0)
650
- merged.templatesDir = fileConfig.templatesDir;
651
- if (fileConfig.boardProvider !== void 0)
652
- merged.boardProvider = { ...merged.boardProvider, ...fileConfig.boardProvider };
653
- if (fileConfig.autoMerge !== void 0)
654
- merged.autoMerge = fileConfig.autoMerge;
655
- if (fileConfig.autoMergeMethod !== void 0)
656
- merged.autoMergeMethod = fileConfig.autoMergeMethod;
657
- if (fileConfig.fallbackOnRateLimit !== void 0)
658
- merged.fallbackOnRateLimit = fileConfig.fallbackOnRateLimit;
659
- if (fileConfig.claudeModel !== void 0)
660
- merged.claudeModel = fileConfig.claudeModel;
661
- if (fileConfig.qa !== void 0)
662
- merged.qa = { ...merged.qa, ...fileConfig.qa };
663
- if (fileConfig.audit !== void 0)
664
- merged.audit = { ...merged.audit, ...fileConfig.audit };
665
- if (fileConfig.jobProviders !== void 0)
666
- merged.jobProviders = { ...fileConfig.jobProviders };
667
- }
668
- if (envConfig.defaultBranch !== void 0)
669
- merged.defaultBranch = envConfig.defaultBranch;
670
- if (envConfig.prdDir !== void 0)
671
- merged.prdDir = envConfig.prdDir;
672
- if (envConfig.maxRuntime !== void 0)
673
- merged.maxRuntime = envConfig.maxRuntime;
674
- if (envConfig.reviewerMaxRuntime !== void 0)
675
- merged.reviewerMaxRuntime = envConfig.reviewerMaxRuntime;
676
- if (envConfig.branchPrefix !== void 0)
677
- merged.branchPrefix = envConfig.branchPrefix;
678
- if (envConfig.branchPatterns !== void 0)
679
- merged.branchPatterns = [...envConfig.branchPatterns];
680
- if (envConfig.minReviewScore !== void 0)
681
- merged.minReviewScore = envConfig.minReviewScore;
682
- if (envConfig.maxLogSize !== void 0)
683
- merged.maxLogSize = envConfig.maxLogSize;
684
- if (envConfig.cronSchedule !== void 0)
685
- merged.cronSchedule = envConfig.cronSchedule;
686
- if (envConfig.reviewerSchedule !== void 0)
687
- merged.reviewerSchedule = envConfig.reviewerSchedule;
688
- if (envConfig.cronScheduleOffset !== void 0)
689
- merged.cronScheduleOffset = envConfig.cronScheduleOffset;
690
- if (envConfig.maxRetries !== void 0)
691
- merged.maxRetries = envConfig.maxRetries;
692
- if (envConfig.reviewerMaxRetries !== void 0)
693
- merged.reviewerMaxRetries = envConfig.reviewerMaxRetries;
694
- if (envConfig.reviewerRetryDelay !== void 0)
695
- merged.reviewerRetryDelay = envConfig.reviewerRetryDelay;
696
- if (envConfig.provider !== void 0)
697
- merged.provider = envConfig.provider;
698
- if (envConfig.executorEnabled !== void 0)
699
- merged.executorEnabled = envConfig.executorEnabled;
700
- if (envConfig.reviewerEnabled !== void 0)
701
- merged.reviewerEnabled = envConfig.reviewerEnabled;
702
- if (envConfig.providerEnv !== void 0)
703
- merged.providerEnv = { ...merged.providerEnv, ...envConfig.providerEnv };
704
- if (envConfig.notifications !== void 0)
705
- merged.notifications = envConfig.notifications;
706
- if (envConfig.prdPriority !== void 0)
707
- merged.prdPriority = [...envConfig.prdPriority];
708
- if (envConfig.roadmapScanner !== void 0)
709
- merged.roadmapScanner = { ...envConfig.roadmapScanner };
710
- if (envConfig.templatesDir !== void 0)
711
- merged.templatesDir = envConfig.templatesDir;
712
- if (envConfig.boardProvider !== void 0)
713
- merged.boardProvider = { ...merged.boardProvider, ...envConfig.boardProvider };
714
- if (envConfig.autoMerge !== void 0)
715
- merged.autoMerge = envConfig.autoMerge;
716
- if (envConfig.autoMergeMethod !== void 0)
717
- merged.autoMergeMethod = envConfig.autoMergeMethod;
718
- if (envConfig.fallbackOnRateLimit !== void 0)
719
- merged.fallbackOnRateLimit = envConfig.fallbackOnRateLimit;
720
- if (envConfig.claudeModel !== void 0)
721
- merged.claudeModel = envConfig.claudeModel;
722
- if (envConfig.qa !== void 0)
723
- merged.qa = { ...merged.qa, ...envConfig.qa };
724
- if (envConfig.audit !== void 0)
725
- merged.audit = { ...merged.audit, ...envConfig.audit };
726
- if (envConfig.jobProviders !== void 0)
727
- merged.jobProviders = { ...envConfig.jobProviders };
636
+ if (fileConfig)
637
+ mergeConfigLayer(merged, fileConfig);
638
+ mergeConfigLayer(merged, envConfig);
728
639
  merged.maxRetries = sanitizeMaxRetries(merged.maxRetries, DEFAULT_MAX_RETRIES);
729
640
  merged.reviewerMaxRetries = sanitizeReviewerMaxRetries(merged.reviewerMaxRetries, DEFAULT_REVIEWER_MAX_RETRIES);
730
641
  merged.reviewerRetryDelay = sanitizeReviewerRetryDelay(merged.reviewerRetryDelay, DEFAULT_REVIEWER_RETRY_DELAY);
@@ -859,6 +770,24 @@ function loadConfig(projectDir) {
859
770
  };
860
771
  }
861
772
  }
773
+ if (process.env.NW_PLANNER_ISSUE_COLUMN) {
774
+ const issueColumn = process.env.NW_PLANNER_ISSUE_COLUMN;
775
+ if (issueColumn === "Draft" || issueColumn === "Ready") {
776
+ envConfig.roadmapScanner = {
777
+ ...envConfig.roadmapScanner ?? DEFAULT_ROADMAP_SCANNER,
778
+ issueColumn
779
+ };
780
+ }
781
+ }
782
+ if (process.env.NW_PLANNER_PRIORITY_MODE) {
783
+ const priorityMode = process.env.NW_PLANNER_PRIORITY_MODE;
784
+ if (priorityMode === "roadmap-first" || priorityMode === "audit-first") {
785
+ envConfig.roadmapScanner = {
786
+ ...envConfig.roadmapScanner ?? DEFAULT_ROADMAP_SCANNER,
787
+ priorityMode
788
+ };
789
+ }
790
+ }
862
791
  if (process.env.NW_AUTO_MERGE) {
863
792
  const autoMerge = parseBoolean(process.env.NW_AUTO_MERGE);
864
793
  if (autoMerge !== null) {
@@ -5258,7 +5187,8 @@ function parsePrDetails(raw) {
5258
5187
  body: details.body ?? "",
5259
5188
  additions: details.additions ?? 0,
5260
5189
  deletions: details.deletions ?? 0,
5261
- changedFiles: details.changedFiles ?? 0
5190
+ changedFiles: details.changedFiles ?? 0,
5191
+ headRefName: details.headRefName ?? ""
5262
5192
  };
5263
5193
  } catch {
5264
5194
  return null;
@@ -5266,12 +5196,60 @@ function parsePrDetails(raw) {
5266
5196
  }
5267
5197
  function fetchPrBySelector(selector, cwd) {
5268
5198
  try {
5269
- const output = execFileSync2("gh", ["pr", "view", selector, "--json", "number,title,url,body,additions,deletions,changedFiles"], { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
5199
+ const output = execFileSync2("gh", [
5200
+ "pr",
5201
+ "view",
5202
+ selector,
5203
+ "--json",
5204
+ "number,title,url,body,additions,deletions,changedFiles,headRefName"
5205
+ ], { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
5270
5206
  return parsePrDetails(output);
5271
5207
  } catch {
5272
5208
  return null;
5273
5209
  }
5274
5210
  }
5211
+ function decodeBase64Value(value) {
5212
+ try {
5213
+ return Buffer.from(value, "base64").toString("utf-8");
5214
+ } catch {
5215
+ return "";
5216
+ }
5217
+ }
5218
+ function splitNonEmptyLines(value) {
5219
+ return value.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
5220
+ }
5221
+ function getQaCommentBodiesBase64(prNumber, cwd, repo) {
5222
+ const bodies = [];
5223
+ try {
5224
+ const ghPrOutput = execFileSync2("gh", ["pr", "view", String(prNumber), "--json", "comments", "--jq", ".comments[]?.body | @base64"], { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
5225
+ bodies.push(...splitNonEmptyLines(ghPrOutput));
5226
+ } catch {
5227
+ }
5228
+ if (repo) {
5229
+ try {
5230
+ const issueCommentsOutput = execFileSync2("gh", ["api", `repos/${repo}/issues/${prNumber}/comments`, "--jq", ".[].body | @base64"], { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
5231
+ bodies.push(...splitNonEmptyLines(issueCommentsOutput));
5232
+ } catch {
5233
+ }
5234
+ }
5235
+ return bodies;
5236
+ }
5237
+ function normalizeQaScreenshotUrl(rawUrl, repo) {
5238
+ const url = rawUrl.trim();
5239
+ if (url.length === 0) {
5240
+ return "";
5241
+ }
5242
+ if (/^https?:\/\//i.test(url)) {
5243
+ return url;
5244
+ }
5245
+ if (repo && url.startsWith("../blob/")) {
5246
+ return `https://github.com/${repo}/${url.replace(/^\.\.\//, "")}`;
5247
+ }
5248
+ if (repo && url.startsWith("blob/")) {
5249
+ return `https://github.com/${repo}/${url}`;
5250
+ }
5251
+ return url;
5252
+ }
5275
5253
  function fetchPrDetailsForBranch(branchName, cwd) {
5276
5254
  return fetchPrBySelector(branchName, cwd);
5277
5255
  }
@@ -5306,6 +5284,44 @@ function fetchReviewedPrDetails(branchPatterns, cwd) {
5306
5284
  return null;
5307
5285
  }
5308
5286
  }
5287
+ function fetchLatestQaCommentBody(prNumber, cwd, repo) {
5288
+ const encodedBodies = getQaCommentBodiesBase64(prNumber, cwd, repo);
5289
+ let latestQaComment = null;
5290
+ for (const encoded of encodedBodies) {
5291
+ const decoded = decodeBase64Value(encoded);
5292
+ if (decoded.includes(QA_COMMENT_MARKER)) {
5293
+ latestQaComment = decoded;
5294
+ }
5295
+ }
5296
+ return latestQaComment;
5297
+ }
5298
+ function extractQaScreenshotUrls(commentBody, repo) {
5299
+ if (!commentBody || commentBody.trim().length === 0) {
5300
+ return [];
5301
+ }
5302
+ const regex = new RegExp(QA_SCREENSHOT_REGEX);
5303
+ const screenshots = [];
5304
+ const seen = /* @__PURE__ */ new Set();
5305
+ let match;
5306
+ match = regex.exec(commentBody);
5307
+ while (match !== null) {
5308
+ const rawUrl = match[1] ?? "";
5309
+ const normalizedUrl = normalizeQaScreenshotUrl(rawUrl, repo);
5310
+ if (normalizedUrl.length > 0 && !seen.has(normalizedUrl)) {
5311
+ seen.add(normalizedUrl);
5312
+ screenshots.push(normalizedUrl);
5313
+ }
5314
+ match = regex.exec(commentBody);
5315
+ }
5316
+ return screenshots;
5317
+ }
5318
+ function fetchQaScreenshotUrlsForPr(prNumber, cwd, repo) {
5319
+ const qaComment = fetchLatestQaCommentBody(prNumber, cwd, repo);
5320
+ if (!qaComment) {
5321
+ return [];
5322
+ }
5323
+ return extractQaScreenshotUrls(qaComment, repo);
5324
+ }
5309
5325
  function extractSummary(body, maxLength = 500) {
5310
5326
  if (!body || body.trim().length === 0) {
5311
5327
  return "";
@@ -5330,9 +5346,13 @@ function extractSummary(body, maxLength = 500) {
5330
5346
  const lastSpace = truncated.lastIndexOf(" ");
5331
5347
  return (lastSpace > 0 ? truncated.slice(0, lastSpace) : truncated) + "...";
5332
5348
  }
5349
+ var QA_COMMENT_MARKER;
5350
+ var QA_SCREENSHOT_REGEX;
5333
5351
  var init_github = __esm({
5334
5352
  "../core/dist/utils/github.js"() {
5335
5353
  "use strict";
5354
+ QA_COMMENT_MARKER = "<!-- night-watch-qa-marker -->";
5355
+ QA_SCREENSHOT_REGEX = /!\[[^\]]*]\(([^)\n]*qa-artifacts\/[^)\n]+)\)/g;
5336
5356
  }
5337
5357
  });
5338
5358
  function rotateLog(logFile, maxSize = DEFAULT_MAX_LOG_SIZE) {
@@ -5546,6 +5566,16 @@ function buildDescription(ctx) {
5546
5566
  lines.push(retryInfo);
5547
5567
  }
5548
5568
  }
5569
+ if (ctx.event === "qa_completed" && (ctx.qaScreenshotUrls?.length ?? 0) > 0) {
5570
+ const screenshotUrls = ctx.qaScreenshotUrls ?? [];
5571
+ lines.push(`QA screenshots: ${screenshotUrls.length}`);
5572
+ for (const [index, screenshotUrl] of screenshotUrls.slice(0, MAX_QA_SCREENSHOTS_IN_NOTIFICATION).entries()) {
5573
+ lines.push(`Screenshot ${index + 1}: ${screenshotUrl}`);
5574
+ }
5575
+ if (screenshotUrls.length > MAX_QA_SCREENSHOTS_IN_NOTIFICATION) {
5576
+ lines.push(`Additional screenshots: ${screenshotUrls.length - MAX_QA_SCREENSHOTS_IN_NOTIFICATION}`);
5577
+ }
5578
+ }
5549
5579
  return lines.join("\n");
5550
5580
  }
5551
5581
  function escapeMarkdownV2(text) {
@@ -5635,6 +5665,17 @@ function formatTelegramPayload(ctx) {
5635
5665
  lines.push(escapeMarkdownV2(`\u{1F501} Attempts: ${ctx.attempts}`));
5636
5666
  }
5637
5667
  }
5668
+ if (ctx.event === "qa_completed" && (ctx.qaScreenshotUrls?.length ?? 0) > 0) {
5669
+ const screenshotUrls = ctx.qaScreenshotUrls ?? [];
5670
+ lines.push("");
5671
+ lines.push(escapeMarkdownV2("\u{1F5BC} Screenshots"));
5672
+ for (const screenshotUrl of screenshotUrls.slice(0, MAX_QA_SCREENSHOTS_IN_NOTIFICATION)) {
5673
+ lines.push(escapeMarkdownV2(screenshotUrl));
5674
+ }
5675
+ if (screenshotUrls.length > MAX_QA_SCREENSHOTS_IN_NOTIFICATION) {
5676
+ lines.push(escapeMarkdownV2(`...and ${screenshotUrls.length - MAX_QA_SCREENSHOTS_IN_NOTIFICATION} more`));
5677
+ }
5678
+ }
5638
5679
  lines.push("");
5639
5680
  lines.push(escapeMarkdownV2(`\u2699\uFE0F Project: ${ctx.projectName} | Provider: ${ctx.provider}`));
5640
5681
  return {
@@ -5702,11 +5743,13 @@ async function sendNotifications(config, ctx) {
5702
5743
  const total = results.length;
5703
5744
  info(`Sent ${sent}/${total} notifications`);
5704
5745
  }
5746
+ var MAX_QA_SCREENSHOTS_IN_NOTIFICATION;
5705
5747
  var init_notify = __esm({
5706
5748
  "../core/dist/utils/notify.js"() {
5707
5749
  "use strict";
5708
5750
  init_ui();
5709
5751
  init_github();
5752
+ MAX_QA_SCREENSHOTS_IN_NOTIFICATION = 3;
5710
5753
  }
5711
5754
  });
5712
5755
  function getOpenBranches(projectDir) {
@@ -6171,7 +6214,7 @@ function loadSlicerTemplate(templateDir) {
6171
6214
  if (cachedTemplate) {
6172
6215
  return cachedTemplate;
6173
6216
  }
6174
- const templatePath = templateDir ? path14.join(templateDir, "night-watch-slicer.md") : path14.resolve(__dirname, "..", "..", "templates", "night-watch-slicer.md");
6217
+ const templatePath = templateDir ? path14.join(templateDir, "slicer.md") : path14.resolve(__dirname, "..", "..", "templates", "slicer.md");
6175
6218
  try {
6176
6219
  cachedTemplate = fs15.readFileSync(templatePath, "utf-8");
6177
6220
  return cachedTemplate;
@@ -6600,7 +6643,8 @@ async function sliceNextItem(projectDir, config) {
6600
6643
  }
6601
6644
  return void 0;
6602
6645
  };
6603
- const targetItem = pickEligibleItem(auditItems) ?? pickEligibleItem(roadmapItems);
6646
+ const roadmapFirst = config.roadmapScanner.priorityMode !== "audit-first";
6647
+ const targetItem = roadmapFirst ? pickEligibleItem(roadmapItems) ?? pickEligibleItem(auditItems) : pickEligibleItem(auditItems) ?? pickEligibleItem(roadmapItems);
6604
6648
  if (!targetItem) {
6605
6649
  return {
6606
6650
  sliced: false,
@@ -7255,10 +7299,13 @@ __export(dist_exports, {
7255
7299
  extractCategory: () => extractCategory,
7256
7300
  extractHorizon: () => extractHorizon,
7257
7301
  extractPriority: () => extractPriority,
7302
+ extractQaScreenshotUrls: () => extractQaScreenshotUrls,
7258
7303
  extractSummary: () => extractSummary,
7304
+ fetchLatestQaCommentBody: () => fetchLatestQaCommentBody,
7259
7305
  fetchPrDetails: () => fetchPrDetails,
7260
7306
  fetchPrDetailsByNumber: () => fetchPrDetailsByNumber,
7261
7307
  fetchPrDetailsForBranch: () => fetchPrDetailsForBranch,
7308
+ fetchQaScreenshotUrlsForPr: () => fetchQaScreenshotUrlsForPr,
7262
7309
  fetchReviewedPrDetails: () => fetchReviewedPrDetails,
7263
7310
  fetchStatusSnapshot: () => fetchStatusSnapshot,
7264
7311
  findEligibleBoardIssue: () => findEligibleBoardIssue,
@@ -7757,21 +7804,21 @@ function initCommand(program2) {
7757
7804
  const customTemplatesDirPath = path17.join(cwd, existingConfig.templatesDir);
7758
7805
  const customTemplatesDir = fs18.existsSync(customTemplatesDirPath) ? customTemplatesDirPath : null;
7759
7806
  const templateSources = [];
7760
- const nwResolution = resolveTemplatePath("night-watch.md", customTemplatesDir, TEMPLATES_DIR);
7761
- const nwResult = processTemplate("night-watch.md", path17.join(instructionsDir, "night-watch.md"), replacements, force, nwResolution.path, nwResolution.source);
7762
- templateSources.push({ name: "night-watch.md", source: nwResult.source });
7807
+ const nwResolution = resolveTemplatePath("executor.md", customTemplatesDir, TEMPLATES_DIR);
7808
+ const nwResult = processTemplate("executor.md", path17.join(instructionsDir, "executor.md"), replacements, force, nwResolution.path, nwResolution.source);
7809
+ templateSources.push({ name: "executor.md", source: nwResult.source });
7763
7810
  const peResolution = resolveTemplatePath("prd-executor.md", customTemplatesDir, TEMPLATES_DIR);
7764
7811
  const peResult = processTemplate("prd-executor.md", path17.join(instructionsDir, "prd-executor.md"), replacements, force, peResolution.path, peResolution.source);
7765
7812
  templateSources.push({ name: "prd-executor.md", source: peResult.source });
7766
- const prResolution = resolveTemplatePath("night-watch-pr-reviewer.md", customTemplatesDir, TEMPLATES_DIR);
7767
- const prResult = processTemplate("night-watch-pr-reviewer.md", path17.join(instructionsDir, "night-watch-pr-reviewer.md"), replacements, force, prResolution.path, prResolution.source);
7768
- templateSources.push({ name: "night-watch-pr-reviewer.md", source: prResult.source });
7769
- const qaResolution = resolveTemplatePath("night-watch-qa.md", customTemplatesDir, TEMPLATES_DIR);
7770
- const qaResult = processTemplate("night-watch-qa.md", path17.join(instructionsDir, "night-watch-qa.md"), replacements, force, qaResolution.path, qaResolution.source);
7771
- templateSources.push({ name: "night-watch-qa.md", source: qaResult.source });
7772
- const auditResolution = resolveTemplatePath("night-watch-audit.md", customTemplatesDir, TEMPLATES_DIR);
7773
- const auditResult = processTemplate("night-watch-audit.md", path17.join(instructionsDir, "night-watch-audit.md"), replacements, force, auditResolution.path, auditResolution.source);
7774
- templateSources.push({ name: "night-watch-audit.md", source: auditResult.source });
7813
+ const prResolution = resolveTemplatePath("pr-reviewer.md", customTemplatesDir, TEMPLATES_DIR);
7814
+ const prResult = processTemplate("pr-reviewer.md", path17.join(instructionsDir, "pr-reviewer.md"), replacements, force, prResolution.path, prResolution.source);
7815
+ templateSources.push({ name: "pr-reviewer.md", source: prResult.source });
7816
+ const qaResolution = resolveTemplatePath("qa.md", customTemplatesDir, TEMPLATES_DIR);
7817
+ const qaResult = processTemplate("qa.md", path17.join(instructionsDir, "qa.md"), replacements, force, qaResolution.path, qaResolution.source);
7818
+ templateSources.push({ name: "qa.md", source: qaResult.source });
7819
+ const auditResolution = resolveTemplatePath("audit.md", customTemplatesDir, TEMPLATES_DIR);
7820
+ const auditResult = processTemplate("audit.md", path17.join(instructionsDir, "audit.md"), replacements, force, auditResolution.path, auditResolution.source);
7821
+ templateSources.push({ name: "audit.md", source: auditResult.source });
7775
7822
  step(8, totalSteps, "Creating configuration file...");
7776
7823
  const configPath = path17.join(cwd, CONFIG_FILE_NAME);
7777
7824
  if (fs18.existsSync(configPath) && !force) {
@@ -7837,15 +7884,12 @@ function initCommand(program2) {
7837
7884
  filesTable.push(["Logs Directory", `${LOG_DIR}/`]);
7838
7885
  filesTable.push([
7839
7886
  "Instructions",
7840
- `instructions/night-watch.md (${templateSources[0].source})`
7887
+ `instructions/executor.md (${templateSources[0].source})`
7841
7888
  ]);
7842
7889
  filesTable.push(["", `instructions/prd-executor.md (${templateSources[1].source})`]);
7843
- filesTable.push([
7844
- "",
7845
- `instructions/night-watch-pr-reviewer.md (${templateSources[2].source})`
7846
- ]);
7847
- filesTable.push(["", `instructions/night-watch-qa.md (${templateSources[3].source})`]);
7848
- filesTable.push(["", `instructions/night-watch-audit.md (${templateSources[4].source})`]);
7890
+ filesTable.push(["", `instructions/pr-reviewer.md (${templateSources[2].source})`]);
7891
+ filesTable.push(["", `instructions/qa.md (${templateSources[3].source})`]);
7892
+ filesTable.push(["", `instructions/audit.md (${templateSources[4].source})`]);
7849
7893
  filesTable.push(["Config File", CONFIG_FILE_NAME]);
7850
7894
  filesTable.push(["Global Registry", "~/.night-watch/projects.json"]);
7851
7895
  console.log(filesTable.toString());
@@ -7861,6 +7905,47 @@ function initCommand(program2) {
7861
7905
  });
7862
7906
  }
7863
7907
  init_dist();
7908
+ init_dist();
7909
+ function deriveProviderLabel(config, jobType) {
7910
+ if (config.providerLabel)
7911
+ return config.providerLabel;
7912
+ const provider = resolveJobProvider(config, jobType);
7913
+ if (provider === "codex")
7914
+ return "Codex";
7915
+ if (config.providerEnv?.ANTHROPIC_BASE_URL)
7916
+ return "Claude (proxy)";
7917
+ return "Claude";
7918
+ }
7919
+ function buildBaseEnvVars(config, jobType, isDryRun) {
7920
+ const env = {};
7921
+ env.NW_PROVIDER_CMD = PROVIDER_COMMANDS[resolveJobProvider(config, jobType)];
7922
+ env.NW_PROVIDER_LABEL = deriveProviderLabel(config, jobType);
7923
+ if (config.defaultBranch) {
7924
+ env.NW_DEFAULT_BRANCH = config.defaultBranch;
7925
+ }
7926
+ if (config.providerEnv) {
7927
+ Object.assign(env, config.providerEnv);
7928
+ }
7929
+ if (isDryRun) {
7930
+ env.NW_DRY_RUN = "1";
7931
+ }
7932
+ env.NW_EXECUTION_CONTEXT = "agent";
7933
+ return env;
7934
+ }
7935
+ function formatProviderDisplay(providerCmd, providerLabel) {
7936
+ const cmd = providerCmd?.trim();
7937
+ if (!cmd)
7938
+ return "unknown";
7939
+ const label2 = providerLabel?.trim();
7940
+ if (!label2)
7941
+ return cmd;
7942
+ if (label2.toLowerCase() === cmd.toLowerCase())
7943
+ return cmd;
7944
+ return `${cmd} (${label2})`;
7945
+ }
7946
+ function getTelegramStatusWebhooks(config) {
7947
+ return (config.notifications?.webhooks ?? []).filter((wh) => wh.type === "telegram" && typeof wh.botToken === "string" && wh.botToken.trim().length > 0 && typeof wh.chatId === "string" && wh.chatId.trim().length > 0).map((wh) => ({ botToken: wh.botToken, chatId: wh.chatId }));
7948
+ }
7864
7949
  function resolveRunNotificationEvent(exitCode, scriptStatus) {
7865
7950
  if (exitCode === 124) {
7866
7951
  return "run_timeout";
@@ -7984,25 +8069,13 @@ function isRateLimitFallbackTriggered(resultData) {
7984
8069
  return resultData?.rate_limit_fallback === "1";
7985
8070
  }
7986
8071
  function buildEnvVars(config, options) {
7987
- const env = {};
7988
- const executorProvider = resolveJobProvider(config, "executor");
7989
- env.NW_PROVIDER_CMD = PROVIDER_COMMANDS[executorProvider];
7990
- if (config.defaultBranch) {
7991
- env.NW_DEFAULT_BRANCH = config.defaultBranch;
7992
- }
8072
+ const env = buildBaseEnvVars(config, "executor", options.dryRun);
7993
8073
  env.NW_MAX_RUNTIME = String(config.maxRuntime);
7994
8074
  env.NW_PRD_DIR = config.prdDir;
7995
8075
  env.NW_BRANCH_PREFIX = config.branchPrefix;
7996
- if (config.providerEnv) {
7997
- Object.assign(env, config.providerEnv);
7998
- }
7999
8076
  if (config.prdPriority && config.prdPriority.length > 0) {
8000
8077
  env.NW_PRD_PRIORITY = config.prdPriority.join(":");
8001
8078
  }
8002
- if (options.dryRun) {
8003
- env.NW_DRY_RUN = "1";
8004
- }
8005
- env.NW_EXECUTION_CONTEXT = "agent";
8006
8079
  const maxRetries = Number.isFinite(config.maxRetries) ? Math.max(1, Math.floor(config.maxRetries)) : 3;
8007
8080
  env.NW_MAX_RETRIES = String(maxRetries);
8008
8081
  if (process.argv[1]) {
@@ -8234,28 +8307,18 @@ function parseFinalReviewScore(raw) {
8234
8307
  return parsed;
8235
8308
  }
8236
8309
  function buildEnvVars2(config, options) {
8237
- const env = {};
8238
- const reviewerProvider = resolveJobProvider(config, "reviewer");
8239
- env.NW_PROVIDER_CMD = PROVIDER_COMMANDS[reviewerProvider];
8240
- if (config.defaultBranch) {
8241
- env.NW_DEFAULT_BRANCH = config.defaultBranch;
8242
- }
8310
+ const env = buildBaseEnvVars(config, "reviewer", options.dryRun);
8243
8311
  env.NW_REVIEWER_MAX_RUNTIME = String(config.reviewerMaxRuntime);
8244
8312
  env.NW_REVIEWER_MAX_RETRIES = String(config.reviewerMaxRetries);
8245
8313
  env.NW_REVIEWER_RETRY_DELAY = String(config.reviewerRetryDelay);
8246
8314
  env.NW_MIN_REVIEW_SCORE = String(config.minReviewScore);
8247
8315
  env.NW_BRANCH_PATTERNS = config.branchPatterns.join(",");
8248
- if (config.providerEnv) {
8249
- Object.assign(env, config.providerEnv);
8250
- }
8316
+ env.NW_PRD_DIR = config.prdDir;
8317
+ env.NW_CLAUDE_MODEL_ID = CLAUDE_MODEL_IDS[config.claudeModel ?? "sonnet"];
8251
8318
  if (config.autoMerge) {
8252
8319
  env.NW_AUTO_MERGE = "1";
8253
8320
  }
8254
8321
  env.NW_AUTO_MERGE_METHOD = config.autoMergeMethod;
8255
- if (options.dryRun) {
8256
- env.NW_DRY_RUN = "1";
8257
- }
8258
- env.NW_EXECUTION_CONTEXT = "agent";
8259
8322
  return env;
8260
8323
  }
8261
8324
  function applyCliOverrides2(config, options) {
@@ -8433,7 +8496,7 @@ ${stderr}`);
8433
8496
  event: "review_completed",
8434
8497
  projectName: path19.basename(projectDir),
8435
8498
  exitCode,
8436
- provider: config.provider,
8499
+ provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
8437
8500
  prUrl: prDetails?.url,
8438
8501
  prTitle: prDetails?.title,
8439
8502
  prBody: prDetails?.body,
@@ -8454,7 +8517,7 @@ ${stderr}`);
8454
8517
  event: "pr_auto_merged",
8455
8518
  projectName: path19.basename(projectDir),
8456
8519
  exitCode,
8457
- provider: config.provider,
8520
+ provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
8458
8521
  prNumber: autoMergedPrDetails?.number ?? autoMergedPrNumber,
8459
8522
  prUrl: autoMergedPrDetails?.url,
8460
8523
  prTitle: autoMergedPrDetails?.title,
@@ -8496,35 +8559,28 @@ function parseQaPrNumbers(prsRaw) {
8496
8559
  }
8497
8560
  return numbers;
8498
8561
  }
8499
- function getTelegramStatusWebhooks(config) {
8500
- return (config.notifications?.webhooks ?? []).filter((wh) => wh.type === "telegram" && typeof wh.botToken === "string" && wh.botToken.trim().length > 0 && typeof wh.chatId === "string" && wh.chatId.trim().length > 0).map((wh) => ({ botToken: wh.botToken, chatId: wh.chatId }));
8562
+ function parseRepoFromPrUrl(prUrl) {
8563
+ if (!prUrl) {
8564
+ return void 0;
8565
+ }
8566
+ const match = prUrl.match(/^https?:\/\/github\.com\/([^/]+\/[^/]+)\/pull\/\d+/i);
8567
+ return match?.[1];
8501
8568
  }
8502
8569
  function buildEnvVars3(config, options) {
8503
- const env = {};
8504
- const qaProvider = resolveJobProvider(config, "qa");
8505
- env.NW_PROVIDER_CMD = PROVIDER_COMMANDS[qaProvider];
8506
- if (config.defaultBranch) {
8507
- env.NW_DEFAULT_BRANCH = config.defaultBranch;
8508
- }
8570
+ const env = buildBaseEnvVars(config, "qa", options.dryRun);
8509
8571
  env.NW_QA_MAX_RUNTIME = String(config.qa.maxRuntime);
8510
8572
  const branchPatterns = config.qa.branchPatterns.length > 0 ? config.qa.branchPatterns : config.branchPatterns;
8511
8573
  env.NW_BRANCH_PATTERNS = branchPatterns.join(",");
8512
8574
  env.NW_QA_SKIP_LABEL = config.qa.skipLabel;
8513
8575
  env.NW_QA_ARTIFACTS = config.qa.artifacts;
8514
8576
  env.NW_QA_AUTO_INSTALL_PLAYWRIGHT = config.qa.autoInstallPlaywright ? "1" : "0";
8515
- if (config.providerEnv) {
8516
- Object.assign(env, config.providerEnv);
8517
- }
8577
+ env.NW_CLAUDE_MODEL_ID = CLAUDE_MODEL_IDS[config.claudeModel ?? "sonnet"];
8518
8578
  const telegramWebhooks = getTelegramStatusWebhooks(config);
8519
8579
  if (telegramWebhooks.length > 0) {
8520
8580
  env.NW_TELEGRAM_STATUS_WEBHOOKS = JSON.stringify(telegramWebhooks);
8521
8581
  env.NW_TELEGRAM_BOT_TOKEN = telegramWebhooks[0].botToken;
8522
8582
  env.NW_TELEGRAM_CHAT_ID = telegramWebhooks[0].chatId;
8523
8583
  }
8524
- if (options.dryRun) {
8525
- env.NW_DRY_RUN = "1";
8526
- }
8527
- env.NW_EXECUTION_CONTEXT = "agent";
8528
8584
  return env;
8529
8585
  }
8530
8586
  function applyCliOverrides3(config, options) {
@@ -8604,20 +8660,22 @@ ${stderr}`);
8604
8660
  const qaPrNumbers = parseQaPrNumbers(scriptResult?.data.prs);
8605
8661
  const primaryQaPr = qaPrNumbers[0];
8606
8662
  const prDetails = primaryQaPr ? fetchPrDetailsByNumber(primaryQaPr, projectDir) : null;
8607
- const repo = scriptResult?.data.repo;
8663
+ const repo = scriptResult?.data.repo ?? parseRepoFromPrUrl(prDetails?.url);
8608
8664
  const fallbackPrUrl = !prDetails?.url && primaryQaPr && repo ? `https://github.com/${repo}/pull/${primaryQaPr}` : void 0;
8665
+ const qaScreenshotUrls = primaryQaPr !== void 0 ? fetchQaScreenshotUrlsForPr(primaryQaPr, projectDir, repo) : [];
8609
8666
  const _qaCtx = {
8610
8667
  event: "qa_completed",
8611
8668
  projectName: path20.basename(projectDir),
8612
8669
  exitCode,
8613
- provider: config.provider,
8670
+ provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
8614
8671
  prNumber: prDetails?.number ?? primaryQaPr,
8615
8672
  prUrl: prDetails?.url ?? fallbackPrUrl,
8616
8673
  prTitle: prDetails?.title,
8617
8674
  prBody: prDetails?.body,
8618
8675
  filesChanged: prDetails?.changedFiles,
8619
8676
  additions: prDetails?.additions,
8620
- deletions: prDetails?.deletions
8677
+ deletions: prDetails?.deletions,
8678
+ qaScreenshotUrls
8621
8679
  };
8622
8680
  await sendNotifications(config, _qaCtx);
8623
8681
  }
@@ -8631,30 +8689,16 @@ ${stderr}`);
8631
8689
  });
8632
8690
  }
8633
8691
  init_dist();
8634
- function getTelegramStatusWebhooks2(config) {
8635
- return (config.notifications?.webhooks ?? []).filter((wh) => wh.type === "telegram" && typeof wh.botToken === "string" && wh.botToken.trim().length > 0 && typeof wh.chatId === "string" && wh.chatId.trim().length > 0).map((wh) => ({ botToken: wh.botToken, chatId: wh.chatId }));
8636
- }
8637
8692
  function buildEnvVars4(config, options) {
8638
- const env = {};
8639
- const auditProvider = resolveJobProvider(config, "audit");
8640
- env.NW_PROVIDER_CMD = PROVIDER_COMMANDS[auditProvider];
8693
+ const env = buildBaseEnvVars(config, "audit", options.dryRun);
8641
8694
  env.NW_AUDIT_MAX_RUNTIME = String(config.audit.maxRuntime);
8642
- if (config.defaultBranch) {
8643
- env.NW_DEFAULT_BRANCH = config.defaultBranch;
8644
- }
8645
- if (config.providerEnv) {
8646
- Object.assign(env, config.providerEnv);
8647
- }
8648
- const telegramWebhooks = getTelegramStatusWebhooks2(config);
8695
+ env.NW_CLAUDE_MODEL_ID = CLAUDE_MODEL_IDS[config.claudeModel ?? "sonnet"];
8696
+ const telegramWebhooks = getTelegramStatusWebhooks(config);
8649
8697
  if (telegramWebhooks.length > 0) {
8650
8698
  env.NW_TELEGRAM_STATUS_WEBHOOKS = JSON.stringify(telegramWebhooks);
8651
8699
  env.NW_TELEGRAM_BOT_TOKEN = telegramWebhooks[0].botToken;
8652
8700
  env.NW_TELEGRAM_CHAT_ID = telegramWebhooks[0].chatId;
8653
8701
  }
8654
- if (options.dryRun) {
8655
- env.NW_DRY_RUN = "1";
8656
- }
8657
- env.NW_EXECUTION_CONTEXT = "agent";
8658
8702
  return env;
8659
8703
  }
8660
8704
  function auditCommand(program2) {
@@ -12516,6 +12560,15 @@ function validateConfigChanges(changes) {
12516
12560
  return `Invalid provider. Must be one of: ${validProviders.join(", ")}`;
12517
12561
  }
12518
12562
  }
12563
+ if (changes.providerLabel !== void 0 && typeof changes.providerLabel !== "string") {
12564
+ return "providerLabel must be a string";
12565
+ }
12566
+ if (changes.defaultBranch !== void 0 && typeof changes.defaultBranch !== "string") {
12567
+ return "defaultBranch must be a string";
12568
+ }
12569
+ if (changes.branchPrefix !== void 0 && (typeof changes.branchPrefix !== "string" || changes.branchPrefix.trim().length === 0)) {
12570
+ return "branchPrefix must be a non-empty string";
12571
+ }
12519
12572
  if (changes.reviewerEnabled !== void 0 && typeof changes.reviewerEnabled !== "boolean") {
12520
12573
  return "reviewerEnabled must be a boolean";
12521
12574
  }
@@ -12534,6 +12587,15 @@ function validateConfigChanges(changes) {
12534
12587
  if (changes.maxLogSize !== void 0 && (typeof changes.maxLogSize !== "number" || changes.maxLogSize < 0)) {
12535
12588
  return "maxLogSize must be a positive number";
12536
12589
  }
12590
+ if (changes.maxRetries !== void 0 && (typeof changes.maxRetries !== "number" || !Number.isInteger(changes.maxRetries) || changes.maxRetries < 1)) {
12591
+ return "maxRetries must be an integer >= 1";
12592
+ }
12593
+ if (changes.reviewerMaxRetries !== void 0 && (typeof changes.reviewerMaxRetries !== "number" || !Number.isInteger(changes.reviewerMaxRetries) || changes.reviewerMaxRetries < 0 || changes.reviewerMaxRetries > 10)) {
12594
+ return "reviewerMaxRetries must be an integer between 0 and 10";
12595
+ }
12596
+ if (changes.reviewerRetryDelay !== void 0 && (typeof changes.reviewerRetryDelay !== "number" || !Number.isInteger(changes.reviewerRetryDelay) || changes.reviewerRetryDelay < 0 || changes.reviewerRetryDelay > 300)) {
12597
+ return "reviewerRetryDelay must be an integer between 0 and 300";
12598
+ }
12537
12599
  if (changes.branchPatterns !== void 0 && (!Array.isArray(changes.branchPatterns) || !changes.branchPatterns.every((p) => typeof p === "string"))) {
12538
12600
  return "branchPatterns must be an array of strings";
12539
12601
  }
@@ -12572,6 +12634,19 @@ function validateConfigChanges(changes) {
12572
12634
  return "roadmapScanner.autoScanInterval must be a number >= 30";
12573
12635
  }
12574
12636
  }
12637
+ if (changes.providerEnv !== void 0) {
12638
+ if (typeof changes.providerEnv !== "object" || changes.providerEnv === null) {
12639
+ return "providerEnv must be an object";
12640
+ }
12641
+ for (const [key, value] of Object.entries(changes.providerEnv)) {
12642
+ if (key.trim().length === 0) {
12643
+ return "providerEnv keys must be non-empty strings";
12644
+ }
12645
+ if (typeof value !== "string") {
12646
+ return "providerEnv values must be strings";
12647
+ }
12648
+ }
12649
+ }
12575
12650
  if (changes.autoMerge !== void 0 && typeof changes.autoMerge !== "boolean") {
12576
12651
  return "autoMerge must be a boolean";
12577
12652
  }
@@ -12597,6 +12672,9 @@ function validateConfigChanges(changes) {
12597
12672
  if (changes.prdDir !== void 0 && (typeof changes.prdDir !== "string" || changes.prdDir.trim().length === 0)) {
12598
12673
  return "prdDir must be a non-empty string";
12599
12674
  }
12675
+ if (changes.templatesDir !== void 0 && (typeof changes.templatesDir !== "string" || changes.templatesDir.trim().length === 0)) {
12676
+ return "templatesDir must be a non-empty string";
12677
+ }
12600
12678
  if (changes.cronScheduleOffset !== void 0 && (typeof changes.cronScheduleOffset !== "number" || changes.cronScheduleOffset < 0 || changes.cronScheduleOffset > 59)) {
12601
12679
  return "cronScheduleOffset must be a number between 0 and 59";
12602
12680
  }
@@ -12661,11 +12739,26 @@ function validateConfigChanges(changes) {
12661
12739
  if (rs.slicerMaxRuntime !== void 0 && (typeof rs.slicerMaxRuntime !== "number" || rs.slicerMaxRuntime < 60)) {
12662
12740
  return "roadmapScanner.slicerMaxRuntime must be a number >= 60";
12663
12741
  }
12742
+ if (rs.priorityMode !== void 0 && rs.priorityMode !== "roadmap-first" && rs.priorityMode !== "audit-first") {
12743
+ return "roadmapScanner.priorityMode must be one of: roadmap-first, audit-first";
12744
+ }
12745
+ if (rs.issueColumn !== void 0 && rs.issueColumn !== "Draft" && rs.issueColumn !== "Ready") {
12746
+ return "roadmapScanner.issueColumn must be one of: Draft, Ready";
12747
+ }
12664
12748
  }
12665
12749
  if (changes.boardProvider !== void 0) {
12666
12750
  if (typeof changes.boardProvider !== "object" || changes.boardProvider === null) {
12667
12751
  return "boardProvider must be an object";
12668
12752
  }
12753
+ if (changes.boardProvider.provider !== void 0 && !["github", "jira", "linear", "local"].includes(changes.boardProvider.provider)) {
12754
+ return "boardProvider.provider must be one of: github, jira, linear, local";
12755
+ }
12756
+ if (changes.boardProvider.projectNumber !== void 0 && (typeof changes.boardProvider.projectNumber !== "number" || !Number.isInteger(changes.boardProvider.projectNumber) || changes.boardProvider.projectNumber <= 0)) {
12757
+ return "boardProvider.projectNumber must be an integer > 0";
12758
+ }
12759
+ if (changes.boardProvider.repo !== void 0 && (typeof changes.boardProvider.repo !== "string" || changes.boardProvider.repo.trim().length === 0)) {
12760
+ return "boardProvider.repo must be a non-empty string";
12761
+ }
12669
12762
  if (changes.boardProvider.enabled !== void 0 && typeof changes.boardProvider.enabled !== "boolean") {
12670
12763
  return "boardProvider.enabled must be a boolean";
12671
12764
  }
@@ -14035,9 +14128,6 @@ function cancelCommand(program2) {
14035
14128
  });
14036
14129
  }
14037
14130
  init_dist();
14038
- function getTelegramStatusWebhooks3(config) {
14039
- return (config.notifications?.webhooks ?? []).filter((wh) => wh.type === "telegram" && typeof wh.botToken === "string" && wh.botToken.trim().length > 0 && typeof wh.chatId === "string" && wh.chatId.trim().length > 0).map((wh) => ({ botToken: wh.botToken, chatId: wh.chatId }));
14040
- }
14041
14131
  function plannerLockPath2(projectDir) {
14042
14132
  return `${LOCK_FILE_PREFIX}slicer-${projectRuntimeKey(projectDir)}.lock`;
14043
14133
  }
@@ -14065,26 +14155,85 @@ function releasePlannerLock(lockFile) {
14065
14155
  } catch {
14066
14156
  }
14067
14157
  }
14158
+ function resolvePlannerIssueColumn(config) {
14159
+ return config.roadmapScanner.issueColumn === "Ready" ? "Ready" : "Draft";
14160
+ }
14161
+ function buildPlannerIssueBody(projectDir, config, result) {
14162
+ const relativePrdPath = path35.join(config.prdDir, result.file ?? "").replace(/\\/g, "/");
14163
+ const absolutePrdPath = path35.join(projectDir, config.prdDir, result.file ?? "");
14164
+ const sourceItem = result.item;
14165
+ let prdContent = "";
14166
+ try {
14167
+ prdContent = fs35.readFileSync(absolutePrdPath, "utf-8");
14168
+ } catch {
14169
+ prdContent = `Unable to read generated PRD file at \`${relativePrdPath}\`.`;
14170
+ }
14171
+ const maxBodyChars = 6e4;
14172
+ const truncated = prdContent.length > maxBodyChars;
14173
+ const prdPreview = truncated ? `${prdContent.slice(0, maxBodyChars)}
14174
+
14175
+ ...[truncated]` : prdContent;
14176
+ const sourceLines = sourceItem ? [
14177
+ `- Source section: ${sourceItem.section}`,
14178
+ `- Source item: ${sourceItem.title}`,
14179
+ sourceItem.description ? `- Source summary: ${sourceItem.description}` : ""
14180
+ ].filter((line) => line.length > 0) : [];
14181
+ return [
14182
+ "## Planner Generated PRD",
14183
+ "",
14184
+ `- PRD file: \`${relativePrdPath}\``,
14185
+ ...sourceLines,
14186
+ "",
14187
+ "---",
14188
+ "",
14189
+ prdPreview
14190
+ ].join("\n");
14191
+ }
14192
+ async function createPlannerIssue(projectDir, config, result) {
14193
+ if (!result.sliced || !result.file || !result.item) {
14194
+ return { created: false, skippedReason: "nothing-created" };
14195
+ }
14196
+ if (!config.boardProvider?.enabled) {
14197
+ return { created: false, skippedReason: "board-disabled" };
14198
+ }
14199
+ const provider = createBoardProvider(config.boardProvider, projectDir);
14200
+ const board = await provider.getBoard();
14201
+ if (!board) {
14202
+ return { created: false, skippedReason: "board-not-configured" };
14203
+ }
14204
+ const existingIssues = await provider.getAllIssues();
14205
+ const existing = existingIssues.find((issue2) => issue2.title.trim().toLowerCase() === result.item.title.trim().toLowerCase());
14206
+ if (existing) {
14207
+ return {
14208
+ created: false,
14209
+ skippedReason: "already-exists",
14210
+ issueNumber: existing.number,
14211
+ issueUrl: existing.url
14212
+ };
14213
+ }
14214
+ const issue = await provider.createIssue({
14215
+ title: result.item.title,
14216
+ body: buildPlannerIssueBody(projectDir, config, result),
14217
+ column: resolvePlannerIssueColumn(config)
14218
+ });
14219
+ return {
14220
+ created: true,
14221
+ issueNumber: issue.number,
14222
+ issueUrl: issue.url
14223
+ };
14224
+ }
14068
14225
  function buildEnvVars5(config, options) {
14069
- const env = {};
14070
- const slicerProvider = resolveJobProvider(config, "slicer");
14071
- env.NW_PROVIDER_CMD = PROVIDER_COMMANDS[slicerProvider];
14226
+ const env = buildBaseEnvVars(config, "slicer", options.dryRun);
14072
14227
  env.NW_SLICER_MAX_RUNTIME = String(config.roadmapScanner.slicerMaxRuntime);
14073
14228
  env.NW_PRD_DIR = config.prdDir;
14074
14229
  env.NW_ROADMAP_PATH = config.roadmapScanner.roadmapPath;
14075
- if (config.providerEnv) {
14076
- Object.assign(env, config.providerEnv);
14077
- }
14078
- const telegramWebhooks = getTelegramStatusWebhooks3(config);
14230
+ env.NW_CLAUDE_MODEL_ID = CLAUDE_MODEL_IDS[config.claudeModel ?? "sonnet"];
14231
+ const telegramWebhooks = getTelegramStatusWebhooks(config);
14079
14232
  if (telegramWebhooks.length > 0) {
14080
14233
  env.NW_TELEGRAM_STATUS_WEBHOOKS = JSON.stringify(telegramWebhooks);
14081
14234
  env.NW_TELEGRAM_BOT_TOKEN = telegramWebhooks[0].botToken;
14082
14235
  env.NW_TELEGRAM_CHAT_ID = telegramWebhooks[0].chatId;
14083
14236
  }
14084
- if (options.dryRun) {
14085
- env.NW_DRY_RUN = "1";
14086
- }
14087
- env.NW_EXECUTION_CONTEXT = "agent";
14088
14237
  return env;
14089
14238
  }
14090
14239
  function applyCliOverrides4(config, options) {
@@ -14130,6 +14279,8 @@ function sliceCommand(program2) {
14130
14279
  `${config.roadmapScanner.slicerMaxRuntime}s (${Math.floor(config.roadmapScanner.slicerMaxRuntime / 60)}min)`
14131
14280
  ]);
14132
14281
  configTable.push(["Planner Schedule", config.roadmapScanner.slicerSchedule]);
14282
+ configTable.push(["Planner Priority Mode", config.roadmapScanner.priorityMode]);
14283
+ configTable.push(["Planner Issue Column", resolvePlannerIssueColumn(config)]);
14133
14284
  configTable.push(["Scanner Enabled", config.roadmapScanner.enabled ? "Yes" : "No"]);
14134
14285
  console.log(configTable.toString());
14135
14286
  header("Roadmap Status");
@@ -14188,8 +14339,21 @@ function sliceCommand(program2) {
14188
14339
  });
14189
14340
  }
14190
14341
  const result = await sliceNextItem(projectDir, config);
14342
+ let issueSummary = "";
14343
+ if (result.sliced) {
14344
+ try {
14345
+ const issueResult = await createPlannerIssue(projectDir, config, result);
14346
+ if (issueResult.created && issueResult.issueNumber) {
14347
+ issueSummary = `; issue #${issueResult.issueNumber} (${resolvePlannerIssueColumn(config)})`;
14348
+ } else if (issueResult.skippedReason === "already-exists" && issueResult.issueNumber) {
14349
+ issueSummary = `; existing issue #${issueResult.issueNumber}`;
14350
+ }
14351
+ } catch (issueError) {
14352
+ warn(`Planner created ${result.file} but failed to create board issue: ${issueError instanceof Error ? issueError.message : String(issueError)}`);
14353
+ }
14354
+ }
14191
14355
  if (result.sliced) {
14192
- spinner.succeed(`Planner completed successfully: Created ${result.file}`);
14356
+ spinner.succeed(`Planner completed successfully: Created ${result.file}${issueSummary}`);
14193
14357
  } else if (result.error) {
14194
14358
  if (result.error === "No pending items to process") {
14195
14359
  spinner.succeed("No pending items to process");
@@ -14797,14 +14961,14 @@ var __dirname4 = dirname8(__filename3);
14797
14961
  function findPackageRoot(dir) {
14798
14962
  let d = dir;
14799
14963
  for (let i = 0; i < 5; i++) {
14800
- if (existsSync30(join33(d, "package.json")))
14964
+ if (existsSync30(join34(d, "package.json")))
14801
14965
  return d;
14802
14966
  d = dirname8(d);
14803
14967
  }
14804
14968
  return dir;
14805
14969
  }
14806
14970
  var packageRoot = findPackageRoot(__dirname4);
14807
- var packageJson = JSON.parse(readFileSync18(join33(packageRoot, "package.json"), "utf-8"));
14971
+ var packageJson = JSON.parse(readFileSync18(join34(packageRoot, "package.json"), "utf-8"));
14808
14972
  var program = new Command2();
14809
14973
  program.name("night-watch").description("Autonomous PRD execution using Claude CLI + cron").version(packageJson.version);
14810
14974
  initCommand(program);